一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情。
在js的日常使用中,我们经常会改变this的指向,其中call便是其中经常用到的一个方法。为了能让自己更加深刻的理解call方法,决定参考一些资料,自己去重新写一下call方法。
内置call方法
代码
function fn(x, y){
console.log(this, x, y)
}
let obj = {
name: 'xh'
}
fn.call(obj, 10, 30)
输出结果是 {name: 'xh'} 10 30
思路,我们只需要考虑如何将this和需要执行的函数关联起来就可以了,而我如果重写call方法,必然要覆盖原有的内置call方法,这就需要在Function的圆形上进行。而将this和要执行的函数关联到一起,只需要将要执行的函数作为this的一个属性就好了。
重写call方法
Function.prototype.call = function call(content, ...params){
const self = this;//习惯比直接使用this
content['fn'] = self;
content['fn'](...params)
}
function fn(x, y){
console.log(this, x, y)
}
let obj = {
name: 'xh'
}
fn.call(obj, 10, 30)
输出结果是{name: 'xh', fn: ƒ} 10 30;
哈哈,不错,和预期差不多。
优化
1.this的指向内容多了一个属性fn,既然多了,那么我们就需要删除掉它。
Function.prototype.call = function call(content, ...params){
const self = this;
content['fn'] = self;
content['fn'](...params);
delete content['fn'];//函数执行完毕后删除相关属性
}
再次执行函数得到结果入下图
虽然依然显示fn属性,但是将对象展开后发现fn消失了,这是因为展开显示的是最新的对象内容,而外层显示的是在console的时候对象对应的内容。虽然展示不是很理想,但勉强能接受了。
- 返回值 现在新的方法还没有返回值,如果fn函数存在返回值,那么我新写的call方法就会出现undefined了,那么就给call加上返回值
Function.prototype.call = function call(content, ...params){
const self = this;
let res = null;
content['fn'] = self;
res = content['fn'](...params);
delete content['fn'];
return res;//增加返回值
}
- this什么时候指向window呢? 在只考虑浏览器环境下,当call的第一个参数为undefined或者null的时候,this就会指向window,加油!
Function.prototype.call = function call(content, ...params){
content == null ? content = window : null;//当content是null或者undefined的时候,将content赋值为window
const self = this;
let res = null;
content['fn'] = self;
res = content['fn'](...params);
delete content['fn'];
return res;
}
- 如果obj中本来就存在fn属性呢?如果按照现有函数执行的话,会被先替换再删除掉的。这怎么处理呢?咦~,symbol似乎是个好东西。
Function.prototype.call = function call(content, ...params){
content == null ? content = window : null
const self = this;
let res = null;
let newAttr = Symbol('newAttr')//使用symbol代替fn,防止属性重名
content[newAttr] = self;
res = content[newAttr](...params);
delete content[newAttr];
return res;
}
- 如果call函数的第一个参数我传入的是非正常对象呢?比如输入字符串“abc”
如果输入字符串“abc”,那么函数执行过程中一定会报错,因为只有“引用类型”能 content['fn'] 这样使用。那么如何让字符串等基本数据类型也可以使用呢?这里有两个方法,一个是使用构造函数的方法去创建,就是使用new方法,但问题是要区分不同的基础数据类型,比如new Number();new String()...,而且symbol不能使用new创建;另外一个办法就是使用Object()方法。
Function.prototype.call = function call(content, ...params){
content == null ? content = window : null
!/^(object|function)$/.test(typeof content) ? content = Object(content) : null;
const self = this;
let res = null;
let newAttr = Symbol('newAttr')
content[newAttr] = self;
res = content[newAttr](...params);
delete content[newAttr];
return res;
}
利用正则表达式和Object()方法,将基础数据类型转化成可以使用 content['fn'] 方式获得属性的数据。
最后测试
在我能想到的一些问题上都得到了解决,可能会存在疏漏,欢迎指正。下面是代码执行截图