浅解析call、apply 和 bind
在各种的源码中,都能够看到对于他们的使用的。但是平时做业务的事情却很少接触到。这就导致了很多人对它们有一种神秘感,看了忘,面试前再看,面试完之后接着忘记。
反正我是这样的。所以想要真正的记住它,平时就有机会就使用,没有机会也有创造机会使用,只有能够面对codeReview。
定义
fn.call(obj, param1, param2, ...)
fn.apply(obj, [param1,param2,...])
fn.bind(obj, param1, param2, ...)
通过什么这个例子能够很明显的看出来,使用三者都是通过.
来使用的。那么是不是可不可以使用[]
来调用呢?
function test() {
console.log(1)
}
test['call']() // 1
答案是可以肯定的。
所以说,它们就是对象的三个方法而已。和自己创建的对象的方法没有什么不同。只是我们平时没有直接看到该对象上面的方法而已。如下图:
- Function.prototype.call()
- Function.prototype.bind()
- Function.prototype.apply()
综上所述:call、apply 和 bind 是挂在 Function 对象上的三个方法,所以调用这三个方法的必须是一个函数。
有了这个认知,我们回到第一个代码片段。
- fn: 需要调用的函数名,Function的实例。
- obj: 当前作用域中的对象,一般使用的时候是
this
所指向的对象。 - param: 实参,没有可不传。
三者的作用
三者和直接调用函数的区别
这个区别很重要。了解它们之间的区别,其实是为了了解它们存在的意义,为什么委员会会将这三个放到Function
上面,它们解决了什么痛点?
call()
允许为不同的对象分配和调用属于一个对象的函数/方法。call()
提供新的 this 值给当前调用的函数/方法。你可以使用call
来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。 - 来自MDN Function.prototype.call()
简单的来说,提供了可以改变当前函数的this
。
如果第一个参数为为null
的话,那么它们没有任何不同,如下代码片段片段:
function A(name) {
this.name = name
console.log('I am A')
}
function B(){
A.call(null, '砂糖橘加盐')
}
const b = new B()
console.log(b) // B {}
执行的结果是空对象,打印出I am A
。
而当null
-> this
,之后:
function B() {
A.call(this, '砂糖橘加盐')
}
const b = new B()
console.log(b) // B { name: '砂糖橘加盐' }
并且也成功打印出console的结果。还'意外'的让B的实力的对象挂载了A构造函数的this
对象。也就是之前说的,它能够改变当前函数的this
。
改变了之后又有什么作用呢?
我们可设想这么一个场景,当很多对象都存在相同的属性的时候,如果给每个构造函数都要添加一个的属性。如果可以抽离出来的话,我们是不是可以少写很多代码。如下代码片段:
function B() {
this.name = '狗不理'
A.call(this, 'wyc')
}
function C() {
A.call(this, 'wyc')
}
function D() {
A.call(this, 'wyc')
}
function E() {
A.call(this, 'wyc')
}
这就是MDN说的继承。
而直接调用函数的话,做不到。这就是call、apply、bind存在的意义。
call、apply、bind三者的区别
这三个方法共有的、比较明显的作用就是,都可以改变函数 func 的 this 指向。
call、apply两者和bind的区别:返回的结果不一样,bind返回的是Function类型。如下:
const a = {
name: '砂糖橘放盐',
getName: function(msg) {
return msg + this.name;
}
}
const b = {
name: '橘子哥'
}
console.log(a.getName('你好,')); // 你好, 砂糖橘放盐
const name = a.getName.bind(b, '你好,');
console.log(name()); // 你好, 橘子哥
call 和 apply 的区别在于:传参的写法不同。
call的参数是列表,apply的则是数组形式。
console.log(a.getName.call(b, '你好,')); // 你好, 橘子哥
console.log(a.getName.apply(b, ['你好,'])) // 你好, 橘子哥
以上就是他们的区别,他们的实际使用场景是什么呢?
使用场景
判断数据类型
toString()
最为完善的类型判断,相比instanceof
、constructor
、typeof
各有各自的缺陷而言。
除了Object对象,其他的类型由于存在隐式转化的关系,需要通过call/apply来调用,如下代码片段:
Object.prototype.getType = function() {
let type = typeof this
if (type !== "object") {
return type
}
return Object.prototype.toString.call(this).replace(/^$/, '$1')
}
const a = '1'
const b = a.getType()
console.log(b) // [object String]
可以直接放到Object.prototype上面,更加的方便。虽然在工作中不推荐。
类数组借用方法
const arrayLike = {
0: '西瓜',
1: '草莓',
length: 2
}
Array.prototype.push.call(arrayLike, '橘子', '老虎')
console.log(typeof arrayLike) // 'object'
console.log(arrayLike)
// {0: "西瓜", 1: "草莓", 2: "橘子", 3: "老虎", length: 4}
继承
实现继承的方法有很多,这么只是简单说一下,其他更多的可以查看我之前的文章。
那么下面我们结合着这一讲的内容再来回顾一下组合继承方式,代码如下。
function Parent () {
this.name = 'parent'
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
const s1 = new Child()
console.log(s1.getName()); // 'parent'
转载自:https://juejin.cn/post/7195935352803360824