在JavaScript中,call,apply, bind 方法都能够帮助我们改变函数执行时this的指向,将一个对象的方法交给另一个对象来执行,接下来让我们详细整理一下三者的区别和实现方法
1. call()
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数,其语法如下;
function.call(thisArg, arg1, arg2, ...)
thisArg 即改变后 function 函数运行时this的指向对象,需要注意的是在非严格模式下,该参数若设置为null 或 undefiend ,this会被自动替换为全局对象 Window, 而在严格模式下则不会发生自动替换
function o() {
console.log(this)
}
o.call(null) //Window
o.call()//Window
-------------------------------------------------------------------------------------------
"use strict";
function o() {
console.log(this)
}
o.call(null) //null
o.call()//undefiend
arg1, arg2, ... 是可选的参数,它们将作为实参在 function 方法执行时的传入
const obj = {
name: 'newThis'
}
const num = 1111
function o(a) {
console.log(this)
console.log(a)
}
o.call(obj,num)//打印 obj 1111
//num作为o方法调用时的实参传入
call() 方法的返回值为 function 函数的返回值,若该函数没有返回值,则返回 undefiend
注意当我们执行 function.call()时, function 方法会被直接调用
根据上面的需求,接下来让我们来实现一个自己的 call 方法,重点主要有两个:
-
将 function 方法绑定给新的
this -
执行 function 方法,并将其结果返回
Function.prototype.myCall = function (context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('error')
} //如果调用myCall方法的并非是一个function对象,则抛出异常
const isStrict = (function () { return this === undefined })()//判断当前环境是否开启严格模式
if (!isStrict) {
context = context || window //非严格模式下当context为null或者undefined时将其赋值为window
const type = typeof context//当context不是Object类型时,使用包装类将基本数据类型转换为对象
if (type === 'number') {
context = new Number(context)
} else if (type === 'string') {
context = new String(context)
} else if (type === 'boolean') {
context = new Boolean(context)
}
} else if (context === undefined || context === null) {
//严格模式若context为null或者undefined则直接执行当前function,此时该function的this为undefined
return this(...args)
}
const k = Symbol('k')//使用Symbol确保不会发生命名冲突
context[k] = this// 相当于 context.k = this 即将function方法添加到context所指向的对象上
const result = context[k](...args)//执行function,并接收其返回值
delete context[k]//删除添加到context所指对象上的function,避免污染context所指向的对象
return result//将function执行的结果作为myCall的返回值
}
2. apply()
apply() 方法与 call() 的区别在于第二个参数的形式不同,需要将 function 方法执行时所需要的参数合并为一个数组或者伪数组对象传入,其语法如下:
function.apply(thisArg, argsArray)
thisArg 与 call 方法的使用相同,表示 function 方法执行时的 this指向
argsArray 一个类数组对象,用于指定调用 function 时的参数,或者如果不需要向函数提供参数,则为 undefiend 或者 null
const obj = {
name: 'newThis'
}
const arr = [1,2]
function o(a,b) {
console.log(this)
console.log(a,b)
}
o.apply(obj, arr)// obj 1 2
//将o方法执行时所需要的参数合并为一个数组传入
apply() 方法的实现与 call() 方法类似,只需要在调用 function 时注意处理参数数组
Function.prototype.myApply = function (context, argsArr) {
if (typeof this !== 'function') {
throw new TypeError('error')
}
const isStrict = (function () { return this === undefined })()
if (!isStrict) {
context = context || window
const type = typeof context
if (type === 'number') {
context = new Number(context)
} else if (type === 'string') {
context = new String(context)
} else if (type === 'boolean') {
context = new Boolean(context)
}
} else if (context === undefined || context === null) {
return this(...argsArr)
}
const k = Symbol('k')
context[k] = this
const result = argsArr ? context[k](...argsArr) : context[k]()//对没有参数的情况进行处理
delete context[k]
return result
}
3. bind()
在实际的使用场景中,有时候我们并不希望 function 方法立即执行,而是在某些条件满足的情况下再进行执行,这时显然 call() 跟 apply() 方法都不适用,bind() 方法能很好的解决这一问题
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用其语法如下:
function.bind(thisArg[, arg1[, arg2[, ...]]])
其参数传递方式跟call方法一致,但和apply、call 稍微有所不同,bind 是一个闭包结构,返回的是一个函数,可以在需要的时候选择执行这个函数
对于bind的实现,最基础的方式直接在返回的函数中调用 call方法即可:
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('error')
}
context = context || window // 判断上下文是否传入,默认window
const _this = this // 保存this
// 返回闭包
return function (...innerArgs) {
return _this.call(context, ...args, ...innerArgs)//注意bind在调用时可以继续传参
}
}
但是这种方式存在一些问题,当返回的函数与 new 配合作为构造函数使用时的情况发生时,构造函数的 this指向其生成的实例对象,此时会造成我们传递的 context 失效,需要进行处理:
Function.prototype.myBind = function (context,...args) {
if (typeof this !== 'function') {
throw new TypeError('error')
}
context = context || window // 判断上下文是否传入,默认window
const _this = this // 保存this
// 返回闭包
return function fn(...innerArgs) {
if (this instanceof fn) {//若使用new关键字返回函数的this指向一个新的fn对象,否则this指向window
return new _this(...args, ...innerArgs)
}
return _this.call(context, ...args, ...innerArgs)
}
}
上面的写法使用了大量ES6语法,采用ES5可以进行如下实现:
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('error')
}
var _this = this
var args = Array.prototype.slice.call(arguments, 1)//arguments是伪数组不能直接调用Array的方法
var fn = function () {
var innerArgs = Array.prototype.slice.call(arguments)
return _this.apply(this instanceof fn ? this : context, args.concat(innerArgs))
}
fn.prototype = Object.create(_this.prototype)//改变fn的原型对象,让fn的实例能够继承绑定函数原型中的值
return fn
}