假装是个标题
准备放假了喂,约个美女UI一起饮奶茶喂!
哦?没人约?那不如一起来复习一下call、apply如何实现啦~~
call()、apply()是否在项目中经常有用到,但却只是简单的调用,知其然不知其所以然,那今天一起梳理一下
每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array的实例,也可以是arguments对象。
上面解释是摘抄红宝石一书,几句话简洁明了,介绍了这两个方法用途以及调用方式,那么我们就开始下面的内容吧
call
首先举个简单的例子
let foo = {
val: 1
}
function bar () {
console.log(this.val)
}
bar() // undefined
现在我们想调用bar的时候,打印的val是foo里面的val,那么抛开call()、apply()方法,要怎么来实现呢。
let foo = {
val: 1,
bar: function () {
console.log(this.val)
}
}
foo.bar() //1
醒目仔就会说了,把bar函数放进去foo对象中,那这样不就可以拿到这个东西了,那么马上动工开始写
Function.prototype.myCall = function (obj) {
obj.fn = this; // this就是当前所调用的函数,将它进行赋值再调用,最后删除即可
console.log(this) // ƒ bar () {console.log(this.val)}
obj.fn()
delete obj.fn
}
这样就实现了一个简易的call()方法了,不信你可以F12试一试
let foo = {
val: 1
}
function bar () {
console.log(this.val)
}
bar.myCall(foo) //1
好了,实现是实现了,但是这也实在是太简陋了,上面介绍都说了call可以传好多个参数,我们这一个参数都接收不了呢,没办法,验收不符合产品需求,只能接着改了。
既然刚才说到传入参数,那么马上可以联系到函数的arguments,那我们循环一下它进行是不是就能完成了呢。
Function.prototype.myCall = function (obj) {
obj.fn = this;
let args = [];
for (let i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']')
}
eval('obj.fn('+ args +')')
delete obj.fn
}
好了,这里可能比较迷惑的就是循环以及eval()了。逐行来理解一遍。
- 1、我们可以通过arguments来获取传入函数的参数,他是一个类数组。
- 2、进行一个循环,将字符串存入数组args,给后面eval调用的时候传入
- 3、调用eval方法,首先我们先搞清楚eval能帮我做什么事
eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。举个例子 let str = 'console.log(1)' eval(str) 上面两行代码丢到F12执行会打印出1,也就是说eval方法可以帮我们将执行字符串中的js代码
- 4、那也就是eval('obj.fn('+ args +')'),这行的解析会变成执行obj.fn(args),可是我们传入的args是[arguments[0],arguments[1]...]这样的形式呢?别慌,精髓就在那个+号上
这里我们又要延伸一个概念:当数组遇上字符串进行拼接时,会先调用toString()的方法,然后在进行拼接,也就是说'+ args +'这么一转换就会变成了[arguments[0],arguments[1]] => 'arguments[0],arguments[1]'
- 5、那么最后这行代码的执行就会是变成了obj.fn(arguments[0],arguments[1]),那么我们传参的功能也就完成了不是,让我们检查一下
Function.prototype.myCall = function (obj) {
obj.fn = this;
let args = [];
for (let i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']')
}
eval('obj.fn('+ args +')')
delete obj.fn
}
let foo = {
val: 1
}
function bar (name, age) {
console.log(this.val)
console.log(name)
console.log(age)
}
bar.myCall(foo, '码桑', '18')
一点毛病都没,那没毛病走两步。突然我们就发现瘸了,因为原生的call可以不传的this,那么兼容一下,没有就指向window.我们进行最后一次改版
Function.prototype.myCall = function (obj) {
var obj = obj || window;
obj.fn = this;
let args = [];
let result;
for (let i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']')
}
result = eval('obj.fn('+ args +')')
delete obj.fn
return result
}
到这里功能都已经实现完了。细心的可能会发现,最后加了一个result的返回,其实是因为有些函数在调用的时候,需要返回一个对象的时候,我们可以通过这种方式进行解决,贴个代码
function bar (name, age) {
return {
name: name,
age: age
}
}
bar.myCall(null, '码桑', '18')
可以将不加result的myCall方法和加了result的方法执行一遍就会发现区别之处了。 好了,到这里我们就已经写完了call()的模拟了,最终代码就是
Function.prototype.myCall = function (obj) {
var obj = obj || window;
obj.fn = this;
let args = [];
let result;
for (let i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']')
}
result = eval('obj.fn('+ args +')')
delete obj.fn
return result
}
apply
引用红宝石原文:apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array的实例,也可以是arguments对象。
其实从上面的描述中得知,apply和call感觉就是一个厂出来的,都差不多,直接贴代码看看两兄弟相似之处
Function.prototype.myApply = function (obj, arr = []) {
var obj = obj || window
obj.fn = this
let args = []
let result;
if (arr.length) {
for (let i = 0; i < arr.length; i++) {
args.push('arr['+i+']')
}
result = eval('obj.fn('+args+')')
} else {
result = obj.fn()
}
delete obj.fn
return result
}
区别之处就是多了一个arr参数以及判断,以及在循环的时候myApply的 i 是从0开始,而myCall开始是从1。 好了,到这里也就接近尾声了,在底部说下废话,不容易被人看到
最近面试了挺多公司,感触颇深。毕业也有一段时间了,在框架横流的现在,是否学会了vue、react这些就畅通无阻?时常在想这条路该如何走下去,通往高级的路又应该如何开始?学习应该往广度还是深度?道阻且长,学习是一个积累的过程,从量到质从来不是一朝一夕的事,那么就需要制定一个计划,从js的基础开始捡起吧。有学习群的看官看到了只能说求带求带求带~~~ 最后祝大家中秋快乐~ 阖家团圆 ~ 回家路上不堵车~~