Heim >Web-Frontend >js-Tutorial >Funktionsausdrücke, Rekursions- und Abschlussfunktionen in Javascript, fortgeschrittene Programmierkenntnisse in Javascript

Funktionsausdrücke, Rekursions- und Abschlussfunktionen in Javascript, fortgeschrittene Programmierkenntnisse in Javascript

WBOY
WBOYOriginal
2016-05-16 15:28:591249Durchsuche

Es gibt zwei Möglichkeiten, Funktionsausdrücke zu definieren: Funktionsdeklaration und Funktionsausdruck.

Die Funktion wird wie folgt deklariert:

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

Zuerst ist das Funktionsschlüsselwort, dann der Name der Funktion.

FF, Safrai, Chrome und Opera definieren alle ein nicht standardmäßiges Namensattribut für Funktionen, über das auf den von der Funktion angegebenen Namen zugegriffen werden kann. Der Wert dieser Funktion entspricht immer dem Bezeichner nach dem Funktionsschlüsselwort.

//只在FF,Safari,Chrome和Opera有效
alert(functionName.name)//functionName

Das Merkmal der Funktionsdeklaration ist das Hochziehen der Funktionsdeklaration. Dies bedeutet, dass die Funktionsdeklaration vor der Ausführung des Codes gelesen wird. Dies bedeutet, dass die Funktionsdeklaration nach der Anweisung platziert werden kann, die sie aufruft.

sayHi();
function sayHi(){
 alert("Hi!");
}

In diesem Beispiel wird kein Fehler ausgegeben, da die Funktionsdeklaration gelesen wird, bevor der Code ausgeführt wird.

Der zweite Typ ist Funktionsausdruck.

var functionName=function(arg0,arg0,arg2){
 //函数体
}

Dieses Formular sieht aus wie eine reguläre Variablenzuweisungsanweisung, das heißt, eine Funktion zu erstellen und sie der Variablen functionName zuzuweisen. Die in diesem Fall erstellte Funktion wird als anonyme Funktion bezeichnet, da nach dem Funktionsschlüsselwortsymbol ( Anonyme Funktionen werden manchmal auch Lambda-Funktionen genannt.) Das Namensattribut der anonymen Funktion ist eine leere Zeichenfolge

Funktionsausdrücken muss wie anderen Ausdrücken vor der Verwendung ein Wert zugewiesen werden.

Der folgende Code verursacht einen Fehler:

syaHi();//Uncaught ReferenceError: syaHi is not defined
var sayHi=function(){
 alert("Hi!");
}

Schreiben Sie keinen Code wie den folgenden. Die JavaScript-Engine wird versuchen, den Fehler zu korrigieren, aber verschiedene Browser werden ihn unterschiedlich ändern.

//不要这样做
if(condition){
 function sayHi(){
  alert("Hi!");
 }
}else{
 function sayHi(){
  alert("Yo!");
 }
}

Wenn Sie Funktionsausdrücke verwenden, gibt es kein Problem.

//可以这样做
var sayHi;
if(condition){
 sayHi=function(){
  alert("Hi!");
 }
}else{
 sayHi=function(){
  alert("Yo!");
 }
}

Sie können eine Funktion erstellen und einer Variablen zuweisen, und Sie können die Funktion auch als Wert anderer Funktionen zurückgeben.

function creatComparisonFunction(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;
  }
 };
}

creatComparisonFunction() gibt eine anonyme Funktion zurück. Die zurückgegebene Funktion kann jedoch innerhalb der Funktion creatComparison() anonym sein. Sie können anonyme Funktionen verwenden.

7.1 Rekursion

Eine rekursive Funktion wird erstellt, wenn eine Funktion sich selbst über ihren Namen aufruft.

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

Das Obige ist eine klassische rekursive Fakultätsfunktion. Der folgende Code kann dazu führen, dass es schief geht.

var anotherFactorial=factorial;
factorial=null;
alert(anotherFactorial(4));//Uncaught TypeError: factorial is not a function

Der obige Code speichert zunächst die Funktion „Factorial()“ in der Variablen „AnotherFactorial“ und setzt dann die Variable „Factorial“ auf Null. Daher bleibt beim nächsten Aufruf von „AnotherFactorial()“ nur noch eine ursprüngliche Referenz übrig. ) muss ausgeführt werden, und Factorial() ist keine Funktion mehr und verursacht daher einen Fehler.

In diesem Fall kann die Verwendung von arguments.callee das Problem lösen.

arguments.callee ist ein Zeiger auf die ausgeführte Funktion und kann daher zum Implementieren rekursiver Aufrufe der Funktion verwendet werden.

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

