vue响应式原理

211 阅读5分钟

vue特点

  1. 易用:用起来比较简单
  2. 灵活:我们可以只使用它的部分功能
  3. 高效:开发速率很快

渐进式框架

所谓的渐进式框架,就是它有一套完整的体系,我们可以只使用它的某一部分 比如说刚开始我们只使用它的生命周期渲染,然后要开发大型应用的话,就需要用到组件系统,把各个页面切成不同的组件 通过组件化的方式来使用vue,再然后又有了spa单页面应用这个概念,里面又包含了像客户端路由,路由也分为两种,像hash模式和history模式,又因为页面中有大量数据需要管理,就出现了vuex。然后又出现了vue-cli脚手架,进行整合。 这一套就叫做渐进式

数据响应式变化

  • vue2.0 Object.definePrope。不支持数组
  • vue3.0 Proxy

实现vue2.0中对象的响应式变化

1.创建1.js

// vue特点,如果是对象会使用Object.defineProperty但是它不支持数组
// 数组的话,会把数组方法重写

// 1.先创建一个对象
let obj = {
    name: 'dlb',
    //让对象的层级深一点
    localtion: { x: 100, y: 100 }
}
// 2.让这个obj变成响应式对象,定义一个更新视图的方法
function render () {
    console.log('更新视图')
}
// 4.那我们就要观察这个对象,只要这个对象有属性变化了,就要调用render方法
// 5.创建一个观察方法
function observer (obj) {
    //里面需要判断入参obj的类型,如果是对象才进行处理
    if (typeof obj == 'object') {
        // 如果是对象,给obj里面每个属性定义成set/get的方式
        // 因为属性可以是多个的,这里需要用forin循环
        for (let key in obj) {
            // 循环完以后单独写个方法去定义,因为vue里还有一些其他的api
            // 比如Vue.$set(),这些 api也可以定义响应式的数据
            // 这里面调用定义响应式方法,相当于这里要把obj的属性重新定义给obj
            // 把参数传进去
            defineReactive(obj, key, obj[key])
        }
    }
}

//7.创建一个定义set/get的定义响应式方法,三个参数,数据、key、值
function defineReactive (data, key, value) {
    // 9.处理深一层的属性,因为value也有可能是一个对象,所以这里也要对vulue进行观察
    observer(value)
    // 内部用的就是Object.defineProperty,给当前的数据定义要给key,值是对象
    /**
     * Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
     * data 要定义属性的对象。
     * key 要定义或修改的属性的名称或 Symbol 。
     * obj 要定义或修改的属性描述符。
     */
    Object.defineProperty(data, key, {
        // 对象里面先加上set和get方法
        get () {
            // get的时候需要把value返回去
            return value
        },
        set (newValue) {
            // set的时候需要修改这个value
            // 如果newValue也是一个对象,需要对newValue也进行观察
            observer(newValue)
            // 还需要判断下新值和旧值不一致才进行处理
            if (newValue !== value) {
                // 并且在设置之前需要调用更新视图方法
                render()
                // 把set方法的入参newValue赋给value
                value = newValue
            }
        }
    })
}
// 6.观察obj
observer(obj)
// 3.现在要做的一件事就是,把对象某个属性更改掉,就要调用render方法
// 8.试下在这里改
obj.name = 'lb'
obj.name = 'lb2'
// 8.修改深一层的属性
obj.localtion.x = 998
obj.localtion = {
    name: 'dlb2',
    localtion: { x: 998, y: 889 }
}
// 9.新增属性的处理
// vue的特点,(1)如果给对象新增属性,是不会被监控的
// (2)vue新增属性要使用$set()
obj.a = 100
// 给对象新增参数的两种方式
// 方法一:如果想要给对象增加不存在的属性,就给obj重新赋值一个对象,把原来的覆盖
// 例:obj.localtion = { ...obj.localtion, 新key: 新value }
// 方法二:手动调用$set
// $set的实现, 接受参数对象,key,值,
function $set (data, key, value) {
    // 调用响应式代码
    defineReactive(data, key, value)
}
// 用的时候,参数:给谁设置、设置那个属性,值
$set(obj, 'a', 998)
console.log(obj.a, 'obj')

  • 运行代码,可以看到视图更新了4次

实现vue2.0中数组的响应式变化

