zoukankan      html  css  js  c++  java
  • 依据ECMA规范,手写一个bind函数

    Function.prototype.bind 函数,参见ECMA规范地址

    如题,这次来实现一个boundFunction函数,不挂载在Function.prototype上,而是一个单独声明的函数。挂载在原型上的bind,可以参考MDN

    主要步骤,摘自ECMA规范,如图:

    实现思想:当然是依葫芦画瓢,这里,我们借用ES6的...运算符与解构赋值。目的是图省事,实现bind函数,主要是了解其内部的操作流程。

    首先,把需要用到的函数,都依照规范声明实现,其中FunctionIsConstructor是自己写的判断一个函数是否为构造函数,比如Proxy就不是构造函数。

    而SetFunctionLength是对设置函数length属性的操作的封装,正如其名。

    function FunctionIsConstructor(fnc) {
      let isConstructor = true;
      try {
        Object instanceof fnc
      } catch (e) {
        if (e instanceof TypeError) {
          isConstructor = false
        }
      }
      return isConstructor
    }
    function BoundFunctionCreate(targetFunction, boundThis, boundArgs) {
      let proto = Object.getPrototypeOf(targetFunction);
      let boundFunction = function () {
        if (new.target) {
          // 实现构造函数功能
          if (FunctionIsConstructor(targetFunction)) {
            return new targetFunction(...boundArgs)
          } else {
            throw new TypeError(`${arguments.callee.name} is not a constructor`)
          }
        } else {
          // 实现函数调用功能
          return targetFunction.call(boundThis, [...boundArgs, ...arguments])
        }
      }
      delete boundFunction.name;
      Object.setPrototypeOf(boundFunction, proto)
      return boundFunction;
    }
    function isCallable(Target) {
      if (typeof Target === 'function') return true;
      return false;
    }
    function ToInteger(arg) {
      let number = Number(arg);
      if (number !== number) return +0;
      if (number === 0 || number === Infinity || number === -Infinity) return number;
      return Math.floor(Math.abs(number));
    }
    function SetFunctionName(F, name, prefix) {
      if (typeof name === 'symbol') {
        let description = name.description
        if (description === undefined) {
          name = ''
        } else {
          name = `[${description}]`
        }
      }
      if (prefix) {
        name = `${prefix} ${name}`
      }
      return Object.defineProperty(F, 'name', {
        value: name,
        writable: false,
        enumerable: false,
        configurable: true
      })
    }
    function SetFunctionLength(F, Target, args) {
      let targetHasLength = Target.hasOwnProperty('length');
      let L;
      if (targetHasLength) {
        let targetLen = Target.length;
        if (typeof targetLen !== 'number') {
          L = 0;
        } else {
          targetLen = ToInteger(targetLen)
          L = Math.max(0, targetLen - args.length)
        }
      } else {
        L = 0;
      }
      Object.defineProperty(F, 'length', {
        value: L,
        writable: false,
        enumerable: false,
        configurable: true
      })
    }

    然后,把这些函数按照规范的流程,组装起来,完全对应。

    function boundFuntion(targetFunction, thisArg, ...args) {
      let Target = targetFunction;
      if (!isCallable(Target)) {
        throw new TypeError(`${Target.name}.bind is not a function`)
      }
      let F = BoundFunctionCreate(Target, thisArg, args);
      SetFunctionLength(F, Target, args)
      let targetName = Target.name
      if (typeof targetName !== 'string') targetName = '';
      SetFunctionName(F, targetName, 'bound')
      // 支持直接new调用创建的绑定函数
      return new.target ? new F() : F
    }

    如此,一个手写的bind函数就出来。函数最后一行,用new.target来判断,以支持直接使用new调用创建的绑定函数,如new boundFunction(fnc)

    最后,简单测试一下。

    var modules = {
      x: 42,
      getX: function() {
        console.log('this', this === modules, this.x)
        return this.x;
      }
    }
    
    var unboundGetX = modules.getX;
    console.log('unbounnd ', unboundGetX()); // The function gets invoked at the global scope
    // // expected output: unbounnd undefined
    
    var boundGetX = boundFuntion(unboundGetX, modules);
    console.log('bounnd ', boundGetX());
    // expected output: bounnd 42

    总结

    手写bind函数,主要是利用闭包功能,将传入的this固定在新函数里,并对原型链进行处理,以免丢失继承关系。

    其他的错误处理,比如判断是否构造函数,是否用new调用当前函数,也是值得去了解的。

  • 相关阅读:
    POJ1034 The dog task
    POJ1033 Defragment
    POJ1032 Parliament
    POJ1031 Fence
    POJ1030 Rating
    POJ1029 False coin
    伪元素的使用
    伪元素选择器:before 以及 :after
    jquery html() 和text()的用法
    用CSS绘制三角形
  • 原文地址:https://www.cnblogs.com/miyosan/p/10580299.html
Copyright © 2011-2022 走看看