Beim Schreiben rekursiver Funktionen ist die Verwendung von arguments.callee immer sicherer als die Verwendung von Funktionsnamen, da dadurch sichergestellt wird, dass es keine Probleme gibt, egal wie die Funktion aufgerufen wird.

Aber im strikten Modus kann auf arguments.callee nicht über Skripte zugegriffen werden.

Sie können jedoch Funktionsausdrücke verwenden, um das gleiche Ergebnis zu erzielen.

var factorial=(function f(num){
 if(num<=1){
  return 1;
 }else{
  return num*f(num-1);
 }
});
console.log(factorial(4));//24

7.2 Schließung

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 andere Funktion innerhalb einer Funktion zu erstellen.

function creatComparisonFunction(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;
  }
 };
}

Die beiden Codezeilen in Fettschrift sind der Code in der internen Funktion (einer anonymen Funktion). Diese beiden Codezeilen greifen auf die Variable propertyName in der externen Funktion zu, auch wenn die interne Funktion an anderer Stelle zurückgegeben und aufgerufen wird kann weiterhin auf die Variable propertyName zugreifen, weil die Bereichskette der internen Funktion den Bereich von creatComparisonFunction() enthält.

Wenn eine Funktion aufgerufen wird, werden ein Ausführungskontext und eine entsprechende Bereichskette erstellt. Anschließend wird das Aktivierungsobjekt der Funktion mithilfe der Werte von Argumenten und anderen benannten Parametern initialisiert Das Objekt der externen Funktion befindet sich immer an zweiter Stelle, und das aktive Objekt der externen Funktion der externen Funktion befindet sich an dritter Stelle ... bis zur globalen Ausführungsumgebung als Endpunkt der Bereichskette.

Um während der Funktionsausführung den Wert einer Variablen lesen und schreiben zu können, müssen Sie die Variable in der Bereichskette finden.

function compare(value1,value2){
  if(value1<value2){
   return -1;
  }else if(value1>value2){
   return 1;
  }else{
   return 0;
  }
 }
 var result=compare(5,10)
 console.log(result)//-1

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

后台的每个执行环境都有一个表示变量的对象--变量对象.全局环境的变量对象始终存在,而像compare()函数这样的局部环境的变量对象,则只在函数执行的过程中存在.在创建compare()函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中.当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链.此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端.

作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象.

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

在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域中.

var compare=creatComparisonFunction("name");
var result=comapre({name:"Nicholas"},{name:"Greg"});

下图展示了上面代码代码执行时,包含函数与内部匿名函数的作用域.


当createComparisonFunction()函数返回后,其执行环境的作用域会被销毁,但它的活动对象仍然会留在内存中;直到匿名函数被销毁后,createComparisonFunction()的活动对象都会被销毁.

//创建函数
 var compare=creatComparisonFunction("name");
 //调用函数
 var result=comapre({name:"Nicholas"},{name:"Greg"});
 //解除对匿名函数的引用(以便释放内存)
 compareNames=null;

通过将compareNames设置为等于null解除该函数的引用,就等于通知垃圾回收例程将其清除.随着匿名函数函数的作用域链被销毁,其他作用域(除了全局作用域)也都可以安全地销毁了.

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存.过度使用闭包可能会导致内存占用过多,慎重使用闭包.

7.2.1 闭包和变量

作用域链的这种配置的机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值.

闭包里所保存的是整个变量对象,而不是某个特殊的变量.

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

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

上面代码里这个函数会返回一个函数数组.表面上看,似乎每个函数都应该返回自己的索引值,但实际上,每个函数都返回10.因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i.当createFunction()函数返回后,变量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;
}

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

7.2.2 关于this对象

this对象是在运行时基本函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象.不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window.

var name="the window";
var object={
 name:"my object",

 getNameFunc:function(){
  return function(){
   return this.name;
  };
 }
};
alert(object.getNameFunc()());//the window(在非严格模式下)

每个函数在被调用时都会自动取得两个特殊变量:this和arguments.内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量.

不过,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了.

var name="the window";
var object={
 name:"my object",

 getNameFunc:function(){
  var that=this;
  return function(){
   return that.name;
  };
 }
};
alert(object.getNameFunc()());//my object

在定义匿名函数之前,我们把this对象赋值给了一个名叫that的变量.而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声明的一个变量.即使在函数返回之后,that也仍然引用着object,所以调用object.getNameFunc()()就返回了my object.

注意:this和arguments也存在同样的问题.如果想访问作用域中的arguments对象,必须将对该对象的引用保存到另一个闭包能够访问的变量中.

var name="the window";

var object={
 name:"my object",

 getName:function(){
  return this.name;
 }
};
console.log(object.getName());//my object
console.log((object.getName)());//my object
console.log((object.getName=object.getName)());//the window

