Open yygmind opened 5 years ago
差一个bind
差一个bind
Function.prototype.apply = function (context, arr) { context = context ? Object(context) : window; context.fn = this;
let result;
if (!arr) {
result = context.fn();
} else {
result = context.fn(...arr);
}
// 是不是应该加个arr 是不是数组的判读 if (arr instanceof Array) { result = context.fn(...arr) } else { result = context.fn() }
delete context.fn
return result;
}
这是关于apply的实现 var result; // 判断是否存在第二个参数 if (!arr) { result = context.fn(); } else { 如果第二个参数是空的话,这里执行函数,干吗要返回result
看完之后更理解call了
call的模拟实现,一开始的例子里的变量用不同值表示比较好。
var value = 1; //改成其他值
var foo = {
value: 1
};
感觉可以出一本书了。 关于push的问题,是不是说如果出现数组太大的情况,如果不循环调用,会出现合并后丢参的情况呢?
call apply 增加了函数的灵活性,使JS更加灵活, 但这样做加深了this的复杂性。估计后期出bind 跟现在的箭头函数 就是为了缓解这一现象。
JavaScript类型化数组是一种类似数组的对象,并提供了一种用于访问原始二进制数据的机制。
个人感觉类型化数组才是真正的数组,只不过在JS中定义的数组承担了更多角色,既能充当栈(push/pop),又能作为队列(unshift/push),但是后一句怎么理解?如何就能访问原始二进制的数据?
想问一下,模拟call的时候,context怎么就是拿的第一个参数啊 执行这句 context.fn = this; 这个context不是代表传入的所有参数吗
想问一下,模拟call的时候,context怎么就是拿的第一个参数啊 执行这句 context.fn = this; 这个context不是代表传入的所有参数吗
因为call第一个参数代表把this指向谁
你好,我想问下,Function.prototype.call.bind和Function.prototype.bind这样写有什么区别吗?
既然你前几篇都特意声明 “ eval ” 性能差,你为什么演示还用这个呢,是否会误导
想问一下,模拟call的时候,context怎么就是拿的第一个参数啊 执行这句 context.fn = this; 这个context不是代表传入的所有参数吗
call
的实现有几个问题:
context
如果是 null 或者 undefined 的话,this 会指向全局对象,这里全局对象不一定是 window 吧(非浏览器环境),window 换成 globalThis 可能更好context
是 false 的话,最终 this 会指向全局对象,但实际上它应该指向 Boolean {false}
apply
的实现,要检查传进去的是不是数组(或者类数组对象),不是数组的话也要抛出错误
之前文章详细介绍了 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。
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!