这是我参与新手入门的第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执行的结果。
代码如下:
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语法有啥不妥?怎么替换?
啊这!好吧,我妥协了,这个暂时不会...
4、存在一个特殊的现象:
console.log('原始call测试null类型:', Object.prototype.toString.call(null))
console.log('原始call测试undefined类型:',Object.prototype.toString.call(undefined))
console.log('手写call测试null类型:', Object.prototype.toString.my_call(null))
console.log('手写call测试undefined类型:', Object.prototype.toString.my_call(undefined))
其结果为:
当时我就迷惑了,这是为啥呀?会不会是null和undefined没有处理好呢?我又去看了一下MDN...
没毛病呀,对于null和undefined处理没错呀...头秃,仍然没有找到想要的原因,就离谱...
不得不妥协了...如果有道友知道,还请指点一下,不胜感激!
手写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权威指南有差异。先在这提一下,以后入手权威指南后再查阅查阅
最后
新人新作,对于怎么写好文章还不够了解...在手写call和apply的方面上,各位道友有不同看法的话,欢迎一起交流...如果发现文章中存在错误,还请告知,十分感谢。
加油!前端人; 加油!六耳。