call、apply 以及 bind 的区别和用法
语法
function.call(thisArg, arg1, arg2, ...)
function.apply(thisArg, argsArray)
function.bind(thisArg,arg1, arg2, ...)
call()、apply()和bind()方法都是用来改变 this 指向.
call()和apply()方法类似,区别在于call()方法接受一个参数列表,而apply()接受的是一个数组bind()和call()、apply()两个之间的区别在于, bind()返回新创建的绑定函数,不会立即执行。而call()、apply()调用时就会执行。
模拟实现
模拟实现 apply
当以 thisArg 和 argArray 为参数在一个对象 func 上调用 apply 方法时,将进行以下步骤:
- 如果 func 不可被调用,则抛出一个 TypeError 异常
- 如果 argArray 是 null 或 undefined, 则返回提供 thisArg 作为 this 值并以空参数列表调用 的 [[Call]] 内部方法的结果
- 如果 argArray 的类型不是 Object ,则抛出一个 TypeError 异常
- 提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 内部方法,返回结果
var foo = {
value: 1,
bar: function (...args) {
console.log(this.name)
console.log(args);
},
}
var person = {
name: 'jack',
}
// 浏览器环境 非严格模式
function getGlobalObject() {
return this
}
Function.prototype.applyFn = function (thisArg, argArray) {
// 1
if (typeof this !== 'function') {
throw new TypeError(`this is not function`)
}
// 2
if (typeof argArray === 'undefined' || argArray === null) {
argArray = []
}
// 3
if (argArray !== new Object(argArray)) {
throw new TypeError(`CreateListFromArrayLike called on non-object`)
}
if (typeof thisArg === 'undefined' || thisArg === null) {
// 在外面传入的 thisArg 值会修改并成为 this 值。
// thisArg 是 undefined 或 null 时它会被替换成全局对象
thisArg = getGlobalObject()
}
thisArg = new Object(thisArg)
var __fn = Symbol()
thisArg[__fn] = this
// 4
var result = thisArg[__fn](...argArray)
delete thisArg[__fn]
return result
}
foo.bar.applyFn(person, [1, 2]) // jack [1,2]
模拟实现 call
当以 thisArg 和可选的 arg1, arg2 等等作为参数在一个 func 对象上调用 call 方法,采用如下步骤:
- 如果 func 不可被调用,则抛出一个 TypeError 异常
- 设置 argList 为一个空列表
- 如果传递了多个参数,则按从左到右的顺序开始,将每个参数作为argList的最后一个元素追加
- 提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 内部方法,返回结果
Function.prototype.callFn = function (thisArg) {
// 1
if (typeof this !== 'function') {
throw new TypeError(`this is not function`)
}
// 2
var argList = []
// 3
var argsLen = arguments.length
for (var i = 0; i < argsLen - 1; i++) {
argList[i] = arguments[i + 1]
}
thisArg = new Object(thisArg)
var __fn = Symbol()
thisArg[__fn] = this
// 4
var result = thisArg[__fn](...argList)
delete thisArg[__fn]
return result
}
foo.bar.callFn(person, 1, 2) // jack [1,2]
模拟实现 bind
bind 方法需要一个或更多参数,thisArg 和(可选的)arg1, arg2, 等等,执行如下步骤返回一个新函数对象:
- 创建变量 target 并将赋值 this
- 如果 target 不可被调用,则抛出一个 TypeError 异常
- 创建变量 args 为(可能为空的)新内部列表,它包含按顺序的 thisArg 后面的所有参数(arg1, arg2 等等)。
- 创建绑定函数
4.1. new 调用判断,通过 instanceof 判断函数是否是 new 调用的
4.2. 绑定 this ,传入合并参数执行
4.3. 返回绑定函数的执行结果 - 设置绑定函数的原型
- 返回绑定函数
Function.prototype.bindFn = function (thisArg) {
// 1 创建变量 target 并将赋值 this
var target = this
// 2 如果 target 不可被调用,则抛出一个 TypeError 异常
if (typeof this !== 'function') {
throw new TypeError(this + 'is not a function')
}
// 3 创建变量 args 为(可能为空的)新内部列表,它包含按顺序的 thisArg 后面的所有参数(arg1, arg2 等等)
var args = [].slice.call(arguments, 1)
// 4
var bound = function () {
// bind 函数的参数
var boundArgs = [].slice.call(arguments)
var argsList = args.concat(boundArgs)
// new 调用
if (this instanceof bound) {
// apply 修改 this 指向 为 bound
var result = target.apply(this, argsList)
// 如果函数返回对象类型是 Object,那么直接返回指向结果
var isObject = typeof result === 'object' && result !== null
var isFunction = typeof result === 'function'
if (isObject || isFunction) {
return result
}
// 如果函数没有返回对象类型 Object,那么 new 操作中的函数将返回 bound
return this
} else {
// apply 修改 this 指向,把两个函数的参数集合传给 target 函数并执行,返回执行结果
return target.apply(thisArg, argsList)
}
}
// 5
// target 不是箭头函数,才进行指向prototype
if (target.prototype) {
// 创建一个新对象,将 bound 的原型指定为 targe.prototype
function Empty() {}
Empty.prototype = target.prototype
bound.prototype = new Empty()
}
// 6
return bound
}
var fn = foo.bar.bindFn(person,1)
fn() // jack [1]