前言
call、apply、bind 都是定义在函数原型上的,也就是说每个函数都能调用这些方法
那么它们都有什么作用呢?它们之间存在什么异同呢?下面让我们一起来探讨一下
正文
1、call
call 可以用于改变函数的执行环境,简单来说就是可以改变函数内部 this 的指向
使用 call 可以让一个对象借用另外一个对象的方法,可以借此实现继承
第一个传入的参数是上下文执行环境,即函数运行时 this 的指向,之后传入的参数将会直接传递给调用函数
在 call 调用完成后,返回调用函数的返回值
// 借用方法 let apple = { color: 'red', getColor: function() { return this.color } } let banana = { color: 'yellow' } let color = apple.getColor.call(banana) console.log(color) /* * 执行结果: * yellow **/
// 实现继承 function Parent(age, name) { this.age = age this.name = name this.getName = function() { return this.name } this.setName = function(name) { this.name = name } } function Child(age, name) { Parent.call(this, age, name) } let child = new Child(18, 'Peter') child.setName('Steve') let name = child.getName() console.log(name) /* * 执行结果: * Steve **/
2、apply
apply 的作用与 call 完全一样,都能用于改变函数的执行环境,两者的区别仅仅在于传入的参数
第一个参数传入的都是上下文执行环境,即函数运行时 this 的指向,参数的区别在于之后传入的参数
之后传入的参数是调用函数执行所需的参数,call 是按照顺序直接传入,apply 是将参数放在数组中再传入
// 判断类型 let number = 0 let string = '' let boolean = true let object = {} let array = [] function typeOf(value) { return Object.prototype.toString.apply(value).slice(8, -1) } console.log(typeOf(number)) console.log(typeOf(string)) console.log(typeOf(boolean)) console.log(typeOf(object)) console.log(typeOf(array)) /* * 执行结果: * Number * String * Boolean * Object * Array **/
// 数值求和 function addNumber() { let isNumber = function(value) { return typeof value === 'number' } let numbers = Array.prototype.filter.apply(arguments, [isNumber]) let sum = numbers.reduce(function(prev, curr) { return prev + curr }) return sum } let result = addNumber(1, 'a', 2, 'b', 3, 'c') console.log(result) /* * 执行结果: * 6 **/
3、bind
传入 bind 的参数与 call 完全相同,作用也与 call 大致一样,但它们还是有所区别的
call 在调用后马上执行函数,bind 不会,调用 bind 返回一个改变了上下文的新函数,可以在需要的时候再调用
// 借用方法 let apple = { color: 'red', getColor: function() { return this.color } } let banana = { color: 'yellow' } let getColorForBanana = apple.getColor.bind(banana) console.log(getColorForBanana) let color = getColorForBanana() console.log(color) /* * 执行结果: * ƒ () { return this.color } * yellow **/
// 解决回调函数 this 指向的问题 let object = { value: 0, asyncPrint: function() { setTimeout(function() { console.log(this.value) }, 2000) }, asyncPrintWithThat: function() { let that = this setTimeout(function() { console.log(that.value) }, 2000) }, asyncPrintWithBind: function() { setTimeout(function() { console.log(this.value) }.bind(this), 2000) } } object.asyncPrint() object.asyncPrintWithThat() object.asyncPrintWithBind() /* * 执行结果: * undefined * 0 * 0 **/
4、手动实现三个函数
- call
Function.prototype.myCall = function(cxt, ...params) { // 处理传入的上下文执行环境 // 若为 null 或 undefined,则要转换成全局对象 // 若为 原始值,也要转换成对应的实例对象 const context = (cxt !== null && cxt !== undefined) ? Object(cxt) : window // 创建一个临时属性 // 为了避免属性冲突,这里使用 Symbol 数据类型 const property = Symbol('property') // 设置临时属性的值为调用函数 context[property] = this // 通过对象方法调用函数,此时 this 指向 context,也就是指向了传入的上下文对象 let result = context[property](...params) // 调用完成之后,删除方法,避免污染传入对象 delete context[property] // 返回执行结果 return result }
- apply
Function.prototype.myApply = function(cxt, arr) { // 处理传入的上下文执行环境 const context = (cxt !== null && cxt !== undefined) ? Object(cxt) : window // 创建一个临时属性 const property = Symbol('property') // 设置临时属性的值为调用函数 context[property] = this // 声明执行结果 let result = null // 用于检测传入的参数是否为类数组 function isArrayLike(value) { if (value && typeof value === 'object' && isFinite(value.length) && value.length >= 0 && value.length === Math.floor(value.length) && value.length < 4294967296) { return true } else { return false } } // 是否传入第二个参数 if (arr) { // 第二个参数是否为类数组 if (isArrayLike(arr)) { let params = Array.from(arr) result = context[property](...params) } else { throw new TypeError() } } else { result = context[property]() } // 调用完成之后,删除方法,避免污染传入对象 delete context[property] // 返回执行结果 return result }
- bind
Function.prototype.myBind = function(cxt, ...outerParams) { let self = this let bound = function(...innerParams) { // 判断绑定好的函数是否通过 new 调用 // 如果通过 new 调用,则绑定到 this;否则就绑定到传入的上下文执行环境 const context = (this instanceof bound) ? this : cxt // 通过 call 模拟实现 return self.call(context, ...outerParams, ...innerParams) } // 使得绑定好的函数与调用 bind 的函数处于同一原型链上 bound.prototype = Object.create(this.prototype) // 返回绑定好的函数 return bound }