zoukankan      html  css  js  c++  java
  • bind的那些事

    最近面头条的时候,要求自己手动实现一个bind函数,然后又问了bind还能干嘛。这里就围绕这两个好好写一下。

    首先bind的文档说明: (链接:传送门

    bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。
    fun.bind(thisArg[, arg1[, arg2[, ...]]])
    参数

    • thisArg
      当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
    • arg1, arg2, ...
      当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。
    • 返回值
      返回由指定的this值和初始化参数改造的原函数拷贝

    bind() 函数会创建(返回)一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当新函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。(注意这句话是重点,也就是说bind支持了这个功能)

    一般新手分不清于call,apply的关系,call/apply一般会伴随这函数的调用的,而bind只是返回了带新this的函数,将在下次调用。一般用法是调用bind后赋给一个变量,支持调用的时候继续传参。

    我们一般快速实现一个bind:

    /*
    
    函数体内的this,就是需要绑定this的实例函数,或者说是原函数。最后我们使用apply来进行参数(context)绑定,并返回。
    同时,将第一个参数(context)以外的其他参数,作为提供给原函数的预设参数,这也是基本的柯里化基础。(应该是偏函数)
    
    */
    Function.prototype.bind =  Function.prototype.bind || function(context) {
        var self = this;
        arg = Array.prototype.slice.call(arguments,1);
        return function() {
            return self.apply(context,arr);
        }
    }
    

    但是这个实现有个问题,我们将参数限定了arguments.slice(1),我们返回的绑定函数中,如果想实现预设传参,上个代码就不能满足了。
    eg:

    function sum(a, b) {
        console.log(a + b)
    }
    
    var sum2 = sum.bind(null,2);   // 固定参数a, 值为2
    sum2(4)                        // 传入参数b, 值为4, 结果为6 重在我们在调用函数的时候可以预设传参
    

    那么我们将上个bind的实现更完善一下:

    Function.prototype.bind =  Function.prototype.bind || function(context) {
        var self = this;
        var args = Array.prototype.slice.call(arguments,1);
        return function() {
            var innerArgs = Array.prototype.slice.call(arguments);
            var FinalArgs = args.concat(innerArgs);
            return self.apply(context,FinalArgs);
        }
    }
    

    这样就是实现了偏函数功能,在一些资料里是说“柯里化”,我觉得还是有点区别的,共同特点是实现了参数复用。

    关于柯里化和偏函数举个小例子就知道了:
    假设有一个Add(x,y,z)函数,接收x,y,z三个参数,返回x+y+z

    • 偏函数

    AddBySeven =Otherbind(Add, 7);
    AddBySeven(5, 10); // returns 22;
    这是偏函数,固定了你函数的某一个或几个参数,返回一个新的函数,接收剩下的参数, 参数个数可能是1个,也可能是2个,甚至更多。

    • 柯里化:把一个有n个参数的函数变成n个只有1个参数的函数

    curryAdd = Curry(Add);
    AddBySeven = curryAdd(7);
    AddBySeven(5)(10); // returns 22
    // curryAdd(7)(5)(10)

    Add = (x, y, z) => x + y + z
    变成了CurryAdd = x => y => z => x + y + z

    很多资料有的叫柯里化有的叫偏函数,这点我觉得还是读者自己判断把。

    到这里可能大家觉得上面的实现已经完美了,但是JS的坑是补不完的,问题又来了!
    看过文档的就知道,在文档上介绍bind时还说了这点:

    一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

    那么如果bind返回的函数当做构造函数调用的时候,我们就需要在内部重新构造原型链了。所以更兼容的写法来了:

    Function.prototype.bind =  Function.prototype.bind || function(context) {
    	if(typeof this !== 'function') {
            throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
        }
        var self = this;
        var args = Array.prototype.slice.call(arguments,1);
        var Fun = {};
        Fun.prototype = this.prototype;//继承原来函数
        var Comb = function(){
            var innerArgs = Array.prototype.slice.call(arguments);
            var FinalArgs = args.concat(innerArgs);
            return self.apply(this instanceof Fun ? this : context || this ,FinalArgs);
        }
        Comb.prototype = new Fun();
        return Comb;
        }
    

    这里的点:

    1. 第一个this是第一次调用bind的函数,也必须是函数,所以在之前就做了一个容错判断。

    2. 如果最后我们是以new 调用bind返回的函数,即当做构造函数调用,那么这里的this就是Comb的实例,这时候因为Fun继承了之前调用的函数,Comb又new了Fun,Comb即是Fun的派生类,因此 this instanceof fNOP === true,这时候无视 bind 效果,因此 this 该是什么还是什么。模仿了原本bind的feature,如果这个条件判断失败,则使用context 去替换 this。如果context没传,那么this该是什么就是什么。

    但这里再想想面试官当时的的问题,"bind还能干嘛",其实这个bind就是改变上下文还能干嘛,其实面试管的意思是利用bind的这个feature可以干嘛

    把bind本身的作用讲讲,在把上面bind本身的偏函数功能(允许第一次传参不完全,后面调用可以继续传参),自己实现的偏函数,作为new 构造函数来调用这些讲讲,就够了。

    最后再加一个bind的小功能把,平常我们转换伪数组,通常是使用:

    var slice = Array.prototype.slice;
    
    // ...
    
    slice.call(arguments);//arguments是一个伪数组
    

    如果我们用bind把对象参数绑定到call上返回给slice,每次就不用调用call了,而且还不影响原函数的this:

    var combSlice = Array.prototype.slice;
    var slice = Function.prototype.call.bind(combSlice);
    
    // ...
    
    slice(arguments);//slice是一个新函数
    

    好了,bind的这些事到此结束,欢迎在下方交流评论。

  • 相关阅读:
    关于 Blog 修改
    How To Manage StartUp Applications In Ubuntu
    C++11 之for 新解 auto
    How To Install Cacti On Ubuntu 14
    Apache 2 解析html中的php
    Raspberry Pi
    Compiling a kernel module for the raspberry pi 2 via Ubuntu host
    ubuntu 14.04/14.10 iptables 防火墙设置
    ajax概述
    ajax 异步 通信 小例子 servlet与 jsp异步 post方法
  • 原文地址:https://www.cnblogs.com/zhangmingzhao/p/8660985.html
Copyright © 2011-2022 走看看