函数调用模式

  这篇主要是记录下对一篇外文的阅读收获,原文链接

  函数调用模式其实以前也比较清楚,但是对于this这个东西总是吃不透,阅读完这篇文章后才豁然开朗。

  首先将函数的几种调用模式以及this的定义列出来,以便结合理解每种调用模式和this之间的关系。

  函数调用模式有以下几种:1)方法调用 2)函数调用 3)构造器调用 4)apply/call调用

  this的定义:this在js中是一个依赖于使用它的执行环境被解析的关键字,this的值是建立在当前函数被调用的上下文基础上的,取决于在哪里、怎么样调用函数

  1)方法调用

var obj = {
    value: 0,
    increment: function() {
        this.value+=1;
    }
};

obj.increment(); //Method invocation

  上面的代码中obj.increment();就是方法调用模式。此时increment函数的调用方式是"对象名.方法名",也就是在调用increment函数时显示的指向了它所属的对象,因此increment函数中的this就指向了obj对象。

  2)函数调用

var value;
function
increment(){   this.value++; } increment();

  这是简单的函数调用,先定义一个函数,在通过圆括号调用。此时因为没有明确指定调用函数时的对象,因此increment函数中的this是指向了全局对象window。令人费解的是,这种情况在子函数中尽然也成立,请看下面代码:

var value = 500; //Global variable
var obj = {
    value: 0,
    increment: function() {
        this.value++;

        var innerFunction = function() {
            alert(this.value);
        }

        innerFunction(); //Function invocation pattern
    }
}
obj.increment(); //Method invocation pattern

  大家看看,obj.increment();调用完后,alert的值应该是多少呢?500,没错就是500。为什么???

  因为innerFunction()是函数调用模式,它里面的this指向的是全局对象window,因此this.value输出的应该是500。

  如何让它输出obj对象中的value值呢?修改成下面这样:

var value = 500; //Global variable
var obj = {
    value: 0,
    increment: function() {
        var that = this;
        that.value++;

        var innerFunction = function() {
            alert(that.value);
        }

        innerFunction(); //Function invocation pattern
    }
}
obj.increment();

  使用that缓存this对象,innerFunction中调用alert(that.value); 这样输出的结果就是1了。这样之所以可以输出1,是应为js中函数都是闭包(this works because functions in JavaScript are closures。这句话很费解,解释下:广义上讲,js中的所有子函数都是闭包。我们一般的函数是定义在全局对象中的,因此一般定义的函数其实是全局对象的子函数,因此也是个闭包。这可以解释原文中的那句话,但是最好不要这样理解闭包。

  闭包的定义:当你在内嵌函数中使用外部函数作用域内的变量时,就是使用了闭包。用一个常用的类比来解释闭包和类(Class)的关系:类是带函数的数据,闭包是带数据的函数。

  根据上面的对闭包的定义,第二段代码中子函数innerFunction调用了父函数的变量that.value,因此形成了闭包。通过函数调用模式调用innerFunction时,它会在定义它的上下文环境中找that.value这个变量,首先找到的就是obj对象中的value值,因此输出是1.

  3)构造器调用

  这种调用比较好理解,和面向对象语言中的调用方式基本相同。

function obj(){
      this.arr=[1,2];  
}

obj.prototype={
      push:function(a){
         this.arr.push(a);
     },
      length:function(){
        alert(this.arr.length);
    }
}
var o1=new obj();
o1.push(2);

  需要注意一点是,之所以将arr放在函数体(构造器)obj中定义,是因为构造器中定义的对象是属于每个实例的,而原型prototype中定义的对象或者方法是所有实例共享的。

  4)apply/call调用

  这种调用方式也比较简单,两种方法只是传参不一样,其他都一样。作用都是显示的改变调用函数运行的上下文环境。

var add = function(num1, num2) {
        return num1+num2;
}

array = [3,4];
add.apply(null,array); //7

  也是需要注意一点,apply第一个参数传递为null,并不是说将add函数的运行上下文环境设置为null,而是设置为全局对象window。

  

posted @ 2012-11-05 17:54  Johnny.Chen  阅读(944)  评论(0编辑  收藏  举报