Heim > Web-Frontend > js-Tutorial > Detaillierte Einführung in anonyme JavaScript-Funktionen

Detaillierte Einführung in anonyme JavaScript-Funktionen

高洛峰
Freigeben: 2017-03-19 16:10:09
Original
2102 Leute haben es durchsucht

AnonymFunktion ist eine Funktion ohne Namen, manchmal auch Lambda-Funktion genannt. Anonyme Funktionen sind ein unglaublich leistungsfähiges Werkzeug mit so vielen Verwendungsmöglichkeiten. Schauen wir uns diese typische Funktionsdeklaration an:

function functionName(arg0, arg1, arg2) {
    //函数体
}
Nach dem Login kopieren

Sie können eine Funktion wie oben deklarieren , Sie können es auch Definieren Sie eine Funktion in Form eines Funktionsausdrucks wie folgt:

var functionName = function(arg0, arg1, arg2) {
    //函数体
};
Nach dem Login kopieren
Obwohl diese beiden Beispiele logisch äquivalent sind, gibt es dennoch einige Unterschiede zwischen ihnen. Der Hauptunterschied zwischen Funktionsdeklarationen und Funktionsausdrücken besteht natürlich darin, dass erstere in den Gültigkeitsbereich geladen werden, bevor der Code ausgeführt wird, während letztere erst definiert werden, in welcher Zeile der Code ausgeführt wird. Ein weiterer wichtiger Unterschied besteht darin, dass eine Funktionsdeklaration der Funktion einen Namen zuweist, während ein Funktionsausdruck eine anonyme Funktion erstellt und die anonyme Funktion einer

-Variable zuweist. Mit anderen Worten: Das zweite Beispiel oben erstellt eine anonyme Funktion mit drei Parametern und weist die anonyme Funktion dann der Variablen functionName zu, gibt jedoch keinen Namen für die anonyme Funktion an.

Es ist auch möglich, eine anonyme Funktion wie die folgende zu schreiben:

function(arg0, arg1, arg2) {
    //函数体
}
Nach dem Login kopieren
Dieser Code ist vollständig gültig, aber das Problem ist, dass niemand diese Funktion aufrufen kann, weil es keinen Zeiger gibt zu dieser Funktion. Allerdings werden anonyme Funktionen normalerweise in dieser Form definiert, wenn eine Funktion als Parameter an eine andere Funktion übergeben wird oder eine Funktion von einer Funktion an eine andere zurückgegeben wird. Das Folgende ist ein Beispiel für eine verwendete createComparisonFunction()-Funktion:

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;
        }
    };
}
Nach dem Login kopieren
createComparisonFunction() gibt eine anonyme Funktion zurück. Die zurückgegebene Funktion kann jedoch einer Variablen zugewiesen oder auf andere Weise aufgerufen werden Innerhalb der Funktion createComparisonFunction() ist sie anonym, und anonyme Funktionen können verwendet werden, wenn die Funktion als Wert betrachtet wird. Dies ist jedoch nicht die einzige Nutzung anonymer Funktionen.


1

Rekursiv

Eine rekursive Funktion wird gebildet, wenn eine Funktion sich selbst beim Namen aufruft, wie unten gezeigt:

function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * factorial(num - 1);
    }
}
Nach dem Login kopieren
Dies ist eine klassische rekursive Fakultätsfunktion. Obwohl an der Oberfläche nichts auszusetzen ist, kann der folgende Code dazu führen, dass sie schief geht:

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //VM848:5 Uncaught TypeError: factorial is not a function(…)
Nach dem Login kopieren
Der obige Code ändert zunächst die Fakultät Die ()-Funktion wird in der Variablen anotherFactory gespeichert und dann wird die Fakultätsvariable auf null gesetzt. Dadurch gibt es nur eine

-Referenz , die auf die ursprüngliche Funktion verweist. Wenn jedoch anotherFacttorial() als nächstes aufgerufen wird, tritt ein Fehler auf, da Factorial() ausgeführt werden muss und Factorial keine Funktion mehr ist. In diesem Fall kann die Verwendung von arguments.callee das Problem lösen:

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

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //24
Nach dem Login kopieren
Durch die Verwendung von arguments.callee anstelle des Funktionsnamens wird der Code mit gelbem Hintergrund angezeigt. Sie können sicherstellen, dass unabhängig davon, wie Sie die Funktion aufrufen, kein Problem auftritt. Daher ist es beim Schreiben rekursiver Funktionen sicherer, arguments.callee zu verwenden, als den Funktionsnamen

