call(), apply() 和 bind() 方法三者功能相似,均可以改变 this 的指向。区别是:
- call(), apply() 方法会改变 this 的指向并执行函数;
- 而 bind() 方法会改变 this 的指向但并不会执行函数;
- call(), apply() 方法区别就是前者接受的是参数列表,而后者接受的是一个参数数组。
下面我们来看看如何实现
function ShowInfo() {
let res = `My name is ${this.name}, I am ${this.age} years old.`;
return res;
}
const Dog = {
name: "Sugary",
age: 10,
};
console.log(ShowInfo.apply(Dog)); // My name is Sugary, I am 10 years old.
console.log(ShowInfo()); // My name is undefined, I am undefined years old.
由上述代码可知,如果不为 ShowInfo() 方法指定特定的对象,this 的值默认指向全局对象,即 window 对象。因此,apply() 方法实现了两个功能:
- 改变了 ShowInfo() 函数的 this 指向;
- 执行了 ShowInfo() 函数。
apply() 源码实现
function showInfo() {
let res = `My name is ${this.name}, I am ${this.age} years old.`;
return res;
}
const Dog = {
name: "Sugary",
age: 10,
};
Function.prototype.createApply = function (context) {
if (typeof this !== "function") {
throw new TypeError("Error");
}
context = handleType(context); // 类型转换
context.fn = this;
let result;
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
delete context.fn;
return result;
};
// 数据类型转化
function handleType(context) {
const type = typeof context;
if (context === undefined || context === null) {
context = "window";
}
switch (type) {
case "window":
context = window;
break;
case "number":
context = Number(context);
break;
case "string":
context = String(context);
break;
case "boolean":
context = Boolean(context);
break;
default:
context = context;
}
return context;
}
console.log(showInfo.createApply(Dog)); // My name is Sugary, I am 10 years old.
在ECMAScript 3和非严格模式中,传入call的第一个参数,如果传入的值为null或者undefined都会被全局对象代替,而其他的原始值则会被相应的包装对象所替代。所以我们可以对传入的参数类型做处理,如上述的 handleType() 方法。
call() 方法的实现和 apply() 方法的实现差不多,只是传入的参数格式不一样,我们来看看源码怎么实现
function showInfo() {
let res = `My name is ${this.name}, I am ${this.age} years old.`;
return res;
}
const Dog = {
name: "Sugary",
age: 10,
};
Function.prototype.createCall = function (context) {
if (typeof this !== "function") {
throw new TypeError("Error");
}
context = handleType(context);
context.fn = this;
let result;
let args = [...arguments].slice(1);
if (args) {
result = context.fn(...args);
} else {
result = context.fn();
}
delete context.fn;
return result;
};
// 数据类型转化
function handleType(context) {
const type = typeof context;
if (context === undefined || context === null) {
context = "window";
}
switch (type) {
case "window":
context = window;
break;
case "number":
context = Number(context);
break;
case "string":
context = String(context);
break;
case "boolean":
context = Boolean(context);
break;
default:
context = context;
}
return context;
}
console.log(showInfo.createCall(Dog)); // My name is Sugary, I am 10 years old.
和 apply(), call() 方法不同的是,bind() 方法并没有在改变 this 指向的时候执行函数,而是返回一个绑定上下文的函数,我们来看一个🌰
function showInfo() {
let res = `My name is ${this.name}, I am ${this.age} years old.`;
return res;
}
const Dog = {
name: "Sugary",
age: 10,
};
const res = showInfo.bind(Dog);
console.log(res()); // My name is Sugary, I am 10 years old.
由上面的代码可知,bind() 方法实现了以下功能:
- 绑定this;
- 传入参数;
- 返回一个函数;
- 函数柯里化。
接下来我们来看看如何实现:
function ShowInfo() {
let res = `My name is ${this.name}, I am ${this.age} years old.`;
return res;
}
const Dog = {
name: "Sugary",
};
Function.prototype.createBind = function (context) {
if (typeof this !== "function") {
throw new Error(
"Function.prototype.bind - what is trying to be bound is not callable"
);
}
let _this = this;
let args = [...arguments].slice(1);
return function () {
return _this.apply(context, args.concat([...arguments]));
};
};
const res = ShowInfo.createBind(Dog);
console.log(res()); // My name is Sugary, I am undefined years old.
但是 bind() 方法还有一个新特性,我们来看一个例子:
const name = "Kara";
const Dog = {
name: "Sugary",
};
function Animals(age) {
this.habit = "shopping";
console.log(this.name);
console.log(age);
}
Animals.prototype.friend = "kevin";
let bindFun = Animals.bind(Dog);
let obj = new bindFun(20);
// undefined
// 20
obj.habit; // shopping
obj.friend; // kevin
上面例子中,运行结果 this.name 输出为 undefined ,这不是全局 name 也不是 Dog 对象中的 name ,这说明 bind 的 this 对象失效了,new 的实现中生成一个新的对象,这个时候的 this 指向的是 obj 。但是柯里化依然有效。
可见 new 对 this 的影响比 bind 优先级要高。
我们来看看如何实现:
const name = "Kara";
const Dog = {
name: "Sugary",
};
function Animals(age) {
this.habit = "shopping";
console.log(this.name);
console.log(age);
}
Animals.prototype.friend = "kevin";
let bindFun = Animals.createBind(Dog);
let obj = new bindFun(20);
console.log(obj);
// undefined
// 20
obj.habit; // shopping
obj.friend; // kevin
Function.prototype.createBind = function (context) {
// 调用 bind 的不是函数,需要抛出异常
if (typeof this !== "function") {
throw new Error(
"Function.prototype.bind - what is trying to be bound is not callable"
);
}
let _this = this;
let args = [...arguments].slice(1);
let fNOP = function () {};
let fBound = function () {
let bindArgs = [...arguments];
return _this.apply(
fNOP.prototype.isPrototypeOf(this) ? this : context,
args.concat(bindArgs)
);
};
if (this.prototype) {
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};