一、含义
函数原型(Function.prototype)上的方法,用于改变函数的 this 指向
二、用法
- 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
- 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
- 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
三、区别
- 三者和直接调用函数的区别
三者调用可以改变函数的 this 指向,而直接调用 函数的 this 只能指向函数的调用者
- call 和 apply 的区别
从第二个参数开始,call 的参数是一个一个传递的,apply 的参数是组成一个参数数组传递的
- 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]
五、实现
- 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 // 返回函数调用的结果
}
- 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 // 返回函数调用的结果
}
- 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
}
}