引言
如何手写实现原型链中的call方法呢?
原本的call方法
首先使用原本的call方法来做一个小demo
/**
* 手写call函数
* 禁止使用bind, apply辅助
*/
// call函数是什么
// 比如有这么一个函数
function method(a,b){
console.log(this,a,b);
return a+b;
}
// 使用call来调用上面的方法
method.call({},1,2);
得到结果
{} 1 2
call调用了method方法并且将this指定为{}
所以输出的this就是{}
思路和实现
手写实现call方法的思路大致如下:
- 在Function的原型链中添加一个自定义的mycall方法,接收参数为指定的this以及其他未知的参数
- 修改this的指向
- 调用原本的方法并且将未知参数传入,获得返回值后,将返回值返回
实现1
代码
Function.prototype.myCall = function (ctx, ...args) {
ctx.func = this;
var res = ctx.func(...args);
delete ctx.func;
return res;
}
解释
接收一个ctx参数为this指向使用
...args是es6的剩余参数使用方式,可以接收未知数量的参数
将原本的this赋值给ctx的指定变量func
调用func方法并传入剩余参数,得到返回值
这个时候func已经没用了,所以delete删掉
最后返回res结果
测试
method.myCall({},2,3);
结果
{func: ƒ} 2 3
上述方法可以,但是有很多细节没有处理
实现2
实现1中比较简陋,没有考虑多种情况,比如:
例如第一个参数是null undefined
method.myCall(null,2,3);
method.myCall(undefined,2,3);
就会报错
代码
我们来优化一下代码
Function.prototype.myCall2 = function (ctx, ...args) {
// 进行一个判断
ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx);
ctx.func = this;
let res = ctx.func(...args);
delete ctx.func;
return res;
}
解释
对ctx进行一个是否为null或者undefined的判断
如果是,就将全局变量赋值给ctx,globalThis针对不同的运行时环境具体是不一样的,比如浏览器中就是window,在node环境中是global
如果为 非null/undefined ,则对ctx进行object包装,这样即使是基础数据类型,也会变成包装类
测试
method.myCall2(undefined,2,3);// 解决了上述问题
结果
chrome
node
实现3
mycall2依然有问题,比如我们设置了一个属性func,但是如果第一个参数本身就有一个同名属性的话,func这个属性就会被覆盖掉了
我们可以使用symbol来解决属性名重复的问题
代码
Function.prototype.myCall3 = function (ctx, ...args) {
// 进行一个判断
ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx);
let sym = Symbol('mycall');
ctx[sym] = this;
let res = ctx[sym](...args);
delete ctx[sym];
return res;
}
解释
因为symbol是唯一的,所以解决了重名问题
测试
method.myCall3({},2,3);//解决上述问题
结果
chrome
node
实现4
上述其实已经实现了手写call,但是有点不够舒服
因为输出的内容中会有设定的symbol属性,不是很美观
那么就需要用到属性描述符的相关内容了
代码
Function.prototype.myCall4 = function (ctx, ...args) {
// 进行一个判断
ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx);
let sym = Symbol('mycall');
Object.defineProperty(ctx, sym, {
enumerable: false,// 在node环境中,不会获取不可枚举的属性;而在浏览器中,会全部获取,所以会出现浏览器console.log能看到该属性,而在node中看不到
configurable: true,
value: this
});
let res = ctx[sym](...args);
delete ctx[sym];
return res;
}
解释
Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。
ctx要定义属性的对象
sym一个字符串或 Symbol,指定了要定义或修改的属性键
enumerable当且仅当该属性在对应对象的属性枚举中出现时,值为 true。默认值为 false。
configurable
当设置为 false 时,
该属性的类型不能在数据属性和访问器属性之间更改
该属性不可被删除
其描述符的其他属性也不能被更改(但是,如果它是一个可写的数据描述符,
则 value 可以被更改,writable 可以更改为 false)。
value
与属性相关联的值。可以是任何有效的 JavaScript 值(数字、对象、函数等)。默认值为 undefined。
使用属性描述符后,可以将指定的属性改为不可枚举的类型
测试
method.myCall4({}, 2, 3);
结果
node
chrome
总结
看似八股文的东西,其实考验的是es6的理解程度,希望能帮到有需要的人,并且也能提升自己对js的理解程度