zu verwenden.

2 Abschlüsse

Viele Entwickler sind immer verwirrt über die Konzepte anonymer Funktionen und Abschlüsse und verwenden sie daher oft synonym. Ein Abschluss ist eine Funktion, die Zugriff auf eine Variable im Gültigkeitsbereich einer anderen

-Funktion

hat. Eine übliche Methode zum Erstellen eines Abschlusses besteht darin, eine weitere Funktion innerhalb einer Funktion zu erstellen. Achten Sie am Beispiel der vorherigen Funktion createComparisonFunction() auf den Code mit gelbem Hintergrund:

In diesem Beispiel: hervorheben Die beiden Codezeilen sind der Code in der
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;
        }
    };
}
Nach dem Login kopieren
internen Funktion

(eine anonyme Funktion). Diese beiden Codezeilen greifen auf die Variable propertyName in der externen Funktion zu, auch wenn die interne Funktion zurückgegeben und an anderer Stelle verwendet wird aufgerufen wird, aber weiterhin Zugriff auf die Variable propertyName hat. Der Grund, warum auf diese Variable weiterhin zugegriffen werden kann, liegt darin, dass die Bereichskette der inneren Funktion den Bereich von createComparisonFunction() enthält. Um die Details vollständig zu verstehen, müssen Sie zunächst verstehen, was passiert, wenn eine Funktion zum ersten Mal aufgerufen wird.

有关如何创建作用域链以及作用域链有什么作用的细节,对彻底理解闭包至关重要。当某个函数第一次被调用时,会创建一个执行环境(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
Nach dem Login kopieren

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

Detaillierte Einführung in anonyme JavaScript-Funktionen

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

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

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

Detaillierte Einführung in anonyme JavaScript-Funktionen

var compare = createComparisonFunction(&#39;name&#39;);
var result = compare({ name: &#39;Nicholas&#39; }, { name: &#39;Greg&#39; });
Nach dem Login kopieren


在匿名函数从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;
Nach dem Login kopieren

首先创建的比较函数被保存在变量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;);
}
Nach dem Login kopieren


这个函数会返回一个函数数组,表面上看,似乎每个函数都应该返回自己的索引值,即位置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;)
}
Nach dem Login kopieren

在重写了前面的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"
Nach dem Login kopieren


以上代码先创建了一个全局变量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"
Nach dem Login kopieren


代码中突出的行展示了这个例子与前一个例子之间的不同之处,在定义匿名函数之前,我们把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);
    };
}
Nach dem Login kopieren

以上代码创建了一个作为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;
}
Nach dem Login kopieren


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


3. 模仿块级作用域

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

function outputNumbers(count) {
    for (var i = 0; i < count; i++) {
        alert(i);
    }
    alert(i); //count
}
Nach dem Login kopieren


这个函数中定义了一个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
}
Nach dem Login kopieren


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

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


(function() {
    //这里是块级作用域
})();
Nach dem Login kopieren


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


var count = 5;
outputNumbers(count);
Nach dem Login kopieren


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


outputNumbers(5);
Nach dem Login kopieren


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


