diff算法优化:节点打Tag
vue3.0底层,会自动识别某个节点是否是动态的,如果是动态的会自动生成标识(不同的动态会有不同的标识对应,如内容文本的动态,或者id的动态),从而在每次更新dom时,直接跳过哪些静态的节点,直接定位到动态的节点,大大节省效率。
1 2 3 4 5
| <div> <p>text</p> <p>string</p> <p>{{msg}}</p> </div>
|
会转化为:
1 2 3 4 5 6 7
| export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", null, "text"), _createVNode("p", null, "string"), _createVNode("p", null, _toDisplayString(_ctx.msg), 1) ])); }
|
由上面代码可以看到前两个p标签和最后一个p标签的区别,最后一个标签1,表示该动态节点是TEXT类型,官方给出的详细说明:github
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| export const enum PatchFlags { TEXT = 1,
CLASS = 1 << 1,
STYLE = 1 << 2,
PROPS = 1 << 3,
FULL_PROPS = 1 << 4,
HYDRATE_EVENTS = 1 << 5,
STABLE_FRAGMENT = 1 << 6,
KEYED_FRAGMENT = 1 << 7,
UNKEYED_FRAGMENT = 1 << 8,
NEED_PATCH = 1 << 9,
DYNAMIC_SLOTS = 1 << 10,
HOISTED = -1,
BAIL = -2 }
|
静态提升 hoistStatic
1 2 3 4 5 6 7
| export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", null, "text"), _createVNode("p", null, "string"), _createVNode("p", null, _toDisplayString(_ctx.msg), 1) ])); }
|
在Vue3中使用了静态提升后,对于不参与更新的元素,只会被创建一次,在渲染时直接复用即可:
1 2 3 4 5 6 7 8 9
| const _hoisted_1 = _createVNode("p", null, "text", -1 ) const _hoisted_2 = _createVNode("p", null, "string", -1 ) export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _hoisted_1, _hoisted_2, _createVNode("p", null, _toDisplayString(_ctx.msg), 1) ])); }
|
事件侦听器缓存 cacheHandlers
默认情况下事件会被视为动态绑定,每次都会追踪它的变化,但是因为是同一个函数,所以将它直接缓存起来复用,就会提升性能:
1 2 3
| <div> <button @click="onclick">点击</div> </div>
|
默认会转化为:
1 2 3 4 5
| export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _ctx.onClick }, "点击", 8 , ["onClick"]); ])); }
|
开启cacheHandlers之后,则会转化成:
1 2 3 4 5 6 7
| export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args))) }, "点击", 8 , ["onClick"]); ])); }
|
使用Proxy代替Object.defineProperty()
ES6 原生提供 Proxy 构造函数,MDN上的解释为:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等),什么意思呢?可以理解为:在对象之前设置一个“拦截”,当该对象被访问的时候,都必须经过这层拦截,这就意味着你可以在这层拦截中,进行各种操作。
1
| const o = new Proxy(target, handler);
|
vue2.x实现数据响应的原理
递归遍历data中的数据,使用 Object.defineProperty()劫持 getter和setter,在getter中做数据依赖收集处理,在setter中 监听数据的变化,并通知订阅当前数据的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class Observer { constructor(data) { for(let key of Object.keys(data)) { if(typeof data[key] === 'object') { data[key] = new Observer(data[key]); } Object.defineProperty(this, key, { enumerable: true, configurable: true, get() { console.log('你访问了' + key); return data[key]; }, set(newVal) { console.log('你设置了' + key); console.log('新的' + key + '=' + newVal); if(newVal === data[key]) { return; } data[key] = newVal; } }) } } }
const obj = { name: 'app', age: '18', a: { b: 1, c: 2, }, } const app = new Observer(obj); app.age = 20; console.log(app.age); app.newPropKey = '新属性'; console.log(app.newPropKey);
|
这样做的问题:
检测不到对象属性的添加和删除:当你在对象上新加了一个属性newProperty,当前新加的这个属性并没有加入vue检测数据更新的机制(因为是在初始化之后添加的)。vue.$set是能让vue知道你添加了属性, 它会给你做处理,$set内部也是通过调用Object.defineProperty()去处理的
无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。
当data中数据比较多且层级很深的时候,会有性能问题,因为要遍历data中所有的数据并给其设置成响应式的。
所以,上面的输出会变成
1 2 3 4 5 6 7
| // 修改 obj原有的属性 age的输出 你设置了age 新的age=20 你访问了age 20 // 设置新属性的输出 新属性
|
vue3.0的实现
因为Proxy是拦截对象,对对象进行一个”拦截”,外界对该对象的访问,都必须先通过这层拦截。无论访问对象的什么属性,之前定义的还是新增的,它都会走到拦截中,而且不需要遍历data了,大大提升了性能。
上面的代码,如果用Proxy实现的话,则会是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const obj = { name: 'app', age: '18', a: { b: 1, c: 2, }, } const p = new Proxy(obj, { get(target, propKey, receiver) { console.log('你访问了' + propKey); return Reflect.get(target, propKey, receiver); }, set(target, propKey, value, receiver) { console.log('你设置了' + propKey); console.log('新的' + propKey + '=' + value); Reflect.set(target, propKey, value, receiver); } }); p.age = '20'; console.log(p.age); p.newPropKey = '新属性'; console.log(p.newPropKey);
|
同理,输出会变成:
1 2 3 4 5 6 7 8 9 10 11
| // 修改原对象的age属性 你设置了age 新的age=20 你访问了age 20
// 设置新的属性 你设置了newPropKey 新的newPropKey=新属性 你访问了newPropKey 新属性
|