L'effort d'une tasse de thé, saisir la portée et la chaîne de portée

RC.jpgBuvez le bon yaourt et obtenez deux fois le résultat avec la moitié de l'effort ! Pour certains termes obscurs, presque mythologiques, ne vous grattez pas la tête, passons directement au code, ajoutons un langage clair au rendu, voyons comment nous gérons la petite colline en javascript aujourd'hui - portée & chaîne de portée, pas seulement raffinée.

avant-propos

Ce que nous devons d'abord connaître, c'est le moteur.Le travail du moteur est simple et grossier, c'est-à-dire qu'il est responsable de l'exécution du code javascript du début à la fin. Un bon ami du moteur est le compilateur, qui est principalement responsable de l'analyse et de la compilation du code ; un autre bon ami du moteur est le protagoniste-scope d'aujourd'hui. Alors, à quoi sert la portée ? Qu'est-ce qu'une chaîne de portée a à voir avec la portée ?

Tout d'abord, la portée (portée)

Définition de la portée : la portée est l'accessibilité des variables, des fonctions et des objets dans une partie spécifique du code au moment de l'exécution.

1. Classification des périmètres
  1. portée mondiale
var name="global";
function foo(){
    console.log(name);
}
foo();//global

La variable name n'est pas déclarée dans la fonction foo(), mais la valeur de name est toujours imprimée, indiquant que la fonction peut accéder à la portée globale et lire la variable name. Un autre exemple:

hobby='music';
function foo(){
    hobby='book';
    console.log(hobby);
}
foo();//book

Ici, ni la portée globale ni la fonction foo() ne déclarent la variable hobby, alors pourquoi n'y a-t-il pas d'erreur ? C'est parce qu'il est hobby='music';écrit dans la portée globale, même s'il n'y a pas de déclaration var, let, const, il sera accroché à l'objet window, donc la fonction foo() peut non seulement lire, mais aussi modifier la valeur. C'est- à-dire qu'il est hobby='music';équivalent à window.hobby='music';.

  1. portée du corps de la fonction

La portée du corps de la fonction est obtenue en masquant les composants internes. En d'autres termes, comme nous le disons souvent, la portée interne peut accéder à la portée externe, mais la portée externe ne peut pas accéder à la portée interne. La raison, en ce qui concerne la chaîne de portée, est facilement résolue.

function foo(){
    var age=19;
    console.log(age);
}
console.log(age);//ReferenceError:age is not defined

De toute évidence, il n'y a pas de variable d'âge dans la portée globale, mais la fonction foo() l'a à l'intérieur, mais elle n'est pas accessible de l'extérieur, donc une erreur sera naturellement signalée et la fonction foo() ne sera pas exécutée si ça ne s'appelle pas.

  1. portée de bloc

La portée au niveau du bloc n'est pas surprenante, comme la portée let que nous avons contactée, le bloc de code {}, la portée lorsque la boucle for utilise let, if, while, switch et ainsi de suite. Cependant, la prémisse d'une compréhension plus approfondie de la portée au niveau du bloc est que nous devons d'abord reconnaître ces termes :

--标识符:能在作用域生效的变量。函数的参数,变量,函数名。需要格外注意的是:函数体内部的标识符外部访问不到

--函数声明:function 函数名(){}

--函数表达式: var 函数名=function(){}

--自执行函数: (function 函数名(){})();自执行函数前面的语句必须有分号,通常用于隐藏作用域。

接下来我们就用一个例子,一口气展示完吧

function foo(sex){
    console.log(sex);
}
var f=function(){
    console.log('hello');
}
var height=180;
(
    function fn(){
        console.log(height);
    }
)();
foo('female');
//依次打印:
//180
//female
//hello

分析一下:标识符:foo,sex,height,fn;函数声明:function foo(sex){};函数表达式:var f=function(){};自执行函数:(function fn(){})();需要注意,自执行函数fn()前面的var height=180;语句,分号不能抛弃。否则,你可以试一下。

二、预编译

说好只是作用域和作用域链的,但是考虑到理解作用域链的必要性,这里还是先聊聊预编译吧。先讨论预编译在不同环境发生的情况下,是如何进行预编译的。

  1. 发生在代码执行之前

(1)声明提升

console.log(b);
var b=123;//undefined

这里打印undefined,这不是报错,与Refference:b is not defined不同。这是代码执行之前,预编译的结果,等同于以下代码:

var b;//声明提升
console.log(b);//undefined
b=123;

(2)函数声明整体提升

test();//hello123  调用函数前并没有声明,但是任然打印,是因为函数声明整体提升了
function test(){
    var a=123;
    console.log('hello'+a);
}

2.发生在函数执行之前

理解这个只需要掌握四部曲

