vue源码学习之Object.defineProperty 对数组监听

网友投稿 420 2023-01-30


vue源码学习之Object.defineProperty 对数组监听

上一篇中,我们介绍了一下defineProperty 对对象的监听,这一篇我们看下defineProperty 对数组的监听

数组的变化

先让我们了解下Object.defineProperty()对数组变化的跟踪情况:

var a={};

bValue=1;

Object.defineProperty(a,"b",{

set:function(value){

bValue=value;

console.log("setted");

},

get:function(){

return bValhttp://ue;

}

});

a.b;//1

a.b=[];//setted

a.b=[1,2,3];//setted

a.b[1]=10;//无输出

a.b.push(4);//无输出

a.b.length=5;//无输出

a.b;//[1,10,3,4,undefined];

可以看到,当a.b被设置为数组后,只要不是重新赋值一个新的数组对象,任何对数组内部的修改都不会触发setter方法的执行。这一点非常重要,因为基于Object.defineProperty()方法的现代前端框架实现的数据双向绑定也同样无法识别这样的数组变化。因此第一点,如果想要触发数据双向绑定,我们不要使用arr[1]=newValue;这样的语句来实现;第二点,框架也提供了许多方法来实现数组的双向绑定。

对于框架如何实现数组变化的监测,大多数情况下,框架会重写Array.prototype.push方法,并生成一个新的数组赋值给数据,这样数据双向绑定就会触发。

实现简单的对数组的变化的监听

var arrayPush = {};

(function(method){

var original = Array.prototype[method];

arrayPush[method] = function() {

// this 指向可通过下面的测试看出

console.log(this);

return original.apply(this, arguments)

};

})('push');

var testPush = [];

testPush.__proto__ = arrayPush;

// 通过输出,可以看出上面所述 this 指向的是 testPush

// []

testPush.push(1);

// [1]

testPush.push(2);

在官方文档,所需监视的只有 push()、pop()、shift()、unshift()、splice()、sort()、reverse() 7 种方法。我们可以遍历一下:

var arrayProto = Array.prototype

var arrayMethods = Object.create(arrayProto)

;[

'push',

'pop',

'shift',

'unshift',

'splice',

'sort',

'reverse'

].forEach(function(item){

Object.defineProperty(arrayMethods,item,{

value:function mutator(){

//缓存原生方法,之后调用

console.log('array被访问');

var original = arrayProto[item]

var args = Array.from(arguments)

original.apply(this,args)

// console.log(this);

},

})

})

完整代码

function Observer(data){

this.data = data;

this.walk(data);

}

var p = Observer.prototype;

var arrayProto = Array.http://prototype

var arrayMethods = Object.create(arrayProto)

;[

'push',

'pop',

'shift',

'unshift',

'splice',

'sort',

'reverse'

].forEach(function(item){

Object.defineProperty(arrayMethods,item,{

value:function mutator(){

//缓存原生方法,之后调用

console.log('array被访问');

var original = arrayProto[item]

var args = Array.from(arguments)

original.apply(this,args)

// console.log(this);

},

})

})

p.walk = function(obj){

var value;

for(var key in obj){

// 通过 hasOwnProperty 过滤掉一个对象本身拥有的属性

if(obj.hasOwnProperty(key)){

value = obj[key];

// 递归调用 循环所有对象出来

if(typeof value === 'object'){

if (Array.isArray(value)) {

var augment = value.__proto__ ? protoAugment : copyAugment

augment(value, arrayMethods, key)

observeArray(value)

}

new Observer(value);

}

this.convert(key, value);

}

}

};

p.convert = function(key, value){

Object.defineProperty(this.data, key, {

enumerable: true,

configurable: true,

get: function(){

console.log(key + '被访问');

return value;

},

set: function(newVal){

console.log(key + '被修改,新' + key + '=' + newVal);

if(newVal === value) return ;

value = newVal;

}

})

};

var data = {

user: {

// name: 'zhangsan',

age: function(){console.log(1)}

},

apg: [{'a': 'b'},2,3]

}

function observeArray (items) {

for (var i = 0, l = items.length; i < l; i++) {

observe(items[i])

}

}

//数据重复Observer

function observe(value){

if(typeof(value) != 'object' ) return;

var ob = new Observer(value)

return ob;

}

//辅助方法

function def (obj, key, val) {

Object.defineProperty(obj, key, {

value: val,

enumerable: true,

writable: true,

configurable: true

})

}

// 兼容不支持__proto__的方法

//重新赋值Array的__proto__属性

function protoAugment (target,src) {

target.__proto__ = src

}

//不支持__proto__的直接修改相关属性方法

function copyAugment (target, src, keys) {

for (var i = 0, l = keys.length; i < l; i++) {

var key = keys[i]

def(target, key, src[key])

}

}

var app = new Observer(data);

// data.apg[2] = 111;

data.apg.push(5);

// data.apg[0].a = 10;

// console.log(data.apg);


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:详解如何使用babel进行es6文件的编译
下一篇:SpringAnimation 实现菜单从顶部弹出从底部消失动画效果
相关文章

 发表评论

暂时没有评论,来抢沙发吧~