写该系列文章的初衷是“让每位前端工程师掌握高频知识点,为工作助力”。这是前端百题斩的第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.关注公众号进群,里面大佬多多,一起向他们学习
2. 前端百题斩【002】——js中6种变量声明方式
3. 前端百题斩【003-004】——从基本类型、引用类型到包装对象
10. 前端百题斩【012】——js中作用域及作用域链的真面目
11. 前端百题斩【013】——用“闭包”问题征服面试官
12. 前端百题斩【014】——js中的这些“this”指向都值得了解
13. 三步法解析Express源码
14. 一篇搞定前端高频手撕算法题(36道)
15. 十七张图玩转Node进程——榨干它
16. 理论与API相结合理解Node中的网络通信
17. 一文彻底搞懂前端监控
18. 前端的葵花宝典——架构
22. 前端也要懂机器学习(上)
23. 前端也要懂机器学习(下)
24. 学架构助力前端起飞
26. Vue源码思想在工作中的应用
27. 一文搞定Diff算法
28. 百度、小红书三面,均遇“赛马”问题
29. 十五张图带你彻底搞懂从URL到页面展示发生的故事
30. 一文搞懂Cookie、Storage、IndexedDB