• vue中数组的响应式


    一,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
    	})
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    三,其他

    但是针对

    Let a = []
    a[0] = 1
    a.length = 0
    
    • 1
    • 2
    • 3

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

  • 相关阅读:
    飞书项目发布3个月,已签约理想汽车、安克创新等100余家公司
    Linux上:安装、网络配置
    ChinaSoft 论坛巡礼 | 系统与网络安全论坛
    【学习笔记01】node的认识和安装
    利用适配器模式使用第三方库
    开课通知 | 《AISHELL-3语音合成实战》课程
    softmax,softmax loss和交叉熵的关系
    IB-MYP课程知道为什么这么难吗?从课程讲起
    JavaWeb 请求响应路径调试
    JavaScript 基础了解
  • 原文地址:https://blog.csdn.net/wlijun_0808/article/details/127714522