导入:结合vue2到vue3的响应式原理变更来理解
vue2的响应式建立过程不是动态的,必须在对象声明期间用静态的 Object.defineProperty() 方法或通过使用计算值(仅适用于新的浏览器)显式地应用于每个属性。
不适合观察整个对象或执行非常简单的操作。因此,ES6引入了代理。代理是内置的 JS 对象,可用于拦截和更改与对象相关的不同操作的行为。
Proxy会创建一个新对象来交互,而不是之前与原始对象直接交互(原始对象在使用 setter/getter 时会直接修改)。在使用 Proxy 的情况下,原始对象(也称为 target)可以理解为一种存储。你对其执行的任何操作都会直接影响代理,但不会触发其任何 trap。
代理的 trap 是执行特定操作时调用的简单方法。它们都是在单个 handler 对象上定义的,然后传递给 Proxy 构造函数。
我的理解:关注语法本身,可以把代理和target理解成两个对象。代理注册了监听,监听是挂载在代理对象上的(只有通过代理才能调用handler),但是代理的操作目标是target(handler的代码中可以对target做操作)。
**代理是真实JavaScript对象的透明抽象层。**设计模式
是一种新的基础性语言能力,很多转译程序不支持把代理行为转换为之前的ECMAScript代码。可以检测代理是否存在,不存在提供后备代码。
代理可以定义包含 捕获器 的处理程序对象,而这些捕获器可以拦截绝大部分JavaScript的基本操作和方法。
在捕获器处理程序中,在遵从捕获器不变式的前提下,可以修改任何基本操作的行为。
捕获不定式trap invariant
因方法不同而异,通常会防止捕获器定义出现过于反常的行为
.e.g:属性是不可修改的情况下,get返回一个与该属性不同的值
与代理随形的反射API:封装了与捕获器拦截的操作相对的方法。可以将其看作一套基本操作,是绝大部分JavaScript对象API的基础。
Reflect 对象的设计目的
(1) 将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty ),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。
(2)修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc) 在无法定义属性时,会抛出一个错误,定义成功时返回修改后的对象。而 Reflect.defineProperty(obj, name, desc) 在定义属性成功时返回 true ,失败时返回 false。
应用
开发者可以使用代理创建出各种编码模式,跟踪属性访问、隐藏属性、阻止修改或者删除属性、函数参数验证、构造函数参数验证、数据绑定、可观察对象。
const target={
id:'target',
o:{},
};
const handler={};//捕获器
const proxy=new Proxy(target,handler);//Proxy构造函数,创建代理(目标对象和处理程序对象)
console.log(target.o === proxy.o);//true接下来无论是给目标属性还是代理属性赋值,都会反映在两个对象上
console.log(target === proxy);//false严格模式可以区分代理和目标
一种基本操作对应一个拦截器,每个处理程序对象可以包含一个或多个。
可以直接或者间接在代理对象上调用,代理会在操作传播到目标对象之前调用捕获器函数。
trap捕获器是借用OS中的概念,是程序流中的一个同步中断,可以暂停程序流,转而执行一段子例程后返回原始程序流。
const target={
foo:'bar'
};
const handler={
//捕获器以方法名为键,基本操作get触发get捕获器
get(){//get拦截器
return 'handle override';
}
};
const proxy=new Proxy(target,handler);//Proxy构造函数,创建代理(目标对象和处理程序对象)
console.log(target.foo);//bar
console.log(proxy.foo);//handle override
所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。
e.g:get()捕获器会接收到目标对象trapTarget、要查询的属性property和代理对象receiver.
开发者并不需要手动重建原始行为,而是可以通过调用全局Reflect反射对象上的同名方法来重建。反射API为开发者准备好了样板代码,可以用最少的代码修改捕获方法。
Reflect的所有属性和方法都是静态的(就像Math对象)。
反射API不限于捕获处理程序
用一等函数代替操作符:
Reflect.get()代替对象属性访问//更直接,不用经过get捕获器
Reflect.set();//=
Reflect.has();//with、in
Reflect.deleteProperty();//delete操作符
Reflect.construct();//new
大多数反射API方法在Object类型上有对应的方法。object上的方法适用于通用程序,而反射方法适用于细粒度的对象控制与操作。
撤销代理:使用静态的 Proxy.revocable() 方法
new Proxy()创建的普通代理,代理对象与目标对象之间的联系会在代理对象的生命周期内一直存在。
proxy暴露了revocable()方法,这个方法支持撤销代理对象与目标对象的关联。
var revocable = Proxy.revocable({}, {
get(target, name) {
return "[[" + name + "]]";
}
});
var proxy = revocable.proxy;
proxy.foo; // "[[foo]]"
revocable.revoke();
console.log(proxy.foo); // 抛出 TypeError
proxy.foo = 1 // 还是 TypeError
delete proxy.foo; // 又是 TypeError
typeof proxy // "object",因为 typeof 不属于可代理操作
此方法会使 Proxy 无效,使以后的任何调用均以 TypeError 结尾。之后该代理将被自动“垃圾收集”,从而释放内存空间。
代理另一个代理,就可以在一个目标对象之上创建多层拦截网。
const target={
foo:'bar'
};
const proxy1=new Proxy(target,{
get(){
console.log("first proxy");
return Reflect.get(...arguments);//访问target
}
});//Proxy构造函数,创建代理(目标对象和处理程序对象)
const proxy2=new Proxy(proxy1,{
get(){
console.log("second proxy");
return Reflect.get(...arguments);//访问proxy1
}
});//Proxy构造函数,创建代理(目标对象和处理程序对象)
console.log(proxy2.foo);
//second proxy
//first proxy
//bar
代理中的this问题
方法中的this指向调用方法的对象,所以target的代码中this不一定指向target
联系之前弱引用、同时区别代理一个实例和代理一个类
const wm = new WeakMap();
class User {
constructor(userId) {
wm.set(this, userId);
}
set id(userId) {
wm.set(this, userId);
}
get id() {
return wm.get(this);
}
}
const user = new User(123);
console.log(user.id); // 123
const userInstanceProxy = new Proxy(user, {});
console.log(userInstanceProxy.id); // undefined
const UserProxy = new Proxy(User, {});//代理类
console.log(UserProxy.id); // undefined
const xx=new UserProxy(456);//代理类的实例
console.log(xx.id); // 456
代理与内部卡槽
代理与部分内置类型可能会依赖代理无法控制的机制。
e.g:Date类方法的执行依赖内部槽位[NumberDate],代理对象上不存在这个槽位所以不能成功。
拦截的操作有:
proxy.property
proxy[property]
Object.create(proxy)[property]
Reflect.get(proxy,property,receiver)
捕获器处理程序参数
目标对象
字符串键属性
代理对象receiver
捕获器不定式
如果target.property不可写且不可配置,则处理程序返回的值必须与target.property匹配
返回布尔值,操作成功与否
拦截的操作有:
proxy.property=value
proxy[property]=value
Object.create(proxy)[property]=value
Reflect.set(proxy,property,value,receiver)
捕获器不定式
如果target.property不可写且不可配置,则不能修改
const target = {};
Object.defineProperty(target,"foo",{
writable:true,
value:"bar"
});
const handler = {
get(trapTarget, property, receiver) {
return trapTarget[property];
},
set(trapTarget,property,value,receiver) {
console.log("set");
return Reflect.set(...arguments);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar
proxy.foo="ywy";
console.log(proxy.foo); // ywy
console.log(target.foo); // ywy
返回布尔值,表示属性是否存在
拦截的操作有:
property in proxy
property in Object.create(proxy)
with(proxy){(property);}
Reflect.has(proxy,property)
返回布尔值,操作成功与否
拦截操作有:
Object.defineProperty(proxy,property,descriptor)
Reflect.defineProperty(proxy,property,descriptor)//descriptor包含可选的enumerable、configurable、writable、value、get、set定义
捕获器不定式
如果目标对象不可扩展,则无法定义属性
返回对象,或者undefined
Object.getOwnPropertyDescriptor(proxy,property)
Reflect.getOwnPropertyDescriptor(proxy,property)
返回布尔值,是否删除
delete proxy.property
delete proxy[property]
Reflect.deleteProperty(proxy,property)
返回包含字符串或符号的可枚举对象
Object.getOwnPropertyNames(proxy)
Object.getOwnPropertySymbols(proxy)
Object.keys(proxy)
Reflect.ownKeys(proxy)
捕获不定式
返回的可枚举对象必须准确地包含target所有不可配置的自有属性
如果target不可扩展,则返回可枚举对象必须准确地包含自由属性键
返回对象或者null
拦截:
proxy.__ proto __
Object.prototype.isPrototypeOf()//用于测试一个对象是否存在于另一个对象的原型链上。
Object.getPrototypeOf(proxy)//方法返回指定对象的原型(内部[[Prototype]]属性的值)。
Reflect.getPrototypeOf(proxy)// 返回指定对象的原型 (例如:内部的 [[Prototype]] 属性的值)
proxy instanceof object//instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置
返回布尔,原型赋值是否成功
返回布尔,是否可以扩展
返回布尔,是否已经不可以扩展
捕获不定式 target必须是一个函数对象
调用代理时候被捕获
function median(...nums) {
return nums.sort()[Math.floor(nums.length / 2)];
}
console.log("proxy前median:"+median(4, 7, 1)); // 4
let count=0;
const proxy = new Proxy(median, {
apply(target, thisArg, ...argumentsList) {
console.log("this proxy apply");
return Reflect.apply(...arguments);
}
});
console.log("proxy后median:"+median(4, 7, 1)); // 4
console.log(proxy(4, 7, 1)); //this proxy apply 4
我的理解:除了代理的操作目标是target之外,可以把代理和target理解成两个对象。
返回一个对象
拦截:
new proxy(…argumentsList)
Reflec.construct(target,argumentsList,newTarget)
捕获get、set、has等操作
const user={
name:'jake'
};
const proxy=new Proxy(user,{
get(target,property,receiver){
console.log('getting ${property}');
return Reflect.get(...arguments);
},
set(target,property,value,receiver){
console.log('setting ${property}=${value}');
return Reflect.set(...arguments);
}
//has
});
proxy.name;//getting name
代理地内部实现对外部代码是不可见地,因此要隐藏目标对象上的属性也很轻易。涉及has、get函数
const hidden=['foo','bar'];//隐藏属性列表
const target={
foo:1,
bar:2,
baz:3
};
const proxy=new proxy(target,{
get(target,property){
if(hidden.includes(property)){
return undefined;
}else{
return Reflect.get(...arguments);
}
},
has(target,property){
if(hidden.includes(property)){
return false;
}else{
return Reflect.has(...arguments);
}
},
});
所有赋值操作都触发set捕获器,可以在其中根据所赋值决定是拒绝还是允许
也对函数和构造函数的参数进行审查。
通过代理可以把运行中原本不相关的部分联系到一起,这样就可以实现各种模式,从而让代码互操作。
将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中
const userList=[];
class User{
constructor(name){
this.name_=name;
}
}
//注意是对类创建代理
const proxy=new Proxy(User,{
constructor(){
const newUser=Reflect.construct(...arguments);
userList.push(newUser);
return newUser;
}
});
因为网络不佳或是图片太大,图片一时加载不出来,
先使用一张loading图片占位,然后用异步的方式加载图片,等图片加载好了再填充到img节点里
很适合使用虚拟代理
var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
<strong>return {
setSrc: function( src ){
imgNode.src = src;
}
};</strong>
})();
var proxyImage = (function(){
var img = new Image;
img.onload = function(){
myImage.setSrc( this.src );
};
<strong>return {
setSrc: function( src ){
myImage.setSrc( 'http://xxx.dlmu.edu.cn/zll/img/lu.jpg' ); //代理对象设置的占位符
img.src = src;
}
};</strong>
})();
proxyImage.setSrc( 'http://xxx.dlmu.edu.cn/zll/img/1.jpg' );