Maison  >  Article  >  interface Web  >  Analyse approfondie de la portée Javascript (exemple de code)

Analyse approfondie de la portée Javascript (exemple de code)

不言
不言avant
2018-11-23 14:54:051470parcourir

Ce que cet article vous apporte est une analyse approfondie (exemple de code) de la portée de Javascript. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Scope

Scope est un ensemble de règles qui déterminent où et comment les variables (identifiants) sont trouvées. Si le but de la recherche est d'attribuer une valeur à une variable, la requête LHS sera utilisée ; si le but est d'obtenir la valeur de la variable, la requête RHS sera utilisée ; Les opérateurs d'affectation provoquent des requêtes LHS. L'opérateur = ou l'opération de passage de paramètres lors de l'appel d'une fonction entraînera une opération d'affectation dans la portée associée.

Le moteur JavaScript compile d'abord le code avant son exécution. Au cours de ce processus, une instruction comme var a = 2 se décompose en deux étapes distinctes :

  1. Première. , var a déclare la nouvelle variable dans sa portée. Cela se produit au tout début, avant l’exécution du code.

  2. Ensuite, a = 2 interrogera (requête LHS) la variable a et lui attribuera une valeur.

Les requêtes LHS et RHS démarreront dans la portée d'exécution actuelle, et si nécessaire (c'est-à-dire qu'elles ne trouvent pas l'identifiant requis), elles continueront à rechercher la portée supérieure . Identifiant cible, de sorte qu'à chaque fois qu'il monte d'un niveau de portée (un étage), et atteint finalement la portée globale (niveau supérieur), il s'arrêtera qu'il soit trouvé ou non.

Les références RHS infructueuses entraîneront la levée d’une exception ReferenceError. Une référence LHS infructueuse entraîne la création automatique et implicite d'une variable globale (en mode non strict) qui utilise la cible de la référence LHS comme identifiant, ou une exception ReferenceError (en mode strict).

Portée lexicale

La portée lexicale signifie que la portée est déterminée par la position de la déclaration de fonction lors de l'écriture du code. La phase d'analyse lexicale de la compilation sait essentiellement où et comment tous les identifiants sont déclarés, ce qui lui permet de prédire comment ils seront recherchés lors de l'exécution.

Il existe deux mécanismes en JavaScript pour "tricher" la portée lexicale : eval(..) et with . Le premier évalue une chaîne de « code » contenant une ou plusieurs déclarations, modifiant ainsi une portée lexicale existante (au moment de l'exécution). Ce dernier crée essentiellement une nouvelle portée lexicale (toujours au moment de l'exécution) en traitant une référence à un objet comme une portée et les propriétés de l'objet comme des identifiants dans la portée.

Un effet secondaire de ces deux mécanismes est que le moteur ne peut pas optimiser les recherches de portée au moment de la compilation, car le moteur ne peut que considérer avec prudence que de telles optimisations sont invalides. L’utilisation de l’un de ces mécanismes entraînera une exécution plus lente de votre code. Ne les utilisez pas.

Étant donné que JavaScript utilise une portée lexicale, la portée de la fonction est déterminée lorsque la fonction est définie.

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();//local scope
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();//local scope
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();//local scope

Expressions de fonction et déclarations de fonction

Déclaration de fonction :nom de la fonction (paramètres : facultatifs) {corps de la fonction}
Expression de la fonction : nom de la fonction (facultatif) (paramètre : facultatif) {corps de la fonction}

Identification :

  • Si la fonction le nom n'est pas déclaré, ce doit être une expression.

  • Si la fonction foo(){} fait partie d'une expression d'affectation, c'est une expression de fonction. Si la fonction foo(){} est incluse dans un corps de fonction, ou si elle est à en haut du programme, c'est une déclaration de fonction.

  • (fonction foo(){}) entre parenthèses, la raison pour laquelle il s'agit d'une expression est que les parenthèses () sont un opérateur de regroupement et ne peuvent contenir que des expressions à l'intérieur. Bien que la déclaration de fonction de la formule

  function foo(){} // 声明,因为它是程序的一部分

  (function(){
    function bar(){} // 声明,因为它是函数体的一部分
  })();

  var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分
  new function bar(){}; // 表达式,因为它是new表达式
  (function foo(){}); // 表达式:包含在分组操作符内
  
  try {
    (var x = 5); // 分组操作符,只能包含表达式而不能包含语句:这里的var就是语句
  } catch(err) {
    // SyntaxError
  }
  • peut être utilisée dans les instructions conditionnelles, elle n'a pas été standardisée. Il est préférable d'utiliser des expressions de fonction

    .
  • La déclaration de fonction remplacera la déclaration de variable, mais pas l'affectation de variable

function value(){
    return 1;
}
var value;
alert(typeof value);    //"function"

Portée de la fonction et portée du bloc

La fonction est en JavaScript L'unité de portée la plus courante. Essentiellement, une variable ou une fonction déclarée à l'intérieur d'une fonction est "cachée" de la portée dans laquelle elle se trouve. Il s'agit d'un principe de conception logicielle intentionnel et efficace. Mais les fonctions ne constituent pas la seule unité de portée.

La portée du bloc signifie que les variables et les fonctions peuvent non seulement appartenir à la portée dans laquelle elles se trouvent, mais également appartenir à un certain bloc de code (généralement dans { .. }).

À partir d'ES3, les constructions try/catch ont une portée de bloc dans la clause catch.

Le mot-clé let (cousin du mot-clé var) a été introduit dans ES6 pour déclarer des variables dans n'importe quel bloc de code.
if(..) { let a = 2; } déclare une variable qui détourne le bloc { .. } de if et ajoute la variable à ce bloc.