(1)创建一个AO(Activation Object)

(2)找形参和变量声明,然后将形参和变量声明作为AO的属性名,属性值为undefined

(3)将实参和形参统一

(4)在函数体内找函数声明,将函数名作为AO对象的属性名,属性值予函数体 那么接下来就放大招了:

var global='window';
function foo(name,sex){
    console.log(name);
    function name(){};
    console.log(name);
    var nums=123;
    function nums(){};
    console.log(nums);
    var fn=function(){};
    console.log(fn);
}
foo('html');

这里的结果是什么呢?分析如下:

//从上到下
//1、创建一个AO(Activation Object)
AO:{
    //2、找形参和变量声明,然后将形参和变量声明作为AO的属性名,属性值为undefined
    name:undefined,
    sex:undefined,
    nums=undefined,
    fn:undefined,
    //3、将实参和形参统一
    name:html,
    sex:undefined,
    nums=123,
    fn:function(){},
    //4、在函数体内找函数声明,将函数名作为AO对象的属性名,属性值予函数体
    name:function(){},
    sex:undefined,
    fn:function(){},
    nums:123//这里不仅存在nums变量声明,也存在nums函数声明,但是取前者的值
    
    以上步骤得到的值,会按照后面步骤得到的值覆盖前面步骤得到的值
}
//依次打印
//[Function: name]
//[Function: name]
//123
//[Function: fn]

3.发生在全局(内层作用域可以访问外层作用域)

同发生在函数执行前一样,发生在全局的预编译也有自己的三部曲:

(1) Créer un objet GO (Global Object) (2) Trouver la déclaration de variable globale, utiliser la déclaration de variable comme nom de propriété de GO, et la valeur de la propriété est indéfinie (3) Trouver la déclaration de fonction globalement et utiliser la fonction name comme nom de propriété de l'objet GO. La valeur de l'attribut est affectée au corps de la fonction, par exemple :

var global='window';
function foo(a){
    console.log(a);
    console.log(global);
    var b;
}
var fn=function(){};
console.log(fn);
foo(123);
console.log(b);

Cet exemple est relativement simple, les mêmes étapes et idées ne seront pas répétées dans l'analyse, je crois que vous le connaissez déjà. Les résultats d'impression sont :

[Function: fn]
123
window
ReferenceError: b is not defined

Très bien, sur la bonne voie, passons à la chaîne de portée.

Troisièmement, la chaîne de portée

La chaîne de portée peut nous aider à découvrir pourquoi la couche interne peut accéder à la couche externe, mais la couche externe ne peut pas accéder à la couche interne ? Mais encore une fois, avant de pouvoir comprendre la chaîne de portée, nous devons voir des termes abstraits plus obscurs.

  1. 执行期上下文: Lorsqu'une fonction s'exécute, un objet appelé contexte d'exécution (objet AO) est créé. Un contexte d'exécution définit l'environnement dans lequel une fonction s'exécute. Chaque fois qu'une fonction est exécutée, le contexte d'exécution correspondant est unique, donc appeler une fonction plusieurs fois entraînera la création de plusieurs contextes d'exécution. Lorsque la fonction est exécutée, les contextes d'exécution générés seront détruits.
  2. 查找变量: Recherche vers le bas à partir du haut de la chaîne de portée.

3. [[scope]]: Les propriétés délimitées, également appelées propriétés implicites, ne sont prises en charge que par le moteur lui-même. Portée de la fonction, inaccessible, où la liaison du contexte d'exécution est stockée.

Examinons d'abord les propriétés intégrées de la fonction :

function test(){//函数被创建的那一刻,就携带name,prototype属性
      console.log(123);
}
console.log(test.name);//test
console.log(test.prototype);//{} 原型
// console.log(test[[scope]]);访问不到,作用域属性,也称为隐式属性

// test() --->AO:{}执行完毕会回收
// test() --->AO:{}执行完毕会回收

Voyons comment la chaîne de portée est implémentée :

var global='window';
function foo(){
    function fn(){
        var fn=222;
    }
    var foo=111;
    console.log(foo);
}
foo();

analyser:

GO:{
    foo:function(){}
}
fooAO:{
    foo:111,
    fn:function(){}
}
fnAO:{
    fn:222
}
// foo定义时 foo.[[scope]]---->0:GO{}
// foo执行时 foo.[[scope]]---->0:AO{}  1:GO{}  后访问的在前面
//fn定义时 fn.[[scope]]---->0:fnAO{} 1:fooAO{}  2:GO{}
fnAO:fn的AO对象;fooAO:foo的AO对象

image.png

Pour résumer:作用域链就是[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。

Je suppose que tu aimes

Origine juejin.im/post/7116516393100853284
conseillé
Classement