间谍活动(apply的运用)
题目:国家有重要的任务要托付给你:完成间谍函数 spy,它可以潜伏到任何一个函数当中,监听它们的所有活动。spy 接受一个函数作为参数,返回一个被间谍潜伏以后的函数。
let america = (a, b) => a + b
america = spy(america)
america(1, 2) // => 3
spy 返回的函数和原来的函数的功能一样,但是它悄悄记录了每一次执行的参数和执行结果,都存放到一个 calls 数组里面:
america(1, 2)
america(3, 4)
america.calls[0].args // => [1, 2]
america.calls[0].result // => 3
america.calls[1].args // => [3, 4]
america.calls[1].result // => 7
注意,spy 可以支持潜伏到对象方法当中:
let user = {
name: 'Jerry',
getName () {
return this.name
}
}
user.getName = spy(user.getName)
user.getName() // => 'Jerry'
user.getName.calls[0].result // => 'Jerry'
另外,不要修改被 spy 的函数(你应该返回一个全新的函数),否则会被敌人发现。
答案
我的答案一:
结果:Wrong Answer(你潜伏到一个对象方法里面的时候的行为不正确)
const spy = (fn) => {
let calls=[]
const newFn=(...args)=>{
let result={
args : [...args],
result :fn(...args)
}
calls.push(result)
return result.result
}
newFn.calls=calls
return newFn
}
分析:答案一的核心思路是利用在闭包中通过引用中间函数来读取内部变量的特性,由于定义了calls,使得spy函数将常驻内存,进而达到缓存结果的目的。这里的args是arguments的别名,...args的作用是取得数量不定的参数,他在定义时只是一个别名,没有实际值,不会检查报错,而在执行时,由于newFn已经代替了fn,newFn会直接根据参数个数进行延展。由于在vue严格模式下无法定义arguments变量,所以此处要使用args。
但是这个方案的问题在于无法处理对象里面方法,关键点在于
let user = {
name: 'Jerry',
getName () {
return this.name
}
}
这里的this指向的user,而在我的答案一中,由于使用了箭头函数,程序使用了一个全部变量_this覆盖了this,使得this始终指向Windows对象,导致加入对象方法时处理失败。
我的答案二:可行的答案
const spy = (fn) => {
let calls = [];
const newFn = function(...args){
let result = {
args: [...args],
result: fn.apply(this, ...args)
};
calls.push(result);
return result.result;
};
newFn.calls = calls;
return newFn;
}
这里去掉了箭头函数,引入了函数的apply方法,apply的用途是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。当fn为普通函数时,this与_this是相等的,而当fn为对象中的函数时,this指向的是该对象,而_this则依旧指向根节点。所以使用fn.apply(this, ...args)
可以使user.getName()中的this可以正确识别,同时由于apply方法能劫持另外一个对象的方法,继承另外一个对象的属性,使得这里我们可以newFn中通过this绑定的方式继承fn,也就是对象中的方法。