资讯专栏INFORMATION COLUMN

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

CatalpaFlat / 2468人阅读

摘要:对于框架如何实现数组变化的监测,大多数情况下,框架会重写方法,并生成一个新的数组赋值给数据,这样数据双向绑定就会触发。实现简单的对数组的变化的监听指向可通过下面的测试看出通过输出,可以看出上面所述指向的是在官方文档,所需监视的只有种方法。

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

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

var a={};
bValue=1;
Object.defineProperty(a,"b",{
    set:function(value){
        bValue=value;
        console.log("setted");
    },
    get:function(){
        return bValue;
    }
});
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.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);

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107806.html

相关文章

  • vue框架的基本原理,简单实现一个todo-list

    摘要:前言最近在学习框架的基本原理,看了一些技术博客以及一些对源码的简单实现,对数据代理数据劫持模板解析变异数组方法双向绑定有了更深的理解。 前言 最近在学习vue框架的基本原理,看了一些技术博客以及一些对vue源码的简单实现,对数据代理、数据劫持、模板解析、变异数组方法、双向绑定有了更深的理解。于是乎,尝试着去实践自己学到的知识,用vue的一些基本原理实现一个简单的todo-list,完成...

    Karrdy 评论0 收藏0
  • JavaScript 进阶之深入理解数据双向绑定

    摘要:当我们的视图和数据任何一方发生变化的时候,我们希望能够通知对方也更新,这就是所谓的数据双向绑定。返回值返回传入函数的对象,即第一个参数该方法重点是描述,对象里目前存在的属性描述符有两种主要形式数据描述符和存取描述符。 前言 谈起当前前端最热门的 js 框架,必少不了 Vue、React、Angular,对于大多数人来说,我们更多的是在使用框架,对于框架解决痛点背后使用的基本原理往往关注...

    sarva 评论0 收藏0
  • Vue源码学习(三)——数据双向绑定

    摘要:就是用于把变化放入观察,并通知其变化更新。这边数据双向绑定差不多就结束了。下一章节通过数据绑定原理结合来实现数据驱动更新的。 在Vue中我们经常修改数据,然后视图就直接修改了,那么这些究竟是怎么实现的呢?其实Vue使用了E5的语法Object.defineProperty来实现的数据驱动。那么Object.defineProperty究竟是怎么实现的呢?我们先来看一下一个简单的demo...

    sevi_stuo 评论0 收藏0
  • vue的数据驱动原理及简单实现

    摘要:监听器构造函数被监听数据属性遍历监听函数属性被监听了,现在值为监听器被监听对象构造函数所有入参监听数据更新视图实现在流程介绍中,我们需要创建一个可以订阅者的订阅器,主要负责手机订阅者,属性变化的时候执行相应的订阅者,更新函数。 1、目标实现 理解双向数据绑定原理; 实现{{}}、v-model和基本事件指令v-bind(:)、v-on(@); 新增属性的双向绑定处理; PS:实例源...

    caoym 评论0 收藏0
  • vue源码学习Object.defineProperty 象属性监听

    摘要:参考版本源码版本相关实现双向数据绑定的关键是,让我们先来看下这个函数。我们可能会有对象中属性的值还是对象这种嵌套情况,可以通过递归解决在源代码文件中观察者构造函数通过过滤掉一个对象本身拥有的属性递归调用循环所有对象出来被访问被修改,新被访问 参考版本 vue源码版本:0.11相关 vue实现双向数据绑定的关键是 Object.defineProperty ,让我们先来看下这个函数。 在...

    teren 评论0 收藏0

发表评论

0条评论

CatalpaFlat

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<