【JS】call、apply、bind

110 阅读3分钟

一、含义

函数原型(Function.prototype)上的方法,用于改变函数的 this 指向

二、用法

  1. call
// 调用者:函数
// 形参:(要设置的函数的 this 指向, param1, param2, ...)
// 返回值:调用者函数本身的返回值

let obj1 = {
    name: "obj1",
    fn: function (param1, param2, param3) {
        console.log(this.name, param1, param2, param3);
    }
}

let obj2 = { name: "obj2" }

obj1.fn(1, 2, 3);  // obj1 1 2 3
obj1.fn.call(obj2, 4, 5, 6)  // obj2 4 5 6
  1. apply
// 调用者:函数
// 形参:(要设置的函数的 this 指向, [param1, param2, ...])
// 返回值:调用者函数本身的返回值

let obj1 = {
    name: "obj1",
    fn: function (param1, param2, param3) {
        console.log(this.name, param1, param2, param3);
    }
}

let obj2 = { name: "obj2" }

obj1.fn(1, 2, 3);  // obj1 1 2 3
obj1.fn.apply(obj2, [4, 5, 6])  // obj2 4 5 6
  1. bind
// 调用者:函数
// 形参:(要设置的函数的 this 指向, param1, param2, ...)
// 返回值:调用者函数的深拷贝
let obj1 = {
    name: "obj1",
    fn: function (param1, param2, param3) {
        console.log(this.name, param1, param2, param3);
        return {a: 1};
    }
}

let obj2 = { name: "obj2" }

obj1.fn(1, 2, 3);  // obj1 1 2 3
let res = obj1.fn.bind(obj2, 4, 5, 6);
res();  // obj2 4 5 6

注意:如果三者的第一个参数是 null 或者 undefined,this 就指向全局对象 window

三、区别

  1. 三者和直接调用函数的区别

三者调用可以改变函数的 this 指向,而直接调用 函数的 this 只能指向函数的调用者

  1. call 和 apply 的区别

从第二个参数开始,call 的参数是一个一个传递的,apply 的参数是组成一个参数数组传递的

  1. call、apply 与 bind 的区别

调用 call 或 apply 后,调用者函数会立即执行;而调用 bind 后,会返回一个函数,函数内部会调用 调用者函数,所以是 非立即执行 的

四、应用

1. call

(1) 对象的继承

let SuperFn = function(){
    this.name = "SuperFn";
    this.print = function(){
        console.log(this.name);
    }
}

let SubFn = function(){
    SuperFn.call(this);  // 继承了 SuperFn
    // 可以访问 SuperFn 的变量和方法
    console.log(this.name);
    this.print();  
}

SubFn();  // SuperFn SuperFn

(2) 借用方法

// 定义一个类数组
let arrayLike = {
    0: 'a',
    1: 'b',
    length: '2'
}
console.log(arrayLike)  // {0: 'a', 1: 'b', length: '2'}

// arrayLike.push('c', 'd')  // Uncaught TypeError: arrayLike.push is not a function
// 借用数组原型上的方法
Array.prototype.push.call(arrayLike, 'c', 'd')

console.log(arrayLike)  // {0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4}

2. apply

(1) 求数组最值

let arr = [2, 5, 9, 3, 6]
// 相当于借助 apply 将数组解构
let max = Math.max.apply(null, arr)
let min = Math.min.apply(null, arr)
console.log(max, min)  // 9 2

// 当然,也可以直接将数组解构使用
console.log(Math.max(...arr))  // 9
console.log(Math.min(...arr))  // 2

(2) 数组合并

let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]

// arr1.push(arr2)
// console.log(arr1)  // (4) [1, 2, 3, Array(3)]

// 借助 apply 将 arr2 解构
Array.prototype.push.apply(arr1, arr2)
console.log(arr1)  // (6) [1, 2, 3, 4, 5, 6]

// 当然,也可以将 arr2 直接解构使用
// arr1.push(...arr2)
// console.log(arr1)  // (6) [1, 2, 3, 4, 5, 6]

五、实现

  1. call
Function.prototype.myCall = function (target, ...args) {
    target = target || window  // 假如没有传入 target,则默认为 window 对象
    const symbolKey = Symbol()  // 定义一个 target 上下文唯一的 key
    target[symbolKey] = this  // 让这个 key 的值为 调用者函数
    const res = target[symbolKey](...args)  // 将 args 解构,并调用 调用者函数
    delete target[symbolKey]  // 调用完后就直接删除这个 key-value
    return res  // 返回函数调用的结果
}
  1. apply(和 call 差不多,只是接收形参的方式不同)
Function.prototype.myApply = function (target, args) {
    target = target || window  // 假如没有传入 target,则默认为 window 对象
    const symbolKey = Symbol()  // 定义一个 target 上下文唯一的 key
    target[symbolKey] = this  // 让这个 key 的值为 调用者函数
    const res = target[symbolKey](...args)  // 将 args 解构,并调用 调用者函数
    delete target[symbolKey]  // 调用完后就直接删除这个 key-value
    return res  // 返回函数调用的结果
}
  1. bind
Function.prototype.myBind = function (target, ...bindArgs) {
    target = target || window
    const symbolKey = Symbol()
    target[symbolKey] = this
    // 返回一个函数,非立即执行
    return function (...innerArgs) {
        const res = target[symbolKey](...bindArgs, ...innerArgs)  // 不管是bind中传递的参数,还是调用bind的返回函数时传入的参数,都老老实实的传递到 调用者方法 中
        // delete target[symbolKey]   // 这里不要销毁这个 key-value,否则会导致第二次调用报错
        return res
    }
}