Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Erläuterung des Ausführungskontexts und des Ausführungsstapels in JS

Detaillierte Erläuterung des Ausführungskontexts und des Ausführungsstapels in JS

青灯夜游
青灯夜游nach vorne
2020-11-06 17:47:031940Durchsuche

Detaillierte Erläuterung des Ausführungskontexts und des Ausführungsstapels in JS

Wenn Sie JavaScript-Entwickler sind oder JavaScript-Entwickler werden möchten, müssen Sie den internen Ausführungsmechanismus eines JavaScript-Programms kennen. Ausführungskontext und Ausführungsstapel sind eines der Schlüsselkonzepte in JavaScript und eine der Schwierigkeiten in JavaScript. Das Verständnis des Ausführungskontexts und des Ausführungsstapels hilft auch, andere JavaScript-Konzepte wie Hebemechanismus, Umfang und Abschluss zu verstehen. In diesem Artikel werden diese Konzepte so leicht verständlich wie möglich vorgestellt.

Empfohlenes Tutorial: „JavaScript-Video-Tutorial“

1. Ausführungskontext

1. Kurz gesagt, Ausführungskontext ist die Umgebung, in der der aktuelle JavaScript-Code analysiert und ausgeführt wird. Abstraktes Konzept Code, der in JavaScript ausgeführt wird, wird in einem Ausführungskontext ausgeführt

2. Arten von Ausführungskontexten:

Globaler Ausführungskontext: Dies ist der standardmäßige und grundlegendste Ausführungskontext. Code, der sich in keiner Funktion befindet, befindet sich im globalen Ausführungskontext. Es führt zwei Dinge aus: 1. Ein globales Objekt erstellen. Im Browser ist dieses globale Objekt das Fensterobjekt. 2. Zeigen Sie mit dem Zeiger this auf dieses globale Objekt. In einem Programm kann nur ein globaler Ausführungskontext vorhanden sein.

Funktionsausführungskontext: Bei jedem Aufruf einer Funktion wird ein neuer Ausführungskontext für die Funktion erstellt. Jede Funktion verfügt über einen eigenen Ausführungskontext, der jedoch nur beim Aufruf der Funktion erstellt wird. In einem Programm können beliebig viele Funktionsausführungskontexte vorhanden sein. Immer wenn ein neuer Ausführungskontext erstellt wird, führt er eine Reihe von Schritten in einer bestimmten Reihenfolge aus, die später in diesem Artikel erläutert wird.

    Ausführungskontext der Auswertungsfunktion: Der in der Auswertungsfunktion ausgeführte Code erhält ebenfalls einen eigenen Ausführungskontext. Da die Auswertungsfunktion jedoch nicht häufig von Javascript-Entwicklern verwendet wird, wird sie hier nicht erläutert.
  • 2. Lebenszyklus des Ausführungskontexts
  • Der Lebenszyklus des Ausführungskontexts umfasst drei Phasen:
  • Erstellungsphase → Ausführungsphase → Recyclingphase
Dieser Artikel konzentriert sich auf die Erstellungsphase.

1. Erstellungsphase

Wenn eine Funktion aufgerufen wird, aber bevor ihr interner Code ausgeführt wird, werden die folgenden drei Dinge durchgeführt:

Ein Variablenobjekt erstellen: Initialisieren Sie zuerst die Parameterargumente der Funktion und stufen Sie sie hoch die Funktionsdeklaration und Variablendeklaration. Dies wird im Folgenden ausführlich erläutert.

Bereichskette erstellen: Während der Erstellungsphase des Ausführungskontexts wird die Bereichskette nach dem variablen Objekt erstellt. Die Bereichskette selbst enthält variable Objekte. Die Bereichskette wird zum Auflösen von Variablen verwendet. Wenn Sie aufgefordert werden, eine Variable zu analysieren, beginnt JavaScript immer auf der innersten Ebene der Codeverschachtelung. Wenn die Variable auf der innersten Ebene nicht gefunden wird, springt es eine Ebene höher zum übergeordneten Bereich, bis es die Variable findet.

    Bestimmen Sie, wohin dies führt: einschließlich vieler Situationen, die im Folgenden ausführlich erläutert werden
  • Bevor ein JS-Skript ausgeführt wird, muss der Code analysiert werden (JS ist also eine Skriptsprache, die beim Parsen interpretiert und ausführt). Die globale Ausführung wird zuerst erstellt. Nehmen Sie für den Kontext zunächst alle Variablen- und Funktionsdeklarationen heraus, die im Code ausgeführt werden sollen. Variablen werden vorübergehend undefiniert zugewiesen und Funktionen werden deklariert und sind einsatzbereit. Nachdem dieser Schritt abgeschlossen ist, kann der formelle Ausführungsprozess beginnen.
  • Darüber hinaus wird vor der Ausführung einer Funktion auch ein Funktionsausführungskontext erstellt, der dem globalen Kontext ähnelt, der Funktionsausführungskontext jedoch über diese Argumente und Funktionsparameter verfügt.
