vue中数组的响应式

目录

    • 一,vue2中的数组响应式原理
      • vue2中数组和对象操作方式的不同
    • 二,重写数组方法源码分析
      • 1,定义拦截器
      • 2,将拦截器挂载到数组上面
      • 3,收集依赖
    • 三,其他

一,vue2中的数组响应式原理

vue2中数组和对象操作方式的不同

在对象中增加或者删除属性的时候,数据的响应式原理是不奏效的,因为vue2是用的Object.definedProperty方法进行数据劫持。
因此在进行添加元素的时候,应该用$set来进行添加属性。使用$remove进行删除属性。

对于数组,因为数组也是一对象,但我们在使用数组的api进行操作数组(添加元素或者是删除元素)的时候,视图是有更新的。
这个的原因是为什么呢?

原本上,数组的一些方法比如push,pop是不会触发getter/setter的。
不会触发的原因是因为这是Array原型上的方法,并没有在Array本身上面。
vue可以使用这些方法的原因是因为vue重写了这些方法。就可以使用 push.pop.shift,unshift,splice,sort,reserve操作数组,并且进行响应式。

二,重写数组方法源码分析

实现的思路:大体上就是说,是使用了拦截器覆盖了Array.prototype上的方法,在执行原型上的方法之外去做数据的响应式。

  • 将数组的原型存到对象arrayMethods中

  • 找到Array上能够改变数组自身的7个方法 push, pop, shift,unshift, splice, sort, reverse

  • 将这7个方法进行响应式处理

  • 处理完成后,用它们把arrayMethods中对应的方法覆盖掉

  • 将需要进行响应式处理的数组arr的__proto__指向arrayMethods,如果浏览器不支持访问__proto__,则直接将响应式处理后的7个方法添加到数组arr上

  • 如果要将一个数组完全实现响应式,需要遍历该数组,将数组中的数组使用该方法进行响应式处理,将对象使用walk方法进行响应式处理

1,定义拦截器

// 获取Array的原型
const arrayProto = Array.prototype;
// 创建一个新对象,该新对象的原型指向Array的原型。
export const arrayMethods = Object.create(arrayProto);
[
	'push',
	'pop',
	'shift',
	'unshift',
	'splice',
	'sort',
	'reverse'
]
.forEach(mentod => {
	 // 缓存原始方法
	const original = arrayProto[method];
	// 对新原型对象上的方法,做数据绑定
	Object.defineProperty(arrayMethods, method, {
		value: function mutator(...args) {
			// 返回原始方法
			return original.apply(this, args); 
		},
		enumerable: false,
		writable: true,
		configurable: true
	})
})

2,将拦截器挂载到数组上面

import { arrayMethods } from './array' // 处理好的Array原型对象
// __proto__是否可用
const hasProto = '__proto__' in {};
// 所有属性名,不论是否可枚举(与Object.keys的区别)
const arrayKeys = Object.getOwnPropertyNames(arrayMethods);

export class Observe {
	// 将value转为响应式
	constructor (value) {
		this.value = value;

		if (Array.isArray(value)) {
			const augment = hasProto ? protoAugment : copyAugment;
			augment(value, arrayMethods, arrayKeys);
		} else {
			this.walk(value); // Object的响应式处理,在其他文章中
		}
	}
}

/**
* __proto__可用的处理方式
* 将target对象的原型对象替换为src
*/
function protoAugment(target, src, keys) {
	target.__proto__ = src;
}

/**
* __proto__不可用的处理方式
* 将src上面的所有属性都copy到target
*/
function copyAugment (target, src, keys) {
	for (let i = 0, len = keys.length; i < len; i ++) {
		const key = keys[i];
		def(target, key, src[key]);
	}
}

// Object.defineProperty()的封装
function def (obj, key, val, enumerable) {
	Object.defineProperty(obj, key, {
		value: val,
		enumerable: !!enumerable,
		writable: true,
		configurable: true
	})
}

3,收集依赖

收集依赖:

function defineReactive(data, key, val) {
    let childOb = observe(val);
    let dep = new Dep(); // 存储依赖
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            dep.depend();

            if (childOb) childOb.dep.depend(); // 收集
            return val;
        },
        set: function (newVal) {
            if (val === newVal) return;
            dep.notify();
            val = newVal;
        }
    })
}

// 返回val的响应式对象
function observe(val, asRootData) {
    if (!isObject(value)) return;
    let ob;
    // 避免重复侦测
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof observer) {
        ob = value.__ob__;
    } else {
        ob = new Observe(value)
    }
    return ob;
}

三,其他

但是针对

Let a = []
a[0] = 1
a.length = 0

下面这两种改变数组的方式是不会触发响应式的
可以使用$set方式来进行添加改变数组。或者是使用$remove来改变数组。

你可能感兴趣的:(Vue,vue.js,javascript,ecmascript)