想看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源码分析上有所帮助,更会对前端代码将来的趋势有所体会。希望本文能给您提供一些帮助,喜欢的话就点个赞吧~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。