任务执行队列
宏任务同步任务 --> 微任务同步任务 --> 微任务异步任务 --> 宏任务异步任务
宏任务:setTimeout setInterval setImmediate (new Promise是宏任务同步任务)
先后顺序:先执行setTimeout 后执行 setImmediate
微任务promise.then() process.nextTick
先后顺序: promise.then() 后执行
new Promise(function (resolve) { console.log('1') resolve() }).then(function () { console.log('2') })
new Promise(function (resolve) {
console.log('1')
resolve()
}).then(function () {
console.log('2')
})
new Promise(function (resolve) {
console.log('1')
resolve()
}).then(function () {
console.log('2')
})
process.nextTick(function () {
console.log('3');
})
setImmediate(() => {
console.info('4')
})
new Promise(function (resolve) {
console.log('5');
resolve();
}).then(function () {
console.log('6')
})
setTimeout(function () {
console.log('7');
setImmediate(() => {
console.info('8')
})
process.nextTick(function () {
console.log('9');
})
new Promise(function (resolve) {
console.log('10');
resolve();
}).then(function () {
console.log('11')
})
})
// 1 5 3 2 6 7 10 9 11 4 8
JS数据类型及深拷贝和浅拷贝,它们的实现方式
Number、String、Boolean、Null、undefined、Object、symbol、bigInt、function、Date
基本类型(单类型): String、Number、boolean、null、undefined。
引用类型:Object、function、Array、Date
简单数据类型存放到栈里面,存储时变量中存储的是值本身
复杂数据类型存放到堆里面,存储的仅仅是地址,真正的对象实例存放在堆空间中,若程序员不释放,由垃圾回收机制回收
浅拷贝共用一个引用地址,如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象
深拷贝会创建新的内存地址,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
| 方式 | 和原数据是否指向同一对象 | 第一层对象为基本数据类型 | 数据包含子对象 |
|---|---|---|---|
| 赋值 | 是 | 改变会一同改变 | 改变会一同改变 |
| 浅拷贝 | 否 | 改变不会一同改变 | 改变会一同改变 |
| 深拷贝 | 否 | 改变不会一同改变 | 改变不会一同改变 |
浅拷贝实现的方式:
-
Object.assign()
-
函数库lodash的_.clone方法
-
展开运算符…
-
Array.prototype.concat()
-
Array.prototype.slice()
深拷贝的实现方式:
-
JSON.parse(JSON.stringify())
-
函数库lodash的_.cloneDeep方法
-
jQuery.extend()方法 // $.extend(true,{},obj)
-
手写递归方法,遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
事件冒泡、事件捕获、事件委托
事件冒泡
在一个对象上触发某类事件(比如单击onclick事件)。这个事件会向这个对象的父级对象传播,从里到外,直至它被处理
text -> div -> body -> document -> window
事件捕获
click事件首先由 document 元素捕获,然后沿DOM树依次向下传播,直至到达实际的目标元素上
window-> document -> body -> div -> text
事件委托
把事件处理器添加到一个上级元素上,这样就避免了把事件处理器添加到多个子级元素上,主要得益于浏览器的事件冒泡机制,可以减少事件注册,节省内存
数组去重的方法(简单数组)
-
Set()
-
indexOf() + for
-
includes + for
-
双重for、slice
-
Filter+indexOf
-
reduce
-
map
防抖节流及应用场景
防抖
防抖通常用来处理一些高频触发的事件,比如用户连续点击按钮、输入框频繁输入,避免频繁更新UI或者发送请求造成性能浪费
export const debounce = (fn: any, wait: num) => {
let timer: any = null
return () => {
if(timer) {
clearTimerout(timer)
}
timer = setTimeout(fn, wait)
}
}
节流
节流: 是指对于连续触发的事件,每隔一段时间内执行一次,节流里面涉及的时间主要指时间执行的间隔时间。比如 鼠标滚动、窗口resize、滚动条滑动 等操作。
export const throttle = (fn: any, wait: number) => {
let timer: any = null;
return function(
节流中,timer 为什么要 等于 null?
定时器是有返回值的,返回值是当前定时器的 ID号,clearTimeout(timer) ,会清除该定时器函数,但不会改变 id 值。如果我们不将 timer 设置为 null,而是直接设置新的定时器 ID,那么就会无法清除之前的定时器,会导致多个定时器同时执行,达不到节流的效果。所以在每次执行定时器函数之前,将 timer 设置为 null,确保下次事件触发时能正确清除之前的定时器。
es6新特性
1.块级作用域变量(let和const)使得变量只在当前作用域内有效
{
let a = 10;
const b = 1;
console.log(a); // 10
console.log(b); // 1
}
console.log(a); // ReferenceError: x is not defined
console.log(b); // ReferenceError: y is not defined
2.箭头函数
箭头函数是一种新的函数声明方式,可以更简洁地定义函数,且无需考虑this指向问题
const add = (x, y) => x + y;
console.log(add(1, 2)); // 3
let a = () => {
console.log(1);
}
a();// 1
3.模板字符串
模板字符串可以用来更方便地拼接字符串。
const name = "ES6";
console.log(`Hello, ${name}!`); // Hello, ES6!
4.解构赋值
解构赋值可以用来快速地从数组或对象中提取值并赋给变量
const arr = [1, 2, 3];
const [a, b, c] = arr;
console.log(a, b, c); // 1 2 3
const obj = {x: 1, y: 2, z: 3};
const {x, y, z} = obj;
console.log(x, y, z); // 1 2 3
5.默认参数
函数可以设置默认参数,当调用函数时没有传入该参数时,会使用默认值。
const greet = (name = "World") => `Hello, ${name}!`;
console.log(greet()); // Hello, World!
console.log(greet("Alice")); // Hello, Alice!
6.扩展运算符
扩展运算符可以用来将数组或对象展开成一系列参数。
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [
7.类和继承
ES6引入了class关键字来定义类和继承关系。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
const d = new Dog("Rex");
d.speak(); // Rex barks.
8.Promise
Promise是一种新的异步编程方式,可以更方便地处理异步操作,解决地狱回调非常好用
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
wait(1000).then(() => console.log("1 second passed."));
resolved 成功后调用 rejected 失败后调用 Promise的状态只有这 2 种, 且一个 promise 对象只能改变一次
HTML、CSS
常见的块元素和行内元素
块元素:
行内元素:<a>、<strong>、<b>、<em>、<i>、<del>、<span>
Sass、Less变量怎么定义
class中嵌套class,从而减少代码的重复,Less可以向上/向下解析;Sass只能向上解析
//Less定义变量@:
@color: #4D926F;
header {
color: @color;
}
//Sass定义变量$:
$blue : #1875e7;
div {
color : $blue;
}
如何让元素水平垂直居中
-
Flex
-
Transform的translate
-
Position fixed或absolute
-
子元素设置display: inline-block,父元素设置text-align: center且line-height等于height
样式优先级
!important > 内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器 > 通配选择器
css引起的重绘和回流
回流必将引起重绘,而重绘不一定会引起回流
当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
VUE
vue生命周期
beforeCreate 、created(数据初始化) 、beforeMount 、mounted 、beforeUpdate 、updated 、beforeDestroy 、destroyed
被缓存的页面还有:activated、deactivated
说说slot插槽、具名插槽、作用域插槽
插槽就是子组件中的提供给父组件使用的一个占位符,在子组件中用< slot>< /slot > 表示。
父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的< slot>< /slot >标签。
默认插槽
我们定义两个组件:parent.vue 和child.vue ,在parent组件中引入child组件
//parent.vue
<child>
world
</child>
//child.vue
<template>
<div>
hello <slot>你好</slot>
</div>
</template>
这样,组件在渲染的时候,<slot></slot>就会被渲染为:hello world,如果在<child></child>中没有放任何内容,就会渲染出子组件的默认内容:hello 你好
具名插槽
有时候,我们在一个组件中需要使用多个插槽,那么为了区分它们,就可以给每个插槽命名,这就是具名插槽。
slot元素有一个特殊的attribute:name,他可以用来指定额外的插槽:
// 子组件
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
// 父组件
<base-layout>
<template v-slot:header>
<h1>header</h1>
</template>
<p>content</p>
<template v-slot:footer>
<p>footer</p>
</template>
</base-layout>
使用v-slot指令来指定该部分要渲染的插槽的名字,这样就可以将三部分内容渲染到相应的位置了。不带name的插槽,会有一个默认的名字default。如果想要更明确一些,可以将它的name设置为default:
<template v-slot:default>
<p> content </p>
</template>
注意:v-slot指令只能添加在template标签上
作用域插槽
一般情况下,是父组件使用查操过程中传入一些内容来决定插槽的内容,他可以访问到父组件中定义的属性,但是不能访问子组件中的数据。如果我们需要子组件以供一些数据,那么就可以使用作用域插槽来解决。
在子组件中动态绑定需要传入的数据:
<slot :data="data"></slot>
export default {
data () {
return {
data: {
username: 'hello'
}
}
}
}
在父组件中访问子元素的数据:
<div>
<test v-slot:default="slotProps">
{{slotProps.data.username}}
</test>
</div>
// 也可以写成下面这样
<div>
<test v-slot:"slotProps">
{{slotProps.data.username}}
</test>
</div>
需要注意: 如果有多个插槽,只要将default改为对应的插槽名称即可。
插槽的解构
作用域插槽的内部工作原理是将插槽的内容包含在一个带有单个参数的函数里,所以slot 的值可以接收任何有效的可以出现在函数定义的参数位置上的 JavaScript 表达式。
原本这样写的代码:
<div>
<test v-slot:"slotProps">
{{slotProps.data.username}}
</test>
</div>
可以改成:
<div>
<test v-slot:{data}>
{{data.username}}
</test>
</div>
这样写代码就更加的简洁。
动态插槽名
在Vue2.6中引入了动态插槽名,动态指令参数使用在v-slot上,来定义动态的插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
具名插槽的缩写
在Vue2.6中,v-slot可以缩写为#:
<template v-slot:header>
<h1>header</h1>
</template>
// 缩写后
<template #header>
<h1>header</h1>
</template>
需要注意,该缩写需要在其有名字的情况下才能使用,下面的情况会弹出警告:
<test #:{data}>
{{data.username}}
</test>
如果非要使用缩写的话,必须给他指定一个插槽名:
<test #default:{data}>
{{data.username}}
</test>
简要介绍Vuex
包含state、mutations、actions、getters、modules
Vuex为什么要有异步更新action
mutation内部必须是同步函数,因为数据的复用,异步会导致内部状态难以追踪,但有些操作又是异步更新的,所以需要通过action提交dispatch给mutation修改state
store.commit()是否可以带第三个参数
不可以,多个参数需要用数组或对象的方式传递
自定义指令
全局注册
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
}
})
局部注册
和data平级
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
}
}
}
应用场景
输入框防抖
抖这种情况设置一个v-debounce自定义指令来实现
// 1.设置v-debounce自定义指令
Vue.directive('debounce', {
bind: (el, binding) => {
let debounceTime = binding.value; // 防抖时间
if (!debounceTime) { // 用户若不设置防抖时间,则默认2s
debounceTime = 2000;
}
let cbFun;
el.addEventListener('click', event => {
if (!cbFun) { // 第一次执行
cbFun = setTimeout(() => {
cbFun = null;
}, debounceTime);
} else {
// 阻止事件冒泡并且阻止该元素上同事件类型的监听器被触发
event && event.stopImmediatePropagation();
}
}, true);
},
});
// 2.为button标签设置v-debounce自定义指令
<button @click="sayHello" v-debounce>提交</button>
图片懒加载
设置一个v-lazy自定义指令完成图片懒加载
const LazyLoad = {
// install方法
install(Vue,options){
// 代替图片的loading图
let defaultSrc = options.default;
Vue.directive('lazy',{
bind(el,binding){
LazyLoad.init(el,binding.value,defaultSrc);
},
inserted(el){
// 兼容处理
if('IntersectionObserver' in window){
LazyLoad.observe(el);
}else{
LazyLoad.listenerScroll(el);
}
},
})
},
// 初始化
init(el,val,def){
// data-src 储存真实src
el.setAttribute('data-src',val);
// 设置src为loading图
el.setAttribute('src',def);
},
// 利用IntersectionObserver监听el
observe(el){
let io = new IntersectionObserver(entries => {
let realSrc = el.dataset.src;
if(entries[0].isIntersecting){
if(realSrc){
el.src = realSrc;
el.removeAttribute('data-src');
}
}
});
io.observe(el);
},
// 监听scroll事件
listenerScroll(el){
let handler = LazyLoad.throttle(LazyLoad.load,300);
LazyLoad.load(el);
window.addEventListener('scroll',() => {
handler(el);
});
},
// 加载真实图片
load(el){
let windowHeight = document.documentElement.clientHeight
let elTop = el.getBoundingClientRect().top;
let elBtm = el.getBoundingClientRect().bottom;
let realSrc = el.dataset.src;
if(elTop - windowHeight<0&&elBtm > 0){
if(realSrc){
el.src = realSrc;
el.removeAttribute('data-src');
}
}
},
// 节流
throttle(fn,delay){
let timer;
let prevTime;
return function(