// 1.先创建数组
let obj = [1, 2, 3]
// 更新视图方法
function render () {
    console.log('更新视图')
}
// 3.定义数组方法集合 删除最后一个元素 删除首个元素 在开头添加一个元素 排序 反转 指定位置添加新项 追加
let methods = ['pop', 'shift', 'unshift', 'sort', 'reverse', 'splice', 'push']
// 4.把这几个方法进行重写
// 4.1 先获取到原型上的数组方法
let arrayProto = Array.prototype
// 创建一个自己的原型,并且重写methods方法
let proto = Object.create(arrayProto)
// 4.2 循环methods,对proto的方法进行重写,
// (个人理解,重写的目的是为了能自定义回调,就可以在回调里面执行更新试图方法)
methods.forEach(method => {
    //4.3 把方法作为属性重新设置上去,进行重写
    proto[method] = function () {
        // 使用继承,拿到原生的数组方法,把this传进去,让它不能操作原来的方法,但是能引用到原来的方法 
        arrayProto[method].call(this, ...arguments)
        // 只要重写方法,就调用更新试图方法更新试图
        render()
    }
})
// 观察者方法
function observer (obj) {
    //2. 判断是否为数组,对数组进行处理
    if (Array.isArray(obj)) {
        // 把重写的方法,挂到原型链上去
        obj.__proto__ = proto
        return
    }
    //判断是否为对象,对对象进行处理
    if (typeof obj == 'object') {
        for (let key in obj) {
            defineReactive(obj, key, obj[key])
        }
    }
}

//创建一个定义set/get的定义响应式方法,三个参数,数据、key、值
function defineReactive (data, key, value) {
    observer(value)
    Object.defineProperty(data, key, {
        get () {
            return value
        },
        set (newValue) {
            observer(newValue)
            if (newValue !== value) {
                render()
                value = newValue
            }
        }
    })
}
// 观察obj
observer(obj)
// 增加不存在的属性或数组元素
function $set (data, key, value) {
    // $set方法下同样要对数组单独处理
    if (Array.isArray(data)) {
        // 当前用户调用了重写的splice方法,所以会触发更新视图方法
        return data.splice(key, 1, value) // 在指定位置添加
    }
    defineReactive(data, key, value)
}

obj.push(123)
// 必须通过$set的方式来新增元素,或者替换成一个新的数组
$set(obj, 0, 668)
obj[1] = 555 // 错误操作,这样不会更新视图,vue2.0正式版这样操作也不会更新视图
obj.length -- // 错误操作,这样不会更新视图,vue2.0正式版这样操作也不会更新视图
console.log(obj)

实现vue3.0中对象的响应式变化

// Proxy优点
// 1.可以解决数组的问题,更改数组的索引、更改长度,它都可以实现更新视图
// 2.不用分类单独处理对象和数组
// proxy缺点
// 1.兼容性稍微差些

// 使用proxy来实现数据的响应式变化

// 2.定义一个更新视图方法
function render () {
    console.log('更新视图')
}

// 1.创建一个对象
let obj = {
    name: 'dlb',
    age: 25,
    age2: { age: 25 },
    // 5.加个数组试下效果
    arr: [0, 1, 2]
}
// 3.1创建配置对象
let handler = {
    //  目标对象  属性 
    get (target, key) {
        // 如果当前的值为对象,且不为null
        if (typeof target[key] == 'object' && target[key] !== null) {
            // 再把值代理一次,把handler递归一次
            return new Proxy(target[key], handler)
        }
        // return target[key] // 上下两句一个效果
        return Reflect.get(target, key)
    },
    //  目标对象  属性   值    原对象
    set (target, key, value, receiver) {
        // 在对数组赋值的时候,会修改数组的长度,所以要对长度特殊处理,不更新视图
        if (key === 'length') {
            return true
        }
        // 每次设置值的时候更新下视图
        render()
        return Reflect.set(target, key, value) // 这里需要返回布尔值
    }
}
// 3.创建一个代理来操作这个对象,参数一是对象,参数二是处理配置
let proxy = new Proxy(obj, handler)
// 4.需要达到效果,一调用代理后的对象的属性,视图就需要更新
// 4.1 只要一更新代理后的对象属性,就会触发代理的配置内的set方法
//
proxy.age = 997
// 4.2处理多层的情况
proxy.age2.age = 998
// 5.1修改数组的值
proxy.arr[0] = 999
proxy.arr.push(1000)
console.log(proxy)