一、Object.defineProperty(核心api)
基本用法:
const data = {};
const name = 'zhangsan';
Object.defineProperty(data, 'name', {
get: function() {
return name;
},
set: function(newVal) {
name = newVal
}
})
注意:
- 对象需要深度监听,递归到底,一次性计算量大
- 无法监听添加/删除属性,需要配合
Vue.set和Vue.delete. - 无法原生监听数组,需要特殊处理,重写数组原型方法,触发更新操作
// 触发更新操作
function updateView() {
console.log('视图更新')
}
// 重新定义数组原型
const oldArrayProperty = Array.prototype;
// 创建新的数组对象, 原型指向oldArrayProperty,在扩展新的方法
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function () {
// 更新视图
updateView();
oldArrayProperty[methodName].call(this, ...arguments);
// 等价于 Array.property[methodName].call(this,...arguments);
}
})
// 重新定义属性, 监听属性值
function defineReactive(target, key, value) {
// 深度监听(对象)
observer(value);
// 核心api
Object.defineProperty(target, key, {
get: function() {
return target[key];
},
set: function(newVal) {
if(newVal !== value) {
value = newVal;
// 设置新值是一个对象 深度监听
observer(newVal);
// 触发更新视图
updateView();
}
}
})
}
// 监听对象属性
function obsrver(target) {
// 不是对象或者数组
if(typeof target !== 'object' || target === null) {
return;
}
// 数组重新定义监听方法
if(Array.isArray(target)) {
target.__proto__ = arrProto;
}
// 重新定义各个属性
for(let key in target) {
defineReactive(targte, key, target[value])
}
}
const data = {
name: 'zhangsan',
age: 20,
info: {
address: 'hangzhou' // 深度监听
},
list: [1, 2,3]
}
observer(data);
二、组件渲染、更新过程
1.初次渲染过程
template模板解析为render函数- 触发响应式,监听data属性getter setter
- 执行render函数(触发getter), 生成vnode,
patch(elem, vnode)
2.更新过程
- 修改data属性值, 触发setter
- 重新执行render函数, 生成newVnode
patch(vnode, newVnode), 更新页面
3.template模板解析
(1)解析器:将模板解析成AST(抽象语法树); (2)优化器:遍历AST标记静态节点(虚拟dom更新节点时判断是否包含该节点,是否渲染); (3)代码生成器:生成render函数。
4.render函数
跟template类似,来创建html模板
- 接收参数:
createElement - 返回值:
VDom
createElement用法:接受三个参数
- 一个 HTML 标签字符串,必需。
- 标签属性,可选。
- 子虚拟节点 (VNodes),可选。
return createElement(
'div',
{
style: {
color: '#333',
border: '1px solid #ccc'
}
},
[
'text', // 文本节点直接写就可以
createElement('div', _header) // createElement()创建的VNodes
]
)
三、diff算法
1.diff比较
- 只比较同一层级,不跨层级比较
tag不同则直接删除重建节点tag和key相同则为同一节点。
v-for使用key原因:diff算法根据tag和key来判断是否为同一节点,来决定节点是否复用,减少渲染次数,提升性能。
2.patch函数
- patch(elem, vnode)渲染新的节点
- patch(vnode, newVnode) 新旧vDOM对比,判断节点是否可复用。