let obj = {
name: "obj"
};
function func(x, y) {
console.log(this);
return x + y;
}
console.log(func.call(obj, 10, 20));
call的原理是让func立即执行,并且让this指向obj,把参数传递给func
那怎么样才能让this指向obj呢?
obj.?xxx = func;
obj.?xxx(10,20);
只要按照成员访问这种方式执行,就可以让FUNC中的THIS变为OBJ【前提OBJ中需要有FUNC这个属性】,当然属性名不一定是FUNC,只要属性值是这个函数即可。
对象可以用成员访问的方式,那基本数据类型呢?
创建一个值的两种方法:对于引用数据类型来讲,两种方式没啥区别,但是对于值类型,字面量方式创建的是基本类型值,但是构造函数方式创造的是对象类型值;再但是,不管基本类型还是对象类型都是所属类的实例,都可以调用原型上的方法;(基本值无法给其设置属性,但是引用值是可以设置属性的)
1.字面量创建
let num1 = 10;
let obj1 = {};
2.构造函数创建
let num2 = new Number(10);
let obj2 = new Object();
对于字面量创建值,我发设置属性,也就不能用成员访问的方式,我们可以用constructor来创建对象类型值
new num1.constructor(num1);
创建一个值的两种方法:对于引用数据类型来讲,两种方式没啥区别,但是对于值类型,字面量方式创建的是基本类型值,但是构造函数方式创造的是对象类型值;再但是,不管基本类型还是对象类型都是所属类的实例,都可以调用原型上的方法;(基本值无法给其设置属性,但是引用值是可以设置属性的)
那此时我们就开始实现call的功能:
核心原理:给CONTEXT设置一个属性(属性名尽可能保持唯一,避免我们自己设置的属性修改默认对象中的结构,例如可以基于Symbol实现,也可以创建一个时间戳名字),属性值一定是我们要执行的函数(也就是THIS,CALL中的THIS就是我们要操作的这个函数);接下来基于CONTEXT.XXX()成员访问执行方法,就可以把函数执行,并且改变里面的THIS(还可以把PARAMS中的信息传递给这个函数即可);都处理完了,别忘记把给CONTEXT设置的这个属性删除掉(人家之前没有你自己加,加完了我们需要把它删了)
如果CONTEXT是基本类型值,默认是不能设置属性的,此时我们需要把这个基本类型值修改为它对应的引用类型值(也就是构造函数的结果)
Function.prototype.call = function call(context, ...params) {
// CONTEXT不能是基本数据类型值,如果传递是值类型,我们需要把其变为对应类的对象类型
if (!/^(object|function)$/.test(typeof context)) {
if (/^(symbol|bigint)$/.test(typeof context)) {
context = Object(context);
} else {
context = new context.constructor(context);
}
}
let key = Symbol('KEY'),
result;
//此时this就是func,把this赋给context[key](也就是obj的成员访问)
context[key] = this;
//把参数传给context[key]并且执行
result = context[key](...params);
//最后删除context[key]
delete context[key];
return result;
};
我们用这个原理来巩固一下:
function fn1(){console.log(1)};
function fn2(){console.log(2)};
fn1.call(fun2);
fn1.call.call(fn2);
Function.prototype.call(fn1);
Function.prototype.call.call(fn1);
首先:fn1.call(fun2);
this => fn1, context => fn2
fn2[xx] = fn1;
fn2[xx]();
执行的是fn1,让fn1中的this变成了fn2,
所以fn1函数中输出的是 "1"
第二问:fn1.call.call(fn2);
this => 第一个call函数,context => fn2
fn2[xx] = 第一个call函数,//此时call已经变成了fn2[xx]
fn2[xx]();
让fn2[xx]执行,就是让第一个call执行,call函数中的this指向fn2,
那call已经变成了fn2[xx],所以这时this就指向了fn2
如果不明白fn2[xx],那就用fn2.xx,这是一样的
fn2.xx执行,那this就是fn2,没有传参,context => window/undefined(严格模式下不传就是undefined)
相当于是window[xx] => fn2
window[xx]()
让fn2执行,this指向window
所以fn2输出的是 "2"
第三问:Function.prototype.call(fn1);
this => Function.prototype, context => fn1
fn1[xx] = Function.prototype;
fn1[xx]();
执行的是Function.prototype,让Function.prototype中的this变成了fn1,
Function.prototype是匿名空函数,所以什么也不输出
第四问:Function.prototype.call.call(fn1);
this => 第一个call函数,context => fn1
fn1[xx] = 第一个call函数,//此时call已经变成了fn1[xx]
fn1[xx]();
让fn1[xx]执行,就是让第一个call执行,call函数中的this指向fn1,
那call已经变成了fn1[xx],所以这时this就指向了fn1
如果不明白fn1[xx],那就用fn1.xx,这是一样的
fn1.xx执行,那this就是fn1,没有传参,context => window/undefined(严格模式下不传就是undefined)
相当于是window[xx] => fn1
window[xx]()
让fn1执行,this指向window
所以fn1输出的是 "1"