vue3去年就已经发布了阿尔法版本,今年尤大表示只剩下些2.0的迁移工作,不过毕竟不想react和ng大树地下好乘凉,尤大单枪匹马的3.0究竟啥时候能到来谁也说不准,但是这并不耽误提前了解和学习,退一万步将哪怕胎死腹中,仍然可以嫖到思想。
github关注动态
简单聊下说烂了的2.0绑定方式,就是使用definedProperty中的get\set方法来完成数据劫持,之后在通过diff算法对比新老dom差异修改vdom,重新render完成渲染;
首先了解下definedProperty
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法
Object.defineProperty(obj, prop, descriptor)
值 | 释义 |
---|---|
obj | 要定义属性的对象。 |
prop | 要定义或修改的属性的名称或 Symbol 。 |
descriptor | 要定义或修改的属性描述符。 |
举个例子
let obj = {};
Object.defineProperty(obj, 'name', {
value: 'dahuang',
writable: false
});
console.log(obj.name);
// 'dahuang'
主要看下descriptor中的属性
属性 | 描述 | 默认值 |
---|---|---|
configurable | 描述符能否被改变 | false |
enumerable | 能否被枚举 | false |
value | 该属性对应的值。 | undefined。 |
writable | 能否被改写 | false |
get | 当访问该属性时调用 | undefined |
set | 当属性值被修改时调用。该方法接受一个被赋予的新值 | undefined |
重点就是这个get和set,对何时触发再举个例子
let obj = {a:1}
Object.defineProperty(obj,'a',{
get:function(){
},
set:function(){
}
})
obj.a //触发get函数
obj.a = 2 //触发set函数
了解了这些就可以创建一个简易版的响应式
function vue(){}//不想起名字就还叫vue了
vue.prototype.observe = function(obj){//完成数据绑定的方法
var val;
var that = this;
for(let key in obj){//遍历所有属性,依次设置监听
val = obj[key];
if(typeof val ==='object'){
this.observe(val) //如果是对象的话进行递归
}else{
Object.defineProperty(this.$data,key,{
get:function(){
//vue中还会进行依赖收集,确定变量需要在哪部分修改,属于优化操作,这里先不考虑,有兴趣可以看源码dep对象
//return this.$data[key]每次那到的都是上一次的值,所以外部保存变量
return val;
},
set:function(newVal){
val = newVal;
that.render();
}
})
}
}
}
vue.prototype.render = function(){}//完成渲染的方法
这里写的很糙,省去了很多东西,只是举个例子说下defineProperty,实际的vue中还做了很多别的,有兴趣可以看看源码,1040+行,或者想了解具体实现的推荐一篇博文
原理剖析
3.0由defineProperty改为了proxy;defineProperty是侵入原对象,改变了原有的一些东西,诸如枚举,读写之类的属性操作,上边列举属性表也可以看的出来,默认值都是false,换句话说就是默认的对象都是稳定的对象,而修改后就需要耗费额外的性能去做更多的处理;
proxy是一个代理,还是老规矩,先看属性
Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
语法
const p = new Proxy(target, handler);
值 | 描述 |
---|---|
target | 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 |
handler | 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。 |
handler对象方法
名称 | 描述 |
---|---|
handler.getPrototypeOf() | Object.getPrototypeOf 方法的捕捉器。 |
handler.setPrototypeOf() | Object.setPrototypeOf 方法的捕捉器。 |
handler.isExtensible() | Object.isExtensible 方法的捕捉器。 |
handler.preventExtensions() | Object.preventExtensions 方法的捕捉器。 |
handler.getOwnPropertyDescriptor() | Object.getOwnPropertyDescriptor 方法的捕捉器。 |
handler.defineProperty() | Object.defineProperty 方法的捕捉器。 |
handler.has() | in 操作符的捕捉器。 |
handler.get() | 属性读取操作的捕捉器。 |
handler.set() | 属性设置操作的捕捉器。 |
handler.deleteProperty() | delete 操作符的捕捉器。 |
handler.ownKeys() | Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。 |
handler.apply() | 函数调用操作的捕捉器。 |
handler.construct() | new 操作符的捕捉器。 |
重点还是看get\set,举个例子
let a = {b:1,c:2}
let proxya = new Proxy(a,{
get:function(){},
set:function(){}
})
乍看上去好像没什么区别,不过这里不对a直接进行操作,而是返回一个代理对象proxya;从代码上来看,也比defineProperty省去了for循环遍历取值的操作,代码更加简洁
把上边observe中的defineProperty替换成proxy对比看下
vue.prototype.observe = function(obj){
let that = this;
this.$data = new Proxy(this.$data,{
get:function(tag,key){
return tag[key]
},
set:function(tag,key,newVal){
tag[key] = newVal;
that.render();
}
})
}
高下立判;
另外就是对vdom的对比进行了升级;
2.0的diff算法简单来讲会将所有的dom节点依次展开,并获取属性和子节点;一段div.wrap>p.txt1+p.txt2
<div class='wrap'>
<p class='txt1'>asdp>
<p class='txt2'>{{asd}}p>
div>
展开大致就是
Diff
div
att from div
children from div
innerHTML from div
p
att from p
children from p
innerHTML from p
p
att from p
children from p
innerHTML from p
想表达的意思vdom会展开templent下所有节点,就是说如果txt1中并没有需要改变的数据,虚拟dom树在进行diff比对的时候仍然会展开并对比这个元素,无疑造成了性能的浪费;
3.0中又新增了一个block tree;对于目标模板,以vue指令进行切分,分成静态堆和动态堆,静态堆仅记录位置,而对比时只对比动态堆;
想了解block tree,可以参看vue3 bolck tree