Maison > interface Web > js tutoriel > le corps du texte

Introduction détaillée aux fonctions anonymes JavaScript

高洛峰
Libérer: 2017-03-19 16:10:09
original
2073 Les gens l'ont consulté

AnonymeFonction est une fonction sans nom, parfois aussi appelée fonction lambda. Les fonctions anonymes sont un outil incroyablement puissant avec de nombreuses utilisations. Jetons un coup d'œil à la déclaration de fonction typique suivante :

function functionName(arg0, arg1, arg2) {
    //函数体
}
Copier après la connexion

Vous pouvez soit déclarer une fonction comme celle ci-dessus , vous pouvez soit déclarer une fonction comme celle ci-dessus . définissez également une fonction sous la forme d'expression de fonction

comme suit :
var functionName = function(arg0, arg1, arg2) {
    //函数体
};
Copier après la connexion

Bien que ces deux exemples soient logiquement équivalents, il existe quand même quelques différences entre eux. Bien entendu, la principale différence entre les déclarations de fonction et les expressions de fonction est que les premières seront chargées dans la portée avant l'exécution du code, tandis que les secondes ne seront définies qu'à quelle ligne le code est exécuté. Une autre différence importante est qu'une déclaration de fonction attribue un nom à la fonction, tandis qu'une expression de fonction crée une fonction anonyme et attribue la fonction anonyme à une variable

. En d'autres termes, le deuxième exemple ci-dessus crée une fonction anonyme avec trois paramètres, puis affecte la fonction anonyme à la variable functionName, mais ne spécifie pas de nom pour la fonction anonyme.

Il est également possible d'écrire une fonction anonyme comme celle-ci :
function(arg0, arg1, arg2) {
    //函数体
}
Copier après la connexion

Ce code est tout à fait valide, mais le problème est que personne ne peut appeler cette fonction car il n'y a pas de pointeur vers cette fonction. Cependant, les fonctions anonymes sont généralement définies sous cette forme lors du passage d'une fonction en tant que paramètre dans une autre fonction ou du renvoi d'une fonction d'une fonction à une autre. Voici un exemple de fonction createComparisonFunction() qui a été utilisée :
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;
        }
    };
}
Copier après la connexion

createComparisonFunction() renvoie une fonction anonyme. La fonction renvoyée peut toutefois être affectée à une variable ou appelée d'une autre manière. , à l'intérieur de la fonction createComparisonFunction(), elle est anonyme et des fonctions anonymes peuvent être utilisées lorsque la fonction est considérée comme une valeur. Cependant, ce n’est pas la seule utilisation des fonctions anonymes.

1 Récursive

Une fonction récursive

se forme lorsqu'une fonction s'appelle par son nom, de la manière suivante :
function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * factorial(num - 1);
    }
}
Copier après la connexion

Il s'agit d'une fonction factorielle récursive classique. Bien qu'il n'y ait rien de mal avec cette fonction en surface, le code suivant peut provoquer un dysfonctionnement :
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //VM848:5 Uncaught TypeError: factorial is not a function(…)
Copier après la connexion

Le code ci-dessus change d'abord la factorielle. () est enregistrée dans la variable anotherFactory, puis la variable factorielle est définie sur null. Par conséquent, il n'y a qu'une seule référence pointant vers la fonction d'origine. Mais lorsque anotherFacttorial() est appelé ensuite, parce que factorial() doit être exécuté et que factorial n'est plus une fonction, une erreur se produira. Dans ce cas, utiliser arguments.call

ee peut résoudre le problème :
function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * arguments.callee(num - 1);
    }
}

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //24
Copier après la connexion

Le code avec un fond jaune est affiché En utilisant arguments.callee à la place du nom de la fonction, vous pouvez vous assurer que peu importe la manière dont vous appelez la fonction, il n'y aura aucun problème. Par conséquent, lors de l'écriture de fonctions récursives, il est plus sûr d'utiliser arguments.callee que d'utiliser le nom de la fonction .


2 Fermetures

De nombreux développeurs sont toujours confus au sujet des deux concepts de fonctions anonymes et de fermetures, ils les utilisent donc souvent de manière interchangeable. Une fermeture est une fonction qui a accès à une variable dans le cadre d'une autre fonction . Une façon courante de créer une fermeture consiste à créer une autre fonction à l'intérieur d'une fonction. En prenant la fonction createComparisonFunction() précédente comme exemple, faites attention au code avec un fond jaune :

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;
        }
    };
}
Copier après la connexion

Dans cet exemple, highlight Les deux lignes de code sont le code de la fonction interne (une fonction anonyme). Ces deux lignes de code accèdent à la variable propertyName dans la fonction externe, même si la fonction interne est renvoyée et utilisée ailleurs. est appelé, mais il a toujours accès à la variable propertyName. La raison pour laquelle cette variable est toujours accessible est que la chaîne de portée de la fonction interne inclut la portée de createComparisonFunction(). Pour bien comprendre les détails, vous devez commencer par comprendre ce qui se passe lorsqu’une fonction est appelée pour la première fois.

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

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

Introduction détaillée aux fonctions anonymes JavaScript

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

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

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

Introduction détaillée aux fonctions anonymes JavaScript

