vue相关原理

312 阅读2分钟

一、Object.defineProperty(核心api)

基本用法:

const data = {};
const name = 'zhangsan';
Object.defineProperty(data, 'name', {
    get: function() {
        return name;
    },
    set: function(newVal) {
        name = newVal
    }
})

注意:

  • 对象需要深度监听,递归到底,一次性计算量大
  • 无法监听添加/删除属性,需要配合Vue.setVue.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不同则直接删除重建节点
  • tagkey相同则为同一节点。

v-for使用key原因:diff算法根据tag和key来判断是否为同一节点,来决定节点是否复用,减少渲染次数,提升性能。

2.patch函数

  • patch(elem, vnode)渲染新的节点
  • patch(vnode, newVnode) 新旧vDOM对比,判断节点是否可复用。