2. Ausführungsphase

Variablenzuweisung und Codeausführung durchführen

Der Ausführungskontext wird aus dem Stapel entfernt und wartet darauf, dass die virtuelle Maschine den Ausführungskontext recycelt.

3 und dieses Zeigen

1. Förderung der Variablendeklaration

Die meisten Programmiersprachen deklarieren Variablen zuerst und verwenden sie dann, aber in JS ist das etwas anders:

console.log(a); // undefined
var a = 10;

Der obige Code gibt normalerweise undefiniertaus > Anstatt einen Fehler zu melden: Uncaught ReferenceError: a ist nicht definiert, liegt dies am Deklarations-Heben, was dem folgenden Code entspricht:

var a; //声明 默认值是undefined “准备工作”
console.log(a);
a = 10; //赋值

2. Heben von Funktionsdeklarationen

Wir alle wissen, dass es zwei Arten von Methoden zum Erstellen einer Funktion gibt, eine durch Funktionsdeklaration function foo(){}
und die andere durch Funktionsausdruck var foo = function(){}, Was ist also der Unterschied zwischen diesen beiden Arten der Funktionsförderung?

console.log(f1); // function f1(){}
function f1() {} // 函数声明
console.log(f2); // undefined
var f2 = function() {}; // 函数表达式

Als nächstes verwenden wir ein Beispiel, um dieses Problem zu veranschaulichen:

function test() {
    foo(); // Uncaught TypeError "foo is not a function"
    bar(); // "this will run!"
    var foo = function() {
        // function expression assigned to local variable 'foo'
        alert("this won't run!");
    };
    function bar() {
        // function declaration, given the name 'bar'
        alert("this will run!");
    }
}
test();

Im obigen Beispiel wird ein Fehler gemeldet, wenn foo() aufgerufen wird, aber bar kann normal aufgerufen werden. undefined而不是报错Uncaught ReferenceError: a is not defined,这是因为声明提升(hoisting),相当于如下代码:

alert(a); //输出:function a(){ alert('我是函数') }
function a() {
    alert("我是函数");
} //
var a = "我是变量";
alert(a); //输出:'我是变量'

2. 函数声明提升

我们都知道,创建一个函数的方法有两种,一种是通过函数声明function foo(){}
另一种是通过函数表达式var foo = function(){} ,那这两种在函数提升有什么区别呢?

function a() {
    alert("我是函数");
}
var a; //hoisting
alert(a); //输出:function a(){ alert('我是函数') }
a = "我是变量"; //赋值
alert(a); //输出:'我是变量'

接下来我们通过一个例子来说明这个问题:

function test(arg) {
    // 1. 形参 arg 是 "hi"
    // 2. 因为函数声明比变量声明优先级高,所以此时 arg 是 function
    console.log(arg);
    var arg = "hello"; // 3.var arg 变量声明被忽略, arg = 'hello'被执行
    function arg() {
        console.log("hello world");
    }
    console.log(arg);
}
test("hi");
/* 输出:
function arg(){
    console.log('hello world') 
    }
hello 
*/

在上面的例子中,foo()调用的时候报错了,而 bar 能够正常调用。

我们前面说过变量和函数都会上升,遇到函数表达式 var foo = function(){}时,首先会将var foo上升到函数体顶部,然而此时的 foo 的值为 undefined,所以执行foo()

Wir haben zuvor gesagt, dass Variablen und Funktionen auf den Funktionsausdruck var foo = function(){} stoßen und zuerst zum Funktionskörper At aufsteigen Oben ist jedoch der Wert von foo zu diesem Zeitpunkt undefiniert, sodass bei der Ausführung von foo() ein Fehler gemeldet wird. 🎜

而对于函数bar(), 则是提升了整个函数,所以bar()才能够顺利执行。

有个细节必须注意:当遇到函数和变量同名且都会被提升的情况,函数声明优先级比较高,因此变量声明会被函数声明所覆盖,但是可以重新赋值。

alert(a); //输出:function a(){ alert('我是函数') }
function a() {
    alert("我是函数");
} //
var a = "我是变量";
alert(a); //输出:'我是变量'

