《深入理解ES6》阅读随笔
函数代理中的 apply 和 construct 陷阱
JavaScript 中函数有两个内部方法 [[call]] 和 [[construct]],[[call]] 方法用来直接调用函数,在代理中对应 Reflect.apply,可接收三个参数:
- trapTargat: 目标函数;
- thisArg:目标函数中的 this;
- argumentsList:目标函数携带的参数;
而 [[construct]] 方法则需要使用 new 来创建构造函数,对应代理中的 Reflect.construct,可接收两个必选参数和一个可选参数:
- trapTargat: 目标函数;
- argumentsList:目标函数携带的参数;
- newTargat:指定目标函数的 new.targat 值(可选);
下面是一个默认的函数代理转发例子:
const targat = function () { return 100 }
const proxy = new Proxy(targat, {
apply(trapTargat, thisArg, argumentsList) {
return Reflect.apply(trapTargat, thisArg, argumentsList)
},
construct(trapTargat, argumentsList) {
return Reflect.construct(trapTargat, argumentsList)
}
})
console.log(proxy()) // 100 (直接调用)
const t1 = new proxy() // 用构造函数的方式新建实例
console.log(t1 instanceof targat) // true (t1 是 targat 的实例)
console.log(t1 instanceof proxy) // true(t1 是 proxy 的实例)
验证函数参数
在创建函数代理时,可以在代理中验证参数类型,正常则通过,如果发现类型不对,则抛出异常:
const sum = function (a, b) {
return a + b;
}
const proxy = new Proxy(sum, {
apply(trapTargat, thisArg, argumentsList) {
for (const i of argumentsList) {
if ((typeof i) !== 'number') {
throw 'arguments is not number'
}
}
return Reflect.apply(trapTargat, thisArg, argumentsList)
},
construct(trapTargat, argumentsList) {
return Reflect.construct(trapTargat, argumentsList)
}
})
console.log(sum(1,2)) // 3
console.log(sum(1,'2')) // 12
console.log(proxy(1,2)) // 3
console.log(proxy(1,'2')) // err :arguments is not number
此处使用代理来验证可以提高程序的安全性,如没有代理,那么在上面例子中,当出现数值类型与字符串类型相加时,数值 1 将被强制转换为字符串 1,因此结果会变为异常的字符串 12。
是否使用 new 来调用构造函数
在使用声明函数时,还可以通过代理的方式,来决定是否需要 new 来创建构造函数。
下面是代理中禁止用 new 的例子:
const Sum = function (a, b) {
this.result = a + b;
return this.result
}
const proxy = new Proxy(Sum, {
apply(trapTargat, thisArg, argumentsList) {
return Reflect.apply(trapTargat, thisArg, argumentsList)
},
construct(trapTargat, argumentsList) {
throw 'can not be constructed'
}
})
const s1 = new Sum(1, 2)
console.log(s1.result) // 3
const s2 = proxy(1, 2)
console.log(s2) // 3
const s3 = new proxy(1, 2)
console.log(s3.result) // err:can not be constructed
下面是代理中只能使用 new 的例子:
const Sum = function (a, b) {
this.result = a + b;
return this.result
}
const proxy = new Proxy(Sum, {
apply(trapTargat, thisArg, argumentsList) {
throw 'can not be called'
},
construct(trapTargat, argumentsList) {
return Reflect.construct(trapTargat, argumentsList)
}
})
const s1 = new Sum(1, 2)
console.log(s1.result) // 3
const s2 = new proxy(1, 2)
console.log(s2.result) //3
const s3 = proxy(1, 2)
console.log(s3.result) // err: can not be called
实际上,在声明函数时,也可以在其内部定义是否可用 new :
const Sum = function (a, b) {
if (new.target === undefined) {
throw 'there is no new'
}
this.result = a + b;
return this.result
}
const s1 = new Sum(1, 2)
console.log(s1.result) // 3
const s2 = Sum(1, 2)
console.log(s1) // err:there is no new
上面是一个必须使用 new 的例子,如果需要禁用 new 则改判 new.targat 的状态即可;如此看来,通过函数内部 new.targat 特性来做这件事也可以,并且更简洁一些;但是当无法直接操作目标函数时(比如调用了已封装好的第三方类库中的函数),代理的作用才真正体现出来:
const Sum = function (a, b) {
if (new.target === undefined) {
throw 'there is no new'
}
this.result = a + b;
return this.result
}
const proxy = new Proxy(Sum, {
apply(trapTargat, thisArg, argumentsList) {
return Reflect.construct(trapTargat, argumentsList)
}
})
const s1 = new Sum(1,2)
console.log(s1.result) // 3
const s2 = proxy(1,2)
console.log(s2.result) // 3
上面的例子绕过目标函数必须使用 new 来构造的限制,通过在 Reflect.apply 中直接返回 Reflect.construct 的方式完美解决了问题。
覆写基于抽象类的构造函数
下面是一个只能用来继承的类:
class Sum {
constructor(a, b) {
if (new.target === Sum) {
throw 'only child'
}
this.result = a + b
}
}
class SumChild extends Sum {}
const c1 = new SumChild(1,2)
console.log(c1.result) // 3
const s1 = new Sum(1,2)
console.log(s1.result) // err:only child
此时可以利用 Reflect.construct 的第三个参数来覆写该抽象类,通过配置 newTarget 的方式,可以顺利绕过目标函数无法直接构建实例的限制:
class Sum {
constructor(a, b) {
if (new.target === Sum) {
throw 'only child'
}
this.result = a + b
}
}
const proxy = new Proxy(Sum, {
apply(trapTargat, thisArg, argumentsList) {
return Reflect.apply(trapTargat, thisArg, argumentsList)
},
construct(trapTargat, argumentsList) {
return Reflect.construct(trapTargat, argumentsList, function () { })
}
})
const p1 = new proxy(1, 2)
console.log(p1.result) // 3
可调用的类构造函数
在使用类构造函数时,本来是不可以直接调用的,但是可以通过函数代理的方式,在 Reflect.apply 环节内部完成实例化,然后在外部直接调用即可:
class Sum {
constructor(a, b) {
this.result = a + b
}
}
const proxy = new Proxy(Sum, {
apply(trapTargat, thisArg, argumentsList) {
return new trapTargat(...argumentsList)
}
})
const p1 = proxy(1, 2)
console.log(p1.result) // 3
网友评论