• Vue2.0 —— Vue.set(vm.$set) 源码探秘


    Vue2.0 —— Vue.set (vm.$set)源码探秘

    《工欲善其事,必先利其器》

    一、官方说明

    大家好,我是vk。之前在学习《Vue2.0 —— 由设计模式切入,实现响应式原理》一文中有提及到,2版本的响应式是依靠 Object.defineProperty 实现的。但考虑到当时的用户群体和时代背景,将这个API与 Proxy 权衡利弊之后,最终采用了这个。为此开发者们不得不手动的来适配它。诸如:Vue.setVue.delarrayMthods 等等手段来完善。

    现如今,随着 IE 浏览器的落幕,3版本的响应式已经更新为 Proxy 拦截,因此 Vue.set 也随之被废弃。但是碍于部分公司技术栈还未完全过渡到3版本,面试的时候还是会问这种知识点,所以就复习一下。

    官方截图
    官方截图
    (图片来自官方截图)

    二、使用方式

    Vue.setvm.$set 在实现的本质上并无异同,唯一的区别仅是前者是全局API,后者是前者的别名,仅此而已。两者的返回值都是你设置的值。

    但是使用的过程中却有很多需要注意的点:

    1. 被添加属性的对象需要是响应式的,否则无法刷新视图;
    2. 被添加的属性不能是 Vue 实例,或实例的根属性;
    3. 被添加的属性其数据实例的数量不能大于0。
    Vue.set 用法
    /**
     * {Object | Array} target
     * {string | number} propertyName/index
     * {any} value
    */
    
    Vue.set(target, key, val); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其中,参数 target 就是你需要添加的对象,它只能是引用数据类型。分别为对象和数组,因此第二个参数对应的分别是属性名和数组下标。参数三则没有要求,任何属性的值都可以传入。

    vm.$set 用法
    /**
     * {Object | Array} target
     * {string | number} propertyName/index
     * {any} value
    */
    
    this.$set(target, key, val); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    跟上面的用法一致。

    三、源码解析

    源码就不多废话了,直接上代码,各处我都添加了注释,大家多看,手写一遍,理解就行了。面试之前记得再回来看一遍:

    import { isArray, isPrimitive, isUnDef, isValidArrayIndex } from "./utils";
    
    /**
     * 
     * {Object | Array} target
     * {string | number} propertyName/index
     * {any} value
     * 
    */
    export default function set(target, key, val) {
        // 判断排除基础数据类型和生产环境
        if (process.env.NODE_ENV !== "production" && (isUnDef(target) || isPrimitive(target))) {
            console.warn(`Cannot set reactive property on undefined, null, or primitive value: ${target}`);
        }
        // 获取target的响应式实例对象
        const ob = target.__ob__;
        // 验证key下标,Vue 改写过数组数据实例的原型,所以会触发splice方法,达到响应式
        if (isArray(target) && isValidArrayIndex(key)) {
            target.length = Math.max(target.length, key);
            target.splice(target, 1, val);
            // 这里还有一个深度监听,即:数组的子项也有可能是object。但是不实现了,属于边缘情况
            return val;
        }
        // 判断是否已存在
        if (key in target && !(key in Object.prototype)) {
            target[key] = val;
            return val;
        }
        // 判断target是否属于根实例的属性,如果是不允许修改
        // 因为vmCount是根据Watcher的实例数量来收集的,假如某个数据有多个依赖,一旦修改了就所有Watcher都会被通知更新
        if (target._isVue || (ob && ob.vmCount)) {
            process.env.NODE_ENV !== "production" && console.warn(
                `Avoid adding reactive properties to a Vue instance or its root $data
                at runtime - declare it upfront in the data option.`
            );
            return val;
        }
        // 不是响应式数据
        if (!ob) {
            target[key] = val;
            return val;
        }
        // 是响应式数据
        defineReactive$$1(ob.value, key, val);
        // 通知视图更新
        ob.dep.notify();
        // 返回值
        return val;
    }
    
    • 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
    • 49

    其中代码的实现穿插了几个工具函数,我也在下面放出来,方便大家理解:

    // 判断参数是否不为 undefined
    export const isDef = function(v) {
        return v !== undefined;
    }
    
    // 判断参数是否为 undefined 或 null
    export const isUnDef = function(v) {
        return v === undefined || v === null;
    }
    
    // 判断参数是否为数组
    export const isArray = function(v) {
        return isDef(v) && Array.isArray(v);
    }
    
    // 判断参数是否为基本数据类型
    export const isPrimitive = function(v) {
        return (typeof v === "string" || typeof v === "number" || v instanceof String || v instanceof Number);
    }
    
    // 判断参数是否为合适的数组下标值
    export const isValidArrayIndex = function(v) {
        const n = parseFloat(String(v));
        return n >= 0 && Math.floor(n) === n && isFinite(v);
    }
    
    • 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

    好了,本文就到这里了,有点水,有疑问的小伙伴可以在评论区讨论,看见会第一时间回复。

    感谢你的阅读~ 愿你的未来一片光明。

  • 相关阅读:
    Reactive.Net绑定Subscribe调用wpf控件报错
    【毕业设计】机器学习二维码识别检测系统 - python opencv 机器视觉
    k8s--基础--21--Statefulset
    云原生 | Kubernetes - 通过KubeKey用脚搭建k8s高可用集群 + KubeSphere
    STM32CubeMX教程25 PWR 电源管理 - 睡眠、停止和待机模式
    大模型微调方法
    Deepstream用户手册——DeepStream应用及配置文件
    qsort 函数的使用
    Docker -- 01实践:使用Docker 快速安装Jenkins
    Python调用Prometheus监控数据并计算
  • 原文地址:https://blog.csdn.net/LizequaNNN/article/details/126937867