浅谈JS函数调用模式,闭包以及bind()方法

来源:互联网 发布:计算机前沿技术大数据 编辑:程序博客网 时间:2024/06/05 23:49

本来是想好好归纳下bind()方法,但是从中又牵扯出了现在读的js语言精粹的一些知识,那这里就从基础开始整理下知识点。

 

函数

  JS中最重要的组成部分就是函数了,由于JS中没有类之说,类的功能实现也是靠函数来完成的,用函数模拟类继承等问题。

  JS中的函数就是对象,对象是“名/值”对的集合并拥有一个连到原型对象的隐藏连接。每个函数对象在创建时也随配有一个prototype属性。它的值是一个拥有constructor属性且值即为该函数的对象。

  因为函数是对象,所以它们可以像任何其他的值一样被使用。函数可以保存在变量,对象和数组中。函数可以被当做参数传递给其他函数,函数也可以再返回函数。而且,因为函数是对象,所以函数可以拥有方法。

 

函数字面量

  函数对象通过函数字面量来创建:

  //创建一个名为add的变量,并用来把两个数字相加的函数赋值给它。

  var add = function (a, b) {

    return a + b;

  }

 

函数字面量包含四个部分,保留字function,函数名(可省略,即匿名函数),参数,函数体。

通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包,它是JS强大表现力的来源。

 

闭包

  在js中,每次函数调用时,新的作用域就会产生。

  在某个作用域中定义的变量只能在该作用域或其内部作用域(改作用域中定义的作用域)中才能访问到。也就是说内部函数变量可以访问到外部,而外部不能访问到内部。

var a = 10;function out() {    a == 10;//true    var a = 11;    function inner() {        a == 11;//true    }    inner();};out();

然后这段代码是来自一本书的,今天才在图书馆借的,上面闭包解释也是这本书的意思,配着闭包的例子就是这个,但是这个例子代码却是错的,

 

 

这个错误其实就是JS里面变量提升的问题,详细可见下一篇博客,,这里的执行顺序其实是这样的:

也就是说变量的声明会提前,这里的a还没有赋值,当然是undefined和false,书上配着闭包的代码原本应该是这个意思:

var a = 10;function out() {    console.log(a == 10);//true    a = 11;    function inner() {        console.log(a == 11);//true    }    inner();};out();

把函数里面的var去掉就OK了,没有二次声明,就不会出现这个问题了。好了好了扯远了,本来是说闭包的...

继续......... 

自执行函数是一种机制,通过这种机制声明和调用一个匿名函数,能够达到仅定义一个新的作用域的作用。

var a = 3;(function () {    var a = 5;})();console.log(a == 3);//true

自执行函数对声明私有变量是很有用的,这样可以让私有变量不被其他代码访问。

 

类和继承

  上面也说到,JS没有class关键词。类只能通过函数来定义.......由于这里不想讲这里的,这里省略。

 

调用模式

方法调用模式

  当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。如果调用表达式包含一个提取属性的动作(即包含一个.. 点表达式或[subscript]下标表达式),那么它就是被当做一个方法来调用。

一个小例子:

var myObject = {    value : 0,    increment: function (inc) {        this.value += typeof inc === 'number' ? inc : 1;    }};myObject.increment();console.log(myObject.value); //1myObject.increment(2);console.log(myObject.value); //3

 

方法可以使用this访问自己所属的对象,所以它能从对象中取值或对对象进行修改。this到对象的绑定发生在调用的时候。这个“超级”延迟绑定(very late binding)使得函数可以对this高度复用。通过this可取得它们所属对象的上下文的方法称为公共方法。

 

函数调用模式

当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的:

  var sum = add(3, 4);    //sum的值为7。

 

以此模式调用函数时,this被绑定到全局对象。这是语言设计上的一个错误。倘若语言设计正确,那么当内部函数被调用时,this应该仍然绑定到外部函数的this变量。这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享改方法对对象的访问权。

解决方案:

1.把该方法定义一个变量并给它赋值为this,那么内部函数就可以通过那个变量访问到this。按照约定,把那个变量命名为that

var add = function (a, b) {    return a + b;};var myObject = {    value : 0,    increment: function (inc) {        this.value += typeof inc === 'number' ? inc : 1;    }};myObject.increment();console.log(myObject.value); //1myObject.increment(2);console.log(myObject.value); //3myObject.double = function () {    var that = this;    var helper = function () {        that.value = add(that.value,that.value);    };    helper(); //以函数的形式调用helper,这里形式被调用时this指向全局对象,浏览器中是window,我们这里提前把this存在that里面};//以方法的形式调用doublemyObject.double();console.log(myObject.value);

 

2.

用call,apply,bind方法解决,前面有博客讲到了call和apply的使用,这里用bind来解决。

bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

在上面例子中,我们也可以用bind这样写:

myObject.double = function () {    // var that = this;    var helper = function () {        this.value = add(this.value,this.value);    }.bind(this);//bind换绑this    helper(); };//以方法的形式调用doublemyObject.double();console.log(myObject.value);

 

在常见的单体模式中,通常我们会使用 _this , that , self 等保存 this ,这样我们可以在改变了上下文之后继续引用到它。

 我们用另一种形式再举一个例子:

var class = {    bar : 1,    eventBind: function(){        var that = this;        $('.someClass').on('click',function(event) {            console.log(that.someValue);             });    }}

还是Javascript 特有的机制,上下文环境在 eventBind:function(){ } 过渡到 $('.someClass').on('click',function(event) { }) 发生了改变,这里还是用that保存了this来解决。用bind方法似乎更优雅的解决:

var class = {    bar : 1,    eventBind: function(){        $('.someClass').on('click',function(event) {            console.log(this.someValue);     //1        }.bind(this));    }}

bind() 创建了一个函数,当这个click事件绑定在被调用的时候,它的 this 关键词会被设置成被传入的值(这里指调用bind()时传入的参数)。因此,这里我们传入想要的上下文 this(其实就是 class )到 bind() 函数中。然后,当回调函数被执行的时候, this 便指向class对象。

 

继续看一个例子:

var u1 = function(){    console.log(this.x);}var f1 = {    x:3}u1(); // undefinedvar res = u1.bind(f1);res(); // 3

创建了一个新的函数 res,当使用 bind() 创建一个绑定函数之后,它被执行的时候,它的 this 会被设置成f1 , 而不是像我们调用 u1 时的全局作用域。

 

构造器调用模式,Apply调用模式、

这里不写了,详情可参考JS语言精粹书。

 

其他一些问题

bind的使用不能叠加,如果连续 bind() 两次,亦或者是连续 bind() 三次,最终绑定的还是第一次bind的对象,详情参考bind()实现原理, 深层次原因,bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。(???)

 

 apply、call、bind比较

举一个最直接的例子:

var obj = {    value: 1111,};var foo = {    getValue: function() {        return this.value;    }};console.log(foo.getValue.bind(obj)());  //111console.log(foo.getValue.call(obj));    //111console.log(foo.getValue.apply(obj));   //111

唯独 bind() 方法后面多了对括号。

区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。

 

最后的总结:

apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
apply 、 call 、bind 三者都可以利用后续参数传参;
bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。

 

当然还有很多没说到,有很多都省略过去了,任何一个点都可以在JS里面扩展到很多,这里重在讲明白基本要点,任何一点可上网或者查相关书籍找资料继续深究。以上引用了一些网上和书籍资料。

 

原创粉丝点击