前言
今天一起来写个Call函数。
手写Call代码,大家肯定不陌生了,或者说,现在都被大家讲烂,只要是个面试相关的文章,都会有这个题目。
当然了,写法也是多种多样。由于只是当成一个面试题来说的话,肯定说的没有那么完美的!
总有点缺陷
分析
首先呢,我们来说说call函数;
call方法使用一个指定的
this
值和单独给出的一个或多个参数来调用一个函数。
当函数执行call方法的时候,其实就是把函数放到call的第一个参数的某个属性上,然后在通过这个属性来执行函数。
代码示例:
fun.call(ctx,arg1,…) ==> ctx.fn=fun;ctx.fn(arg1,…);
再来看下官方对于call函数的参数解释:
参数
thisArg
可选的。在
function
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装。
arg1,arg2…
参数列表
我可以发现,call函数在严格模式和非严格模式下还是有区分的!再来看看网上一些普遍的写法。
示例1
我们来看下大部分网友的写法。
Function.prototype.call=function(context){
context=context||window;
context['fn']=this;
let fnArgs=[...arguments].slice(1);
const result=context['fn'](...fnArgs);
delete context['fn']
return result
}
看过这段代码后,我们是不是会有疑惑:
- 假如context上面有
fn
属性的时候,咋办 this
肯定是一个函数吗?- context如果不是一个Object类型咋办?
- 用es6来写,本身就隐藏了很多细节
接下来,我们看下一个进阶版本的:
示例2
Function.prototype.call=function(context){
let fn=Symbol('fn');
context=(context===null || context === undefined)?window:new Object(context)
context[fn]=this;
var realArgs=[];
for(var i=1;i<arguments.length;i++){
realArgs.push(`arguments[${i}]`);
}
var result=eval(`context[fn](${realArgs.join(',')})`)
delete context[fn]
return result
}
这个示例呢,在细节上考虑的比较多
- context 进行Object包装
- 对于参数的处理,也考虑了兼容性,用了很基础的语法
- 也考虑了fn属性的唯一
通过这俩个示例,其实我们可以发现一些通用的细节问题!
手写call的细节问题
我这里归纳了一下:
-
this 是否是一个函数
-
当context为null,undefined的时候,一定是指向window吗?node环境呢?
-
fn属性要保证唯一
-
安全的执行函数
- 如果用eval的话,eval是否能保证一定可以执行?
-
尽量使用es5的语法来实现
-
当前是否是严格模式
针对以上的细节,我们这里来实现一个比较完美的版本吧
代码
前面说的差不多了,这里直接上代码吧,代码有注释;
我这里没有处理严格模式还是非严格模式,直接都包装Object了
//动态生成函数
function createFn(argumentsLength) {
var str = []
for (var i = 0; i < argumentsLength; i++) {
str.push('agrs[' + i + ']')
}
var fnCode = 'return context[fnName](' + str.join(',') + ')'
return new Function('context', 'fnName', 'agrs', fnCode)
}
Function.prototype.call = function (context) {
//判断是否是在函数上调用call
if (typeof this !== 'function') {
throw new Error('this is not function')
}
//获取当前环境的上下文
var defaultContext = null
//web workers 中是self
if (typeof self !== 'undefined') {
defaultContext = self
}
if (typeof global !== 'undefined') {
defaultContext = global
}
if (typeof window !== 'undefined') {
defaultContext = window
}
//设置上下文
context = context === null || context === undefined ? defaultContext : new Object(context)
var fnName = 'fn'
var temp = null
if (context[fnName]) {
temp = context[fnName]
}
context[fnName] = this
//获取参数列表
var realArgs = []
for (var i = 1; i < arguments.length; i++) {
realArgs.push(arguments[i])
}
// var result=eval(`context[fnName](${realArgs.join(',')})`)
//这里使用Function来调用
var fnRun = createFn(realArgs.length)
var result = fnRun(context, fnName, realArgs)
if (temp) {
context[fnName] = temp
} else {
delete context[fnName]
}
return result
}
总结
这道手写Call的题,还是能学习很多东西
- Function.prototype.call this不一定是Function
- 动态生成函数
- 区分环境上下文
- call的使用方法
- delete函数