Vue30前置还不知道这些就来不及啦

5次阅读

共计 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 源码分析上有所帮助,更会对前端代码将来的趋势有所体会。希望本文能给您提供一些帮助,喜欢的话就点个赞吧~

正文完
 0