《工欲善其事,必先利其器》
大家好,我是vk。之前在学习《Vue2.0 —— 由设计模式切入,实现响应式原理》一文中有提及到,2版本的响应式是依靠 Object.defineProperty 实现的。但考虑到当时的用户群体和时代背景,将这个API与 Proxy 权衡利弊之后,最终采用了这个。为此开发者们不得不手动的来适配它。诸如:Vue.set、Vue.del 和 arrayMthods 等等手段来完善。
现如今,随着 IE 浏览器的落幕,3版本的响应式已经更新为 Proxy 拦截,因此 Vue.set 也随之被废弃。但是碍于部分公司技术栈还未完全过渡到3版本,面试的时候还是会问这种知识点,所以就复习一下。


(图片来自官方截图)
Vue.set 和 vm.$set 在实现的本质上并无异同,唯一的区别仅是前者是全局API,后者是前者的别名,仅此而已。两者的返回值都是你设置的值。
但是使用的过程中却有很多需要注意的点:
Vue 实例,或实例的根属性;Vue.set 用法/**
* {Object | Array} target
* {string | number} propertyName/index
* {any} value
*/
Vue.set(target, key, val);
其中,参数 target 就是你需要添加的对象,它只能是引用数据类型。分别为对象和数组,因此第二个参数对应的分别是属性名和数组下标。参数三则没有要求,任何属性的值都可以传入。
vm.$set 用法/**
* {Object | Array} target
* {string | number} propertyName/index
* {any} value
*/
this.$set(target, key, val);
跟上面的用法一致。
源码就不多废话了,直接上代码,各处我都添加了注释,大家多看,手写一遍,理解就行了。面试之前记得再回来看一遍:
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;
}
其中代码的实现穿插了几个工具函数,我也在下面放出来,方便大家理解:
// 判断参数是否不为 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);
}
好了,本文就到这里了,有点水,有疑问的小伙伴可以在评论区讨论,看见会第一时间回复。
感谢你的阅读~ 愿你的未来一片光明。