这是我参与新手入门的第1篇文章
手写call,apply总结
十分忐忑,第一次发博客,怀揣着尽可能写好的心态,写下了这篇文章。本文章主要是对于今天学习手写call和apply的总结…
手写call
基本思路:
假设以 thisArg
和可选的 arg1
, arg2
等等作为参数在一个 func
对象上调用 call
方法:
1. 判断调func
是不是可调用的函数。若不是,则抛出TypeError
。
2. 判断thisArg
是否是null
或者undefined
。若是,则将thisArg
替换成全局对象。若不是,则对其应用Object()
转换为对象(其内容不变)。
3. 将func
绑定到thisArg
,并将从arg1
开始的参数按照从左到右的顺序传递给func
4. 获取并返回绑定thisArg
且传递参数后的func
执行的结果。
以上思路参靠es5规范总结而来
代码如下:
Function.prototype.my_call = function (thisArg, ...args) {
//1、如果调用者不是一个可调用的函数,则抛出TypeError
if (typeof this !== 'function') {
throw new TypeError(`${this} must be a function`)
}
//2、如果绑定对象是null或者undefined,那么则将其绑定到全局对象上去(浏览器环境下全局对象是window)
if (thisArg == null) { //类型转换
thisArg = window
} else {
thisArg = Object(thisArg) // 如果是普通类型则将其转换为对象包裹
}
const fn = Symbol('唯一标识')
thisArg[fn] = this
const res = thisArg[fn](...args)
delete thisArg[fn] // 此处若不删除属性,thisArg则会出现Symbol属性,破坏了原来内容
return res
}
复制代码
在学习并手写这段代码时存在以下几个问题:
1、为什么在判断thisArg在判断是否是null或者undefined时不能使用thisArg = thisArg || window
这种方式?
如果采用这种方式,那么对于thisArg
值为0
,''
, false
, NaN
时,则会出现this指向window。
另外,采用空值合并运算符??
也能正确执行。
2、采用了Symbol作为唯一标识,是否有啥不妥?可以用其他方式替换?
能有啥不妥?不就是Symbol
是es6以后的么。(就这?这不有手就行?哎,我手呢…) 要是能用Symbol
,那还需要手写call
?替换方式如下
...
const fn = new Date().getTime() // 生成随机数?!
const originData = thisArg[fn] // 保存原来的数据
const hasProp = thisArg.hasOwnProperty(fn) // 是否存在fn属性
thisArg[fn] = this
const res = thisArg[fn](...args)
//这里可以用originData!==undefined来判断属性是否存在吗?
//貌似不行,若原对象就存在一个undefined值得属性呢...
if(hasProp) { // 如果存在fn属性
thisArg[fn] = originData //修改为原来的属性
} else {
delete thisArg[fn]
}
return res
复制代码
3、那传递参数时使用rest
和spread
语法有啥不妥?怎么替换?
啊这!好吧,我妥协了,这个暂时不会…
手写apply
基本思路:
假设以 thisArg
和 argArray
为参数在一个 func
对象上调用 apply
方法:
1. 判断func
是否是一个可调用的函数。如果不是,则抛出TypeError
。
2. 判断thisArg
是否是null
或者undefined
。若是,则将thisArg
替换成全局对象。若不是,则对其应用Object()
转换为对象(其内容不变)。
3. 判断argArray
是否是null
或者undefined
。若是,则用[]
替换argArray
原来的值。
4. 判断argArray
是否是Object
类型。若不是,则抛出TypeError
。
5. 判断argArray
是否是类数组对象。若不是,则用[]
替换argArray
原来的值。
6. 将func
绑定到thisArg
。获取并返回将参数argArray
传递给绑定后func
执行的值
代码如下:
function isArrayLike(val) {
//val.length >= 0 排除了负数 以及NaN
if (typeof val.length === 'number' && val.length >= 0) {
if(!isFinite(val.length)) {
throw new RangeError('Invalid array length')
}
return true
}
return false
}
Function.prototype.my_apply = function (thisArg, argArray) {
//1、判断func是否是可调用的函数
if (typeof this !== 'function') {
throw new TypeError(`${this} is not a function`)
}
//2、判断thisArg是否是null或者undefined
if (thisArg == null) {
thisArg = window //此处为全局对象,浏览器环境下为window
} else {
thisArg = Object(thisArg) //其他则转换为对象
}
//3、判断thisArg是否是null或者undefined
if (argArray == null) {
argArray = []
}
//4、判断argArray是否是对象
if (!(argArray instanceof Object)) {
throw new TypeError('CreateListFromArrayLike called on non-object')
}
// 5、判断argArray是否是类数组对象
const argList = []
if (isArrayLike(argArray)) { // argArrar为null或undefined已经转换为空数组
//有可能argArray.length 不是整数
for (let i = 0; i < Math.floor(argArray.length); i++) {
argList[i] = argArray[i]
}
}
const fn = Symbol('唯一标识')
thisArg[fn] = this
const res = thisArg[fn](...argList)
delete thisArg[fn]
return res
}
复制代码
在学习apply时有几个问题:
1、isFinite()
在MDN上对isFinite的描述为:
你可以用这个方法来判定一个数字是否是有限数字。isFinite 方法检测它参数的数值。如果参数是 NaN,正无穷大或者负无穷大,会返回false,其他返回 true。
也就是说isFinite()
不能判断其参数是否是数字。只能在事先确定其参数是数字的情况下判断参数是不是有限数字。所以,在代码isArrayLike()
中将isFinite()
放在了条件语句里面。
2、在第5步,在确定其是ArrayLike
后,可以使用Array.from()
将其转换为数组。
3、值得一提的是,这里用的判断是否是ArrayLike
貌似与js权威指南有差异。先在这提一下,以后入手权威指南后再查阅查阅
最后
新人刚开始接触造轮子,一定有很多地方没考虑到。读者大大有不同看法的话,欢迎一起交流…如果发现文章中存在错误,还请告知,十分感谢。