Home > Web Front-end > JS Tutorial > body text

Detailed introduction to JavaScript anonymous functions

高洛峰
Release: 2017-03-19 16:10:09
Original
2035 people have browsed it

AnonymousFunction is a function without a name, sometimes also called a lambda function. Anonymous functions are an incredibly powerful tool with so many uses. Let’s take a look at the following typical function declaration:

function functionName(arg0, arg1, arg2) {
    //函数体
}
Copy after login

You can either declare a function like the above , you can also define the function in the form of function expression as follows:

var functionName = function(arg0, arg1, arg2) {
    //函数体
};
Copy after login

Although these two examples are logically equivalent, there are still differences between them There are some differences. Of course, the main difference between function declarations and function expressions is that the former will be loaded into the scope before the code is executed, while the latter will not be defined until which line the code is executed. Another important difference is that a function declaration assigns a name to the function, while a function expression creates an anonymous function and assigns the anonymous function to a

variable. In other words, the second example above creates an anonymous function with three parameters, and then assigns the anonymous function to the variable functionName, but does not specify a name for the anonymous function.

It is also possible to write an anonymous function like the following:

function(arg0, arg1, arg2) {
    //函数体
}
Copy after login

These codes are completely valid, but the problem is that no one can call this function because there is no pointer to this function. However, anonymous functions are usually defined in this form when passing a function as a parameter into another function, or returning a function from one function to another. The following is an example of a createComparisonFunction() function that has been used:

function createComparisonFunction(propertyName) {
    return function(object1, object2) {
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];

        if (value1 < value2) {
            return -1;
        } else if (value1 > value2) {
            return 1;
        } else {
            return 0;
        }
    };
}
Copy after login

createComparisonFunction() returns an anonymous function. The returned function may be assigned to a variable or called in other ways, but , inside the createComparisonFunction() function, it is anonymous, and anonymous functions can be used when the function is regarded as a value. However, this is not the only use of anonymous functions.


1

Recursion

Recursive function is formed when a function calls itself by name, as shown below:

function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * factorial(num - 1);
    }
}
Copy after login

This is a classic recursive factorial function. Although there is nothing wrong with this function on the surface, the following code may cause it to go wrong:

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //VM848:5 Uncaught TypeError: factorial is not a function(…)
Copy after login

The above code first saves the factorial() function In the variable anotherFacttorial, the factorial variable is then set to null, resulting in only one

reference pointing to the original function. But when anotherFacttorial() is called next, because factorial() must be executed, and factorial is no longer a function, an error will occur. In this case, using arguments.callee can solve this problem:

function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * arguments.callee(num - 1);
    }
}

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //24
Copy after login

The code with a yellow background is displayed. By using arguments.callee instead of the function name, you can ensure that no matter how it is called There will be no problem with the functions. Therefore, when writing recursive functions, it is safer to use arguments.callee than

using the function name.


2 Closure

Many developers are always confused about the two concepts of anonymous functions and closures, so they often use them interchangeably. A closure is a function that has access to a variable in the scope

of another function. A common way to create a closure is to create another function inside a function. Still taking the previous createComparisonFunction() function as an example, pay attention to the code with a yellow background:

function createComparisonFunction(propertyName) {
    return function(object1, object2) {
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];

        if (value1 < value2) {
            return -1;
        } else if (value1 > value2) {
            return 1;
        } else {
            return 0;
        }
    };
}
Copy after login

In this example, the highlighted one The two lines of code are code in

internal function (an anonymous function). These two lines of code access the variable propertyName in the external function, even though the internal function is returned and called elsewhere , but it can still access the variable propertyName. The reason why this variable can still be accessed is because the scope chain of the inner function includes the scope of createComparisonFunction(). To fully understand the details, you must start by understanding what happens when a function is called for the first time.

有关如何创建作用域链以及作用域链有什么作用的细节,对彻底理解闭包至关重要。当某个函数第一次被调用时,会创建一个执行环境(execution context)及相应的作用域链,并把作用域链赋值给一个特殊的内部属性(即[[Scope]])。然后,使用this.arguments和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,...直至作为作用域链终点的全局执行环境。

在函数执行过程中,为读取和写入变量的值,就需要在作用域中查找变量,来看下面的例子:

function compare(value1, value2) {
    if (value1 < value2) {
        return -1;
    } else if (value2 > value1) {
        return 1;
    } else {
        return 0;
    }
}

var result = compare(5, 10); //-1
Copy after login

以上代码先定义了compare()函数,然后又在全局作用域中调用了它,当第一次调用compare()时,会创建一个包含this、arguments、value1和value2的活动对象。全局执行环境的变量对象(this、compare和result)在compare()执行环境的作用域链中则处于第二位。下图展示了包含上述关系的compare()函数执行时的作用域链:

Detailed introduction to JavaScript anonymous functions

后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部环境的变量对象,则只在函数执行的过程中存在。在创建compare()函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]](scope chain)属性中。当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]](scope chain)属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。对于这个例子中的compare()函数的执行环境而言,其作用域链中包含两个对象,本地活动对象和全局活动对象。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量,一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是闭包的情况又有所不同。

但另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。因此,在createComparisonFunction()函数内部定义的匿名函数的作用域链中,实际上将会包含外部函数createComparisonFunction()的活动对象。下图展示了当下列代码执行时,包含函数与内部匿名函数的作用域链:

Detailed introduction to JavaScript anonymous functions