var someFunction = function() {
    //这里是块级作用域};
someFunction();
Nach dem Login kopieren


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


function() { 
   //这里是块级作用域}(); //出错
Nach dem Login kopieren


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


(function() { 
   //这里是块级作用域})();
Nach dem Login kopieren


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


function outputNumbers(count) {

    (function() { 
           for (var i = 0; i < count; i++)
            {
            alert(i);
        }
    })();
    alert(i); //导致一个错误}
Nach dem Login kopieren


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

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


(function() {
    var now = new Date();
        if (now.getMonth() == 0 && now.getDate() == 1)
         {
        alert(&#39;Happy new year!&#39;);
    }
})();
Nach dem Login kopieren


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


4 私有变量

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


function add(num1, num2) 
{  
  var sum = num1 + num2; 
     return sum;
}
Nach dem Login kopieren


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

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


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


这个模式在构造函数内部定义了所有私有变量和函数。然后,又继续创建了能够访问这些私有成员的特权方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。对这个例子而言,变量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"
Nach dem Login kopieren


以上代码的构造函数中定义了两个特权方法: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;
    };
})();
Nach dem Login kopieren


这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中,首先定义了私有变量和私有函数,然后又定义了构造函数及其公有方法。公有方法是在原型上定义的,这一点体现了典型的原型模式。需要注意的是,这个模式在定义构造函数模式时并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。出于同样的原因,我们也没有在声明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"
Nach dem Login kopieren

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

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

4.2 模块模式

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

var singleton = {
    name: value,
    method: function() {
        //这里是方法的代码
    }
};
Nach dem Login kopieren


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

var privateVariable = function() {

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

    function privateFunction() {
        return false;
    }

    //特权/共有方法和属性
    return {
        publicProperty: true;
        publicMethod: function() {
            privateVariable++;
            return privateFunction();
        }
    };
}();
Nach dem Login kopieren


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

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
Nach dem Login kopieren


在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;
}();
Nach dem Login kopieren


如果前面演示模块模式的例子中的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;
}();
Nach dem Login kopieren


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

5 小结

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

a, jeder Funktionsausdruck ist technisch gesehen eine anonyme Funktion, da es keine eindeutige Möglichkeit gibt, auf sie zu verweisen

b, wenn es unmöglich ist, zu bestimmen, wie auf die Funktion verwiesen werden soll, wird die rekursive Funktion zu „Es ist“. komplizierter;

c, rekursive Funktionen sollten immer arguments.callee verwenden, um sich selbst rekursiv aufzurufen, verwenden Sie keine Funktionsnamen – Funktionsnamen können sich ändern

Wenn innerhalb einer Funktion Abschlüsse erstellt werden, wenn andere Funktionen definiert sind. Der Abschluss hat Zugriff auf alle Variablen innerhalb der enthaltenden Funktion. Das Prinzip ist wie folgt:

a. In der Hintergrundausführungsumgebung umfasst die Bereichskette des Abschlusses seinen eigenen Bereich, den Bereich der enthaltenden Funktion und der globale Geltungsbereich;

b, normalerweise werden der Geltungsbereich einer Funktion und alle ihre Variablen zerstört, nachdem die Funktionsausführung endet

e, aber wenn die Funktion einen Abschluss zurückgibt, Der Umfang dieser Funktion wird im Speicher gespeichert, bis der Abschluss nicht mehr vorhanden ist. Durch die Verwendung von Verschlüssen kann der Umfang auf Blockebene in JavaScript imitiert werden (in JavaScript selbst gibt es kein Konzept für den Umfang auf Blockebene).

a, erstellen und rufen Sie sofort eine Funktion auf, sodass der darin enthaltene Code ausgeführt werden kann, ohne einen Verweis auf die Funktion im Speicher zu hinterlassen

b, das Ergebnis ist, dass alle Variablen innerhalb der Funktion ausgeführt werden werden sofort zerstört – es sei denn, diese Variablen werden Variablen im enthaltenden Bereich (d. h. dem äußeren Bereich) zugewiesen.

Abschlüsse können auch zum Erstellen privater Variablen in Objekten verwendet werden. Die relevanten Konzepte und Punkte sind wie folgt:

a. Auch wenn es in JavaScript kein formales Konzept für private Objekteigenschaften gibt kann verwendet werden, um öffentliche Methoden zu implementieren. Auf Variablen, die im enthaltenden Bereich definiert sind, kann über öffentliche Methoden zugegriffen werden.

b. Öffentliche Methoden, die Zugriff auf private Variablen haben, werden als privilegierte Methoden bezeichnet. Der Konstruktormodus und der Prototypmodus können zum Implementieren privilegierter Methoden benutzerdefinierter Typen verwendet werden. Der Modulmodus und der erweiterte Modulmodus können auch zum Implementieren privilegierter Singleton-Methoden verwendet werden

Anonyme Funktionen in JavaScript

und Abschlüsse sind sehr nützliche Funktionen, mit denen viele Funktionen erreicht werden können. Da beim Erstellen von Abschlüssen jedoch zusätzliche Bereiche beibehalten werden müssen, kann eine übermäßige Verwendung dieser Funktionen viel Speicher beanspruchen.

Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in anonyme JavaScript-Funktionen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage