盒马鲜生面经以及个人总结
nextTick原理
在vue中数据有改变时,Vue并没有马上去更新DOM数据,而是将这个操作放进一个队列中;如果我们重复执行的话,队列还会进行去重操作;等待同一事件循环中的所有数据变化完成之后,会将队列中的事件拿出来处理,这也就是为什么每次修改数据是VUE每次只会更新一次DOM。我们的Vue.nextTick(callback)就可以拿到最新的DOM元素了。
看了源码后其实可以了解到Vue.nextTick是做了四个判断,对当前环境进行不断的降级处理,尝试使用原生的Promise.then、MutationObserver和setImmediate,上述三个都不支持最后使用setTimeout,降级处理的目的都是将flushCallbacks函数放入微任务(判断1和判断2)或者宏任务(判断3和判断4)。
但最后执行的代码其实非常的简单,把callbacks数组复制一份,然后把callbacks置为空,最后把复制出来的数组中的每个函数依次执行一遍;所以它的作用仅仅是用来执行callbacks中的回调函数
微任务和宏任务区别
同步任务和异步任务
提微任务和宏任务不得不提的是同步和异步了。同步任务他们会直接进入主线程进行执行,一旦某一任务暂停或者出现bug将直接中断后续任务的执行。而异步任务会被暂存在EVENT TABLE并注册函数,当主线程任务执行结束后,EVENT TABLE会将注册函数存放进EVENT Queue,这时候主线程就回去调取EVENT Queue继续执行主线程任务,上述过程会不断重复,也就是常说的Event Loop(事件循环)。
微任务和宏任务
异步任务里面又分微任务和宏任务,他们也有执行顺序的区别,最开始执行微任务,当前确认没有微任务后将会执行宏任务,每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有微任务之后,再继续执行下一个宏任务。总之只要当前执行任务期间有遇到微任务就先执行微任务没有执行宏任务。
自定义指令
例如:
Vue.directive('focus', {
// 当被绑定的元素插入到 dom 中时…… ,其中参数 el 为dom元素
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
directives: {
focus: {
// 当被绑定的元素插入到 dom 中时…… ,其中参数 el 为dom元素
inserted: function (el) {
// 聚焦元素
el.focus()
}
}
}
上述两种都是创建自定义指令的方法,只需在需要使用的标签上加上v-focus即可。该自定义指令一共有5个常用的钩子函数。
bind:只执行一次,当自定义指令第一次绑定到标签上时触发该函数。
inserted:当元素插入父节点时候调用。
update 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。
componentUpdated:所在组件VNode及其子组件VNode全部更新完成后调用。
unbind:只执行一次,当标签与指令解绑时候触发。
自定义插件
自定义插件一般情况下是在全局作用域下的,一般自定义插件用来放置全局的自定义指令,全局方法或者属性,也可以调用VUE的APImixins混入一些方法属性或者周期函数特定的API,添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。定义插件其实很简单,创建一个JS文件以后,定义一个对象,对象中必须含有一个名叫install的属性名,他的值是一个函数。函数带有两个参数,第一个参数是 Vue 构造器 , 第二个参数是一个可选的选项对象。
export default {
install (Vue, option) {
Vue.prototype.$mydata = 'haha'
Vue.prototype.$myfc = function () {
return 1
}
// 添加全局混入
Vue.mixin({
mounted () {
console.log('进入mounted')
},
data () {
return {
mydata: 1
}
},
methods: {
btn1 () {
return 2
}
}
})
Vue.directive('mydata', {
inserted: function (el) {
console.log(el)
}
})
}
}
自定义组件
全局组件
Vue.component('my-component-name', {
// ... 选项 ...
})
局部组件
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
//
}
keep-alive里面的周期函数
作用
在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性。
原理
在 created 函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode 实例进行渲染
Props
include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
max - 数字。最多可以缓存多少组件实例。
生命周期函数
1. activated
- 在 keep-alive 组件激活时调用
- 该钩子函数在服务器端渲染期间不被调用
2. deactivated
- 在 keep-alive 组件停用时调用
- 该钩子在服务器端渲染期间不被调用
- 被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated 与 deactivated
- 使用 keep-alive 会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在 activated 阶段获取数据,承担原来 created 钩子函数中获取数据的任务。
- 注意: 只有组件被 keep-alive 包裹时,这两个生命周期函数才会被调用,如果作为正常组件使用,是不会被调用的,以及在 2.1.0 版本之后,使用 exclude 排除之后,就算被包裹在 keep-alive 中,这两个钩子函数依然不会被调用!另外,在服务端渲染时,此钩子函数也不会被调用。
$set用法
什么时候使用set?
set为解决双向绑定失效而生,总结了下就是只要值的地址没有改变,vue是检测不到数据变化的,这个时候可以使用set
增删改数组或对象的值
例如:
this.items[2] = 5
当我们使用如下方法进行修改数组或对象的内部的值时,并虽然数组或对象值发生了改变,视图值却没更新这就导致双向绑定失败,为了使试图也发生变化我们采用:
this.$set(this.items, 2, 5)
如果我们添加的属性很多条,可能就需要写一个循环来多次set。当然你也可能使用Object.assign:
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
created mounted区别
created:
在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图,此时已经可以访问到data数据也可以调用方法,无法操作dom
mounted:
在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作
ES6用法
扩展运算符 三个点(…)
对象中的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中
let bar = { a: 1, b: 2 };
let baz1 = { ...bar }; // { a: 1, b: 2 }
let baz2 = Object.assign({}, bar); // { a: 1, b: 2 }
let baz3 = {...bar, ...{a:2, b: 4}}; // {a: 2, b: 4}
数组同理
let、const、var的区别
var的弊端
存在声明提前,且声明后不存在块级作用域,并且同一个参数可以多次去声明它并且不会报错
let
不存在声明提前,存在块级作用域,并且同一个参数只能被声明一次
const
一般用来定义常量,也存在块级作用域,声明时必须赋值,且一旦声明值不能改变(除非是改变了数组或者对象里面的值)
模板字符串
略
箭头函数
- 箭头函数是一种更加简洁的函数书写方式
- 箭头函数本身没有作用域(无this)
- 箭头函数的this指向上一层,上下文决定其this
- 基本语法:参数 => 函数体
Class类
可以理解为创建一个构造函数的语法糖,他其实就是一个函数,在用Class创建的构造函数内部必须存在一个constructor方法,一般用来定义构造函数的内部属性或方法,且使用this时指向的是创建的实例对象,在Class定义的函数内部的方法指向的都是该构造函数的prototype上的方法,所以他的实例对象根据原型链的原理均可以调取实例对象的构造函数上的方法。例如
//类的所有方法都定义在类的prototype属性上面。
class piont{
constructor(){
//
}
play(){
}
}
//上述代码等价于
point.prototype={
constructor() {},
play(){};
}
//在类的实例上面调用方法,其实就是调用原型上的方法。
class Ba{
//
}
let b=new Ba();
b.constructor===Ba.prototype.constructor//true
不仅如此,Class内部可以进行定义静态属性或静态方法,如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。