共计 5556 个字符,预计需要花费 14 分钟才能阅读完成。
想看 Vue3 的源码,想了解 Vue3 的响应式原理、深度代理、如何避免多次 trigger,想了解 Vue3 的 Computed、Ref、Effect,想了解 Vue3 的 DOM-Diff、模板编译,就必须要了解一下这些基础知识:
Proxy
Proxy 可用来包装一个任意类型的对象,包括数组,函数,甚至另一个代理。
对数据的处理,对构造函数的处理,对数据的验证,说白了,就是在我们访问对象前添加了一层拦截,可以过滤很多操作,而这些过滤,由你来定义。
let proxy = new Proxy(target,handler)
target:需要使用 Proxy 包装的目标对象,可以是任意类型的对象,包括数组、函数、甚至另一个代理;
handler:一个对象,其属性是当执行一个操作时定义代理行为的函数。
举个栗子:
let obj = {name: 'demo'};
obj = new Proxy(obj,{get(target,key,receiver){console.log('获取了 getter 属性');
if(key === 'name'){target[key] = 'name:' + target[key];
}
return target[key];
}
})
console.log(obj.name);
// 获取了 getter 属性
//name:demo
recevier
表示 Proxy 或者继承 Proxy 的对象。
这个例子很简单,定义一个 obj 对象,然后通过 Proxy 进行了包装,此时 obj 成为了 Proxy 实例,我们对 obj 的操作都将通过 Proxy 进行拦截。
通过 set 也可以进行一些拦截操作:
let obj = {
name: 'demo',
age: 10
};
obj = new Proxy(obj,{set(target,key,value,receiver){if(key === 'age' && typeof value !== 'number'){throw new Error('age 字段必须为 Number 类型');
}
target[key] = value;
return true;
}
});
obj.age = 12;
console.log(obj.age); //12
obj.age = 'aaa'; //Error: age 字段必须为 Number 类型
set()方法应当返回一个 true,用来表示属性设置的成功
handler 中不仅有 get 和 set 的拦截器,一般常用的还有:
handler.has()
handler.ownKeys()
handler.apply()
handler.construct()
handler.getPrototypeOf()
handler.setPrototypeOf()
handler.defineProperty()
handler.deleteProperty()
再举一个栗子
let obj = {
name: 'hello',
age: 12
};
let p = new Proxy(obj,{has(target,key){console.log('called has' + key);
return key in target
},
ownKeys(target){console.log('called own keys');
let arr = Reflect.ownKeys(target);
return arr;
}
});
//called own keys
console.log(Object.keys(obj)); //['name', 'age']
//called has name
console.log('name' in obj); //true
ownKeys
必须返回可枚举对象,否则就会违反 Proxy 约束,抛出类型错误。类似地,has 必须返回一个 boolean 属性的值,若拦截的对象不可扩展、不可被配置也会违反约束,抛出类型错误。
function sum(...params){this.sum = params.reduce((prev,current) => prev + current,0)
return this.sum;
}
let p = new Proxy(sum,{apply(target,thisArg,argumentList){console.log('called apply fn');
return target(...argumentList) * 10;
},
construct(target,argumentList,newTarget){return new target(...argumentList);
}
})
//called apply fn
console.log(p(1,2,3,4)); //100
console.log(p(1,2,'3',4));
//throw new TypeError('参数必须是数字类型!')
let res = new p(1,2,3,4);
console.log(res.sum); //10
apply 用于拦截函数的调用,target 为目标函数,thisArg 为被调用时上下文对象 (即 this 指向),argumentList 为被调用时的参数 数组
construct 用户拦截 new 操作符,即函数发生 new 行为时将被拦截,因此被代理的对象必须自身具有 construct 内部方法。construct 方法必须返回一个对象。
Reflect
Reflect
不是一个函数对象 / 构造函数,因此它不可以用来被 new。
Reflect
的所有属性和方法都是静态的(就像 Math 一样)。
Reflect 出现的最直接表现就是,保持 JS 的简单,举个栗子
let s = Symbo('foo');
let obj = {
name: 'hello',
age: 12,
[s]: 1
}
console.log([s]);
//getOwnPropertyNames 获取 string 类型的 key;getOwnPropertySymbols 获取 Symbol 类型的 key
let res = Object,getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))
等同于
let s = Symbo('foo');
let obj = {
name: 'hello',
age: 12,
[s]: 1
}
let res = Reflect.ownKeys(obj,'name');
//['name', 'age', Symbol(foo) ]
- Reflect 将 Object 对象的一些明显属于语言内部的方法拿了过来;
- 修改了某些 Object 方法的返回结果,比如
Object.defineProperty()
在无法定义属性时会抛出一个错误,而Reflect.defineProperty()
则会返回 false; - 并且 Reflect 让 Object 的操作都变成了函数行为,一些命令式的操作都转为了函数式行为,例如
name in obj
,delete obj[name]
等操作; - 只要是 Proxy 对象有的方法,Reflect 与其一一对应,这可以使 Proxy 更方便地调用 Reflect 方法。
再看一下 Reflect.apply 的用法,它是通过指定的参数列表发起对目标函数的调用:
let ages = [11, 33, 12, 54, 18, 96];
let youngest = Reflect.apply(Math.min,Math,ages);
console.log(youngest); //11
可以理解为调用 Math.min 方法,this 指向为 Math,参数为 ages;相比于 Math.min.apply(Math,ages),Reflect 的最大好处是可以避免别人也写了一个同名的 apply 函数时,再去写一长串代码:
Function.prototype.apply.call(context,...args)
Reflect.set(target,key,value,receiver);
Reflect.get(target,key,receiver);
Reflect.has(target,key);
Reflect.defineProperty(target,key,attributes);
Reflect.deleteProperty(target,key);
Reflect.getPrototypeOf(target);
Reflect.setPrototypeOf(target,prototype);
Reflect.isExtensible(target); // 判断一个对象是否可扩展
Reflect.getOwnPropertyDescriptor(target,key); // 返回给定属性描述符
以上列举了 Reflect 的方法,下面是一些属性是使用示例:
let Obj = {
name: 'hello',
age: 12
}
console.log(Reflect.has(Obj,'name')); //true
console.log(Reflect.defineProperty(Obj,'sex',{value: 'female'})); //true
console.log(Obj.sex); //female
console.log(Reflect.deleteProperty(Obj,'age'); //true
console.log(Obj.age); //undefined
通过这里例子的展示,能感受到反射机制主要是用来从一个类中获取额外的元信息,或者是给类的一些方法添加注解,在实时运行中获取到对应的元信息。
这不仅仅在一个对象上生效,更在 Proxy 及对应的 traps 上也有效。
WeakMap
WeakMap` 对象是一组键值对的集合,其中键是弱引用,键必须是对象 而值可以是任意值。
WeakMap
对每个键对象都是 弱引用 ,意味着在没有其他引用存在时垃圾回收能正确进行;但由于是弱引用,WeakMap
的 key 又是不可枚举的。因此,如果你想往对象上添加数据,又不想干扰垃圾回收机制,就可以选择WeakMap
。
let map = new WeakMap();
let map2 = new WeakMap();
let obj1 = {};
let obj2 = function(){};
let obj3 = global;
map.set(obj1,28);
map.set(obj2,'absolute');
map.set(obj3,undefined);
console.log(map.get(obj1)); //28
console.log(map.get(obj2)); //absolute
console.log(map.get(obj3)); //undefined
console.log(map2.get(obj1)); //undefined
console.log(map.has(obj1)); //true
map.delete(obj1);
console.log(map.has(obj1)); //false
WeakMap
的原型上只有四个方法:set,get,delete,has;掌握这四种方法十分容易,现在是不是对 WeakMap 已经了解了呢?
Set
Set 对象允许存储任何类型的唯一值。它是值的集合,并且 相同的元素只会出现一次,即元素是唯一的。
一个例子展示所有方法
let set = new Set();
set.add(1);
set.add(2);
set.add(2); // 重复的值不会被添加进去
set.add(3).add(4); // 可以链式调用
console.log(set); //Set {1, 2}
let obj = {
name: 'hello',
age: 12
};
set.add(obj);
set.add({name: 'hello',age: 12}) // 这里 add 的对象和 obj 不是同一个引用对象,因此可以 add
console.log(set); //Set {1, 2, { name: 'hello', age: 12}, {name: 'hello', age: 12} }
console.log(set.has(1)); //true 若指定值存在于 Set 对象中则返回 true
console.log(set.has(6)); //false 否则返回 false
console.log(set.delete(1)); //true 成功删除指定元素则返回 true
console.log(set.delete(6)); //false 否则返回 false
console.log(set.has(1)); //false
let setIterator = set.entries();
console.log(setIterator.next().value); //[2,2]
console.log(setIterator.next().value); //[3,3]
console.log(setIterator.next().value); //...
console.log(setIterator.next().value); //[{ name: 'hello', age: 12}, {name: 'hello', age: 12} ]
set.clear(); // 用来清空 Set 对象中所有元素
console.log(set) //Set {}
值得注意的是 entries 方法,它返回的是一个新的迭代器对象,类似 [value,value] 形式的数组。因为 Set 集合不像 Map 一样有 key,因此为了和 Map 形式保持一致,使得每一个 entry 的 key 和 value 都拥有相同的值,所以是 [value,value] 形式。
小结
掌握了这些基本概念,不仅在 Vue3 源码分析上有所帮助,更会对前端代码将来的趋势有所体会。希望本文能给您提供一些帮助,喜欢的话就点个赞吧~