[JavaScript]深入理解arguments,aplly,call,bind,手写bind

此文为进阶理解,如果不熟悉标题中的arguments,aplly,call,bind,先去看他们的作用与基本用法
这里将简易版的手写bind放在开头,方便快速查阅

        Function.prototype._bind = function (){
            var slice = Array.prototype.slice
            var thatFunc = this
            var thatArg = arguments[0]
            var args = slice.apply(arguments,1)
            return function (){
            	// 这里调用slice.apply(arguments)是因为arguments是类数组不是真实数组
                var funcArgs = args.concat(slice.apply(arguments))
                return thatFunc.apply(thatArg,funcArgs)
            }
        }

一、函数属性&arguments

        function foo(x,y,z){
            arguments.length // 2 (只传入了两个参数)
                             // 注意,arguments是类数组对象,是没有join,slice这些数组对象的方法的
            arguments[0] // 1
            arguments[0] = 10 // 此时x change to 10
                              // 注意,如果是在严格模式下,aruguments则是形参的副本,赋值不会改变真实的形参

            arguments[2] = 100
            //此时z still undefined (没有传入第三个参数,所以z为undefined)
            arguments.callee === foo // true (arguments.callee指向当前arguments指向的函数)
                                     // 注意,严格模式下arguments.callee是禁止使用的,会报错
        }
        foo(1,2)
        console.log(foo.length) // 3 (返回形参的个数,形参有xyz所以返回3)
        console.log(foo.name) // "foo" (返回函数名)

二、apply/call方法

        function foo(x,y){
            // 'use strict'
            console.log(this,x,y)
        }

        /* 第一个参数不是对象会转换为对象,比如100就变成了包装类Number(100) */
        foo.call(100,1,2) // Number(100),1,2
        foo.apply(true,[3,4]) // Boolean(true),3,4

        /* 第一个参数如果是null或undefined,会转换为全局对象,对于浏览器就是window对象,对于nodeJS就是Global对象 */
        foo.apply(null) // window,undefined,undefined
        foo.apply(undefined) // window,undefined,undefined
        /* 如果是在严格模式下,null或undefined则不会转换,打印的this就是null或者undefined */

三、bind方法

        this.x=9
        var module = {
            x:81,
            getX:function(){return this.x}
        }

        module.getX() // 81

        var getX = module.getX
        getX() // 9
        
        var boundGetX = getX.bind(module)
        boundGetX() // 81

四、bind与currying(柯里化)

        function add(a,b,c){
            return a+b+c
        }
        
        var func = add.bind(undefined,100) // 绑定add的this为window,第一个参数(add中的a)为100
        func(1,2) // 103
        
        var func2 = func.bind(undefined,200) // 绑定func的this为window,第一个参数(add中的b)为200
        func2(10) // 310
        function getConfig(colors,size,otherOptions){
            console.log(colors,size,otherOptions)
        }

		// 比如在某个模块下colors和size的参数都是一样的,要改动的只有otherOptions,则可用bind柯里化
		// 这样使得代码更加清晰,更加方便重用
        var defaultConfig = getConfig.bind(null,'#CC0000','1024 * 768')

        defaultConfig("123") // #CC0000 1024 * 768 123
        defaultConfig("456") // #CC0000 1024 * 768 456

五、bind与new

如果 bind 得到的函数用作构造函数(带 new 关键字使用),则 bind 不生效,也就是说new的优先级是高于bind的

        function foo(){
            this.b = 100
            return this.a
        }

        var func = foo.bind({a:1})

        func() // 1
        new func() // {b:100}
        function foo(something) {
            this.a = something
        }

        var obj1 = {};

        var bar = foo.bind(obj1);
        bar(2);

        console.log(obj1.a); // 2

        var baz = new bar(3);
        console.log(obj1.a); //2
        console.log(baz.a); //3

六、bind方法模拟(polyfill)(来源MDN)

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
因为较旧的浏览器通常也是较慢的浏览器,所以比大多数人认识到的要重要的是创建性能填充,以使过时的浏览器中的浏览体验的恐怖程度略微降低。

因此,下面介绍了Function.prototype.bind()填充的两个选项:

  • 第一个小得多,性能更高,但是在使用new运算符时不起作用。

  • 第二个函数较大,性能较低,但是允许在绑定函数上使用new运算符。

通常,在大多数代码中,很少见到在绑定函数上使用了new,因此通常最好选择第一个选项。(不过参考第五点,正常情况下new的优先级是高于bind的,只是简易版的polyfill没有实现这个功能)

第一个
//  与 `new (funcA.bind(thisArg, args))`不兼容
if (!Function.prototype.bind) (function(){ /* 如果没有Function.prototype.bind方法 */
  var slice = Array.prototype.slice; /* 数组对象的slice方法 */
  Function.prototype.bind = function() { /* 给Function.prototype绑定一个bind方法 */
    var thatFunc = this, thatArg = arguments[0]; /* this为调用bind方法的对象,aruguments[0]为绑定的对象 */
    var args = slice.call(arguments, 1); /* arguments只是类数组,不是数组对象,本身是没有slice方法的 */
    if (typeof thatFunc !== 'function') { /* 如果调用bind的对象不是一个function */
      throw new TypeError('Function.prototype.bind - ' +
             'what is trying to be bound is not callable');
    }
    return function(){
      var funcArgs = args.concat(slice.call(arguments)) 
      /* 这个arguments已经是本函数作用域中的arguments了,不是上文的arguments */
      /* 用slice.call(arguments)将其转化为数组对象并且与之前固定的args进行拼接 */
      return thatFunc.apply(thatArg, funcArgs);
      /* thatFunc、args是foo.bind(thatFunc,args),即bind固定的对象和参数*/
      /* arguments是(foo.bind(thatFunc,args))(arguments),即调用已经bind修改指向后的函数时传入的参数 */
    };
  };
})();

去除掉各种判断条件的核心代码:

        Function.prototype._bind = function () {
            var slice = Array.prototype.slice
            var thatFunc = this,
                thatArg = arguments[0]
            var args = slice.call(arguments, 1)
            return function () {
                var funcArgs = args.concat(slice.call(arguments))
                return thatFunc.apply(thatArg, funcArgs);
            }
        }
第二个(兼容new操作符的版本)
//  兼容 `new (funcA.bind(thisArg, args))`(满足bind()方法new改变this优先级大于bind的性质)
if (!Function.prototype.bind) (function(){
  var ArrayPrototypeSlice = Array.prototype.slice;
  Function.prototype.bind = function(otherThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var baseArgs= ArrayPrototypeSlice.call(arguments, 1),
        baseArgsLength = baseArgs.length,
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          baseArgs.length = baseArgsLength; // reset to default base arguments
          baseArgs.push.apply(baseArgs, arguments);
          return fToBind.apply(
                 fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
                 /* 如果是new 构造函数().bind(otherThis,baseArgs),则this指向构造函数而不是bind绑定的对象 */
                  /* 这里笔者也不太懂是不是这个意思,欢迎大家在评论区讨论一下 */
          );
        };

    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
})();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值