最近由于各种原因又开始面试了,然后想把这方面关于vue做一个整理,以便于自己后续巩固复习~
若有不足,欢迎大佬补充并指证~
1、vue的生命周期钩子函数
1)berforeCreate new vue()触发的第一个钩子,现阶段data、methods、computed以及watch上的数据和方法都不能被访问。
2)created 实力创建完成后触发,此阶段完成了数据观测,可是使用数据,更改数据。需要注意的是,在这个阶段更改数据的话,不会触发updated函数哦~,能获取初始数据,不能和dom进行交互,如果实在忍不住想访问dom,你用vm.$nextTick试试!
3) beforeMount 挂在之前触发,此阶段templete已经导入渲染函数进行编译,而且此时虚拟dom已经创建完成,马上就开始渲染了.注意哈,此阶段更改数据的话和上一个一样,不会触发updated的哦
4) mount 挂在完成后触发,此阶段真实dom挂载完成了,数据也双向绑定了,可以访问dom节点了,操作dom的话用$refs试试,结果会让你满意的。
5)beforeUpdate 数据更新之前(响应式数据发生更新,虚拟dom重新渲染之前)触发,这个阶段可以进行数据更改,那会造成重渲染吗?答案当然是不会啦,放手干吧!
6)updated 更新完成,dom完成更新触发。注意注意:避免在这个期间更改数据,不然就导致无线循环的更新啦。
7)beforeDestroyed 实例销毁之前触发,当前阶段可以进行善后收尾工作,比如说清楚计时器之类的。
8)destroyed 实例销毁之后触发剩一个dom空壳,组件被拆解了,数据绑定被卸除,监听被移出,子实例也统统被销毁。
2 理解一下MVVM,和MVC有什么区别
##哎呀,先说说MVC吧,MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式,这种模式一般用于应用程序的分层开发。
Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。
View(视图) - 视图代表模型包含的数据的可视化。Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。
##再来说说MVVM吧,
MVVM是Model-View-ViewModel的简写,本质上就是MVC 的改进版。
即 MODEL 模型- VIEW 视图- VIEWMODEL 视图模型。
MODEL 指的是后端传递的数据。
VIEW 指的是所看到的页面。
VIEWMODEL mvvm模式的核心,它是连接view和model的桥梁。那么你得知道它做了什么,它做了两件事情,或者说它走了两个方向。
1)将 MODEL 转化成 VIEW,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。俗称联调接口,把后端返回的数据展示在前端页面。
2)将 VIEW 转化成 MODEL ,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。
上述两项都完成了,就是我们常说的,数据的双向绑定。
2、vue 2.0 如何实现的双向数据绑定(说白了就是考原理,有兴趣就去看看源码吧。)
先网上扒一张图过来
看图,首先就是data,在vue里,要实现对data的数据相应,利用Observer进行数据劫持。初始化数据之后,使用Object.definedProperty重新定义data中的所有属性,然后进行依赖收集,也就是图中的watcher(当前组件的watcher)
注释:Object.definedProperty(obj, prop, descriptor)
obj:必需。目标对象
prop:必需。需定义或修改的属性的名字
descriptor:必需。目标属性所拥有的特性
存取器了解一下,getter、setter
getter:当访问该属性时,该方法会被执行。函数的返回值会作为该属性的值返回
setter:当属性值修改时,该方法会被执行。该方法将接受唯一参数,即该属性新的参数值。
动动爪子试试
附一下代码:
var obj = {}; //定义一个新对象
var initVal = 'nihao'; //设置一个初始值
Object.defineProperty(obj,"newVal",{ //重定义数据,也就是数据劫持,数据代理
get:function (){//当获取值的时候触发的函数,也就是obj.newVal 的默认值是 nihao
return initVal;
},
set:function (value){//当设置值的时候触发的函数,设置的新值通过参数value拿到,如果设置新的值,obj.newVal就会发生改变
initVal = value;
}
});
//获取值
console.log( obj.newVal ); // 打印出来 hello
//设置值
obj.newVal = 'hao ge der';
console.log( obj.newVal ); //打印出来 hao ge der
打印结果 不信你自己试试
注意:当使用了getter或setter方法,不允许使用writable和value这两个属性
注意:不要在getter中再次获取该属性值,也不要在setter中再次设置改属性,否则会栈溢出
简单写个vue 数据代理和劫持
文件目录
简单示例
在js文件里面写个简单的vue 实例 进行数据代理 和数据劫持
class Vue {
// 构造器
constructor(params){
this.$options = params // vm
this._data = params.data //vm.data
this.initData() //初始化
}
initData(){
let data = this._data;
console.log(data) //结果如下
// {name: "xuxiansheng", message: "this is vue.js"}
// message: "this is vue.js"
// name: "xuxiansheng"
// proto: Object 原型
//收集所有集合中的key 结果是一个数组 也就是收集watcher
let keys = Object.keys(data); //结果如下
// ["name", "message"]
console.log(keys)
// 遍历数组 使用objectDefine.property()对数据进行代理
for(let i = 0; i < keys.length; i++){
// this指向vue实例
// 注意这里要用关键字let 而不要用var 否则会出现变量名提升 访问不到data中的数据
// Object.definedProperty(obj, prop, descriptor)
// obj:必需。目标对象
// prop:必需。需定义或修改的属性的名字
// descriptor:必需。目标属性所拥有的特性
Object.defineProperty(this, keys[i], {
// 可枚举?true or false
// 至于for...in循环和Object.keys方法的区别,在于for...in包括对象继承自原型对象的属性,而Object.keys只包括对象本身的属性。
enumerable:true,
// 是否可以删除目标属性或是否可以再次修改属性的特性
configurable:true,
get: function() { //获取属性值
return this._data[keys[i]]
},
set: function(value) { //设置新的值
// 设置值
data[keys[i]] = value
}
})
}
// 为了实现data 数据的响应,需要对data中的数据进行劫持
for(let i = 0;i < keys.length; i++){//遍历数组
// 拿到data对象里面 key 为 keys[i]的值
let value = data[keys[i]]
console.log(value) // xuxiansheng this is vue.js
Object.defineProperty(data, keys[i], {
enumerable:true,
configurable:true,
get: function reactiveGetter(){
console.log(`data的属性${keys[i]}的获取`);
return value;
},
set: function reactiveSetter(val){
console.log(`data设置属性${keys[i]}`)
value = val;
}
})
}
}
}
注意一下,get set函数里面打印不出来具体的属性值,所以reactiveGetter() reactiveSetter()这两个需要手动触发一下,才能打印里面的内容
你要想知道打印出来的是什么,,那就去浏览器的控制台输入命令 如下
有个东西值得好好注意一下,那就是没有进行数据劫持 只进行数据代理的时候,在浏览器控制台执行vm 回车 结果如下
但是做了数据劫持之后 如下图
vm明明是一个实例,可结果不一样。
所以数据代理劫持的目的就是为了实现vue里面的data数据访问的时候,如this.name 直接拿到的就是message的值,实际上this.name访问的应该是this.data.message
结合实际就是这么理解
以上是简单的数据劫持,如果遇到复杂的数据 那就进行扁平化处理,比如先判断当前数据的类型是复杂还是简单
简单数据注释掉 把它和复杂数据一起处理 封装一个方法
先判断数据类型
进行数据劫持
这个就暂时这样吧.....写太累了 后续整体更新吧
3 vue 2.x如何监测数据变化
那就是上面说的函数劫持了呗,重写了数组的方法(也就是上面提到的object.key()得到的数组),将data中的数据进行原型链的重写,这样一来调用api的时候就可以通知依赖更新了。
但是数组中如果包含引用类型的话,也就是复杂数据类型,然后进行递归遍历,实现数据监测,看不明白就自己动动爪子写写,好记性不如烂笔头
4 nextTick 实现原理是什么
在下一次dom更新循环结束之后执行延迟回调,这个主要使用了微任务和宏任务,不通执行环境分别尝试,promise mutationObserver setImmediate ,如果这三个都不行就用setTimeout 通过异步方法清空任务队列。
5 说说宏任务和微任务 (上个问题答了 这个问题紧随而来)
第一件事 先盗个图来
请自信告诉我,上面答应出来的是什么?
1、2、3、4? ×
2、4、1、3?×
2、4、3、1? ×
2、4、1 ? √ 想想为啥是这个答案
从一开始写代码的时候就知道一句话 js代码是从上往下一行一行执行的,所以很多人先入为主觉得输出应该是1234,可事实并非如此。
这就涉及到上面的宏任务 微任务了,结合上面宏任务微任务的图来看,同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。上述过程会不断重复,也就是面试官经常会问你的,什么是循环机制,这个就是Event Loop(事件循环)机制。
再偷个图
js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入eventqueue,然后在执行微任务,将微任务放入eventqueue。
记住这俩规则:
宏任务一般是:包括整体代码script,setTimeout,setInterval。
微任务:Promise,process.nextTick。
所以上述代码就是,setTimeout是宏任务先执行但是特么它是异步的,所以要往下检查是不是有微任务,遇到了new Promise立即执行之后,打印console.log('2'),Promise.then()又被放入了微任务的队列里,所以先去执行了宏任务console.log('4'),执行完成之后才执行了setTimeout,所有主线程的任务栈执行完了才去执行了Promise.then()
这块有点复杂 暂时就理解这么多 后续补上吧