-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
【进阶3-3期】深度解析 call 和 apply 原理、使用场景及实现 #22
Comments
差一个bind |
|
Function.prototype.apply = function (context, arr) {
// 是不是应该加个arr 是不是数组的判读
} |
这是关于apply的实现 |
看完之后更理解call了 |
call的模拟实现,一开始的例子里的变量用不同值表示比较好。 var value = 1; //改成其他值
var foo = {
value: 1
}; |
感觉可以出一本书了。 |
call apply 增加了函数的灵活性,使JS更加灵活, 但这样做加深了this的复杂性。估计后期出bind 跟现在的箭头函数 就是为了缓解这一现象。 |
个人感觉类型化数组才是真正的数组,只不过在JS中定义的数组承担了更多角色,既能充当栈(push/pop),又能作为队列(unshift/push),但是后一句怎么理解?如何就能访问原始二进制的数据? |
想问一下,模拟call的时候,context怎么就是拿的第一个参数啊 |
因为call第一个参数代表把this指向谁 |
你好,我想问下,Function.prototype.call.bind和Function.prototype.bind这样写有什么区别吗? |
既然你前几篇都特意声明 “ eval ” 性能差,你为什么演示还用这个呢,是否会误导 |
|
|
之前文章详细介绍了 this 的使用,不了解的查看【进阶3-1期】。
call() 和 apply()
call()
和apply()
的区别在于,call()
方法接受的是若干个参数的列表,而apply()
方法接受的是一个包含多个参数的数组举个例子:
使用场景
下面列举一些常用用法:
1、合并两个数组
当第二个数组(如示例中的
moreVegs
)太大时不要使用这个方法来合并数组,因为一个函数能够接受的参数个数是有限制的。不同的引擎有不同的限制,JS核心限制在 65535,有些引擎会抛出异常,有些不抛出异常但丢失多余参数。如何解决呢?方法就是将参数数组切块后循环传入目标方法
2、获取数组中的最大值和最小值
为什么要这么用呢,因为数组
numbers
本身没有max
方法,但是Math
有呀,所以这里就是借助call / apply
使用Math.max
方法。3、验证是否是数组
可以通过
toString()
来获取每个对象的类型,但是不同对象的toString()
有不同的实现,所以通过Object.prototype.toString()
来检测,需要以call() / apply()
的形式来调用,传递要检查的对象作为第一个参数。另一个验证是否是数组的方法
上面方法首先使用
Function.prototype.call
函数指定一个this
值,然后.bind
返回一个新的函数,始终将Object.prototype.toString
设置为传入参数。其实等价于Object.prototype.toString.call()
。这里有一个前提是
toString()
方法没有被覆盖4、类数组对象(Array-like Object)使用数组方法
类数组对象有下面两个特性
length
属性push
、shift
、forEach
以及indexOf
等数组对象具有的方法要说明的是,类数组对象是一个对象。JS中存在一种名为类数组的对象结构,比如
arguments
对象,还有DOM API 返回的NodeList
对象都属于类数组对象,类数组对象不能使用push/pop/shift/unshift
等数组方法,通过Array.prototype.slice.call
转换成真正的数组,就可以使用Array
下所有方法。类数组对象转数组的其他方法:
Array.from()
可以将两类对象转为真正的数组:类数组对象和可遍历(iterable)对象(包括ES6新增的数据结构 Set 和 Map)。PS扩展一:为什么通过
Array.prototype.slice.call()
就可以把类数组对象转换成数组?其实很简单,
slice
将Array-like
对象通过下标操作放进了新的Array
里面。下面代码是 MDN 关于
slice
的Polyfill,链接 Array.prototype.slice()PS扩展二:通过
Array.prototype.slice.call()
就足够了吗?存在什么问题?在低版本IE下不支持通过
Array.prototype.slice.call(args)
将类数组对象转换成数组,因为低版本IE(IE < 9)下的DOM
对象是以com
对象的形式实现的,js对象与com
对象不能进行转换。兼容写法如下:
PS 扩展三:为什么要有类数组对象呢?或者说类数组对象是为什么解决什么问题才出现的?
一句话就是,可以更快的操作复杂数据。
5、调用父构造函数实现继承
在子构造函数中,通过调用父构造函数的
call
方法来实现继承,于是SubType
的每个实例都会将SuperType
中的属性复制一份。缺点:
更多继承方案查看我之前的文章。JavaScript常用八种继承方案
call的模拟实现
先看下面一个简单的例子
通过上面的介绍我们知道,
call()
主要有以下两点call()
改变了this的指向bar
执行了模拟实现第一步
如果在调用
call()
的时候把函数bar()
添加到foo()
对象中,即如下这个改动就可以实现:改变了this的指向并且执行了函数
bar
。但是这样写是有副作用的,即给
foo
额外添加了一个属性,怎么解决呢?解决方法很简单,用
delete
删掉就好了。所以只要实现下面3步就可以模拟实现了。
foo.fn = bar
foo.fn()
delete foo.fn
代码实现如下:
完美!
模拟实现第二步
第一版有一个问题,那就是函数
bar
不能接收参数,所以我们可以从arguments
中获取参数,取出第二个到最后一个参数放到数组中,为什么要抛弃第一个参数呢,因为第一个参数是this
。类数组对象转成数组的方法上面已经介绍过了,但是这边使用ES3的方案来做。
参数数组搞定了,接下来要做的就是执行函数
context.fn()
。上面直接调用肯定不行,
args.join(',')
会返回一个字符串,并不会执行。这边采用
eval
方法来实现,拼成一个函数。上面代码中
args
会自动调用args.toString()
方法,因为'context.fn(' + args +')'
本质上是字符串拼接,会自动调用toString()
方法,如下代码:所以说第二个版本就实现了,代码如下:
完美!!
模拟实现第三步
还有2个细节需要注意:
null
或者undefined
,此时 this 指向 window实现上面的三点很简单,代码如下
完美!!!
call和apply模拟实现汇总
call的模拟实现
ES3:
ES6:
apply的模拟实现
ES3:
ES6:
思考题
call
和apply
的模拟实现有没有问题?欢迎思考评论。PS: 上期思考题留到下一期讲解,下一期介绍重点介绍
bind
原理及实现参考
进阶系列目录
交流
进阶系列文章汇总如下,内有优质前端资料,觉得不错点个star。
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
The text was updated successfully, but these errors were encountered: