JS 实现 call、apply 和 bind 函数
思路
- 如果不传第一个参数,那么上下文对象默认是 window
- 改变了 this 指向,让新的对象可以执行该函数,并能接受参数
实现 call
Function.prototype.myCall = function (context) {
// this就是调用bind的函数
if (typeof this !== "function") {
throw new Error("this必须是函数");
}
context = context || window;
context.fn = this;
// 除去第一个参数
const args = [...arguments].slice(1);
const result = context.fn(...args);
// 调用函数并删除绑定对象上的函数
delete context.fn;
return result;
};
// ES6 实现
Function.prototype.myCall = function (context, ...args) {
context = (context ?? window) || new Object(context);
const key = Symbol();
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
};
function bar() {
console.log("bar");
}
function foo(params) {
console.log("---foo---", params);
console.log("---this---", this);
}
foo.myCall(bar, "testMyCall");
// ---foo--- testMyCall
// ---this--- function bar() {
// console.log("bar");
// }
实现 apply
apply 和 call 的实现方式大部分相同,不同的地方是 apply 需要对传入的参数进行判断
Function.prototype.myApply = function (context) {
if (typeof this !== "function") {
throw new Error("this必须是一个函数");
}
context = context || window;
context.fn = this;
const args = arguments[1];
// 要判断传入参数的参数,如果只传入了一个参数,那么args应该是undefined
const result = args ? context.fn(...args) : context.fn();
delete context.fn;
return result;
};
// ES6实现
Function.prototype.myApply = function (context) {
context = (context ?? window) || new Object(context);
const key = Symbol();
const args = arguments[1];
context[key] = this;
const result = args ? context[key](...args) : context[key]();
delete context[key];
return result;
};
function bar() {
console.log("bar");
}
function foo(params) {
console.log("---foo---", params);
console.log("---this---", this);
}
foo.myApply(bar, ["testMyApply"]);
// ---foo--- testMyApply
// ---this--- function bar() {
// console.log("bar");
// }
实现 bind
- bind 和 apply、call 的区别在于,bind 不是立即执行的,而是返回一个函数,这个函数是还可以传参的,且可以用做构造函数使用。因此在实现时需要对此时的情况加以判断;
- bind 函数的传参方式与 call 函数相同;
- 过程:
- 使用 call / apply 指定 this
- 返回一个绑定函数
- 当返回的绑定函数作为构造函数被 new 调用,绑定的上下文指向实例对象
- 设置绑定函数的 prototype 为原函数的 prototype
Function.prototype.myBind = function (context) {
if (typeof this !== "function") {
throw new TypeError("this必须是一个函数");
}
const _this = this;
// 这里的args是bind时传入的参数
const args = [...arguments].slice(1);
// 返回一个函数
const bindFn = function () {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
// 此时这里的arguments是bind之后的函数再传入的值
if (this instanceof bindFn) {
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
return new _this(...args, ...arguments);
}
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
return _this.apply(context, args.concat(...arguments));
};
bindFn.prototype = Object.create(_this.prototype);
return bindFn;
};
function bar() {
console.log("bar");
}
function foo(name, age) {
console.log("---name---", name);
console.log("---age---", age);
console.log("---this---", this);
}
const bindFoo = foo.myBind(bar, "testMyBind");
// 此时的this应该是指向bar
bindFoo();
console.log("============");
// 此时的bind之后的函数做构造函数使用,其this应该指向实例对象,也是就是foo函数
const bindFoo2 = new bindFoo(24);
// ---name--- testMyBind
// ---age--- undefined
// ---this--- function bar() {
// console.log("bar");
// }
// ============
// ---name--- testMyBind
// ---age--- 24
// ---this--- foo {}
// ES6实现
Function.prototype.myBind = function (context, ...args) {
const fn = this;
const bindFn = function (...newFnArgs) {
return fn.call(
this instanceof bindFn ? this : context,
...args,
...newFnArgs
);
};
bindFn.prototype = Object.create(fn.prototype);
return bindFn;
};