为什么可以用Object.assign触发$watch
Object.assign,这个api在简单拷贝可枚举对象的属性值时经常用到。这里介绍一个在vue2中Object.assign的用法,这个用法在官网文档 有详细介绍:
1 2 3 4 5 6 7 | watch: { someObject(nvalue, ovalue) { ... } } // 为对象添加新属性 this .someObject = Object.assign({}, this .someObject, { a: 1, b: 2 }) |
而且要注意的是如果像下面这样添加上去的新属性无法触发更新:
1 | this .someObject = Object.assign( this .someObject, { a: 1, b: 2 }) |
问题是为什么前面那种写法会有效?
先看vue2文档
在vue2的文档中有详细说明,在组件的依赖收集过程中,所有property 在被访问和修改时会通知变更,对于对象来说,Vue 无法检测 property 的添加或移除。
一般情况下,在vue中,如果要对data对象中实例添加根级别property,我们可以这样操作:
1 | Vue.set(someObject, 'name' , value) |
或者这样操作
1 | this .$set( this .someObject, 'name' ,2) |
但是如果我们要对一个对象添加多个属性,同时还要保持对象的响应性,这种情况下就要用到开篇提到的方法。
在mdn 上,对 Object.assign
有这一句解释:该方法使用源对象的[[Get]]
和目标对象的[[Set]]
,所以它会调用相关 getter 和 setter。对这句,我们用下面的例子a来理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | var obj = {}; var c = null Object.defineProperty(obj, 'c' , { set: function (x){ console.log( 'c被赋值:' ,x); c=x }, get: function (){ console.log( 'c被取出:' ,c) return c } }) obj.c=3 //c被赋值: 3 obj.c //c被取出: 3 obj = Object.assign(obj, {c: 'wer23e' }) // 触发了set! obj = Object.assign(obj, {a: 'wer23e' }) // 由于事先未用defineProperty定义a,所以无法监听 // 由于目标对象未定义属性,无法监听 obj = Object.assign({},obj, {a: 'wer23e' ,c: 'dfrr23e' }) |
通过这段代码可以理解上面说的“Object.assign会使用目标对象的[[Set]]
”,同时,这段代码也演示了vue2中响应原理,因为vue2中所有需要响应的属性都是用 Object.defineProperty
进行响应绑定,这样所有的访问和修改动作都会被追踪到。
但是对于没有事先被 Object.defineProperty
定义的属性,比如添加一个属性就无法监听到了。在上面的示例中,即使我用文档提到的用法 obj = Object.assign({},obj, {a: 'wer23e',c: 'dfrr23e'})
仍然无法触发c属性的 set
。走到这一步,是不是得看watch源码了?其实不必!
用Object.assign触发watch原理
针对这个问题,watch的源码不必看,但是 Object.assign
的源码必须要看,
1 2 3 4 5 6 7 8 9 10 | if ( typeof Object.assign !== 'function' ) { // Must be writable: true, enumerable: false, configurable: true Object.defineProperty(Object, "assign" , { value: function assign(target, varArgs) { // .length of function is 2 'use strict' ; if (target === null || target === undefined) { throw new TypeError( 'Cannot convert undefined or null to object' ); } var to = Object(target); for ( var index = 1; index |
其实就是把 assign
方法中的参数的可枚举属性全部复制到此方法的第一参数上去。回头再去理解下例子a, obj = Object.assign({},obj, {a: 'wer23e',c: 'dfrr23e'})
无法触发c属性的 set
函数是因为,obj引用关系已经被改变了,不再是原来那个对象,也就没有了对应的属性监控,但是为什么官方文档会建议这么用呢?
接下来,我写了个小demo,来帮你理解,你可以试下效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <template><div style= "width: 100px; height: 50px; position: absolute; top: 100px" > 点我啊 {{ test.a }}</div> </template><script> export default { data() { test: { a: 3 }, }, watch: { test(n, o) { debugger console.log(n) } }, methods: { clickme() { debugger this .test = Object.assign( this .test, { a: 1 }) debugger this .test = Object.assign( this .test, { a: 1, b: 2 }) debugger this .test = Object.assign({}, this .test, { a: 1, b: 2 }) }, } } </script> |
可以看到,确实,最后一种Object.assign方法会触发test对象监听。前面两种写法,只能触发对象的属性更新响应,如果给obj对象添加属性,就无法监测到obj的变化。到这一步,其实如果要彻底弄清楚,最好还是看下 watch
源码。我在仔细分析了后,才发现其实跟watch
源码没有什么关系,所以本文就不展开分析了,这是另一篇文章的事了。
如果看不明白 watch
源码也没关系,其实简单解释就是,用obj = Object.assign({},obj, {a: 'wer23e',c: 'dfrr23e'})
这种方式,改变了obj的引用关系,也就是obj的值变了,所以如果你在watch函数中监听了obj,obj是变化了的,只不过obj的值是一个 Object
对象而已,所以会触发obj对象的响应。
参考资料:
developer.mozilla.org/zh-CN/docs/…
https://www.jb51.net/article/265630.htm
https://www.jb51.net/article/266683.htm
以上就是Object.assign触发watch原理示例解析的详细内容,更多关于Object.assign触发watch的资料请关注IT俱乐部其它相关文章!