想看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);        //12obj.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 keysconsole.log(Object.keys(obj));        //[ 'name', 'age' ]//called has nameconsole.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 fnconsole.log(p(1,2,3,4));        //100console.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类型的keylet 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'));        //trueconsole.log(Reflect.defineProperty(Obj,'sex',{value: 'female'}));        //trueconsole.log(Obj.sex);        //femaleconsole.log(Reflect.deleteProperty(Obj,'age');        //trueconsole.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));        //28console.log(map.get(obj2));     //absoluteconsole.log(map.get(obj3));     //undefinedconsole.log(map2.get(obj1));    //undefinedconsole.log(map.has(obj1));     //truemap.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不是同一个引用对象,因此可以addconsole.log(set);       //Set { 1, 2, { name: 'hello', age: 12 }, { name: 'hello', age: 12 } }console.log(set.has(1));             //true     若指定值存在于Set对象中则返回trueconsole.log(set.has(6));             //false    否则返回falseconsole.log(set.delete(1));          //true     成功删除指定元素则返回trueconsole.log(set.delete(6));          //false    否则返回falseconsole.log(set.has(1));             //falselet 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源码分析上有所帮助,更会对前端代码将来的趋势有所体会。希望本文能给您提供一些帮助,喜欢的话就点个赞吧~