fn.call(isThis, arg1, arg2, ....)
fn.apply(isThis, [arg1, arg2, ....])
相同点:
- 改变 this 指向
- 可以传参
- 立即调用
区别:
- apply 接收一个数组参数,call 直接接收参数
- apply 的性能会比call差,因为要对数组参数进行判断和解构
模拟实现:
Function.prototype.callLike = function (isThis) {
//...
}
Function.prototype.applyLike = function (isThis) {
//...
}
- 传递的 isThis 如果是 undefined 或者 null,那么 this 就是 window,否则传递进来的就是要指向的this
- call 从第二个参数开始就是要调用时用到参数
- apply 的第二个参数为数组,数组中的元素就是调用时用到参数
第一个条件很简单,判断下 isThis 的类型即可
isThis = typeof isThis === 'undefined' || isNull(isThis) ? window : isThis;
function isNull (value) {
return typeof value === 'object' && !value === true
}
第二个条件和第三个条件是一样的,call 的参数我们需要处理下,因为我们预期不到它的参数个数
我们从第二个参数开始遍历一遍 arguments,然后放到一个数组里面去
var args = [];
for (var i = 1, l = arguments.length; i< l; i++) {
args.push(arguments[i]);
}
但是最关键的调用怎么办呢,怎么改变 this 的指向,一般来说谁调用谁就是 this,我们要想改变 this,那么就要用传递进来的 isThis 来调用
isThis.fn = this;
isThis.fn();
delete isThis.fn;
这样的话就改变了 this 的指向,之后再 delete 掉,就 ok 了,但是这里会有一个问题,如果传递进来的是值类型呢,值类型我们是不能给它添加属性和方法的,所以 isThis.fn()
肯定会提示 isThis.fn is not a function
,这里我们可以想一想值类型也是可以像对象一样有属性和方法的,并且可以添加属性和方法,但是为什么赋值完后就找不到呢
这里要说下包装类型了,值类型按理说是不可能有自己的属性和方法的,但是考虑到有时候需要处理下杂七杂八的琐事,所以当我们访问或者赋值的时候,它会临时给我们创建一个对应的包装对象,在我们访问或者赋值结束后那么这个包装对象就会被清理掉,那么我们就可以这样做,来模拟下包装对象
var valueType = typeof isThis;
if (valueType === 'string') {
isThis = new String(isThis)
}else if (valueType === 'number') {
isThis = new Number(isThis)
}else if (valueType === 'boolean') {
isThis = new Boolean(isThis)
}else if (valueType === 'symbol') {
isThis = Object(isThis)
}
恩,这样一来就差不多了,接下来看看传参,args 的元素才是我们想要的参数,所以怎么拆开
var result = eval('isThis.fn(' + args.join() + ')');
使用 eval,得益于 eval 强大的能力,我们可以把字符串当做 js 代码来执行,并且可以得到返回值,完美
但是这里会有一个问题,如果参数是个对象,那么 eval 对参数 toString 后我们我们就得不到想要的参数了,所以这里改造下,因为 eval 可以动态的改变作用域
var args = [];
for (var i = 1, l = arguments.length; i< l; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('isThis.fn(' + args.join() + ')');
这里我们用 args 存放着 arguments[1] arguments[1]... 这样的字符串,那么 eval 执行的时候会在上下文查找并绑定所需要的变量,这样就可以实现参数传递了
完整代码:
Function.prototype.callLike = function (isThis) {
isThis = typeof isThis === 'undefined' || isNull(isThis) ? window : isThis;
var valueType = typeof isThis;
if (valueType === 'string') {
isThis = new String(isThis)
}else if (valueType === 'number') {
isThis = new Number(isThis)
}else if (valueType === 'boolean') {
isThis = new Boolean(isThis)
}else if (valueType === 'symbol') {
isThis = Object(isThis)
}
isThis.fn = this;
var args = [];
for (var i = 1, l = arguments.length; i< l; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('isThis.fn(' + args.join() + ')');
delete isThis.fn;
function isNull (value) {
return typeof value === 'object' && !value === true
}
return result;
}
Function.prototype.applyLike = function (isThis, args) {
isThis = typeof isThis === 'undefined' || isNull(isThis) ? window : isThis;
var valueType = typeof isThis;
if (valueType === 'string') {
isThis = new String(isThis)
}else if (valueType === 'number') {
isThis = new Number(isThis)
}else if (valueType === 'boolean') {
isThis = new Boolean(isThis)
}else if (valueType === 'symbol') {
isThis = Object(isThis)
}
isThis.fn = this;
var args = [];
for (var i = 1, l = arguments.length; i< l; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('isThis.fn(' + args.join() + ')');
delete isThis.fn;
function isNull (value) {
return typeof value === 'object' && !value === true
}
return result;
}