最后一行代码先执行了一条赋值语句,然后再调用赋值后的结果.因为这个赋值表达式的值是函数本身,所以this的值不能得到维持,结果就返回了"this window".

7.2.3 内存泄露

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

function assignHandlet(){
 var element=document.getElementById("someElement");
 element.onclick=function(){
  alert(element.id);
 };
}

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

function assignHandlet(){
 var element=document.getElementById("someElement");
 var id=element.id;

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

Im obigen Code wird der Zirkelverweis eliminiert, indem eine Kopie von element.id in einer Variablen gespeichert und im Abschluss auf die Variable verwiesen wird.

Script Home möchte alle daran erinnern: Der Abschluss verweist auf das gesamte aktive Objekt, das die Funktion enthält, das Element enthält. Auch wenn der Abschluss nicht direkt auf das Element verweist, speichert das aktive Objekt, das die Funktion enthält, dennoch eine Referenz , es ist notwendig, die Elementvariable auf null zu setzen. Auf diese Weise kann die Referenz auf das DOM-Objekt freigegeben, die Anzahl der Referenzen erfolgreich reduziert und der von ihm belegte Speicher ordnungsgemäß recycelt werden >

Lassen Sie mich Ihnen Funktionsausdrücke vorstellen.

Bei der JavaScript-Programmierung sind Funktionsausdrücke eine sehr nützliche Technik. Durch die Verwendung von Funktionsausdrücken entfällt die Notwendigkeit, Funktionen zu benennen, wodurch eine dynamische Programmierung ermöglicht wird. Anonyme Funktionen, auch Lambda-Funktionen genannt, sind eine leistungsstarke Möglichkeit, JavaScript-Funktionen zu verwenden. Im Folgenden werden die Merkmale von Funktionsausdrücken zusammengefasst.


Funktionsausdrücke unterscheiden sich von Funktionsdeklarationen. Funktionsdeklarationen erfordern Namen, Funktionsausdrücke jedoch nicht. Funktionsausdrücke ohne Namen werden auch anonyme Funktionen genannt.


Rekursive Funktionen werden komplizierter, wenn Sie nicht sicher sind, wie Sie auf die Funktion verweisen sollen. Rekursive Funktionen sollten immer arguments.callee verwenden, um sich selbst rekursiv aufzurufen, und niemals den Funktionsnamen verwenden – der Funktionsname kann sich ändern.


Ein Abschluss wird erstellt, wenn andere Funktionen innerhalb einer Funktion definiert werden. Abschlüsse haben Zugriff auf alle Variablen innerhalb der enthaltenden Funktion, Prinzip

wie folgt.

In der Hintergrundausführungsumgebung umfasst die Bereichskette des Abschlusses seinen eigenen Bereich, den Bereich der enthaltenden Funktion und den globalen Bereich. Normalerweise werden der Umfang einer Funktion und alle ihre Variablen zerstört, nachdem die Funktionsausführung beendet ist.


Wenn eine Funktion jedoch einen Abschluss zurückgibt, wird der Umfang dieser Funktion im Speicher gespeichert, bis der Abschluss nicht mehr vorhanden ist.


Die Verwendung von Abschlüssen kann den Bereich auf Blockebene in JavaScript imitieren (JavaScript selbst hat kein Konzept für den Bereich auf Blockebene. Die wichtigsten Punkte sind wie folgt).


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

Das Ergebnis ist, dass alle Variablen innerhalb der Funktion sofort zerstört werden – es sei denn, einige 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 zugehörigen Konzepte und Schlüsselpunkte sind wie folgt. Auch wenn es in JavaScript kein formales Konzept für private Objekteigenschaften gibt, können Abschlüsse verwendet werden, um öffentliche Methoden zu implementieren, die Zugriff auf Variablen ermöglichen, die im enthaltenden Bereich definiert sind.
Öffentliche Methoden, die Zugriff auf private Variablen haben, werden privilegierte Methoden genannt.

Sie können den Konstruktormodus und den Prototypmodus verwenden, um benutzerdefinierte privilegierte Methoden zu implementieren, und Sie können auch den Modulmodus und den erweiterten Modulmodus verwenden, um Singleton-privilegierte Methoden zu implementieren.


Funktionsausdrücke und -abschlüsse in JavaScript sind äußerst nützliche Funktionen, mit denen Sie viele Funktionen erreichen können. Da beim Erstellen von Abschlüssen jedoch zusätzlicher Umfang gewährleistet werden muss, kann eine übermäßige Verwendung dieser Abschlüsse viel Speicher beanspruchen.

Stellungnahme:
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