Vue监听数组变化源码解析


Posted in Javascript onMarch 09, 2017

上一篇的代码中,忽略了对数组的处理,只关心了需要关心的部分,假装数组不存在。

这一篇开始考虑数组的问题。

从最简单的入手

先考虑一个问题,如何监听数组中的对象变化?忽略掉数组本身及其中的一般值,只考虑对象数组中的对象。

遍历数组,而后对数组中的每个对象调用 observe 方法

// 上一篇中出现的未曾重写的代码,这一篇中不再重复
var Observer = function Observer(value) {
  this.value = value;
  this.dep = new Dep();
  // 如果是数组,则遍历所有元素
  if(Array.isArray(value)) {
    this.observeArray(value);
  } else {
    this.walk(value);
  }
};
Observer.prototype.observeArray = function observeArray(items) {
  // 遍历数组所有元素,对单个元素进行 getter、setter 绑定
  for (var i = 0, l = items.length; i < l; i++) {
    observe(items[i]);
  }
};

现实的要求

实际实现中当然不会如上例那么简单,官方文档中对监听数组是这样描述的:

Vue 包含一组观察数组的突变方法,所以它们也将会触发视图更新。这些方法如下:
push()、pop()、shift()、unshift()、splice()、sort()、reverse()

由于 JavaScript 的限制, Vue 不能检测以下变动的数组:

当你直接设置一个项的索引时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength
所以,要对数组本身的一些方法进行监听。

经常要用到的一个小函数

def,在整个 Vue 源码中反复出现,利用Object.defineProperty() 在 obj 上定义属性 key(也又可能是修改已存在属性 key):

function def(obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    // 转变为 boole 值,如果不传参,转为 false
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  });
}

给对象添加一组方法

给对象添加一组方法,如果所在环境支持 proto,就简单了,直接把对象的 proto 指向这一组方法就好了;如果不支持,则遍历这一组方法,依次添加到对象中,作为隐藏属性(即 enumerable: false,不能被 in 关键字找到):

var hasProto = '__proto__' in {};
var augment = hasProto ? protoAugment : copyAugment;

function protoAugment(target, src) {
  target.__proto__ = src;
}
function copyAugment(target, src, keys) {
  for(var i = 0; i < keys.length; i++) {
    var key = keys[i];
    def(target, key, src[key]);
  }
}

先来一发简单的

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 种方法,这 7 种方法有可分为两类:

1、push()、unshift()、splice() 这三种可能会给数组添加新元素的方法;

2、其余的不会新增元素的方法。

为了避免污染全局的 Array,新建一个以 Array.prototype 为原型的对象,而后在这个对象本身附加属性,再把这个新建的对象作为原型或者作为属性添加到 Observer 的 value 中,来达到监视其变化的目的。

var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);

接着就是遍历需要触发更新的几个方法,依次将其附加到 arrayMethods 上:

['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function(method) {
  // 获取原始的数组操作方法
  var original = arrayProto[method];
  // 在 arrayMethods 上新建属性 method,并为 method 指定值(函数)
  // 即改写 arrayMethods 上的同名数组方法
  def(arrayMethods, method, function mutator() {
    var arguments$1 = arguments;

    var i = arguments.length;
    var args = new Array(i);
    // 将伪数组 arguments 转变为数组形式
    // 为何不用 [].slice.call(arguments)?
    while(i--) {
      args[i] = arguments$1[i];
    }
    var result = original.apply(this, args);
    // 因 arrayMethods 是为了作为 Observer 中的 value 的原型或者直接作为属性,所以此处的 this 一般就是指向 Observer 中的 value
    // 当然,还需要修改 Observer,使得其中的 value 有一个指向 Observer 自身的属性,__ob__,以此将两者关联起来
    var ob = this.__ob__;
    // 存放新增的数组元素
    var inserted;
    // 对几个可能有新增元素的方法单独考虑
    switch(method) {
      case 'push':
        inserted = args;
        break;
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        // splice 方法第三个参数开始才是新增的元素
        inserted =args.slice(2);
        break;
    }
    if(inserted) {
      // 对新增元素进行 getter、setter 绑定
      ob.observerArray(inserted);
    }
    // 触发方法
    ob.dep.notify();
    return result;
  });
};

var arrayKeys = Object.getOwnPropertyNames(arrayMethods);

更新 Observer

根据上例代码中的注释,改写 Observer,使得两者关联起来,达到监听数组变化的目的:

