You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Function.prototype.myBind=function(context){if(typeofthis!=='function'){thrownewError('Function.prototype.bind - what is trying to be bound is not callable.')}letself=this// 获取传入的从下标为[1]开始的参数作为「预设参数」:letargs=Array.prototype.slice.call(arguments,1)returnfBound=function(){// 预设参数之外传入的参数,即 bind() 返回的新函数调用时传入的『新增参数』letbindArgs=Array.prototype.slice.call(arguments)// 改变绑定函数中 this 的指向,并将「预设参数」和『新增参数』合并self.myApply(context,args.concat(bindArgs))}}
模拟构造函数版:
Function.prototype.myBind=function(context){if(typeofthis!=='function'){thrownewError('Function.prototype.bind - what is trying to be bound is not callable.')}letself=thisletargs=Array.prototype.slice.call(arguments,1)letfBound=function(){letbindArgs=Array.prototype.slice.call(arguments)self.myApply(thisinstanceofself ? this : context,args.concat(bindArgs))}/* 如果只是 fBound.prototype = this.prototype, 当我们直接修改 fBound.prototype 的时候, 也会一并顺带修改绑定函数的prototype,这不是我们想要的结果。 这个时候,我们可以通过一个空函数来进行中转: */letfEmpty=function(){}fEmpty.prototype=self.prototypefBound.prototype=newfEmpty()returnfBound}
.call
、.apply
和.bind
方法是函数对象的原型方法(Function.prototype),有「改变this
指向的」特殊作用,初学 JavaScript 不太容易理解其机制,本文将通过逐步模拟实现这些方法,以加深对这些方法的理解。一、Function.prototype.call()
1. 简介
call()
简单来说,call方法的作用就是:在指定一个this值和若干个指定的参数值的前提下调用某个函数或方法。
因为接下来介绍的函数方法和 this 指针都有很密切的关系,所以我首先向各位推荐一篇文章帮助理解 this 指针:《关于 this 你想知道的一切都在这里》。
举个栗子来说明:
2. 模拟实现:
我们模拟的步骤是:
call
方法可带任意个参数。null
和undefined
。1.为什么
targetArg.push('arguments['+i+']')
的值是字符串,而非targetArg.push(arguments[i])
的值?答:为了兼容字符串类型的值,
例如参数为
myCall(obj, "me", 21)
时,push(arguments[i])
后的targetArg
是:["me", 21]
,经过'context.fn('+targetArg+')'
之后,会变成字符串:"context.fn(me,21)"
,把
me
当成了一个变量,而非原本的字符串,但me
作为一个变量,并未声明过,所以会报错、不可行。而
targetArg.push('arguments['+i+']')
的值是字符串,经过'+targetArg+'
后,是"context.fn(arguments[1],arguments[2])"
,对字符串类型不会报错,可行!2. 为什么用
eval()
这么 hack 的方式实现?还有别的实现方式吗?首先,因为
targetArg
最终为「字符串」类型,所以需要通过eval
转化为对应的变量其次,如果不考虑兼容性,不需要 ES3 就支持的
eval
,也可以用 ES6 的 spread operator:...
替代eval
,执行这一步,即:3. 嗅探兼容
这种给内置对象扩展方法的实现形式,通常称为
Monkey patching(猴子补丁)
,可以做一步嗅探
来保证兼容:后续的几个模拟实现,都可以用嗅探兼容保证兼容性。
二、Function.prototype.apply()
1.简介
apply()
语法:
func.apply(thisArg, [argsArray])
apply 和 call 的用法基本相同,两者的第一个参数都是 func 函数运行时的 this值。
唯一的区别在于,call 可以接受传入多个参数,而 bind 只能接受一个数组传入作为参数。
2. 模拟实现
三、Function.prototype.bind()
1. 简介
bind()
语法:
func.bind(thisArg[, arg1[, arg2[, ...]]])
这个方法比较特别,该方法会创建一个新函数并返回,称之为绑定函数,
绑定函数会以创建它时传入bind方法的第一个参数作为
this
值,第二个以及以后的参数,将当做这个新的绑定函数的初始预设参数。之后调用新绑定函数时,传递给绑定函数的其他参数会跟在预设参数之后传入。
看一个栗子就明白了:
2. 模拟实现
注意
bind()
的几个特点:new
操作符创建对象。因为模拟构造函数的效果比较不容易理解,且不被推荐,所以在这里我们分别来实现。
不模拟构造函数:
模拟构造函数版:
为什么「构造函数版」需要特殊处理?
使用 new 时,绑定函数内的 this 值会被改成
fBound()
(仍然是this指向的基本规则:this 永远指向最后调用它的那个对象),而外层的
self = this
,self 则会指向调用myBind()
的对象,即bar
通过这3行代码,使 fBound.prototype 指向了 self.prototype,
所以用
this instanceof self
可以判断「绑定后函数」是否用作了构造函数。这部分的内容已经逐步深入到 JavaScript 的内部实现原理之中,确实有些晦涩,可以通过亲手写代码并多多调试观察来加深理解。
参考资料:
JavaScript深入之call和apply的模拟实现,
JavaScript深入之bind的模拟实现,
可能遇到假的面试题:不用call和apply方法模拟实现ES5的bind方法
The text was updated successfully, but these errors were encountered: