手写 call、apply、bind
闲时要有吃紧的心思,忙时要有悠闲的趣味
- 掘金团队号上线,助你 Offer 临门! 点击 查看详情
前言
我们都知道 call apply bind 都可以改变函数调用的 this 指向。那么它们三者有什么区别,什么时候该用哪个呢?
然后我们尝试一下自己实现这三个函数
正文
call
详情请参考:Function.prototype.call() | MDN
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
x
注意:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
语法:
function.call(thisArg, arg1, arg2, ...)
参数:
thisArg:可选的。在 function 函数运行时使用的 this 值。请注意,- 非严格模式:如果不传参数,或者第一个参数是 null 或 nudefined,this 都指向window。
- 严格模式:第一个参数是谁,this 就指向谁,包括 null 和 undefined,如果不传参数 this 就是undefined
arg1, arg2, ...:指定的参数列表
返回值:
使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。
使用:
function Product(name, price) {
this.name = name
this.price = price
}
function Food(name, price) {
Product.call(this, name, price)
this.category = 'food'
}
const food = new Food('cheese', 5)
console.log(food.name) // 'cheese'
apply
详情请参考:Function.prototype.apply() | MDN
apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
语法:
function.apply(thisArg, [argsArray])
参数:
thisArg:必选的。在 function 函数运行时使用的 this 值,和 call 基本一致。[argsArray]:可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。
返回值:
调用有指定 this 值和参数的函数的结果。
使用:
const numbers = [5, 6, 2, 3, 7]
const max = Math.max.apply(null, numbers)
console.log(max) // 7
const min = Math.min.apply(null, numbers)
console.log(min) // 2
Math.min.apply(null, 1)
bind
详情请参考:Function.prototype.bind() | MDN
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
语法:
function.bind(thisArg, arg1, arg2, ...)
参数:
-
thisArg:调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用 new 运算符构造绑定函数,则忽略该值。当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者 thisArg 是 null 或 undefined,执行作用域的 this 将被视为新函数的 thisArg。 -
arg1, arg2, ...:当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
返回值:
一个原函数的拷贝,并拥有指定的 this 值和初始参数。我们如果需要使用的话,需要去主动调用这个函数。
使用:
const module = {
x: 42,
getX: function () {
return this.x
},
}
const unboundGetX = module.getX
console.log(unboundGetX()) // undefined
// 谁调用指向谁,这里 unboundGetX = module.getX
// 让 getX 里面的 this 指向了 window
// 而 window 里面并没有 x 方法
// 当然,在前面加上 window.x = 43 就有了
const boundGetX = unboundGetX.bind(module)
console.log(boundGetX()) // 42
// 通过 bind,将 this 指向 module
实现 myCall
考虑两点:
- 第一个参数为 undefined 或 null 的时候,那么会转变为 window
- 改变了 this 执行,让新的对象可以执行该函数。
代码实现:
Function.prototype.myCall = function (context, ...args) {
// 首先context为可选参数,如果不传的话默认上下文是window
context = context || window
// 接下来给content创建一个_fn属性,并将值设置为需要调用的函数
context._fn = this
// 调用函数,将这个执行结果传给 result
let result = context._fn(...args)
// 将对象上的函数删除
delete context._fn
// 返回 result 结果
return result
}
实现 myApply
apply 和 call 实现类似,不同的就是对参数的判断及处理
代码实现:
Function.prototype.myApply = function (context, args) {
// 首先context为可选参数,如果不传的话默认上下文是window
context = context || window
// 接下来给content创建一个_fn属性,并将值设置为需要调用的函数
context._fn = this
// 判断参数数组是否存在,不存在直接返回
if (!args) {
return context._fn()
}
// 如果参数不是数组,则抛出错误
if (!(args instanceof Array)) {
throw new Error('params must be array')
}
// 调用函数,将这个执行结果传给 result
let result = context._fn(...args)
// 将对象上的函数删除
delete context._fn
// 返回 result 结果
return result
}
实现 myBind
因为 bind 转换后的函数可以作为构造函数使用,此时 this 应该指向构造出的实例,而不是 bind 绑定的第一个参数
代码实现:
Function.prototype.myBind = function (context, ...args1) {
// 首先context为可选参数,如果不传的话默认上下文是window
context = context || window
///返回一个绑定this的函数,这里我们需要保存this指向
let that = this
// 返回的一个新函数
return function (...args2) {
// 接下来给content创建一个_fn属性,并将值设置为需要调用的函数
context._fn = that
// 调用函数,将这个执行结果传给 result
let result = context._fn(...[...args1, ...args2])
// 将对象上的函数删除
delete context._fn
// 返回 result 结果
return result
}
}
验证我们的实现
我们可以验证一下自己实现的 myCall、myApply 、myBind 函数:
function add(c, d) {
return this.a + this.b + c + d
}
const obj = {
a: 1,
b: 2,
}
console.log('===============myCall================')
console.log(add.myCall(obj, 3, 4)) // 10
console.log(add.myCall({ a: 3, b: 9 }, 3, 4)) // 19
console.log(add.myCall({ a: 3, b: 9 }, { xx: 1 }, 4)) // 12[object Object]4
console.log('==============myApply=================')
console.log(add.myApply(obj)) // NaN
console.log(add.myApply(obj, [3, 4])) // 10
console.log(add.myApply(obj, [1, 'abc', '2'])) // 4abc
console.log('===============myBind================')
console.log(add.myBind(obj, 3, 4)()) // 10
console.log(add.myBind({ a: 3, b: 9 }, 3, 4)()) // 19
console.log(add.myBind({ a: 3, b: 9 }, { xx: 1 }, 4)()) // 12[object Object]4
总结
我们对比一下call / apply / bind
区别
- call 跟 apply 的用法几乎一样,唯一的不同就是传递的参数不同
- call 只能一个参数一个参数的传入。
- apply 则只支持传入一个数组,哪怕是一个参数也要是数组形式。最终调用函数时候这个数组会拆成一个个参数分别传入。
- bind 参数方式跟 call 方法一致。不过 bind 是直接改变这个函数的 this 指向并且返回一个新的函数,之后再次调用这个函数的时候 this 都是指向 bind 绑定的第一个参数
使用场景
- 当我们使用一个函数需要改变 this 指向的时候才会用到
call/apply/bind - 如果你要传递的参数不多,则可以使用
fn.call(thisObj, arg1, arg2 ...) - 如果你要传递的参数很多,则可以用数组将参数整理好调用
fn.apply(thisObj, [arg1, arg2 ...]) - 如果你想生成一个新的函数长期绑定某个函数给某个对象使用,则可以使用
const newFn = fn.bind(thisObj); newFn(arg1, arg2...)
参考文献
后记:Hello 小伙伴们,如果觉得本文还不错,记得点个赞或者给个 star,你们的赞和 star 是我编写更多更丰富文章的动力!GitHub 地址
文档协议
db 的文档库 由 db 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/danygitgit上的作品创作。
本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。