本系列面试题旨在学会相关知识点,从而轻松应对面试题的各种形式,本文讲解了 JavaScript
的基础 call
,apply
,bind
的作用。
感觉有帮助的小伙伴请点赞👍鼓励一下 ~
call/apply
作用
用来改变函数内部 this
的指向。
特点
任何函数都可以调用这两个方法,说明它们是添加在函数原型上的方法(Function.prototype
)。
console.dir(Function.prototype)
调用 call
和 apply
的函数会立即执行。
call
和 apply
的返回值就是函数的返回值。
var name = '一尾流莺'
var obj = {
name: 'warbler',
}
function foo() {
console.log(this.name);
return 'success'
}
foo.call(obj) //=> warbler
console.log(foo.call(obj)); // => success
调用 call
和 apply
指向 undefined
或者 null
,会将 this
指向 window
。
function foo() {
console.log(this)
}
foo.call(undefined)
foo.call(null)
foo.apply(undefined)
foo.apply(null)
调用 call
和 apply
指向一个值类型, 会将 this
指向由它们的构造函数创建的实例。
function foo() {
console.log(this)
}
foo.call(11)
foo.call('11')
foo.call(true)
调用 call
和 apply
指向一个引用类型, 会将 this
指向这个对象。
我们声明了一个全局变量 name
和一个全局作用域下的函数 foo
。
var name = '一尾流莺'
var obj = {
name: 'warbler',
}
function foo() {
console.log(this.name)
}
foo() //=> 一尾流莺
这段代码很好理解,name
等价于 window.name
, foo()
等价于 window.foo()
,我们打印出this.name
,当前的 this
指向它的调用者 window
, 也就是 window.name
得到 一尾流莺。
但是如果我想打印出 warbler 该怎么办呢?在 obj
里面再定义一个 obj.fn
么? 当然不需要,
我们只需要调用 call/apply
改变 this
的指向,指向 obj
这个对象就可以了。这个时候 this.name
等价于 obj.name
,就得到了 warbler 。
foo.call(obj) //=> warbler
foo.apply(obj) //=> warbler
call 和 apply的区别
除了传参的形式不同没什么区别。
传给fn
的参数写法不同:
call
接收多个参数,第一个为函数上下文也就是this
,后边参数为函数本身的参数。apply
接收两个参数,第一个参数为函数上下文this
,第二个参数为函数参数只不过是通过一个 数组 的形式传入的。
只要记住 apply
是以 a
开头,它传给 fun
的参数是 Array
,也是以 a
开头的,就可以很好的分别这两个函数了。
手写call/apply
手写call
var name = '一尾流莺'
var obj = {
name: 'warbler',
}
function foo() {
console.dir(this);
return 'success'
}
/**
* Object()方法
* 如果传入的是值类型 会返回对应类型的构造函数创建的实例
* 如果传入的是对象 返回对象本身
* 如果传入 undefined 或者 null 会返回空对象
*/
Function.prototype._call = function(ctx, ...args) {
// 判断上下文类型 如果是undefined或者 null 指向window
// 否则使用 Object() 将上下文包装成对象
const o = ctx == undefined ? window : Object(ctx)
// 如何把函数foo的this 指向 ctx这个上下文呢
// 把函数foo赋值给对象o的一个属性 用这个对象o去调用foo this就指向了这个对象o
// 下面的this就是调用_call的函数foo 我们把this给对象o的属性fn 就是把函数foo赋值给了o.fn
//给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol()
o[key] = this
// 立即执行一次
const result = o[key](...args)
// 删除这个属性
delete o[key]
// 把函数的返回值赋值给_call的返回值
return result
}
开始验证
foo._call(undefined) // window
foo._call(null) // window
foo._call(1) // Number
foo._call('11') // String
foo._call(true) // Boolean
foo._call(obj) // {name: 'warbler'}
console.log(foo._call(obj)); // success
可以看到我们手写的 _call
方法和原生的 call
方法得到的结果是一样的。
手写 apply
之前讲过,call
和 apply
的唯一区别就是传递参数的不同,所以我们只需要改一下对参数的处理,其它的和 call
一致就可以了。
var age = 10
var obj = {
age: 20,
}
function foo(a, b) {
console.dir(this.age + a + b);
}
// 只需要把第二个参数改成数组形式就可以了。
Function.prototype._apply = function(ctx, array = []) {
const o = ctx == undefined ? window : Object(ctx)
//给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol()
o[key] = this
const result = o[key](...array)
delete o[key]
return result
}
foo(3, 4) // => 17
foo._apply(obj, [3, 4]) //=> 27
bind
作用
也是用来改变函数内部 this
的指向。
bind 和 call/apply 的区别
是否立刻执行:
call/apply
改变了函数的this
上下文后 马上 执行该函数。bind
则是返回改变了上下文后的函数, 不执行该函数 。
返回值的区别:
call/apply
返回fun
的执行结果。bind
返回fun
的拷贝,并指定了fun
的this
指向,保存了fun
的参数。
var name = '一尾流莺'
var obj = {
name: 'warbler',
}
// this 指向调用者document
document.onclick = function() {
console.dir(this); // => #document
}
// this 指向 obj
document.onclick = function() {
console.dir(this); // => #Object{name:'warbler}
}.bind(obj)
手写bind
Function.prototype._bind = function(ctx, ...args) {
// 下面的this就是调用_bind的函数,保存给_self
const _self = this
// bind 要返回一个函数, 就不会立即执行了
const newFn = function(...rest) {
// 调用 call 修改 this 指向
return _self.call(ctx, ...args, ...rest)
}
if (_self.prototype) {
// 复制源函数的prototype给newFn 一些情况下函数没有prototype,比如箭头函数
newFn.prototype = Object.create(_self.prototype);
}
return newFn
}