var Observer = function Observer(value) {
  this.value = value;
  this.dep = new Dep();
  def(value, '__ob__', this);
  // 如果是数组,则遍历所有元素
  if(Array.isArray(value)) {
    var argument = hasProto ? protoAugment : copyAugment;
    argument(value, arrayMethods, arrayKeys);
    this.observeArray(value);
  } else {
    this.walk(value);
  }
};

参考资料:
vue早期源码学习系列之二:如何监听一个数组的变化

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery动态创建html元素的常用方法汇总
Sep 05 Javascript
jquery实现选中单选按钮下拉伸缩效果
Aug 06 Javascript
Node.js实现数据推送
Apr 14 Javascript
jQuery绑定事件的几种实现方式
May 09 Javascript
使用JQuery中的trim()方法去掉前后空格
Sep 16 Javascript
判断颜色是否合法的正则表达式(详解)
May 03 Javascript
将jquery.qqFace.js表情转换成微信的字符码
Dec 01 jQuery
微信小程序使用slider设置数据值及switch开关组件功能【附源码下载】
Dec 09 Javascript
vue 子组件向父组件传值方法
Feb 26 Javascript
小程序日历控件使用方法详解
Dec 29 Javascript
JavaScript之Blob对象类型的具体使用方法
Nov 29 Javascript
js实现鼠标点击飘爱心效果
Aug 19 Javascript
Node.js通过身份证号验证年龄、出生日期与性别方法示例
Mar 09 #Javascript
基于jQuery实现一个marquee无缝滚动的插件
Mar 09 #Javascript
jQuery插件HighCharts绘制2D圆环图效果示例【附demo源码下载】
Mar 09 #Javascript
js实现显示手机号码效果
Mar 09 #Javascript
jQuery插件HighCharts绘制2D半圆环图效果示例【附demo源码下载】
Mar 09 #Javascript
javascript 秒表计时器实现代码
Mar 09 #Javascript
react实现pure render时bind(this)隐患需注意!
Mar 09 #Javascript
You might like
详细介绍PHP应用提速面面观
2006/10/09 PHP
php 获得汉字拼音首字母的函数
2009/08/01 PHP
php 伪造本地文件包含漏洞的代码
2011/11/03 PHP
让PHP更快的提供文件下载的代码
2012/06/13 PHP
php实现扫描二维码根据浏览器类型访问不同下载地址
2014/10/15 PHP
CI分页类首页、尾页不显示的解决方法
2016/03/28 PHP
php gd等比例缩放压缩图片函数
2016/06/12 PHP
PHP面向对象程序设计之对象克隆clone和魔术方法__clone()用法分析
2019/06/12 PHP
PHP中类与对象功能、用法实例解读
2020/03/27 PHP
AJAX架构之Dojo篇
2007/04/10 Javascript
用js实现随机返回数组的一个元素
2007/08/13 Javascript
javascript中IE浏览器不支持NEW DATE()带参数的解决方法
2012/03/01 Javascript
JavaScript数字和字符串转换示例
2014/03/26 Javascript
jquery常用特效方法使用示例
2014/04/25 Javascript
jquery通过select列表选择框对表格数据进行过滤示例
2014/05/07 Javascript
简单的jquery左侧导航栏和页面选中效果
2014/08/21 Javascript
JS限制文本框只能输入数字和字母方法
2015/02/28 Javascript
四种参数传递的形式——URL,超链接,js,form表单
2015/07/24 Javascript
JS模拟实现方法重载示例
2016/08/03 Javascript
js/jquery控制页面动态加载数据 滑动滚动条自动加载事件的方法
2017/02/08 Javascript
js实现数组去重方法及效率?Ρ? target=
2017/02/14 Javascript
jQuery插件Echarts实现的渐变色柱状图
2017/03/23 jQuery
基于JavaScript实现新增内容滚动播放效果附完整代码
2017/08/24 Javascript
开发Vue树形组件的示例代码
2017/12/21 Javascript
6种JavaScript继承方式及优缺点(小结)
2020/02/06 Javascript
使用Kivy将python程序打包为apk文件
2017/07/29 Python
python时间与Unix时间戳相互转换方法详解
2020/02/13 Python
Python查找不限层级Json数据中某个key或者value的路径方式
2020/02/27 Python
Python第三方库安装缓慢的解决方法
2021/02/06 Python
女孩每月服装订阅盒:kidpik
2019/04/17 全球购物
成语的广告词
2014/03/19 职场文书
交通事故赔偿协议书
2014/04/15 职场文书
银行职员自我鉴定
2014/04/20 职场文书
个人四风问题对照检查材料
2014/09/26 职场文书
经典格言警句:没有热忱,世间便无进步
2019/11/13 职场文书
MySQL完整性约束的定义与实例教程
2021/05/30 MySQL