理解 call、apply、bind 原理,手写简单的 call、apply、bind 方法
call 原理及实现
MDN定义:call()
方法使用给定的 this
值和单独提供的参数调用函数。
用自己的话说就是:改变调用 call 方法的 this 指向。
使用语法:function.call(target, arg1, arg2, arg3)
- 上菜说明:
// 全局的name
var name = "全局的name";
// obj对象
const obj = {
name: "obj对象的name",
};
// 方法
function printName(a) {
console.log(this.name + a);
}
printName(); // 直接调用this指向全局
// 全局的name
printName.call(obj, "附加"); // 改变this指向为obj
// obj对象的name附加
理解了 call 是做什么事情的,接下来根据理解的思路自己写一个 myCall 方法出来。
须知:1. call 实际上是 Function.prototype 上面的方法;2. 任何函数都可以访问自己的 prototype 上面的方法
- 上 DIY 的菜:
Function.prototype.myCall = function (targetObj = {}, ...args) {
const fn = Symbol("fn"); // 定义一个唯一的 Symbol 避免意外污染替换 targetObj 的属性
targetObj[fn] = this; // this 就是调用 myCall 方法的“函数本身”
const result = targetObj[fn](...args); // 通过 targetObj 执行方法,并传入额外参数
delete targetObj[fn]; // 删除添加的属性
return result; // 返回执行结果
};
const obj = {
name: "alice",
};
// 方法
function printName(a) {
console.log(this.name + a);
}
printName.myCall(obj, " is a beauty");
// alice is a beauty
搞定!根据思路自己写出来后是不是很 nice,感觉很简单
apply 原理及实现
apply 的效果与 call 功效相同,都是改变 this 指向的问题。不同点:apply 接收的参数是目标对象、参数数组两个参数
- 直接 DIY 菜
Function.prototype.myApply = function (target, argList) {
const targetObj = target || {};
const fn = Symbol("fn");
targetObj[fn] = this;
const result = targetObj[fn](...argList);
delete targetObj[fn];
return result;
};
// 测试一下
const obj = {
name: "crk",
};
function print(...arr) {
console.log(this.name + arr);
}
print.myApply(obj, [1, 2, 3]);
bind 的实现与原理
用法与前二者类似,被指定 this 指向后不立即执行,会返回一个可以执行的新函数,不会自动执行。
- 注意点:
1、bind() 除了 this 外还接收其他参数,其返回的函数也可以接收参数(意思是执行返回的函数的时候可以传参)。并且这两部分参数都需要传给返回的函数
2、bind() 返回的方法如果被 new 执行了,那么内部的 this 指向又会被更改
3、需要保留“元方法”在原型链上的属性和方法
简单的 bind 实现:
Function.prototype.myBind = function () {
const args = Array.prototype.slice.call(arguments); // myBind 接口的所有参数
const targetObj = args.shift(); // 第一个参数是 this 的目标对象,args剩下的是绑定时的参数
const self = this; // 调用 myBind 的"元方法"
// new 优先改变 this
const cbFn = function () {
// 当是new的时候,this instanceof self 的值为true,这个时候this应该指向被new的函数自己
self.apply(
this instanceof self ? this : targetObj,
args.concat(Array.prototype.slice.call(arguments))
);
};
// 被bind后生产的函数可以多次调用,是持久的。所以需要保留原型链上的方法和属性。这里使用原型继承
cbFn.prototype = Object.create(self.prototype);
return cbFn;
};
测试一下基础功能:
const obj = {
name: "小强",
};
function foo() {
console.log(this.name, ...arguments);
}
foo.myBind(obj, 1, 2, 3)(); // 小强 1 2 3
foo.myBind(obj, 1, 2, 3)(4); // 小强 1 2 3 4
测试bind之后的函数 new 的功能
function factory(name) {
this.name = name;
this.fn = function () {
return this.name;
};
}
const newObj = {
name: "大强",
};
const bindF = factory.myBind(newObj);
const f = new bindF("大枪");
console.log(f.fn()); // 大枪
- 虽然myBind的时候指定了this,但是new的使用改变了this的指向
至此,毕