js中call、apply和bind
浏览 16最近编辑于
在this的文章中提到,可以显式绑定函数的this。使用call、apply、bind就可以实现,但是它们之间还是有一些差别,值得我们继续学习下。
call:立即调用,参数一个一个传递
基本语法
fn.call(thisArg, arg1, arg2, ...)
- thisArg:函数要指定的this
- 后面的参数,按顺序传递给函数
示例
function greet(city, country) {
console.log(`我叫 ${this.name},来自 ${city},${country}`);
}
const user = { name: '墨墨' };
greet.call(user, '杭州', '中国');
// 我叫 墨墨,来自 杭州,中国
apply: 立即调用,参数以数组形式调用
基本语法
fn.apply(thisArg, [arg1, arg2, ...])
- thisArg:函数要指定的this
- 数组或者类数组参数
示例
function greet(city, country) {
console.log(`我叫 ${this.name},来自 ${city},${country}`);
}
const user = {name: '墨墨'}
greet.apply(user, ['杭州', '中国']);
// 我叫 墨墨,来自 杭州,中国
bind:不立即调用,返回一个新函数,参数一个一个传递
基本语法
const newFn = fn.bind(thisArg, arg1, arg2, ...)
bind和前面两个最大的区别就是:它不会立即执行,而是返回一个绑定好的this函数
示例
const user = {name: '墨墨'}
function greet() {
console.log(`你好,我是 ${this.name}`)
}
const newGreet = greet.bind(user)
newGreet()
// 你好,我是 墨墨
bind还有一个特点就是:可以预设参数
function add(a, b) {
return a + b
}
const newAdd = add.bind(null, 10)
const sum = newAdd(5)
console.log(sum)
// 15
上面相当于把参数a预设成了10, 后续新函数调用,只需传递b即可。这类方法也叫做偏函数
所以我们可以把bind记成:
bind = 不立即执行 + 返回新函数 + 可预置参数
开发中实际可能遇到的场景
- 方法借用
// 1. 一个对象借用另一个对象方法
const obj1 = {
name: "Tom",
say() {
console.log(this.name)
}
}
const obj2 = {
name: "Jerry"
}
obj1.say.call(obj2) // Jerry,相当于obj2自己有say方法一样,将this指向自己
// 2. 借用数组方法,讲类数组转为数组
function test() {
const args = Array.prototype.slice.call(arguments)
}
// 因为arguments是类数组对象,不是真正的数组,它没有slice方法。所以通过call借用了数组原型上的slice方法
// 当然还有其他写法; Array.form(arguments) [...arguments]
// 3. 借用构造函数继承
function Animal (name) {
this.name = name
}
function Dog(name, breed) {
Animal.call(this, name)
this.breed = breed
}
// 这里的Animal.call(this, name),意思就是在Dog的构造过程中,借用Animal来给当前实例赋值
- 参数预置和函数封装
bind常用于创建返回一个带固定参数的新函数
function multiply (a, b) {
return a * b
}
const double = multiply.bind(2)
console.log(double(8)) // 16
- 解决回调中this丢失问题
const counter = {
count: 0,
inc() {
this.count++
console.log(this.count)
}
}
setTimeout(counter.inc, 1000)
上面代码看上去没有问题,1秒后执行counter.inc,打印1。但实际上是有问题。因为传给setTimeout的只是一个函数引用,不再是通过counter.inc() 形式调用,所以this丢失了。
正确写法:
setTimeout(counter.inc.bind(counter), 1000)
这样无论谁来调用这个函数,this都会指向counter。new调用除外,new调用的权重更高,后面讲。
优先级问题
我们知道,显示绑定是高于隐式绑定和默认绑定的。但是显示绑定内部和new绑定,谁的优先级更高呢?
- bind绑定后this,通常不能更改,即bind高于call、appy
function show() {
console.log(this.name)
}
const obj1 = {name: "A"}
const obj2 = {name: "B"}
const newShow = show.bind(obj1)
newShow() // A
newShow.call(obj2) // A 还是打印A,bind的优先级高于call
newShow.apply(obj2) // A 还是打印A,bind的优先级高于apply
- new绑定优先级又高于bind
function Person(name) {
this.name = name
}
const obj = {name: "oldName"}
const BoundPerson = Person.bind(obj)
const p = new BoundPerson("newName")
console.log(p.name) // newName
console.log(obj.name) // oldName
很多人以为bind之后,this永远就是obj,但是这里的obj打印的还是oldName。因为在new调用时,情况就变了。
也就是说:bind指定的this会失效,this会指向新创建的实例对象。
箭头函数不能使用call、apply、bind
因为箭头函数没有自己的this,他的this来自它的外层作用域。所以他恰恰相反,它的this是词法绑定,并不是和普通函数一样由调用的方式决定的。
手写call、apply、bind
- call
Function.prototype.myCall = function(context, ...args) {
// 兼容this,如果没有传递,使用全局this
context = context ?? globalThis
// 创建一个独一无二的key,防止覆盖
const fnKey = Symbol('fn')
// 将原函数挂载在指定的context对象上
context[fnKey] = this
// 使用默认绑定方式调用函数,此时原函数的this,变成了context,指定成功
const result = context[fnKey](...args)
// 删除挂载
delete context[fnKey]
return result
}
- apply
Function.prototype.myApply = function(context, args) {
context = context ?? globalThis
const fnKey = Symbol('fn')
context[fnKey] = this
const result = args ? context[fnKey](...args) : context[fnKey]()
delete context[fnKey]
return result
}
- bind
Function.prototype.myBind = function(context, ...presetArgs) {
if (typeof this !== 'function') {
throw new typeError('myBind must be called on a function')
}
const targeFn = this
function boundFn(...laterArgs) {
const isNewCall = this instanceof boundFn
const thisArg = isNewCall
? this
: (context == null ? globalThis : Object(context))
return targeFn.apply(thisArg, presetArgs.concat(laterArgs))
}
// 让new boundFn() 的实例能继承targetFn.prototype
if (targeFn.prototype) {
boundFn.prototype = Object.create(targeFn.prototype)
boundFn.prototype.constructor = boundFn
}
rturn boundFn
}
评论 (0)
为了减少垃圾评论,请先使用 GitHub 登录后再发表评论。
使用 GitHub 登录评论暂无评论,成为第一个评论者!