var compare = createComparisonFunction(&#39;name&#39;);
var result = compare({ name: &#39;Nicholas&#39; }, { name: &#39;Greg&#39; });
Copy after login


在匿名函数从createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction()中定义的所有变量。更为重要的是,createComparisonFunction()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说,当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。例如:

//创建函数
var compareNames = createComparisonFunction(&#39;name&#39;);

//调用函数
var result = compareNames({ name: &#39;Nicholas&#39; }, { name: &#39;Greg&#39; });

//删除对匿名函数的引用(以便释放内存)
compareNames = null;
Copy after login

首先创建的比较函数被保存在变量compareNames中,而通过compareNames设置为等于null解除该函数的引用,就等于通知垃圾回收例程将其清除。随着匿名函数的作用域链被销毁,其他作用域(除了全局作用域)也都可以安全地销毁了。上图展示了调用compareNames()的过程中产生的作用域链之间的关系。(由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存。过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包。)

2.1 闭包与变量

作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。下面这个例子可用清晰地说明这个问题:

function createFunctions() {
    var result = new Array();

    for (var i = 0; i < 10; i++) {
        result[i] = function() {
            return i;
        };

    }

    return result;
}

var funcs = createFunctions();

//每个函数都输出10
for (var i = 0; i < funcs.length; i++) {
    document.write(funcs[i]() + &#39;<br />&#39;);
}
Copy after login


这个函数会返回一个函数数组,表面上看,似乎每个函数都应该返回自己的索引值,即位置0的函数返回0,位置1的函数返回1,以此类推。但实际上,每个函数都返回10。因为每个函数的作用域链中都保存着createFunctions()函数的活动对象。所以它们引用的都是同一个变量i。当createFunctions()函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10,但是,我们可以通过创建另一个匿名函数让闭包的行为符合预期,如下所示:

function createFunctions() {
    var result = new Array();

    for (var i = 0; i < 10; i++) {
        result[i] = function(num) {
            return function() {
                return num;
            };
        }(i);
    }

    return result;
}

var funcs = createFunctions();

//分别输出0,1,2,3...
for (var i = 0; i < funcs.length; i++) {
    document.write(funcs[i]() + &#39;<br />&#39;)
}
Copy after login

在重写了前面的createFunctions()函数后,每个函数就会返回各自不同的索引值了。在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量i。由于函数参数是按值传递的,所以就会将变量i的当前值复制给参数num,而在这个匿名函数内部,又创建并返回了一个访问num的闭包。这样一来,result数组中的每个函数都有自己num变量的一个副本。因此就可以返回各自不同的数值了。

2.2关于this对象

在闭包中使用this对象也可能会导致一些问题。我们知道,this对象是在运行时基于函数的执行环境绑定的,在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window(当然,在通过call()或apply()改变函数执行环境的情况下,this就会指向其他对象),但有时候由于编写闭包的方式不同,这一点可能不会那么明显。下面来看一个例子:

var name = &#39;The Window&#39;;

var object = {
    name: &#39;My Object&#39;,

    getNameFunc: function() {
        return function() {
            return this.name;
        };
    }
};

alert(object.getNameFunc()()); //"The Window"
Copy after login


以上代码先创建了一个全局变量name,又创建了一个包含name属性的对象。这个对象还包含一个方法——getNameFunc(),他返回一个匿名函数,而匿名函数又返回this.name,由于getNameFunc()返回一个函数,因此调用object.getNameFunc()()就会立即调用它返回的函数,结果就是返回一个字符串。然而,这个例子返回的字符串是"The Window",即全局变量name的值。为什么匿名函数没有取得其包含作用域(或外部作用域)的this对象呢?

前面曾经提到过,每个函数在调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量,不过,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了,如下所示:

var name = &#39;The Window&#39;;

var object = {
    name: &#39;My Object&#39;,

    getNameFunc: function() {
        var that = this;
        return function() {
            return that.name;
        };
    }
};

alert(object.getNameFunc()()); //"My Object"
Copy after login


代码中突出的行展示了这个例子与前一个例子之间的不同之处,在定义匿名函数之前,我们把this对象赋值给了一个名叫that的变量。而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声明的一个变量。即使在函数返回之后,that也仍然引用着object,所以调用object.getNameFunc()就返回了"My Object"。(this和arguments也存在同样的问题,如果想访问作用域中的arguments对象,必须将对该对象的引用保存到另一个闭包能够访问的变量中。)

2.3 内存泄漏

由于IE对JScript对象和COM对象使用不同的垃圾收集例程,因此闭包在IE中会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。来看下面的例子:

function assignHandler() {
    var element = document.getElementById(&#39;someElement&#39;);
    element.onclick = function() {
        alert(element.id);
    };
}
Copy after login

以上代码创建了一个作为element元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用(事件将在后面篇章中讨论)。由于匿名函数保存了一个对assignHandler()的活动对象的引用,因此就会导致无法减少element的引用数。只要匿名函数存在,element的引用数至少是1,因此它占用的内存就永远不会被回收。不过,这个问题可以通过稍微改写一下代码来解决。如下所示:

function assignHandler() {
    var element = document.getElementById(&#39;someElement&#39;);
    var id = element.id;

    element.onclick = function() {
        alert(id);
    };

    element = null;
}
Copy after login


在上面的代码中,通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。但仅仅做到这一步,还是不能解决内存泄漏的问题。必须要记住,闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用,因此,有必要把element变量设置为null,这样就能解除对DOM对象的引用,顺利地减少其引用数,确保正常回收其占用的内存。


3. 模仿块级作用域

如前所述,JavaScript没有块级作用域的概念,这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的,来看下面的例子:

function outputNumbers(count) {
    for (var i = 0; i < count; i++) {
        alert(i);
    }
    alert(i); //count
}
Copy after login


这个函数中定义了一个for循环,而变量i的初始值被设置为0。在Java、C++等语言中,变量i只会在for循环的语句块中有定义,循环一旦结束,变量i就会被销毁。可是在JavaScript中,变量i是定义在outputNumbers()的活动对象中的,因此从它有定义开始,就可以在函数内部随处访问它。即使像下面这样错误地重新声明一个变量,也不会改变它的值:

function outputNumbers(count) {
    for (var i = 0; i < count; i++) {
        alert(i);
    }
    var i; //重新声明变量
    alert(i); //count
}
Copy after login


JavaScript从来不会告诉你是否多次声明了同一个变量,遇到这种情况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)。匿名函数可以用来模仿块级作用域并避免这个问题

用作块级作用域(通常称为私有作用域)的匿名函数的语法如下所示:


(function() {
    //这里是块级作用域
})();
Copy after login


以上代码定义并立即调用了一个匿名函数,将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。如果有读者感觉这种语法不太好理解,可以再看看下面的例子:


var count = 5;
outputNumbers(count);
Copy after login


这里初始化了变量count,将其值设置为5,当然,这里的变量是没有必要的,因为可以把值直接传给函数,为了让代码更简洁,我们在调用函数时用5来调用变量count,如下所示:


outputNumbers(5);
Copy after login


这样做之所以可行,是因为变量只不过是值的另一种表现形式,因此用实际的值替换变量没有问题,再看下面的例子:


var someFunction = function() {
    //这里是块级作用域};
someFunction();
Copy after login


这个例子先定义了一个函数,然后立即调用了它。定义函数的方式是创建一个匿名函数,并把匿名函数赋值给变量someFunction。而调用函数的方式是在函数名称后面添加一对圆括号,即someFunction()。通过前面的例子我们知道,可用使用实际的值来取代变量count,那在这里是不是也可以用函数的值直接取代函数名呢?然而,下面的代码却会导致错误:


function() { 
   //这里是块级作用域}(); //出错
Copy after login


这段代码会导致语法错误,是因为JavaScript将function关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号,然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只要像下面这样给它加上一对圆括号即可:


(function() { 
   //这里是块级作用域})();
Copy after login


无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:


function outputNumbers(count) {

    (function() { 
           for (var i = 0; i < count; i++)
            {
            alert(i);
        }
    })();
    alert(i); //导致一个错误}
Copy after login


在这个重写后的outputNumbers()函数中,我们在for循环外部插入了一个私有作用域。在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。而在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。

这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽量少向全局作用域中添加变量和函数。在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突,而通过创建私有作用域,每个开发人员既可以使用自己的变量,又不比担心搞乱全局作用域。例如:


(function() {
    var now = new Date();
        if (now.getMonth() == 0 && now.getDate() == 1)
         {
        alert(&#39;Happy new year!&#39;);
    }
})();
Copy after login


把上面这段代码放在全局作用域中,可用用来确定哪一天是1月1日,如果到了这一天,就会向用户显示一条祝贺新年的消息。其中的变量now现在是匿名函数中的局部变量,而我们不必在全局作用域中创建它。(这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域链了。


4 私有变量

严格来讲,JavaScript中没有私有成员的概念,所有对象属性都是公有的,不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量,私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。来看下面的例子:


function add(num1, num2) 
{  
  var sum = num1 + num2; 
     return sum;
}
Copy after login


在这个函数内部,有三个私有变量:num1,num2,sum.在函数内部可以访问这几个变量,但在函数外部则访问不到它们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。而利用这一点,就可以创建用于访问私有变量的共有方法。

我们把有权访问私有变量和私有函数的公有方法称为特权方法(privileged method)。有两种在对象上创建特权方法的方式,第一种是在构造函数中定义特权方法,基本模式如下:


function MyObject() { 
   //私有变量和私有函数
    var privateVariable = 10;
        function privateFunction()
  {
        return false;
    }   
     //特权方法
    this.publicMethod = function()
     {
        privateVariable++;
        return privateFunction();
    };
}
Copy after login


这个模式在构造函数内部定义了所有私有变量和函数。然后,又继续创建了能够访问这些私有成员的特权方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。对这个例子而言,变量privateVariable和函数privateFunction()只能通过特权方法publicMethod()来访问。在创建MyObject的实例后,除了使用publicMethod()这一个途径外,没有任何方法可以直接访问privateVariable和privateFunction()。

利用私有和特权成员,可用隐藏那些不应该被直接修改的数据,例如:

function Person(name) { 
   this.getName = function()
    {       
     return name;
    };    
    this.setName = function(value)
     {
        name = value;
    };
}var person = new Person(&#39;Nicholas&#39;);
alert(person.getName()); //"Nicholas"person.setName(&#39;Greg&#39;);
alert(person.getName()); //"Greg"
Copy after login


以上代码的构造函数中定义了两个特权方法:getName()和setName()。这两个方法都可以在构造函数外部使用,而且都有权访问私有变量name,但在Person构造函数外部,没有任何办法访问name。由于这两个方法是在构造函数内部定义的,它们作为闭包能够通过作用域链访问name。私有变量name在Person的每一个实例中都不相同,因为每次调用构造函数都会重新创建这两个方法。不过,在构造函数中定义特权方法也有一个缺点,那就是你必须使用构造函数模式来达到这个目的,构造函数模式的缺点是针对每个实例都会创建一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题。

4.1 静态私有变量

通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法,其基本模式如下:

(function() {

    //私有变量和私有函数
    var privateVariable = 10;

    function privateFunction() {
        return false;
    }

    //构造函数
    MyObject = function() {
    };

    //公有/特权方法
    MyObject.prototype.publicMethod = function() {
        privateVariable++;
        return privateFunction;
    };
})();
Copy after login


这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中,首先定义了私有变量和私有函数,然后又定义了构造函数及其公有方法。公有方法是在原型上定义的,这一点体现了典型的原型模式。需要注意的是,这个模式在定义构造函数模式时并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。出于同样的原因,我们也没有在声明MyObject时使用var关键字。记住,初始化未经声明的变量,总是会创建一个全局变量。因此,MyObject就成了一个全局变量,能够在私有作用域之外被访问到。

这个模式与在构造函数中定义的特权方法的主要区别,就在于私有变量和函数是由实例共享的。由于特权方法是在原型上定义的,因此所有实例都使用同一个函数。而这个特权方法,作为一个闭包,总是保存着对包含作用域的引用。来看一看下面的代码:

(function() {

    var name = &#39;&#39;;

    Person = function(value) {
        name = value;
    };

    Person.prototype.getName = function() {
        return name;
    };

    Person.prototype.setName = function(value) {
        name = value;
    };
})();

var person = new Person(&#39;Nicholas&#39;);
alert(person.getName()); //"Nicholas"
person.setName(&#39;Greg&#39;);
alert(person.getName()); //"Greg"

var person2 = new Person(&#39;Michael&#39;);
alert(person.getName()); //"Michael"
alert(person2.getName()); //"Michael"
Copy after login

这个例子中的Person构造函数与getName()和setName()方法一样,都有权访问私有变量name,在这种模式下,变量name就变成了一个静态的、由所有实例共享的属性,也就是说,在一个实例上调用setName()会影响所有实例。而调用setName()或新建一个Person实例都会赋予name属性一个新值。结果就是所有实例都会返回相同的值。

以这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量,到底是使用实例变量、还是静态私有变量,最终还是要视你的具体需求而定。(多查找作用域链中的一个层次,就会在一定程度上影响查找速度。而这正是使用闭包和私有变量的一个显明的不足之处。)

4.2 模块模式

前面的模式是用于为自定义类型创建私有变量和特权方法的。而道格拉斯所说的模块模式(module pattern)则是为单例创建私有变量和特权方法。所谓单例(singleton),指的就是只有一个实例的对象。按照惯例,JavaScript是以对象字面量的方式来创建单例对象的:

var singleton = {
    name: value,
    method: function() {
        //这里是方法的代码
    }
};
Copy after login


模块模式通过为单例添加私有变量和特权方法能够使其得到增强,其语法形式如下:

var privateVariable = function() {

    //私有变量和私有函数
    var privateVariable = 10;

    function privateFunction() {
        return false;
    }

    //特权/共有方法和属性
    return {
        publicProperty: true;
        publicMethod: function() {
            privateVariable++;
            return privateFunction();
        }
    };
}();
Copy after login


这个模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部,首先定义了私有变量和函数,然后,将一个对象字面量作为函数的值返回。返回的对象字面量中只包含可以公开的属性和方法。由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数。从本质来讲,这个对象字面量定义的是单例的公共接口。这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。 例如:

function BaseComponent() {}

function OtherComponent() {}

var application = function() {

    //私有变量和函数
    var components = new Array();

    //初始化
    components.push(new BaseComponent());

    //公共
    return {
        getComponentCount: function() {
            return components.length;
        },
        registerComponent: function(component) {
            if (typeof component == &#39;object&#39;) {
                components.push(component);
            }
        }
    };
}();

application.registerComponent(new OtherComponent());
alert(application.getComponentCount()); //2
Copy after login


在web应用程序中,经常需要使用一个单例来管理应用程序级的信息,这个简单的例子创建了一个用于管理组件的application对象,在创建这个对象的历程中,首先声明了一个私有的components数组,并向数组中添加了一个BaseComponent的新实例(在这里不需要关心BaseComponent的代码,我们只是用它来展示初始化操作)。而返回对象的getComponentCount()和registerComponent()方法,都是有权访问components的特权方法。前者只是返回已注册的组件数目,后者用于注册新组件。

简言之,如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式,以这种模式创建的每个单例都是Object的实例。因为最终要通过一个对象字面量来表示它。事实上,这也没有什么,毕竟,单例通常都是作为全局对象存在的。我们不必将它传递给一个函数,因此,也就没有必要使用instanceof操作符来检查其对象类型了。

4.3 增强的模块模式

有人进一步改进了模块模式,即在返回对象之前加入对其增强的代码。这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。来看下面的例子:

var singleton = function() {

    //私有变量和私有函数
    var privateVariable = 10;

    function privateFunction() {
        return false;
    }

    //创建对象
    var object = new CustomType();

    //添加特权/公有属性和方法
    object.publicProperty = true;

    object.publicMethod = function() {
        privateVariable++;
        return privateFunction();
    };
    //返回这个对象
    return object;
}();
Copy after login


如果前面演示模块模式的例子中的application对象必须是BaseComponent的实例,那么就可以使用以下代码:

var application = function() {

    //私有变量和函数
    var components = new Array();

    //初始化
    components.push(new BaseComponent());

    //创建application的一个局部副本
    var app = new BaseComponent();

    //公共接口
    app.getComponentCount = function() {
        return components.length;
    }

    app.registerComponent = function(component) {
        if (typeof component == &#39;object&#39;) {
            components.push(component);
        }
    };

    //返回这个副本
    return app;
}();
Copy after login


在这个重写后的应用程序(applicaiton)单例中,首先也是像前面例子中一样定义了私有变量,主要的不同之处在于命名变量app的创建过程。因为它必须是BaseComponent的实例,这个实例实际上是 applicaiton对象的局部变量版。此后,我们又为app对象添加了能够访问私有变量的公有方法。最后一步是返回app对象,结果仍然是将它赋值给全局变量applicaiton.

5 小结

匿名函数,也称为拉姆达函数,是一种使用JavaScript函数的强大方式,以下总结了匿名函数的特点:

a, any function expression is technically an anonymous function, because there is no certain way to reference them;

b, in the case where it is impossible to determine how to reference the function, the recursive function becomes It's more complicated;

c, recursive functions should always use arguments.callee to call themselves recursively, and don't use function names - function names can change;

When inside a function Closures are created when other functions are defined. The closure has access to all variables inside the containing function. The principle is as follows:

a. In the background execution environment, the scope chain of the closure includes its own scope, the scope of the containing function and the global scope. Scope;

b, Normally, the scope of a function and all its variables will be destroyed after the function execution ends;

e, but when the function returns a closure, The scope of this function will be saved in memory until the closure no longer exists. Using closures can imitate block-level scope in JavaScript (JavaScript itself has no concept of block-level scope). The key points are as follows:

a, create and call a function immediately, so that the code in it can be executed without leaving a reference to the function in the memory;

b, the result is that all variables inside the function will be Destroyed immediately - unless these variables are assigned to variables in the containing scope (i.e. the outer scope).

Closures can also be used to create private variables in objects. The relevant concepts and key points are as follows:

a. Even though there is no formal concept of private object properties in JavaScript, closures can be used To implement public methods, variables defined in the containing scope can be accessed through public methods;

b, public methods that have access to private variables are called privileged methods;

c, can be used Constructor mode and prototype mode are used to implement privileged methods of custom types, and module mode and enhanced module mode can also be used to implement singleton privileged methods;

Anonymous functions in JavaScript and closures are very useful features that can be used to achieve a lot of functions, however, because creating closures must maintain additional scopes, overuse of them can take up a lot of memory.

The above is the detailed content of Detailed introduction to JavaScript anonymous functions. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!