J'ai récemment appris les fonctions JavaScript. Les fonctions sont les objets de première classe de JavaScript. Si vous voulez bien apprendre JavaScript, vous devez avoir une compréhension approfondie des fonctions. J'ai organisé le processus d'apprentissage en articles, d'une part, pour approfondir ma compréhension de mes propres fonctions, et d'autre part, pour proposer aux lecteurs des moyens d'apprendre et d'éviter les détours. Il y a beaucoup de contenu, mais c'est le résumé des fonctions de l'auteur.
1. Paramètres de fonction
1.1 : Que sont les paramètres
1.2 : Omission des paramètres
1.3 : Valeurs des paramètres par défaut
1.4 : Méthode de passage des paramètres
1.5 : Paramètres de même nom
1.6 : Objet arguments
2. Fermeture
2.1 : Définition de la fermeture
2.2 : Expression de fonction immédiatement invoquée (IIFE, Expression de fonction immédiatement invoquée)
1. Paramètres de fonction
1.1 : Quels sont les paramètres
Lors de la définition d'une fonction. , Parfois, il est nécessaire de transmettre des données supplémentaires à une fonction. Différentes données externes produiront des résultats différents. Ces données externes sont appelées paramètres.
function keith(a){ return a+a; } console.log(keith(3)); //6
Dans le code ci-dessus, le paramètre a est passé à la fonction Keith et l'expression a a est renvoyée.
1.2 : Omission des paramètres
Les paramètres de fonction ne sont pas obligatoires, et la spécification JavaScript permet d'omettre les paramètres réels passés lors de l'appel.
function keith(a, b, c) { return a; } console.log(keith(1, 2, 3)); //1 console.log(keith(1)); //1 console.log(keith()); // 'undefined'
Dans le code ci-dessus, la fonction Keith définit trois paramètres, mais quel que soit le nombre de paramètres transmis lors de l'appel, JavaScript ne signalera pas d'erreur. La valeur par défaut des paramètres omis devient indéfinie. Quiconque comprend les définitions des fonctions et la portée de la fonction sait que l'attribut length d'une fonction renvoie le nombre de paramètres. Il convient de noter que l'attribut length n'a rien à voir avec le nombre de paramètres réels, mais renvoie uniquement le nombre de paramètres formels.
(Paramètres réels : paramètres passés lors de l'appel. Paramètres formels : paramètres passés lors de la définition.)
Mais il n'y a aucun moyen d'omettre uniquement les éléments avant et de conserver les éléments arrière. Si l'élément avant doit être omis, seul undéfini sera affiché.
function keith(a, b) { return a; } console.log(keith(, 1)); //SyntaxError: expected expression, got ',' console.log(keith(undefined, 2)); //'undefined'
Dans le code ci-dessus, si le premier paramètre est omis, le navigateur signalera une erreur. Si undefined est passé comme premier paramètre, aucune erreur ne sera signalée.
1.3 : Valeur par défaut
En JavaScript, la valeur par défaut des paramètres de fonction n'est pas définie. Cependant, il existe des situations dans lesquelles la définition de valeurs par défaut différentes est utile. La stratégie générale consiste à tester dans le corps de la fonction si la valeur du paramètre est indéfinie, à attribuer une valeur si c'est le cas et, dans le cas contraire, à renvoyer la valeur du paramètre réellement transmis.
function keith(a, b) { (typeof b !== 'undefined') ? b = b: b = 1; return a * b; } console.log(keith(15)); //15 console.log(keith(15, 2)) //30
Dans le code ci-dessus, un jugement est rendu. Lorsque le paramètre b n'est pas transmis lors de l'appel, la valeur par défaut est 1.
À partir d'ECMAScript 6, les paramètres par défaut sont définis. Avec les paramètres par défaut, les vérifications dans le corps de la fonction ne sont plus nécessaires.
function keith(a, b = 1) { return a * b; } console.log(keith(15)); //15 console.log(keith(15, 2)) //30
1.4 : Méthode de transmission des paramètres
Il existe deux façons de transmettre les paramètres d'une fonction, l'une par valeur et l'autre par adresse.
Lorsque le paramètre de fonction est un type de données primitif (chaîne, valeur numérique, valeur booléenne), la méthode de transmission du paramètre se fait par valeur. En d'autres termes, la modification des valeurs des paramètres dans le corps de la fonction n'affectera pas l'extérieur de la fonction.
var a = 1; function keith(num) { num = 5; } keith(a); console.log(a); //1
Dans le code ci-dessus, la variable globale a est une valeur de type primitif, et la façon de transmettre la fonction Keith est par valeur. Par conséquent, à l'intérieur de la fonction, la valeur de a est une copie de la valeur d'origine, et quelle que soit la manière dont elle est modifiée, cela n'affectera pas la valeur d'origine.
Cependant, si le paramètre de fonction est une valeur de type composite (tableau, objet, autre fonction), la méthode de livraison est passée par référence. En d’autres termes, ce qui est transmis à la fonction est l’adresse de la valeur d’origine, donc la modification des paramètres à l’intérieur de la fonction affectera la valeur d’origine.
var arr = [2, 5]; function keith(Arr) { Arr[0] = 3; } keith(arr); console.log(arr[0]); //3
Dans le code ci-dessus, ce qui est passé dans la fonction Keith est l'adresse de l'objet paramètre arr. Par conséquent, modifier la première valeur de arr à l’intérieur de la fonction affectera la valeur d’origine.
Notez que si ce qui est modifié à l'intérieur de la fonction n'est pas un certain attribut de l'objet paramètre, mais remplace l'intégralité du paramètre, la valeur d'origine ne sera pas affectée.
var arr = [2, 3, 5]; function keith(Arr) { Arr = [1, 2, 3]; } keith(arr); console.log(arr); // [2,3,5]
Dans le code ci-dessus, à l'intérieur de la fonction Keith, l'objet paramètre arr est complètement remplacé par une autre valeur. La valeur originale ne sera pas affectée pour le moment. En effet, il existe une relation d'affectation entre le paramètre formel (Arr) et le paramètre réel arr.
1.5 : Paramètres du même nom
S'il existe un paramètre du même nom, la valeur qui apparaît en dernier est prise. Si la valeur du dernier paramètre n'est pas fournie, la valeur. devient indéfini.
function keith(a, a) { return a; } console.log(keith(1, 3)); //3 console.log(keith(1)); //undefined
Si vous souhaitez accéder au premier paramètre du paramètre du même nom, utilisez l'objet arguments.
function keith(a, a) { return arguments[0]; } console.log(keith(2)); //2
Objet arguments 1.6
Chaque fonction en JavaScript peut accéder à des arguments de variable spéciale. Cette variable maintient la liste de tous les arguments passés à cette fonction.
L'objet arguments contient tous les paramètres lorsque la fonction est exécutée. arguments[0] est le premier paramètre, arguments[1] est le deuxième paramètre, et ainsi de suite. Cet objet ne peut être utilisé qu'à l'intérieur du corps de la fonction.
Vous pouvez accéder à l'attribut length de l'objet arguments pour déterminer combien de paramètres sont inclus dans l'appel de fonction.
function keith(a, b, c) { console.log(arguments[0]); //1 console.log(arguments[2]); //3 console.log(arguments.length); //4 } keith(1, 2, 3, 4);
La relation entre les objets arguments et les tableaux
arguments 对象不是一个数组(Array)。 尽管在语法上它有数组相关的属性 length,但它不从 Array.prototype 继承,实际上它是一个类数组对象。因此,无法对 arguments 变量使用标准的数组方法,比如 push, pop 或者 slice。但是可以使用数组中的length属性。
通常使用如下方法把arguments对象转换为数组。
var arr = Array.prototype.slice.call(arguments);
2.闭包
2.1:闭包定义
要理解闭包,需要先理解全局作用域和局部作用域的区别。函数内部可以访问全局作用域下定义的全局变量,而函数外部却无法访问到函数内部定义(局部作用域)的局部变量。
var a = 1; function keith() { return a; var b = 2; } console.log(keith()); //1 console.log(b); //ReferenceError: b is not defined
上面代码中,全局变量a可以在函数keith内部访问。可是局部变量b却无法在函数外部访问。
如果需要得到函数内部的局部变量,只有通过在函数的内部,再定义一个函数。
function keith(){ var a=1; function rascal(){ return a; } return rascal; } var result=keith(); console.log(result()); //1 function keith(){ var a=1; return function(){ return a; }; } var result=keith(); console.log(result()) //1
上面代码中,两种写法相同,唯一的区别是内部函数是否是匿名函数。函数rascal就在函数keith内部,这时keith内部的所有局部变量,对rascal都是可见的。但是反过来就不行,rascal内部的局部变量,对keith就是不可见的。这就是JavaScript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。函数keith的返回值就是函数rascal,由于rascal可以读取keith的内部变量,所以就可以在外部获得keith的内部变量了。
闭包就是函数rascal,即能够读取其他函数内部变量的函数。由于在JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如rascal记住了它诞生的环境keith,所以从rascal可以得到keith的内部变量。
闭包可以使得它诞生环境一直存在。看下面一个例子,闭包使得内部变量记住上一次调用时的运算结果。
function keith(num) { return function() { return num++; }; } var result = keith(2); console.log(result()) //2 console.log(result()) //3 console.log(result()) //4
上面代码中,参数num其实就相当于函数keith内部定义的局部变量。通过闭包,num的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包result使得函数keith的内部环境,一直存在。
通过以上的例子,总结一下闭包的特点:
1:在一个函数内部定义另外一个函数,并且返回内部函数或者立即执行内部函数。
2:内部函数可以读取外部函数定义的局部变量
3:让局部变量始终保存在内存中。也就是说,闭包可以使得它诞生环境一直存在。
闭包的另一个用处,是封装对象的私有属性和私有方法。
function Keith(name) { var age; function setAge(n) { age = n; } function getAge() { return age; } return { name: name, setAge: setAge, getAge: getAge }; } var person = Keith('keith'); person.setAge(21); console.log(person.name); // 'keith' console.log(person.getAge()); //21
2.2:立即调用的函数表达式(IIFE)
通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
循环中的闭包
一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号
for(var i=0;i<10;i++){ setTimeout(function(){ console.log(i); //10 }, 1000) }
上面代码中,不会符合我们的预期,输出数字0-9。而是会输出数字10十次。
当匿名函数被调用的时候,匿名函数保持着对全局变量 i 的引用,也就是说会记住i循环时执行的结果。此时for循环结束,i 的值被修改成了10。
为了得到想要的效果,避免引用错误,我们应该使用IIFE来在每次循环中创建全局变量 i 的拷贝。
for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); //1,2,3,....,10 }, 1000); })(i); }
外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。