Maison > interface Web > js tutoriel > Propriétés internes de JavaScript [[Scope]] et chaîne de portée et leurs problèmes de performances

Propriétés internes de JavaScript [[Scope]] et chaîne de portée et leurs problèmes de performances

黄舟
Libérer: 2017-02-28 14:57:03
original
1457 Les gens l'ont consulté

J'apprends JavaScript depuis longtemps

Aujourd'hui, je prévois de rappeler la connaissance de la portée
La connaissance de la portée est très basique et très importante
Laissez-moi résumer le connaissance de la portée en JavaScript Connaissance de la portée et de la chaîne de portée

Portée

Qu'est-ce que la portée ?
Le scope est la zoneoù les variables peuvent être référencées et les fonctions peuvent prendre effet
Il limite votre capacité à obtenir et modifier des valeurs dans l'espace mémoire
Les scopes existent dans toutes les langues
Nous pouvons comprendre la portée comme Un ensemble de règles permettant au moteur js de trouver des variables en fonction de leurs noms
Comprenant la portée, nous pouvons comprendre une série de problèmes tels que les fermetures

[[Scope ]]

Comme nous le savons tous, les fonctions sont des objets exécutables spéciaux
Comme ce sont des objets, elles peuvent avoir des attributs
Il y a cet attribut interne [[Scope]] dans le fonction (nous ne pouvons pas l'utiliser, Pour une utilisation par les moteurs js)
Lorsqu'une fonction est créée, cette propriété interne contiendra la collection d'objets dans le périmètre où la fonction a été créée
Cette collection est liée dans une chaîne et est appelée la chaîne de portée de la fonction
Chaque objet de la chaîne de portée est appelé un objet variable (Variable Object
Chaque objet variable existe sous la forme de paires clé-valeur
Pour un exemple, regardez). à la fonction globale suivante

var a = 1;function foo(){    ...}
Copier après la connexion

Lorsque la fonction foo est créée, un objet global GO (Global Object) est inséré dans sa chaîne de portée, y compris toutes les variables définies globalement

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

Environnement d'exécution

Lorsqu'une fonction est exécutée, un objet interne appelé contexte d'exécution est créé
qui définit un environnement lors de l'exécution de la fonction
L'environnement d'exécution est unique à chaque fois que la fonction est exécutée
plusieurs fois L'appel d'une fonction crée un environnement d'exécution plusieurs fois
et une fois la fonction exécutée, l'environnement d'exécution sera détruit
L'environnement d'exécution a sa propre chaîne de portée pour analyser les identifiants

Voir ici c'est possible Tout le monde est un peu confus, laissez-moi vous expliquer ma compréhension
Bien que [[Scope]] et le contexte d'exécution sauvent tous deux les chaînes de portée, ce n'est pas la même chose
Clarifions maintenant la différence
L'attribut [[Scope]] est généré lors de la création de la fonction et existera toujours
Le contexte d'exécution est généré lorsque la fonction est exécutée. L'instance

sera détruite lors de l'exécution de la fonction.

J'ai mis ce qui précède Développons l'exemple et expliquons en détail

var a = 1;function foo(x, y){
    var b = 2;    function bar(){
        var c = 3;
    }
    bar();
}
foo(100, 200);
Copier après la connexion

Je vais maintenant expliquer la chaîne de portée et l'environnement d'exécution en détail à travers ces lignes de code
Je recommande toujours que tous les étudiants jettent un œil à cet aperçu que j'ai écrit

  • Tout d'abord, dans le flux d'exécution, la fonction foo() est créée dans l'environnement global (créé dans le pré -étape de compilation), donc la fonction foo a des attributs [[Scope] ]

// 伪代码:foo函数创建产生[[Scope]]对象
foo.[[Scope]] = {
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: undefined, //预编译阶段还不知道a的值是多少,执行过程中会修改
        foo: function(){...},
        ......
    }
}
Copier après la connexion
  • Avant l'exécution de la fonction foo, un contexte d'exécution est créé ( J'écrirai le contexte d'exécution comme EC pour le moment. Je ne sais pas quel est le nom interne) ), le contexte d'exécution obtient la chaîne de portée (copiée) enregistrée par l'attribut [[Scope]] dans foo, puis précompile la fonction foo avant l'exécution pour générer un objet actif AO (Active Object). Cet objet est poussé dans la chaîne de portée EC Le front-end

// 伪代码:foo函数执行前产生执行期上下文EC复制foo中[[Scope]]属性保存的作用域链
foo.EC = {
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: 1,
        foo: function(){...},
        ......
    }
}
Copier après la connexion
// 伪代码: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(){...},
        ......
    }
}
Copier après la connexion
    .
  • La fonction foo crée la fonction bar lors de la phase de pré-compilation, donc la fonction bar crée l'attribut [[Scope]], contient la collection d'objets dans la portée où la barre a été créée, qui est une copie de 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(){...},
        ......
    }
}
Copier après la connexion
  • exécution de la fonction bar, le processus est similaire à l'exécution de la fonction foo , je viens d'écrire le résultat final

// 伪代码: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(){...},
        ......
    }
}
Copier après la connexion
  • La fonction bar est exécutée et l'environnement d'exécution est détruit, ce qui équivaut à delete bar.EC

  • La fonction foo est exécutée et l'environnement d'exécution est détruit, ce qui équivaut à delete foo.EC

  • Le programme se termine

Processus d'analyse variable

Je ne sais pas si vous comprenez tellement ce que j'ai écrit
Le moteur js utilise les règles de la chaîne de portée pour rechercher des variables (pour être précis, ce devrait être la chaîne de portée du contexte d'exécution)
Le processus de recherche est Prenez le code ci-dessus comme exemple. Par exemple, si j'ajoute une ligne console.log(a);
à la fonction bar, alors lorsque la fonction bar est exécutée, le moteur js veut imprimer un, donc il va à la chaîne de portée pour trouver le
AO de premier niveau Non
Il n'y a pas d'AO de deuxième niveau
Le GO de troisième niveau a trouvé la variable a
Il a donc renvoyé la valeur de la variable a
Je crois qu'une fois que tout le monde aura compris la portée, ils comprendront pourquoi l'environnement mondial ne peut pas accéder à l'environnement local

性能问题

今天写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(...);
}
Copier après la connexion

这段代码看起来缓存了,优化了代码
但其实这段代码执行过程中,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(...);
}
Copier après la connexion

总结

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

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

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

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

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

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

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


Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal