写该系列文章的初衷是“让每位前端工程师掌握高频知识点,为工作助力”。这是前端百题斩的第15斩,希望朋友们关注公众号“执鸢者”,用知识武装自己的头脑。
在百题斩【014】中已经简要概述了call、apply、bind三个方法,这三者作用是相同的,均可以改变this指向,从而让某对象可以调用自身不具备的方法,本节将深入理解这三者的实现原理。
15.1 call()
15.1.1 基础
call()
方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数。其返回值是使用调用者提供的this值和参数调用该函数的返回值,若该方法没有返回值,则返回undefined。
基本用法:
function.call(thisArg, arg1, arg2, ...)
小试牛刀
function method(val1, val2) {
return this.a + this.b + val1 + val2;
}
const obj = {
a: 1,
b: 2
};
console.log(method.call(obj, 3, 4)); // 10
15.1.2 实现
实现一个call函数,将通过以下几个步骤:
获取第一个参数(注意第一个参数为null或undefined时,this指向window),构建对象
将对应函数传入该对象中
获取参数并执行相应函数
删除该对象中函数,消除副作用
返回结果
Function.prototype.myCall = function (context, ...args) {
// 获取第一个参数(注意第一个参数为null或undefined时,this指向window),构建对象
context = context ? Object(context) : window;
// 将对应函数传入该对象中
context.fn = this;
// 获取参数并执行相应函数
let result = context.fn(...args);
// 消除副作用
delete context.fn;
// 返回结果
return result;
}
// ……
console.log(method.myCall(obj, 3, 4)); // 10
15.2 apply()
15.2.1 基础
apply()
方法调用一个具有给定this
值的函数,以及以一个数组(或类数组对象)的形式提供的参数。其返回值是指定this值和参数的函数的结果。call()
和apply()
的区别是call()
方法接受的是参数列表,而apply()
方法接受的是一个参数数组;
基本用法
func.apply(thisArg, [argsArray])
小试牛刀
function method(val1, val2) {
return this.a + this.b + val1 + val2;
}
const obj = {
a: 1,
b: 2
};
console.log(method.apply(obj, [3, 4])); // 10
15.2.2 实现
apply和call的区别主要是参数的不同,所以其实现步骤的call大体类似,如下所示:
Function.prototype.myApply = function (context, arr) {
context = context ? Object(context) : window;
context.fn = this;
let result = arr ? context.fn(...arr) : context.fun();
delete context.fn;
return result;
}
// ……
console.log(method.myApply(obj, [3, 4])); // 10
15.3 bind()
15.3.1 基础
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。该函数的返回值是一个原函数的拷贝,并拥有指定的this值和初始参数。
基本用法
function.bind(thisArg[, arg1[, arg2[, ...]]])
小试牛刀
function method(val1, val2) {
return this.a + this.b + val1 + val2;
}
const obj = {
a: 1,
b: 2
};
const bindMethod = method.bind(obj, 3, 4);
console.log(bindMethod()); // 10
15.3.2 实现
实现一个bind函数相对较复杂一些,应该注意以下几点:
能够改变this指向;
返回的是一个函数;
能够接受多个参数;
支持柯里化形式传参 fun(arg1)(arg2);
获取到调用bind()返回值后,若使用new调用(当做构造函数),bind()传入的上下文context失效。
Function.prototype.myBind = function (context, ...args) {
if (typeof(this) !== 'function') {
throw new TypeError('The bound object needs to be a function');
}
const self = this;
// 定义一个中装函数
const fNOP = function() {};
const fBound = function(...fBoundArgs) {
// 利用apply改变this指向
// 接受多个参数+支持柯里化形式传参
// 当返回值通过new调用时,this指向当前实例 (因为this是当前实例,实例的隐士原型上有fNOP的实例(fnop);fnop instanceof fNOP为true)
return self.apply(this instanceof fNOP ? this : context, [...args, ...fBoundArgs]);
}
// 将调用函数的原型赋值到中转函数的原型上
if (this.prototype) {
fNOP.prototype = this.prototype;
}
// 通过原型的方式继承调用函数的原型
fBound.prototype = new fNOP();
return fBound;
}
1.如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~
2.关注公众号执鸢者,领取学习资料,定期为你推送原创深度好文
3.关注公众号进群,里面大佬多多,一起向他们学习
1. 前端百题斩[001]——typeof和instanceof
3. 前端百题斩【003-004】——从基本类型、引用类型到包装对象
6. 前端百题斩【007】——js中必须知道的四种数据类型判断方法
7. 前端百题斩【008-009】——从JavaScript的代码执行过程到函数执行过程
8. 前端百题斩【010】——通俗易懂的JavaScript执行上下文
10. 前端百题斩【012】——js中作用域及作用域链的真面目
12. 前端百题斩【014】——js中的这些“this”指向都值得了解
13. 三步法解析Express源码
17. 一文彻底搞懂前端监控
18. 前端的葵花宝典——架构
19. canvas从入门到猪头
21. 2021 年前端宝典【超三百篇】
22. 前端也要懂机器学习(上)
23. 前端也要懂机器学习(下)
24. 学架构助力前端起飞
26. Vue源码思想在工作中的应用
27. 一文搞定Diff算法