var compare = createComparisonFunction(&#39;name&#39;);
var result = compare({ name: &#39;Nicholas&#39; }, { name: &#39;Greg&#39; });
Copier après la connexion


在匿名函数从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;
Copier après la connexion

首先创建的比较函数被保存在变量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;);
}
Copier après la connexion


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

在重写了前面的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"
Copier après la connexion


以上代码先创建了一个全局变量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"
Copier après la connexion


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

以上代码创建了一个作为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;
}
Copier après la connexion


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


3. 模仿块级作用域

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

function outputNumbers(count) {
    for (var i = 0; i < count; i++) {
        alert(i);
    }
    alert(i); //count
}
Copier après la connexion


这个函数中定义了一个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
}
Copier après la connexion


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

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


(function() {
    //这里是块级作用域
})();
Copier après la connexion


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


var count = 5;
outputNumbers(count);
Copier après la connexion


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


outputNumbers(5);
Copier après la connexion


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


var someFunction = function() {
    //这里是块级作用域};
someFunction();
Copier après la connexion


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


function() { 
   //这里是块级作用域}(); //出错
Copier après la connexion


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


(function() { 
   //这里是块级作用域})();
Copier après la connexion


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


function outputNumbers(count) {

    (function() { 
           for (var i = 0; i < count; i++)
            {
            alert(i);
        }
    })();
    alert(i); //导致一个错误}
Copier après la connexion


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

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


(function() {
    var now = new Date();
        if (now.getMonth() == 0 && now.getDate() == 1)
         {
        alert(&#39;Happy new year!&#39;);
    }
})();
Copier après la connexion


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


4 私有变量

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


function add(num1, num2) 
{  
  var sum = num1 + num2; 
     return sum;
}
Copier après la connexion


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

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


function MyObject() { 
   //私有变量和私有函数
    var privateVariable = 10;
        function privateFunction()
  {
        return false;
    }   
     //特权方法
    this.publicMethod = function()
     {
        privateVariable++;
        return privateFunction();
    };
}
Copier après la connexion


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


以上代码的构造函数中定义了两个特权方法: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;
    };
})();
Copier après la connexion


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

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

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

4.2 模块模式

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

var singleton = {
    name: value,
    method: function() {
        //这里是方法的代码
    }
};
Copier après la connexion


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

var privateVariable = function() {

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

    function privateFunction() {
        return false;
    }

    //特权/共有方法和属性
    return {
        publicProperty: true;
        publicMethod: function() {
            privateVariable++;
            return privateFunction();
        }
    };
}();
Copier après la connexion


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

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
Copier après la connexion


在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;
}();
Copier après la connexion


如果前面演示模块模式的例子中的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;
}();
Copier après la connexion


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

5 小结

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

a, toute expression de fonction est techniquement une fonction anonyme, car il n'y a pas de moyen défini de les référencer

b, lorsqu'il est impossible de déterminer comment référencer la fonction, la fonction récursive devient C'est le cas ; plus compliqué ;

c, les fonctions récursives doivent toujours utiliser arguments.callee pour s'appeler de manière récursive, n'utilisez pas de noms de fonction - les noms de fonction peuvent changer

À l'intérieur d'une fonction Lorsque d'autres fonctions sont définies ; , des fermetures sont créées. La fermeture a accès à toutes les variables à l'intérieur de la fonction contenant. Le principe est le suivant :

a. Dans l'environnement d'exécution en arrière-plan, la chaîne de portée de la fermeture comprend sa propre portée, la portée de la fonction contenant et. la portée globale.

b, généralement, la portée d'une fonction et toutes ses variables seront détruites après la fin de l'exécution de la fonction

e, mais lorsque la fonction renvoie une fermeture, La portée de cette fonction sera enregistrée en mémoire jusqu'à ce que la fermeture n'existe plus. L'utilisation de fermetures peut imiter la portée au niveau du bloc en JavaScript (JavaScript lui-même n'a pas de concept de portée au niveau du bloc). Les points clés sont les suivants :

.

a, créez et appelez une fonction immédiatement, afin que le code qu'elle contient puisse être exécuté sans laisser de référence à la fonction dans la mémoire

b, le résultat est que toutes les variables à l'intérieur de la fonction le seront ; être détruit immédiatement - à moins que ces variables ne soient affectées à des variables dans la portée conteneur (c'est-à-dire la portée externe).

Les fermetures peuvent également être utilisées pour créer des variables privées dans des objets. Les concepts et points pertinents sont les suivants :

a Même s'il n'y a pas de concept formel de propriétés d'objet privé en JavaScript, les fermetures. peut être utilisé Pour implémenter des méthodes publiques, les variables définies dans la portée contenant sont accessibles via des méthodes publiques

b, les méthodes publiques qui ont accès aux variables privées sont appelées méthodes privilégiées ; peut être utilisé Le mode constructeur et le mode prototype sont utilisés pour implémenter des méthodes privilégiées de types personnalisés, et le mode module et le mode module amélioré peuvent également être utilisés pour implémenter des méthodes privilégiées singleton

Fonctions anonymes en JavaScript et les fermetures sont des fonctionnalités très utiles qui peuvent être utilisées pour réaliser de nombreuses fonctions. Cependant, comme la création de fermetures doit conserver des étendues supplémentaires, leur surutilisation peut prendre beaucoup de mémoire.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!