Heim  >  Artikel  >  Web-Frontend  >  Interne JavaScript-Eigenschaften [[Scope]] und die Scope-Kette sowie deren Leistungsprobleme

Interne JavaScript-Eigenschaften [[Scope]] und die Scope-Kette sowie deren Leistungsprobleme

黄舟
黄舟Original
2017-02-28 14:57:031407Durchsuche

Ich lerne seit langem JavaScript

Heute möchte ich mich an das Wissen über den Umfang erinnern
Das Wissen über den Umfang ist sehr grundlegend und sehr wichtig
Lassen Sie mich das zusammenfassen Kenntnisse über Scope in JavaScript Kenntnisse über Scope und Scope-Kette

Scope

Was ist Scope?
Bereich ist der Bereichin dem auf Variablen verwiesen werden kann und Funktionen wirksam werden können
Es schränkt Ihre Fähigkeit ein, Werte im Speicherbereich abzurufen und zu ändern
Bereiche gibt es in allen Sprachen
Wir können den Bereich verstehen als Eine Reihe von Regeln für die js-Engine, um Variablen basierend auf ihren Namen zu finden
Wenn wir den Bereich verstehen, können wir eine Reihe von Problemen wie Abschlüsse verstehen

[[Scope]]

Wie wir alle wissen, sind Funktionen spezielle ausführbare Objekte
Da es sich um Objekte handelt, können sie Attribute haben
Es gibt dieses interne Attribut [[Scope]] im Funktion (wir können sie nicht verwenden, zur Verwendung durch JS-Engines)
Wenn eine Funktion erstellt wird, enthält diese interne Eigenschaft die Sammlung von Objekten in dem Bereich, in dem die Funktion erstellt wurde.
Diese Sammlung ist in einer Kette verknüpft und wird als Gültigkeitsbereichskette der Funktion bezeichnet.
Jedes Objekt in der Gültigkeitsbereichskette wird als Variablenobjekt (Variablenobjekt) bezeichnet.
Jedes Variablenobjekt existiert in Form von Schlüssel-Wert-Paaren.
Sehen Sie sich ein Beispiel an bei der folgenden globalen Funktion

var a = 1;function foo(){    ...}

Wenn die foo-Funktion erstellt wird, wird ein globales Objekt GO (Global Object) in seine Gültigkeitsbereichskette eingefügt, einschließlich aller global definierten Variablen

// 伪代码
foo.[[Scope]] = {
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        ......
        a: undefined, // 预编译阶段还不知道a值是多少
        foo: function(){...},
    }
}

Ausführungsumgebung

Wenn eine Funktion ausgeführt wird, wird ein internes Objekt namens Ausführungskontext erstellt
, das eine Umgebung definiert, wenn die Funktion ausgeführt wird
Die Ausführungsumgebung ist bei jeder Ausführung der Funktion eindeutig
mehrfacher Aufruf einer Funktion erstellt mehrmals eine Ausführungsumgebung
und nachdem die Funktion ausgeführt wurde, wird die Ausführungsumgebung zerstört
Die Ausführungsumgebung verfügt über eine eigene Bereichskette zum Parsen von Bezeichnern

Siehe hier Es ist möglich, dass jeder ein wenig verwirrt ist.
[[Umfang]] und der Ausführungskontext speichern beide Bereichsketten, aber sie sind nicht dasselbe.
Lassen Sie uns nun den Unterschied klären.
Das Attribut [[Scope]] wird beim Erstellen der Funktion generiert und ist immer vorhanden.
Der Ausführungskontext wird generiert, wenn die Funktion ausgeführt wird wird ausgeführt.

Ich habe das obige Beispiel erweitert und im Detail erklärt

Jetzt werde ich die Bereichskette und die Ausführungsumgebung anhand dieser Codezeilen ausführlich erläutern
var a = 1;function foo(x, y){
    var b = 2;    function bar(){
        var c = 3;
    }
    bar();
}
foo(100, 200);
Ich empfehle dennoch allen Schülern, sich diese Vorschau anzusehen, die ich geschrieben habe.


    Zuerst wird im Ausführungsfluss die Funktion foo() in der globalen Umgebung erstellt (created in der Vorkompilierungsphase), sodass die Foo-Funktion Attribute hat [[Scope] ]
// 伪代码:foo函数创建产生[[Scope]]对象
foo.[[Scope]] = {
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: undefined, //预编译阶段还不知道a的值是多少,执行过程中会修改
        foo: function(){...},
        ......
    }
}
    Bevor die Foo-Funktion ausgeführt wird, ein Ausführungskontext erstellt wird (ich werde den Ausführungskontext vorerst als EC schreiben. Ich weiß nicht, wie der interne Name lautet) ), erhält der Ausführungskontext die Bereichskette (kopiert), die durch das Attribut [[Scope]] in foo gespeichert wird , und kompiliert dann die foo-Funktion vor der Ausführung, um ein aktives Objekt AO (aktives Objekt) zu generieren. Dieses Objekt wird in die EC-Bereichskette verschoben. Das Front-End
// 伪代码:foo函数执行前产生执行期上下文EC复制foo中[[Scope]]属性保存的作用域链
foo.EC = {
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: 1,
        foo: function(){...},
        ......
    }
}
// 伪代码:foo函数预编译产生AO活动对象,挂载到foo中EC作用域链的最前端
foo.EC = {
    AO: {
        this: window,
        arguments: [100,200],
        x: 100,
        y: 200,
        b: undefined,
        bar: function(){...}
    },
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: 1,
        foo: function(){...},
        ......
    }
}
    foo-Funktion erstellt die Bar-Funktion in der Vorkompilierungsphase, daher erstellt die Bar-Funktion das Attribut [[Scope]], das die Sammlung von Objekten in dem Bereich enthält, in dem bar erstellt wurde eine Kopie von foo.EC
// 伪代码:bar函数创建产生[[Scope]]对象
bar.[[Scope]] = {
    AO: {
        this: window,
        arguments: [100,200],
        x: 100,
        y: 200,
        b: undefined,
        bar: function(){...}
    },
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: 1,
        foo: function(){...},
        ......
    }
}
    Bar-Funktionsausführung, der Prozess ähnelt der Foo-Funktionsausführung, ich habe gerade das Endergebnis geschrieben
// 伪代码:bar函数执行产生执行上下文
bar.EC = {
    AO: {
        this: window,
        arguments: [],
        c: undefined,
    },
    AO: {
        this: window,
        arguments: [100,200],
        x: 100,
        y: 200,
        b: 2,
        bar: function(){...}
    },
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: 1,
        foo: function(){...},
        ......
    }
}
    Die Bar-Funktion wird ausgeführt und die Ausführungsumgebung wird zerstört, was
  • delete bar.EC

  • Foo entspricht Die Funktion wird ausgeführt und die Ausführungsumgebung wird zerstört. Dies entspricht
  • delete foo.EC

  • Das Programm endet
  • Variablenanalyseprozess

Ich weiß nicht, ob Sie verstehen, was ich geschrieben habe.

Die js-Engine verwendet die Regeln der Bereichskette, um nach Variablen zu suchen (genauer gesagt sollte es die Bereichskette des Ausführungskontexts sein )

Der Suchvorgang ist Nehmen Sie den obigen Code als Beispiel: Wenn ich der Balkenfunktion eine Zeile hinzufüge, möchte die js-Engine eine drucken , also geht es zur Bereichskette, um das
AO der ersten Ebene zu finden. Nein
Es gibt kein AO der zweiten Ebene. console.log(a); Das GO der dritten Ebene hat die Variable a gefunden.
Also hat es den Wert von zurückgegeben Variable a
Ich glaube, nachdem jeder den Umfang verstanden hat, wird er verstehen, warum die globale Umgebung nicht auf die lokale Umgebung zugreifen kann

性能问题

今天写high了,像吃了炫迈一样,那就顺便把性能问题也说清楚了吧
js引擎查找作用域链是为了解析标识符
占用了时间理所当然的产生了性能开销
所以解析标识符有代价,你的变量在执行环境作用域链的位置越深,读写速度就越慢
这很容易理解
在函数中读写局部变量总是最快的,读写全局变量通常最慢
当然了,这些额外的性能开销对于优化js引擎(比如chrome的V8 (⊙▽⊙))来说可能微不足道,甚至可以毫不夸张的说没有性能损失
但是还是要照顾大多浏览器
所以推荐大家养成这样的编码习惯:尽量使用局部变量(缓存)
我举一个小例子

function demo(){
    var a = document.getElementById('a');
    var b = document.getElementById('b');
    var c = document.getElementById('c');
    a.onclick = function(){        ...
    }
    a.style.left = ...;
    a.style.top = ...;
    b.style.backgroundColor = ...;
    c.className = ...;
    document.body.appendChild(...);
    document.body.appendChild(...);
    document.body.appendChild(...);
}

这段代码看起来缓存了,优化了代码
但其实这段代码执行过程中,js引擎解析标识符,要查找6次document
而且document存在于window对象
也就是作用域链的最末尾
所以我们再进行缓存,包括document.body、a.style
再加上单一var原则
重构函数

function demo(){
    var doc = document,
        bd = doc.body,
        a = doc.getElementById('a'),
        b = doc.getElementById('b'),
        styleA = a.style;
    a.onclick = function(){        ...
    }
    styleA.left = ...;
    styleA.top = ...;
    styleA.backgroundColor = ...;
    b.className = ...;
    bd.appendChild(...);
    bd.appendChild(...);
    bd.appendChild(...);
}

总结

其实写了这么多,还有一个问题我没写到,就是作用域链在某些特殊情况下是可以动态改变的
比如with()、eval()等等,当然这些都不建议使用,我总结了一篇文章
有兴趣的同学可以看看 ->传送门<-
还是总结一下今天写的作用域链相关知识

  • 作用域是变量能够引用、函数能够生效的区域

  • 函数创建时,产生内部属性[[Scope]]包含函数被创建的作用域中对象的集合(作用域链)
    作用域链上每个对象称为可变对象(Variable Obejct),
    每一个可变对象都以键值对形式存在(VO要细分的话,全局对象GO和活动对象AO)

  • 函数执行时,创建内部对象叫做执行环境/执行上下文(execution context)
    它定义了一个函数执行时的环境,函数每次执行时的执行环境独一无二
    函数执行结束便会销毁

  • js引擎就通过函数执行上下文的作用域链规则来进行解析标识符(用于读写),从作用域链顶端依次向下查找

  • 尽量缓存局部变量,减少作用域查找性能开销(照顾未优化浏览器)

 以上就是JavaScript内部属性[[Scope]]与作用域链及其性能问题的内容,更多相关内容请关注PHP中文网(www.php.cn)!


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