Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Erläuterung der Javascript-Funktionsdeklaration und des rekursiven Aufrufs

Detaillierte Erläuterung der Javascript-Funktionsdeklaration und des rekursiven Aufrufs

高洛峰
高洛峰Original
2016-12-09 13:21:521311Durchsuche

Die Art und Weise, wie Javascript-Funktionen deklariert und aufgerufen werden, ist bereits ein abgedroschenes Klischee, aber manche Dinge sind so. Du sagst es einmal und ich werde es noch einmal sagen. Jedes Mal, wenn ich sehe, dass es vier Möglichkeiten gibt, eine in einem Buch oder Blog geschriebene Javascript-Funktion aufzurufen, denke ich an Kong Yiji: Es gibt vier Möglichkeiten, das Wort „Fenchel“ zu schreiben. Kennen Sie das?

Trotz seiner vielen Mängel ist Javascript faszinierend. Das Herzstück der vielen schönen Funktionen von Javascript sind Funktionen als erstklassige Objekte. Funktionen werden wie andere normale Objekte erstellt, Variablen zugewiesen, als Argumente übergeben, geben Werte zurück und enthalten Eigenschaften und Methoden. Als Objekte der obersten Ebene verleihen Funktionen Javascript leistungsstarke funktionale Programmierfunktionen und bringen darüber hinaus eine Flexibilität mit sich, die nicht leicht zu kontrollieren ist.

1. Funktionsdeklaration

Variablendeklaration erstellt zunächst eine anonyme Funktion und weist sie dann einer angegebenen Variablen zu:

