Vue2.0 vs Vue3.0 响应式原理

11 篇文章 0 订阅

Vue2.0 vs Vue3.0 响应式原理

Vue2.0 Object.defineProperty()

只学习用法

// Object.defineProperty()

// 参数:obj           prop        descriptor
//  需要定义的对象 属性名称      描述符(配置集合)
function defineProperty() {
    var _obj = {}
    Object.defineProperty(_obj, 'a', {
        value: 1
    })
    return _obj
}

var obj = defineProperty()

console.log(obj)

这就是使用方法,也可以如下

function defineProperty() {
    var _obj = {}
    Object.defineProperties(_obj, {
        a: {
            value: 1
        },
        b: {
            value: 2
        }
    })
    return _obj
}

var obj = defineProperty()

console.log(obj)

然后你会发现

obj.a = 5
console.log(obj)//属性值不可修改

for(var k in obj){
    console.log(K + ':' + obj[k])
}//属性不可枚举

delete obj.a
console.log(obj)//属性不可删除

实际上

// 实际上
function defineProperty() {
    var _obj = {}
    Object.defineProperties(_obj, {
        a: {
            value: 1,
            writable:false,//默认为false,不可修改,所以改为true,才能修改
            enumerable:false,//默认为false,不可枚举,所以改为true,才能枚举
            configurable:false//默认为false,不可删除,所以改为true,才能删除
        },
        b: {
            value: 2
        }
    })
    return _obj
}

在一个,如果设置了value或者writable中的任意一个,那么就不能设置get和set,否则会报错

function defineProperty() {
    var _obj = {},a=2;
    // 每个属性定义的时候 会内置有 getter setter
    Object.defineProperties(_obj, {
        a: {
            get(){
                console.log('get触发')
                return a
            },
            set(newVal){
                console.log('set触发')
                a = newVal;
            }
        }
    })
    return _obj
}

var obj = defineProperty()
obj.a = 1 //触发set
console.log(obj.a)//触发get 打印1 然后才是打印obj.a

如果是数组的话

function DataArr (){
    var _val = null,_arr = [];
    Object.defineProperty(this,'val',{
        get(){
            return _val
        },
        set(newVal){
            _val =newVal
            _arr.push({val:_val})
            console.log('set')
        }
    })

    this.getArr = function(){
        return _arr
    }
}

var dataArr = new DataArr()

dataArr.val = 123
dataArr.val = 234
console.log(dataArr.getArr())

在Vue2.x中,数组中的变化没有引起视图的更新是一个缺陷。
所以通过索引或者length又或者pop/push等数组api来改变数组触发defineProperty的set方法引起视图更新是不可行的。
解决方法一般有以下两种

<body>
    <div id="app">
        {{colors}}
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    colors: ['red', 'green', 'blue']
                }
            }
        })
        // 比如我现在要改变第0项
        vm.colors[0] = 'black'//发现无效
        // 只能通过
        vm.$set(vm.colors, 0, 'black')
        // 第一项是数组,第二项是索引,第三项是值 来重新设置数组,并更新视图
        // 或者
        vm.colors.splice(0, 1, 'black')
        // 因为vue是劫持了splice方法的(实际上是大量重写的数组的API) 使用splice操作数组以后,视图会更新

        // 又比如 我现在想要改变colors的长度,删除最后一项
        vm.colors.length--
        // 无效 视图不更新
        // 于是使用
        vm.$delete(vm.colors, 1);//删除1项
        // 或者
        vm.colors.splice(2)
    </script>
</body>

Vue2.x的响应式原理,重点在于vm和diff算法,使用Object.defineProperty只是实现数据劫持的一种手段,在Vue3.0中,就换成使用ES6 的new Proxy来实现了。

Vue3.0 new Proxy()

let obj = new Proxy(target, handler)
// target 目标对象  你需要进行处理的对象
// handler 容器  里面有若干可以处理对象属性的方法

从这里可以看出proxy和defineProperty的区别,defineProperty是定义一个对象和其中的属性,而proxy是传入目标对象,传入的对象中可能已经有一些属性或者方法了。

下面看

var target = {
    a: 1,
    b: 2
}

let proxy = new Proxy(target, {
    get(target, prop) {
        console.log('prop val ' + target[prop])
    },
    set(target, prop, value) {
        console.log('set ' + JSON.stringify(target) + ' 中的 ' + prop + ' = ' + value)
        target[prop] = value
    }
})
console.log(proxy.a) //这里undefined 是因为get没有返回值 如果直接return target[prop] 就可以拿到proxy.a的值了
console.log(target.a)
proxy.b = 3
console.log(target.b)//proxy相当于target的代理,代理的属性改变,那么原对象的值也会改变

那么如果是数组呢?

let arr = [{
        name: '小明',
        age: 18
    },
    {
        name: '小红',
        age: 17
    },
    {
        name: '小黄',
        age: 15
    },
    {
        name: '小仔',
        age: 16
    },
    {
        name: '小小',
        age: 10
    },
    {
        name: '小呆',
        age: 5
    }
]

let persons = new Proxy(arr, {
    get(arr, prop) {
        return arr[prop]
    },
    set(arr, prop, value) {
        arr[prop] = value
    }
})

console.log(person[3])
person[1] = {
    name: '小张',
    age: 20
}
console.log(persons, arr)

在这里插入图片描述
可以看到proxy出来的persons实际上还是一个对象,他的target是arr,handler中是我们定义的get和set方法。

那么,如果是函数呢?

let fn = function () {
    console.log('fn')
}
fn.a = 123

let newFn = new Proxy(fn, {
    get(fn, prop) {
        return fn[prop]
    }
})
console.log(newFn.a)

可以发现,引用类型的数据都是可以进行代理。

下面尝试用Object.defineProperty来重写Proxy

// 重写
function MyProxy(target, handler) {
    let _target = deepClone(target);
    Object.keys(_target).forEach((key) => {
        Object.defineProperty(_target, key, {
            get() {
                return handler.get && handler.get(target, key)
            },
            set(newVal) {
                handler.set && handler.set(target, key, newVal)
            }
        })
    })

    return _target

    function deepClone(org, tar) {
        var tar,
            toStr = Object.prototype.toString,
            arrType = '[object Array]';
            tar = toStr.call(org) === arrType ? [] : {}
        for (var key in org) {
            if (org.hasOwnProperty(key)) {
                if (typeof (org[key]) === 'object' && org[key] !== null) {
                    tar[key] = deepClone(org[key], tar[key])
                } else {
                    tar[key] = org[key]
                }
            }
        }
        return tar
    }
}
let target = [{b:2},{a:1}]
let proxy = new MyProxy(target, {
    get(target, prop) {
        return 'get:' + prop + '=' + JSON.stringify(target[prop])
        // return target[prop]
    },
    set(target, prop, value) {
        target[prop] = value
        console.log('set:' + prop + '=' + value)
    }
})

console.log(proxy[0],proxy[1].a)
proxy[0] = 3

下面在看一下另外几个方法

var target = {
    a: 1,
    b: 2
}

let proxy = new Proxy(target, {
    get(target, prop) {
        console.log('prop val ' + target[prop])
    },
    set(target, prop, value) {
        console.log('set ' + JSON.stringify(target) + ' 中的 ' + prop + ' = ' + value)
        target[prop] = value
    },
    has (target,prop){
        console.log(target[prop])
    },
    deleteProperty(target,prop){
        delete target[prop]
        console.log('delete触发了')
    }
})

console.log('a' in proxy)//false 实际上proxy下面只有[[handler]] [[target]] [[isRevoked]]三个属性,而a在[[target]]中

delete proxy.b
console.log(proxy) //你会发现b没有了,且出发了deleteProperty函数

再讲一下Reflect

var target = {
    a: 1,
    b: 2
}

let proxy = new Proxy(target, {
    get(target, prop) {
        // 直接return target[prop],其实也不是特别好,我们希望函数式编程来返回结果
        return Reflect.get(target, prop)
        // Reflect实际上是一个对象方法的容器,(对象底层有14中方法来操作对象),通过它提供的get API 我们能够实现 相同的效果
    },
    set(target, prop, value) {
        target[prop] = value
        // 同理
        Reflect.set(target, prop, value)
        // 这种函数方法 是有返回值的, const isOk = Reflect.set(target, prop, value)  可以打印看一下结果
    }
})
// 在Object上有很多很多方法,这其实并不合理,于是后来整理了部分API放入了Reflect中,Reflect则相当于作为一个公共方法集合的容器,来对这些方法进行统一的管理
// 在一个 Object中很多操作和方法 都会抛出异常,你需要用try catch来捕获异常,或者是使用各种关键字如 in 等等来驱动,而Reflect的方法,调用以后则有返回值,这是一种更合理的编程方式
// Reflect 是一个全局对象,里面全部是静态方法,你可以全局直接使用,而不用实例化。就像Math一样

最后说一下Object底层的14中方法

// ECMAScript 对 对象操作 有14中方法
var obj = {
    a: 1,
    b: 2
}
// 1 获取原型 [[GetPrototyOf]]
var proto = Object.getPrototypeOf(obj)
console.log(proto)
// 或者
console.log(obj.__proto__)
console.log(obj.prototype)
// 实际上三种方法都是可以的

// 2 设置原型 [[SetPrototypeOf]]
Object.setPrototypeOf(obj, {c:3,d:4})
// 同理只用__proto__ 或者prototype直接赋值也是可以的
console.log(obj)

// 3 获取对象的可扩展性 [[IsExtensible]]
var extensible = Object.isExtensible(obj)
console.log(extensible)
Object.freeze(obj)
var extensible2 = Object.isExtensible(obj)
console.log(extensible2)

// 4 封闭对象
Object.seal(obj)
obj.c = 3
console.log(obj)//不可新增,不可删除,但可写

// 5 冻结对象 
Object.freeze(obj) //然后只可读,不可新增,不可删除,不可写,但是可以枚举

// 4 获取自有属性 [[GetOwnProperty]]
Object.setPrototypeOf(obj,{c:3,d:4})
console.log(Object.getOwnPropertyDescriptor(obj))
console.log(Object.getOwnPropertyNames(obj))
console.log(Object.getOwnPropertySymbols(obj))

// 5 禁止扩展对象 [[PreventExtensions]]
Object.preventExtensions(obj)
obj.c = 3
console.log(obj)

// 6 拦截对象操作 [[DefineOwnProperty]]
Object.defineProperty()

// 7 判断属性是否是自身属性 [[HasProperty]]
console.log(obj.hasOwnProperty('a'))

// 8 [[GET]]
console.log('a' in obj)
console.log(obj.a)

// 9 [[SET]]
obj.a=3
obj['b'] = 4
console.log(obj)

// 10 [[Delete]]
delete obj.a
console.log(obj)

// 11 [[Enumerable]]
// for... in ...

// 12 获取键集合 [[OwnPropertyKeys]]
console.log(Object.keys(obj))

// 13 调用函数
function test(){}
test()
//call / apply属于函数调用,相当于在Object分支下面,bind不是

obj.test =function(){}

obj.test()


// 14  实例化过程 new
function Test(){}
new Test()

这次就总结到这里。我也是看b站小野的视频学习的,有些东西讲的是通俗易懂。但有的代码示例什么,建议还是手敲一遍,有的有逻辑错误,自己发现改正并实现对于你的学习有更好的帮助。
加油学习。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值