• home > webfront > ECMAS > javascript >

    JS中的bind的用法与注意事项

    Author:[email protected] Date:

    众所周知 在jQuery和prototype js之类的框架里都有个bind jQuery里的用途是给元素绑定事件,在EcmaScript5中也扩展了叫bind的方法(IE6,7,8不支持),使用方法如下

    众所周知 在jQuery和prototype.js之类的框架里都有个bind

    jQuery里的用途是给元素绑定事件

    $("#scroll").bind("click", function() {});
    
    但是:
    说的不是这个:
    在EcmaScript5中也扩展了叫bind的方法(IE6,7,8不支持)


    张鑫旭 bind方法

    但是看完后,还是没有弄明白…………张鑫旭!你忽悠我玩啊……

    好吧…不能抱怨,神的世界,我不了解……

    举个栗子……


    function T(c) {

        this.id = "Object";

        this.dom = document.getElementById("scroll");

    }

    T.prototype = {

        init: function() {

           //①

            this.dom.onmouseover = function() {

                console.log("Over-->"+this.id);

            }

           //②

            this.dom.onmouseout = function() {

                console.log("Out -->"+this.id);

            } .bind(this)

        }

    };

    (new T()).init();

    over----》scroll    out--》Object

    知道,bindcall,apply很相似,,例如,可接受的参数都分为两部分,且第一个参数都是作为执行时函数上下文中的this的对象。其它,母鸡了……

    然后,去MDN看……


    明白 bind 的用法就必须要知道 apply 的用法,MDN 指出,apply 是直接修改了函数内部的指向到第一个参数,并将第二个参数数组传参进函数并运行这个函数。也就是说

    var obj = {test: function() { console.log(this, arguments) }},
        func = obj.test;
    
    obj.test("Hello", ",", "world", "!");
    func.apply(obj, ["Hello", ",", "world", "!"]);

    这两种运行方式是一样的。那么回到 Polyfill 中发现参数的写法是 args.concat(slice.call(arguments))args 是将 bind 时候定义的除第一个参数外的其它参数,而此时的 arguments 是指函数调用时候的参数,通过数组的操作将这两个参数合并成一个数组传入函数内部。看个例子你可能更容易明白:

    /** 代码接上 **/var newFunc = func.bind(obj, "Hello", ",");
    newFunc("world", "!");

    那么再来回答问题一,这个是典型的属性继承的方法,本来使用

    bound.prototype = self.prototype

    就可以将原属性集成过来了,但是这样两个对象属性都指向同一个地方,修改 bound.prototype 将会造成self.prototype 也发生改变,这样并不是我们的本意。所以通过一个空函数 nop 做中转,能有效的防止这种情况的发生。

    ………………

    20160517120605108.png

    尼玛,还是看不懂……

    js里函数调用有 4 种模式: 方法调用 、 正常函数调用 、 构造器函数调用 、apply/call 调用 。

    同时,无论哪种函数调用除了你声明时定义的形参外,还会自动添加 2 个形参,分别是 this 和 arguments 。

    arguments不涉及到上述 3 个函数,所以这里只谈 this 。 this 的值,在上面 4 中调用模式下,分别会绑定不同的值。分别来说一说:

    方法调用 

    这个很好理解,函数是一个对象的属性,比如

    var a = {    
        v : 0,    
        f : function(xx) {                
            this.v = xx;    
        }
    }
    a.f(5);

    这个时候,上面函数里的this就绑定的是这个对象a。所以this.v可以取到对象a的属性v。

    正常函数调用: 
    依然看代码

    function f(xx) {        
        this.x = xx;
    }
    f(5);

    这个时候,函数f里的this绑定的是全局对象,如果是在浏览器运行的解释器中,一般来说是 window对象。所以这里this.x访问的其实是window.x,当然,如果window没有x属性,那么你这么一写,按照js的坑爹语法,就是给window对象添加了一个x属性,同时赋值。

    构造器函数调用 : 
    构造函数一直是我认为是 js 里最坑爹的部分,因为它和 js 最初设计的基于原型的面向对象实现方式格格不入,就好像是特意为了迎合大家已经被其他基于类的面相对象实现给惯坏了的习惯。 
    如果你在一个函数前面带上 new 关键字来调用,那么 js 会创建一个 prototype 属性是此函数的一个新对象,同时在调用这个函数的时候,把 this 绑定到这个新对象上。当然 new 关键字也会改变 return 语句的行为,不过这里就不谈了。看代码

    function a(xx) {        
        this.m = xx;
    }
    var b = new a(5);

    上面这个函数和正常调用的函数写法上没什么区别,只不过在调用的时候函数名前面加了关键字new罢了,这么一来,this绑定的就不再是前面讲到的全局对象了,而是这里说的创建的新对象,所以说这种方式其实很危险,因为光看函数,你不会知道这个函数到底是准备拿来当构造函数用的,还是一般函数用的。所以我们可以看到,在jslint里,它会要求你写的所有构造函数,也就是一旦它发现你用了new关键字,那么后面那个函数的首字母必须大写,这样通过函数首字母大写的方式来区分,我个人只有一个看法:坑爹:)

    apply/call 调用: 
    我们知道,在 js 里,函数其实也是一个对象,那么函数自然也可以拥有它自己的方法,有点绕,在 js 里,每个函数都有一个公共的 prototype ―― Function ,而这个原型自带有好几个属性和方法,其中就有这里困惑的 bind 、 call 、 apply 方法。先说 apply 方法,它让我们构造一个参数数组传递给函数,同时可以自己来设置 this 的值,这就是它最强大的地方,上面的 3 种函数调用方式,你可以看到, this 都是自动绑定的,没办法由你来设,当你想设的时候,就可以用 apply() 了。 apply  函数接收 2 个参数,第一个是传递给这个函数用来绑定  this 的值,第二个是一个参数数组。看代码

    function a(xx, yy) {	  
      alert(xx, yy);	
      alert(this);	
      alert(arguments);
    }
    a.apply(null, [5, 55]);
    a.call(null, 5, 55);

    是不是很神奇,函数a居然可以给o加属性值。当然,如果你apply的第一个参数传递null,那么在函数a里面this指针依然会绑定全局对象。

    call() 方法和 apply() 方法很类似,它们的存在都是为了改变 this 的绑定,那 call()和 apply() 有什么区别呢?就我个人看来,没啥鸟区别。。。开玩笑!刚刚说了,上面 apply() 接收两个参数,第一个是绑定  this 的值,第二个是一个参数数组,注意它是一个数组,你想传递给这个函数的所有参数都放在数组里,然后 apply() 函数会在调用函数时自动帮你把数组展开。而 call() 呢,它的第一个参数也是绑定给 this 的值,但是后面接受的是不定参数,而不再是一个数组,也就是说你可以像平时给函数传参那样把这些参数一个一个传递。所以如果一定要说有什么区别的话,看起来是这样的

    function a(xx, yy) {	
      alert(xx, yy);	
      alert(this);	
      alert(arguments);
    }
    a.apply(null, [5, 55]);
    a.call(null, 5, 55);

    仅此而已。

    最后再来说bind()函数,上面讲的无论是call()也好,apply()也好,都是立马就调用了对应的函数,而bind()不会,bind()会生成一个新的函数,bind()函数的参数跟call()一致,第一个参数也是绑定this的值,后面接受传递给函数的不定参数。bind()生成的新函数返回后,你想什么时候调就什么时候调,看下代码就明白了

    var m = {	
      "x" : 1
    };
    function foo(y) {	
      alert(this.x + y);
    }
    foo.apply(m, [5]);
    foo.call(m, 5);
    var foo1 = foo.bind(m, 5);
    foo1();

    末了来个吐槽,你在js里想定义一个函数,于是你会这么写: 

    function jam() {};

    其实这是js里的一种语法糖,它等价于: 

    var jam = function() {};

    然后你想执行这个函数,脑洞大开的你会这么写: 

    function jam() {}();

    但是这么写就报错了,其实这种写法也不算错,因为它确实是js支持的函数表达式,但是同时js又规定以function开头的语句被认为是函数语句,而函数语句后面是肯定不会带 () 的,所以才报错,于是聪明的人想出来,加上一对括号就可以了。于是就变成了这样: 

    (function jam() {}());

    不要滥用bind方法

    我测试了一下浏览器原生的Function.prototype.bind,发现使用了bind之后,函数的内存占用增加了近2倍!CoffeeScript实现的绑定稍微轻量一点,内存占用也增加了1倍多。


    再顺便测试了下ES6新增的Arrow function(也是=>),因为这个特殊函数是自带绑定技能的,结果惊奇地发现,它的内存占用和普通的Function没啥区别。所以以后需要或者不需要bind的场景如果一定要滥用bind图个安心的话,可以通通上高逼格的箭头函数。:)




    转载本站文章《JS中的bind的用法与注意事项》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/js/2016_0517_7828.html