var f = function () { // function body };

Normalerweise muss es uns egal sein, ob der Gültigkeitsbereich des Ausdrucks auf der rechten Seite des Gleichheitszeichens global ist oder sich innerhalb eines Abschlusses befindet, da er nur über die Variable f auf der linken Seite des Gleichheitszeichens referenziert werden kann sign. Worüber man sich Gedanken machen sollte, ist der Umfang der Variablen f . Wenn der Verweis von f auf die Funktion zerstört wird (f = null) und die Funktion keiner anderen Variablen oder Objekteigenschaft zugewiesen ist, wird die anonyme Funktion durch den Garbage-Collection-Mechanismus zerstört, da alle Verweise verloren gehen.

Sie können auch Funktionsausdrücke verwenden, um Funktionen zu erstellen:

function f() { // function body }

Im Gegensatz zu Variablenausdrücken ist diese Deklarationsmethode eine integrierte Funktion. Zuweisen einen Wert für den Attributnamen. Weisen Sie die Funktion gleichzeitig einer Variablen mit demselben Namen im aktuellen Bereich zu. (Die Namensattribute der Funktion, konfigurierbar, aufzählbar und beschreibbar, sind alle falsch)

function f() { // function body }
console.log(f.name); // "f"
console.log(f); // f()


Javascript-Variablen haben nämlich eine besondere Funktion Sie rücken die Deklaration von Variablen voran, und die Funktionsdeklaration eines Ausdrucks treibt auch die Definition der gesamten Funktion voran, sodass Sie sie vor der Funktionsdefinition verwenden können:

console.log(f.name); // "f"
console.log(f); // f()
function f() { // function body }

Funktionsausdrucksdeklarationen werden auf die oberste Ebene des Bereichs hochgestuft. Probieren Sie den folgenden Code aus. Sie stehen nicht im Mittelpunkt dieses Artikels:

var a = 0;
console.log(a); // 0 or a()?
function a () {}

Crockford empfiehlt, immer den ersten Weg zu verwenden Um Funktionen zu deklarieren, wird angenommen, dass die zweite Methode die Anforderung lockert, dass Funktionen zuerst deklariert und dann verwendet werden müssen, was zu Verwirrung führen wird. (Crockford ist ein „Gewissensprogrammierer“, ähnlich dem „Gewissenskünstler“, mit dem Russell Wittgenstein verglich. Dieser Satz ist sehr schwer auszusprechen)

Funktionsdeklaration

function f() {}

scheint die Abkürzung von

var f = function f(){};

zu sein. Erstellen Sie für den Ausdruck

var a = function b(){};


eine Funktion, weisen Sie das integrierte Namensattribut „b“ zu und weisen Sie diese Funktion dann der Variablen a zu Der Aufruf erfolgt extern mit a(), aber b() kann nicht verwendet werden. Da die Funktion a zugewiesen wurde, wird eine Variable b nicht automatisch erstellt, es sei denn, Sie deklarieren eine Variable b mit var b = a. Natürlich ist der Name dieser Funktion „b“, nicht „a“.

Mit dem Funktionskonstruktor können auch Funktionen erstellt werden:

var f = new Function("a,b,c","return a+b+c;");


Diese Methode ist tatsächlich global Generieren Sie eine anonyme Funktion im Bereich und weisen Sie sie der Variablen f zu.

2. Rekursive Aufrufe

Rekursion wird zur Vereinfachung vieler Probleme verwendet, die einen Aufruf in einem Funktionskörper erfordern:

// 一个简单的阶乘函数
var f = function (x) {
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
};


Die große Flexibilität von Funktionen in Javascript führt zu Schwierigkeiten bei der Verwendung von Funktionsnamen bei der Rekursion. Für die obige Variablendeklaration ist f eine Variable, daher kann ihr Wert leicht ersetzt werden:

var fn = f;
f = function () {};

Numerischer Wert, aber da die Variable f immer noch innerhalb der Funktion referenziert wird, kann sie nicht richtig funktionieren.


Funktionsdeklaration sieht besser aus, ist aber schade:


Achten Sie nach der Definition einer rekursiven Funktion darauf, den Namen der Variablen nicht leicht zu ändern.

Was wir oben besprochen haben, sind alle Funktionsaufrufe. Es gibt andere Möglichkeiten, Funktionen aufzurufen, beispielsweise als Objektmethoden.
function f(x) {
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
}
var fn = f;
f = function () {}; // may been warning by browser
fn(5); // NaN

Wir deklarieren oft Objekte wie folgt:




Deklarieren Sie eine anonyme Funktion und weisen Sie sie dem Objekt zu Eigenschaften(fac).

Wenn wir hier eine Rekursion schreiben wollen, müssen wir die Eigenschaft selbst zitieren:
var obj1 = {
  num : 5,
  fac : function (x) {
    // function body
  }
};



Natürlich tritt auch das gleiche Problem auf wie bei der Funktionsaufrufmethode:

var obj1 = {
  num : 5,
  fac : function (x) {
    if (x === 1) {
      return 1;
    } else {
      return x * obj1.fac(x - 1);
    }
  }
};


Nachdem die Methode dem fac-Attribut von zugewiesen wurde obj2, die interne Referenz obj1.fac, also... fehlgeschlagen.

Eine andere Möglichkeit wird verbessert:
var obj2 = {fac: obj1.fac};
obj1 = {};
obj2.fac(5); // Sadness



Erhalten Sie die Ausführungszeit der Funktion über dieses Schlüsselwort Attribute im Kontext, sodass bei der Ausführung von obj2.fac das fac-Attribut von obj2 innerhalb der Funktion referenziert wird.

Die Funktion kann aber auch durch willkürliche Änderung des Kontexts aufgerufen werden, also durch den universellen Aufruf und die Anwendung:
var obj1 = {
   num : 5,
   fac : function (x) {
    if (x === 1) {
      return 1;
    } else {
      return x * this.fac(x - 1);
    }
  }
};
var obj2 = {fac: obj1.fac};
obj1 = {};
obj2.fac(5); // ok



Die rekursive Funktion kann also nicht wieder richtig funktionieren.

Wir sollten versuchen, dieses Problem zu lösen. Erinnern Sie sich an die zuvor erwähnte Funktionsdeklarationsmethode?
obj3 = {};
obj1.fac.call(obj3, 5); // dead again
var a = function b(){};

   


这种声明方式叫做内联函数(inline function),虽然在函数外没有声明变量b,但是在函数内部,是可以使用b()来调用自己的,于是

var fn = function f(x) {
  // try if you write "var f = 0;" here
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
};
var fn2 = fn;
fn = null;
fn2(5); // OK

    

// here show the difference between "var f = function f() {}" and "function f() {}"
var f = function f(x) {
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
};
var fn2 = f;
f = null;
fn2(5); // OK

    

var obj1 = {
  num : 5,
  fac : function f(x) {
    if (x === 1) {
      return 1;
    } else {
      return x * f(x - 1);
    }
  }
};
var obj2 = {fac: obj1.fac};
obj1 = {};
obj2.fac(5); // ok
  
var obj3 = {};
obj1.fac.call(obj3, 5); // ok

   


就这样,我们有了一个可以在内部使用的名字,而不用担心递归函数被赋值给谁以及以何种方式被调用。

Javascript函数内部的arguments对象,有一个callee属性,指向的是函数本身。因此也可以使用arguments.callee在内部调用函数:

function f(x) {
  if (x === 1) {
    return 1;
  } else {
    return x * arguments.callee(x - 1);
  }
}

   


但arguments.callee是一个已经准备被弃用的属性,很可能会在未来的ECMAscript版本中消失,在ECMAscript 5中"use strict"时,不能使用arguments.callee。

最后一个建议是:如果要声明一个递归函数,请慎用new Function这种方式,Function构造函数创建的函数在每次被调用时,都会重新编译出一个函数,递归调用会引发性能问题——你会发现你的内存很快就被耗光了。


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