Certaines personnes pensent que la portée du bloc ne devrait pas être entièrement utilisée pour remplacer la portée de la fonction. Les deux fonctions doivent exister en même temps. Les développeurs peuvent et doivent choisir la portée à utiliser en fonction de leurs besoins pour créer un bon code lisible et maintenable.

提升

我们习惯将 var a = 2; 看作一个声明,而实际上 JavaScript 引擎并不这么认为。它将 var a和 a = 2 当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。

这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升。

声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
要注意避免重复声明,特别是当普通的 var 声明和函数声明混合在一起的时候,否则会引
起很多危险的问题!

var a;
if (!("a" in window)) {
    a = 1;
}
alert(a);

作用域闭包

通常,程序员会错误的认为,只有匿名函数才是闭包。其实并非如此,正如我们所看到的 —— 正是因为作用域链,使得所有的函数都是闭包(与函数类型无关: 匿名函数,FE,NFE,FD都是闭包), 这里只有一类函数除外,那就是通过Function构造器创建的函数,因为其[[Scope]]只包含全局对象。 为了更好的澄清该问题,我们对ECMAScript中的闭包作两个定义(即两种闭包):

ECMAScript中,闭包指的是:

从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量

循环闭包

for (var i=1; i<=5; i++) {
    (function(j) {
        setTimeout( function timer() {
        console.log( j );
        }, j*1000 );
    })( i );
}

for (var i=1; i<=5; i++) {
    let j = i; // 是的,闭包的块作用域!
    setTimeout( function timer() {
    console.log( j );
    }, j*1000 );
}

for (let i=1; i<=5; i++) {
    setTimeout( function timer() {
    console.log( i );
    }, i*1000 );
}
var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();//3
data[1]();//3
data[2]();//3

模块

模块有两个主要特征:(1)为创建内部作用域而调用了一个包装函数;(2)包装函数的返回
值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭
包。

现代模块机制

var MyModules = (function Manager() {
    var modules = {};
    function define(name, deps, impl) {
        for (var i=0; i<deps.length; i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply( impl, deps );
    }
    function get(name) {
        return modules[name];
    }
    return {
        define: define,
        get: get
    };
})();

未来模块机制

//bar.js
function hello(who) {
    return "Let me introduce: " + who;
}
export hello;
//foo.js
// 仅从 "bar" 模块导入 hello()
import hello from "bar";
var hungry = "hippo";
function awesome() {
    console.log(
        hello( hungry ).toUpperCase()
    );
}
export awesome;
baz.js
// 导入完整的 "foo" 和 "bar" 模块
module foo from "foo";
module bar from "bar";
console.log(
    bar.hello( "rhino" )
); // Let me introduce: rhino
foo.awesome(); // LET ME INTRODUCE: HIPPO

块作用域替代方案

Google 维护着一个名为 Traceur 的项目,该项目正是用来将 ES6 代码转换成兼容 ES6 之前的环境(大部分是 ES5,但不是全部)。TC39 委员会依赖这个工具(也有其他工具)来测试他们指定的语义化相关的功能。

{
    try {
        throw undefined;
    } catch (a) {
        a = 2;
        console.log( a );
    }
}
console.log( a )

上下文

EC(执行环境或者执行上下文,Execution Context)

EC={
    VO:{/* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */},
    this:{},
    Scope:{ /* VO以及所有父执行上下文中的VO */}
}

ECS(执行环境栈Execution Context Stack)

//ECS=[Window]
A(//ECS=[Window,A]
    B(//ECS=[Window,A,B]
        //run B 
    )
    //ECS=[Window,A]
)
//ECS=[Window]

VO(变量对象,Variable Object)

var a = 10;
function test(x) {
  var b = 20;
};
test(30);
/*
VO(globalContext)
  a: 10,
  test: 
VO(test functionContext)
  x: 30
  b: 20
*/

AO(活动对象,Active Object)

function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
} 
test(10);
/*
AO(test) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: <reference to FunctionDeclaration "d">
  e: undefined
};
*/

scope chain(作用域链)和[[scope]]属性

Scope = AO|VO + [[Scope]]

例子

var x = 10;
 
function foo() {
  var y = 20;
 
  function bar() {
    var z = 30;
    alert(x +  y + z);
  }
 
  bar();
}
 
foo(); // 60
  • 全局上下文的变量对象是:

globalContext.VO === Global = {
  x: 10
  foo: <reference to function>
};
  • 在“foo”创建时,“foo”的[[scope]]属性是:

foo.[[Scope]] = [
  globalContext.VO
];
  • 在“foo”激活时(进入上下文),“foo”上下文的活动对象是:

fooContext.AO = {
  y: 20,
  bar: <reference to function>
};
  • “foo”上下文的作用域链为:

fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:
 
fooContext.Scope = [
  fooContext.AO,
  globalContext.VO
];
  • 内部函数“bar”创建时,其[[scope]]为:

bar.[[Scope]] = [
  fooContext.AO,
  globalContext.VO
];
  • 在“bar”激活时,“bar”上下文的活动对象为:

barContext.AO = {
  z: 30
};
  • “bar”上下文的作用域链为:

barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:
 
barContext.Scope = [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];
  • 对“x”、“y”、“z”的标识符解析如下:

- "x"
-- barContext.AO // not found
-- fooContext.AO // not found
   globalContext.VO // found - 10

- "y"
-- barContext.AO // not found
   fooContext.AO // found - 20

- "z"
   barContext.AO // found - 30

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer