引言:一般在面试或工作中对call、apply、bind会用就可以了,但有的时候面试还是会问到“能说说它们的实现吗”,所以对原理还是要有一定了解。
Call的实现
一、回顾用法
call(修改后的this指向, 参数1, 参数2, 参数3...)
二、思路
先想一下call做了哪些事情
- 修改this指向
- 执行这个函数
- 返回函数执行的结果
- 可以传入多个参数
- 异常处理
三、实现
第一步:修改指向,执行函数,返回结果
Function.prototype.myCall = function(context) {
context.fn = this;
var result = context.fn();
delete context.fn;
return result;
}
先看效果
var obj = {
a: 1
}
function test() {
console.log(this.a);
}
test(); // 输出 undefined
test.myCall(obj); // 输出 1
实现解释
context.fn = this中context作为第一个参数,表示修改后的指向,所以context对应的就是obj,这里的this指向调用者,对应的就是test。那这里的意思就是在obj上创建一个fn来接收test。- 在
obj上接收test后,执行这个fn,再将执行结果保存到result上。 - 删除临时创建的
fn - 返回执行结果
result
第二步:支持传入多个参数
Function.prototype.myCall = function (context, ...args) {
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
}
先看效果
var obj = {
a: 1
}
function test(agr1, arg2, arg3) {
console.log(this.a, agr1, arg2, arg3);
}
test(); // 输出:undefined undefined undefined undefined
test.myCall(obj, "第一个", "第二个", "第三个"); // 输出:1 第一个 第二个 第三个
实现解释
支持多个参数的话,那就用...args来接收参数,然后在执行函数的时候传入进去就可以了
第三步:异常处理,this参数可以为null
当this参数为null时,this指向的是window
所以最后还要加一个判断
最终版
Function.prototype.myCall = function (context, ...args) {
if (typeof context === "undefined" || context === null) {
context = window
}
let symbolFn = Symbol()
context[symbolFn] = this
const result = context[symbolFn](...args)
delete context[symbolFn]
return result
}
效果
var obj = {
a: 1
}
function test(agr1, arg2, arg3) {
console.log(this.a, agr1, arg2, arg3);
}
test(); // 输出:undefined undefined undefined undefined
test.myCall(obj, "第一个", "第二个", "第三个"); // 输出:1 第一个 第二个 第三个
test.myCall(null, "第一个", "第二个", "第三个"); // 输出:undefined 第一个 第二个 第三个
实现解释
- 在修改
this指向时判断,如果context是null或undefined,this就指向window。 - 在创建
fn属性时可能在context上本身就有一个fn,所以使用Symbol()来保证用的是唯一值
Apply的实现
一、回顾用法
apply(修改后的this指向, [参数1, 参数2, 参数3...])
二、思路
和call类似,唯一不同的就是只有两个参数,第二个参数是数组
三、实现
Function.prototype.myApply = function (context, args) {
if (typeof context === "undefined" || context === null) {
context = window
}
let symbolFn = Symbol()
context[symbolFn] = this
const result = context[symbolFn](...args)
delete context[symbolFn]
return result
}
效果
var obj = {
a: 1
}
function test(agr1, arg2, arg3) {
console.log(this.a, agr1, arg2, arg3);
}
test(); // 输出:undefined undefined undefined undefined
test.myApply(obj, "第一个", "第二个", "第三个"); // 输出:1 第一个 第二个 第三个
test.myApply(null, "第一个", "第二个", "第三个"); // 输出:undefined 第一个 第二个 第三个
实现解释
和call类似,因为只有两个参数,所以把接收参数的方式修改了
call接收方式是function (context, ...args),apply接收方式是function (context, args)
Bind的实现
这里介绍的是es5通过apply的实现方式
一、回顾用法
bind(修改后的this指向, 参数1, 参数2, 参数3...)
二、思路
- 和
call类似,接收多个参数 - 返回新函数
- 新函数执行后是修改this后的执行结果
- 新函数可传参
- 异常处理
三、实现
第一步:接收多个参数,返回新函数
Function.prototype.myBind = function (context, ...args) {
return function() {
}
}
实现解释
通过...args接收this参数外的其他参数,然后再返回一个funtion
第二步:接收多个参数,返回新函数
Function.prototype.myBind = function (context, ...args) {
let self = this
return function() {
return self.apply(context, args)
}
}
实现解释
let self = this返回结果不是立即执行的,需要外部调用,所以这里需要把当前调用者(this指向调用者)保存起来,避免在返回的function中找不到原来指向的函数return self.apply(context, args),由于返回的function体内是会返回一个修改this指向后的执行结果,所以用apply返回一个修改指向后的结果
第三步:新函数可传参
Function.prototype.myBind = function (context, ...args) {
let self = this
return function(...params) {
return self.apply(context, args.concat(params))
}
}
实现解释
args.concat(params),因为返回的函数还可以传参,所以用...params接收执行时的参数,并与调用myBind时的参数连接起来
第四步:异常处理,this参数可以为null
当this参数为null时,this指向的是window
所以最后还要加一个判断
最终版
Function.prototype.myBind = function (context, ...args) {
if (typeof context === "undefined" || context === null) {
context = window
}
// 这里的 this 就是 test
let self = this
return function(...params) {
// 这里的 self 就是 test
return self.apply(context, args.concat(params))
}
}
效果
var obj = {
a: 1
}
function test(agr1, arg2, arg3) {
console.log(this.a, agr1, arg2, arg3);
}
test.myBind(obj, "第一个", "第二个")("第三个"); // 输出:1 第一个 第二个 第三个
self.apply(context, args.concat(params))这里用call也行,把后面args.concat(params)展开即可