哈喽,我是前端菜鸟JL😄 下面分享一下call、apply、bind这个专题
概念
在javascript中,这些方法都是为了改变某个函数运行时的上下文(context)
即改变函数内部this的指向
函数原型上的方法
function test() {}
test.prototype = {
color: 'red',
say: function() {
console.log('my color is' + this.color)
}
}
const check = new test()
check.say() // My color is red const
test1 = { color: 'yellow' }
check.say.call(test1) // My color is yellow
check.say.apply(test1) // My color is yellow
可以看出,this被改变,当test1没有say方法时候,可以借助其他函数方法
用法
apply、call用法类似,主要区别在参数方面
- call()方法接受的是参数列表
- apply()方法接受的是一个参数数组
fn.apply(thisArg, [argsArray]) // apply
fn.call(thisArg, arg1, arg2, arg3...) // call
// 例子
const fn = function(arg1, arg2) {}
fn.call(this, arg1, arg2)
fn.apply(this, [arg1, arg2]) // 按顺序传递
参数解析:
- thisArg:必选值,需要fn运行时改变指向的this值,在非严格模式下,该参数指定为null或undefined时会自动替换为指向全局对象
- argsArray:可选的,一个数组或者类数组对象,即相当于实参传进fn函数中。如果该参数值为null或undefined,表示不需要传入任何参数。
bind
bind和其他两个主要区别是call、apply会立即执行函数,而bind会创建一个函数但是不会立即执行。
const obj = { a: 1 }
const fn = {
say: function() {
return this.x
}
}
console.log(foo.getX.bind(obj)()) // 1
console.log(foo.call(obj)) // 1
console.log(foo.apply(obj)) // 1
注意:bind还有个主要区别是,bind参数列表可以分多次传入,因为不是立即执行,而其他两个需要一次性传入
const arr = [1,2,3,4]
const max = Max.max.bind(null, arr[0], arr[1], arr[2])
console.log(max(arr[3]))
应用场景
数组之间追加
const arr1 = [1,2,3]
const arr2 = [4,5,6]
Array.prototype.push.apply(arr1, arr2)
arr1 // [1,2,3,4,5,6]
获取数组中的最大值或最小值
const arr = [1,2,3,4,5,6]
const MaxNum = Math.max.apply(Math, arr)
const MaxNum = Math.max.call(Math, 1, 2, 3, 4, ,5 ,6)
判断数据类型(特别常用!)
const test = Object.prototype.toString
test.call('') // '[oboject String]'
test.call(1) // '[object Number]'
test.call(true) // '[object Boolean]'
test.call(null) // '[object Null]'
test.call(undefined) '[object Undefined]'
test.call([]) 'object Array'
test.call(function() {}) 'object Function'
test.call({}) 'object Object'
手撕来了
call
思路:
- 将函数变成对象的属性
- 执行该函数
- 删除该函数
Function.prototype.newCall = function (context) {
// 判断传入this,为null/undefined时要赋值为window或global
// 浏览器为window、其他环境(node等)为global
if (!context) {
context = typeof window === 'undefined' ? global : window
}
// 将调用call的函数变成需要修改this指向的对象的属性
context.fn = this // this获取调用call的函数
const rest = [...arguments].slice(1) // 获取参数
const result = context.fn(...rest) // 隐式绑定,传入参数
delete context.fn
return result
}
apply
apply和call类似,区别在于参数
Function.prototype.applytest = function (context, rest) {
if (!context) {
context = typeof window === 'undefined' ? global : window
}
context.fn = this
let result
if (rest === null || rest === undefined) { // 不是类数组或者数组不能...扩展运算符
result = context.fn()
} else {
result = context.fn(...rest)
}
delete context.fn
return result
}
bind
区别于其他两个:
- 创建的是一个新函数,不会立即执行
- bind参数列表可以分多次传入,call、apply只能一次性传入所有参数
Function.prototype.bindtest = function (context) {
if (typeof this !== "function") {
throw new TypeError("not a function")
}
let self = this
let args = [...argument].slice(1) // 获取第一次的参数
function Fn() {}
Fn.prototype = this.prototype
const bound = function () {
const res = [...args, ...arguments] // 拼接第二次参数
// 注意,这里this和上面this不一样,是新方法bound的执行this
// 两种情况,原型指向Fn(构造函数),原型不指向Fn(普通函数,this指向window)
context = this instanceof Fn ? this : context || this
// 通过apply改变this指向,call也行
return self.apply(context, res)
}
// 原型链继承
bound.prototype = new Fn()
return bound
}
结语
希望能给你带来帮助✨~
分享不易,点赞鼓励🤞