function 声明的优先级比 var 声明高,也就意味着当两个同名变量同时被 function 和 var 声明时,function 声明会覆盖 var 声明

这代码等效于:

function a() {
    alert("我是函数");
}
var a; //hoisting
alert(a); //输出:function a(){ alert('我是函数') }
a = "我是变量"; //赋值
alert(a); //输出:'我是变量'

最后我们看个复杂点的例子:

function test(arg) {
    // 1. 形参 arg 是 "hi"
    // 2. 因为函数声明比变量声明优先级高,所以此时 arg 是 function
    console.log(arg);
    var arg = "hello"; // 3.var arg 变量声明被忽略, arg = 'hello'被执行
    function arg() {
        console.log("hello world");
    }
    console.log(arg);
}
test("hi");
/* 输出:
function arg(){
    console.log('hello world') 
    }
hello 
*/

这是因为当函数执行的时候,首先会形成一个新的私有的作用域,然后依次按照如下的步骤执行:

  • 如果有形参,先给形参赋值
  • 进行私有作用域中的预解释,函数声明优先级比变量声明高,最后后者会被前者所覆盖,但是可以重新赋值
  • 私有作用域中的代码从上到下执行

3. 确定 this 的指向

先搞明白一个很重要的概念 —— this 的值是在执行的时候才能确认,定义的时候不能确认! 为什么呢 —— 因为 this 是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候。看如下例子:

// 情况1
function foo() {
  console.log(this.a) //1
}
var a = 1
foo()

// 情况2
function fn(){
  console.log(this);
}
var obj={fn:fn};
obj.fn(); //this->obj

// 情况3
function CreateJsPerson(name,age){
//this是当前类的一个实例p1
this.name=name; //=>p1.name=name
this.age=age; //=>p1.age=age
}
var p1=new CreateJsPerson("尹华芝",48);

// 情况4
function add(c, d){
  return this.a + this.b + c + d;
}
var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

// 情况5
<button id="btn1">箭头函数this</button>
<script type="text/javascript">
    let btn1 = document.getElementById(&#39;btn1&#39;);
    let obj = {
        name: &#39;kobe&#39;,
        age: 39,
        getName: function () {
            btn1.onclick = () => {
                console.log(this);//obj
            };
        }
    };
    obj.getName();
</script>

接下来我们逐一解释上面几种情况

  • 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window
  • 对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象
  • 在构造函数模式中,类中(函数体中)出现的 this.xxx=xxx 中的 this 是当前类的一个实例
  • call、apply 和 bind:this 是第一个参数
  • 箭头函数 this 指向:箭头函数没有自己的 this,看其外层的是否有函数,如果有,外层函数的 this 就是内部箭头函数的 this,如果没有,则 this 是 window。

Detaillierte Erläuterung des Ausführungskontexts und des Ausführungsstapels in JS

四、执行上下文栈(Execution Context Stack)

函数多了,就有多个函数执行上下文,每次调用函数创建一个新的执行上下文,那如何管理创建的那么多执行上下文呢?

JavaScript 引擎创建了执行上下文栈来管理执行上下文。可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出的原则

Detaillierte Erläuterung des Ausführungskontexts und des Ausführungsstapels in JS

从上面的流程图,我们需要记住几个关键点:

  • JavaScript 执行在单线程上,所有的代码都是排队执行。
  • 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
  • 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。
  • 浏览器的 JS 执行引擎总是访问栈顶的执行上下文。
  • 全局上下文只有唯一的一个,它在浏览器关闭时出栈。

我们再来看个例子:

var color = "blue";
function changeColor() {
    var anotherColor = "red";
    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }
    swapColors();
}
changeColor();

上述代码运行按照如下步骤:

  • 当上述代码在浏览器中加载时,JavaScript 引擎会创建一个全局执行上下文并且将它推入当前的执行栈
  • 调用 changeColor 函数时,此时 changeColor 函数内部代码还未执行,js 执行引擎立即创建一个 changeColor 的执行上下文(简称 EC),然后把这执行上下文压入到执行栈(简称 ECStack)中。
  • 执行 changeColor 函数过程中,调用 swapColors 函数,同样地,swapColors 函数执行之前也创建了一个 swapColors 的执行上下文,并压入到执行栈中。
  • swapColors 函数执行完成,swapColors 函数的执行上下文出栈,并且被销毁。
  • changeColor 函数执行完成,changeColor 函数的执行上下文出栈,并且被销毁。

Detaillierte Erläuterung des Ausführungskontexts und des Ausführungsstapels in JS

更多编程相关知识,请访问:编程学习网站!!

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des Ausführungskontexts und des Ausführungsstapels in JS. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen