-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # 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: