阅读 361

学习手写call,apply

这是我参与新手入门的第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、那传递参数时使用restspread语法有啥不妥?怎么替换?
          啊这!好吧,我妥协了,这个暂时不会...
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))
复制代码

其结果为:

image.png
    当时我就迷惑了,这是为啥呀?会不会是null和undefined没有处理好呢?我又去看了一下MDN...

image.png
    没毛病呀,对于null和undefined处理没错呀...头秃,仍然没有找到想要的原因,就离谱...
    不得不妥协了...如果有道友知道,还请指点一下,不胜感激!

                                                   image.png

手写apply

基本思路:
假设以 thisArgargArray 为参数在一个 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权威指南有差异。先在这提一下,以后入手权威指南后再查阅查阅

最后

    新人新作,对于怎么写好文章还不够了解...在手写callapply的方面上,各位道友有不同看法的话,欢迎一起交流...如果发现文章中存在错误,还请告知,十分感谢。
    加油!前端人; 加油!六耳。

参考文献

文章分类
前端
文章标签