模拟实现 call, apply 和 bind
唉,广州疫情又开始有苗头了,待在室内写博客比干啥都好。
Function.prototype.call
首先先看一下这个函数的syntax是怎么样的
call()
call(thisArg)
call(thisArg, arg1)
call(thisArg, arg1, arg2)
call(thisArg, arg1, ... , argN)
那么自己实现的函数,第一个参数就是thisArg
,其余的参数就用可变参数来接收
由于这个函数在Function.prototype
上,因此这个函数里头的this
只能是一个Function
或者是Function.prototype
。但是直接用后者调用是不被允许的(这种做法是直接调用了对象方法)。因此要加个判断。
接着,就是做些什么,使得被调用的函数的this
是传入的ctx
本身。不难想出,比如obj.fn()
这样调用时候,fn
里头的this
就会是obj
了。
但是万一ctx
有许许多多的properties而产生重名冲突了呢?
此时ES6的Symbol
就闪亮登场,它可以帮我们创建一个不会重名的属性,值会存在Symbol
里头,如下代码这样加进去。当然用完之后记得删除。
还要注意,这个函数的ctx
如果为undefined
,那么就会是globalThis
Function.prototype.call_ = function(ctx = window, ...args) {
if (this === Function.prototype) {
return undefined;
}
const f = Symbol();
ctx[f] = this;
const result = ctx[f](...args);
delete ctx[f];
return result;
}
测试一下
let f = function(num = 1) {
console.log(this.x + num);
}
const obj = {
x: 114510,
};
f.call_(obj, 4); // 114514;
Function.prototype.call_(obj, 4); // undefined
Function.prototype.apply
跟上者类似除了接受的是参数数组。只需要改一下就行,记得判断是否为数组。还需要注意argsArray
为可选参数,记得处理。
Function.prototype.apply_ = function(ctx = window, argsArray) {
if (this === Function.prototype) {
return undefined;
}
const f = Symbol();
ctx[f] = this;
let result;
if (Array.isArray(argsArray)) {
result = ctx[f](...argsArray);
} else if (argsArray === undefined) {
result = ctx[f]();
}
else {
throw TypeError("The args passed in is not an array");
}
delete ctx[f];
return result;
}
测试一下(接上面的测试代码)
f.apply_(obj, [4]); // 114514
f.apply_(obj); // 114511
Function.prototype.bind
Function.prototype.bind
的作用是返回一个绑定了this
的函数。
先看看syntax,依旧采用可变参数
bind(thisArg)
bind(thisArg, arg1)
bind(thisArg, arg1, arg2)
bind(thisArg, arg1, ... , argN)
大致思路是:先把当前的函数缓存下来。然后返回一个与缓存下来的函数形成闭包的函数。注意参数的处理。
我认为这里不需要做(中伏啦,要是invoke as a constructor怎么办this
的判断
Function.prototype.bind_ = function(ctx, ...args) {
if (this instanceof Function.prototype.bind_) {
throw "Can not invoke this as a constructor";
}
const this_ = this;
return function fn(...args1) {
if (this instanceof fn) {
return new this_(...args, ...args1);
} else {
if (ctx === null || ctx === undefined) {
this_(...args, ...args1);
} else {
this_.call(ctx, ...args, ...args1);
}
}
}
}
chrome下测试(接上面的测试代码)
let myFn = f.bind_(); // window
let myFn1 = f.bind_(obj);
this.x = 110;
myFn(4); // 114
myFn1(4); // 114514