前言
本系列查阅顺序:
vue2 数据响应式原理——数据劫持(初始篇)
vue2 数据响应式原理——数据劫持(对象篇)
vue2 数据响应式原理——数据劫持(数组篇)
vue2 数据响应式原理——依赖收集和发布订阅
通过前两篇的学习,想必你已经对Object.defineProperty()和对象的侦听有了一定的理解,现在就让我们来继续研究如何使用Object.defineProperty()来对数组进行数据劫持,以便我们能够侦听到数组的变化。
数据劫持(数组篇)
首先新建一个:
array.js
import { def } from "./utils.js";//获取数组的原型const arrayProto = Array.prototype;//以Array.prototype为原型创建arrayMethods对象,并将该对象暴露出去//之后数组调用这7个方法时,让数组调用arrayMethods对象上我们已经进行重写的这7个方法export const arrayMethods = Object.create(arrayProto);const methodName = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse",];methodName.forEach(function(method) { //备份原来的方法 const original = arrayProto[method]; // 定义新的方法:在arrayMethods对象上进行 def( arrayMethods, method, function(...args) { //执行原生Array.prototype上的方法,实现push等这7个方法的功能 //不能直接original()调用,因为这样调用的话original的上下文是window对象 //需要使用apply改变original的上下文为调用该函数的数组 //因为push这7个方法中有些方法(例如pop,splice)含有返回值 //所以我们需要利用result储存一下返回值,之后再把result return出去 const result = original.apply(this, args);
//监听到数组改变之后就可以执行一些我们需要增加的操作了:
//数组肯定不是最高层,比如obj.c是数组,则obj一定不是数组,第一层遍历obj对象时,已经给c属性(就是这个数组)添加了__ob__属性。 //所以我们能拿到这个数组的__ob__ //这里的this指的就是调用该函数的数组,因为该函数绑定到了arrayMethods对象上的push等7个属性上,而arrayMethods对象被我们变成了数组的原型 //所以我们对数组执行push等7个方法时,实际就是数组调用了它arrayMethods原型上对应得方法,也即是该函数 //所以该函数的this指向是调用该函数的数组 const ob = this.__ob__; //push,unshift,splice会向数组内增加元素,这个增加的元素我们需要调用数组身上的Observer类(即__ob__)上的observeArray方法,对新增的元素进行响应式处理 let inserted; switch (method) { case "push": case "unshift": inserted = args; break;
case "splice": //splice格式是splice(下标,数量,插入的新项) inserted = args.slice(2); break; } if (inserted) { ob.observeArray(inserted); }
console.log("调用了我们改写的7方法");
return result; }, false );});
复制代码
在数组中,我们知道能够改变数组本身的方法只有七种:
push
pop
shift
unshift
splice
sort
reverse
所以在对数组进行数据劫持,侦听数组的变化时我们只需要对这七个方法进行一定的改写。
数组的方法都是在数组的原型Array.prototype中,所以看array.js我们就能理解为什么第一步要先获取到Array.prototype。
之后我们以Array.prototype为原型创建了一个arrayMethods 对象,并在该对象上利用def函数添加了那七个方法,添加这些方法时我们对原本的那七个方法进行了封装,增加了一些我们侦听到数组变化后需要执行的操作,之后我们只需让数组的原型变成我们写的arrayMethods 对象,这样在数组调用push等这七个方法时实际是在调用arrayMethods.push而非Array.prototype.push。
上述代码中的 ob.observeArray实际就是Observer类上的observeArray方法,并且让数组的原型变成我们写的arrayMethods 对象都需要在Observer.js中书写,所以我们需要对Observer.js进行一些修改:
Observer.js
import { def } from "./utils.js";import defineReactive from "./defineReactive.js";import { arrayMethods } from "./array.js";import observe from "./observe.js";export default class Observer { constructor(value) { def(value, "__ob__", this, false); //检查它是不是数组 if (Array.isArray(value)) { //如果是数组,将该数组的原型指向我们定义的arrayMethods //以便使用数组那7个方法时是调用arrayMethods上我们处理过的而不是Array.prototype上的 Object.setPrototypeOf(value, arrayMethods); //调用数组的遍历函数 this.observeArray(value); } else { //调用对象的遍历函数 this.walk(value); } } //遍历对象 walk(value) { for (let k in value) { defineReactive(value, k); } } //遍历数组 observeArray(arg) { //这里不直接i < l.length,是为了防止数组在遍历过程中出现长度的变化 for (let i = 0, l = arg.length; i < l; i++) { // 逐项进行observe observe(arg[i]); } }}
复制代码
至此我们就实现了对数组的变化侦测:
let obj = { c: [1, 2, 3, { p: "我是p呀!!!" }],};observe(obj);
console.log(obj.c[3].p);obj.c[3].p = "哇哈哈";console.log("-------------");obj.c.push(9);console.log("-------------");const a = obj.c.splice(1, 2, 888);console.log("splice的返回值", a);
复制代码
可以看到每当我们调用能够改变数组的方法时都会输出《调用了我们改写的 7 方法》这句话,说明我们已经能够侦听到数组的变化,并且数组内有对象时,当我们改变该对象的值时也能够被侦测到。
关于 vue2 数据响应式原理中数据劫持的部分到这里就结束了,接下来来看:vue2 数据响应式原理之依赖收集和发布订阅
评论