let/const/var
let
基本语法
ES6新增了let命令,用于声明变量,其语法类似于var,但是所声明的变量只在let命令所在的代码块内有效
不存在变量提升
var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。
暂时性死区
只要块级作用域内存在let命令,他所声明的变量就”绑定“这个区域,不在受外部影响。
在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上称为”暂时性死区“
不允许重复声明
let 不允许在相同作用域内重复声明同一个变量
const
基本语法
const声明一个只读的常量。一旦声明,常量值就不能改变。
const一旦声明常量,就必须立即初始化,不能留到以后赋值
const不存在变量提升,同样存在暂时性死区,只能在声明后使用,不可重复声明。
本质
const本质上保证的并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。
对于简单类型的数据(数值、字符串、布尔值)而言,值就保存在变量指向的内存地址中,因此等同于常量。
但对于复合类型的数据(主要是对象和数组)而言,变量指向的内存地址保存的只是一个指针,const只能保证这个指针是固定的,至于他指向的数据结构是不是可变的,这完全不能控制。
因此,将一个对象声明为常量时必须非常小心。
如果真的想将对象冻结,应该使用Object.freeze方法。
执行上下文
概念
- 执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。
执行上下文的类型
- 全局执行上下文
这是默认的或基本的执行上下文。任何不在函数内部的代码位于全局执行上下文中。它执行两件事:它创建一个全局对象,它是一个window对象(在浏览器的情况下),并将this的值设置为等于全局对象。一个程序中只能有一个全局执行上下文。
- 函数执行上下文
每次调用函数时,都会为该函数创建一个全新的执行上下文。每个函数都有自己的执行上下文,但它是在调用或调用(原文是it’s created when the function is invoked or called)函数时创建的。可以有任意数量的函数执行上下文。每当创建一个新的执行上下文时,它都会按照已定义的顺序执行一系列步骤,我将在本文后面讨论这些步骤。
- Eval 函数执行上下文
在Eval函数内部执行的代码也会获得它自己的执行上下文,但JavaScript开发人员通常不使用Eval
执行上下文包含部分
- 在ES3 中
-
scope:作用域,也常常被叫做作用域链。
-
variable object:变量对象,用于存储变量的对象。
-
this value:this 值。
- 在ES5 中
-
lexical environment:词法环境,当获取变量时使用。
-
variable environment:变量环境,当声明变量时使用。
-
this value:this 值。
- 在ES2018 中, this 值被归入 lexical environment
- lexical environment:词法环境,当获取变量或者 this 值时使用。
- variable environment:变量环境,当声明变量时使用
- code evaluation state:用于恢复代码执行位置。
- Function:执行的任务是函数时使用,表示正在被执行的函数。
- ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码。
- Realm:使用的基础库和内置对象实例。-Generator:仅生成器上下文有这个属性,表示当前生成器。
执行上下文的创建分为两个阶段
一、创建阶段
1、LexicalEnvironment(词法环境) 组件被创建。
2、VariableEnvironment(变量环境) 组件被创建
ExecutionContext = {
LexicalEnvironment = <ref. to LexicalEnvironment in memory>,
VariableEnvironment = <ref. to VariableEnvironment in memory>,
}
词法环境(Lexical Environment)
-
环境记录器(Environment Record)
-
声明性环境记录(Declarative environment record)
顾名思义,它存储变量和函数声明。
函数代码的词法环境包含一个声明性环境记录。 -
对象环境记录(Object environment record)
全局代码的词法环境包含一个客观环境记录。
除了变量和函数声明,对象环境记录还存储了一个全局绑定对象(浏览器中的window对象)。
因此,对于每个绑定对象的属性(在浏览器中,它包含浏览器提供给window对象的属性和方法),记录中会创建一个新条目(new entry)。
-
-
对外部环境的引用(Reference to the outer environment)
指的是它能够接触到外部的词法环境。这意味着,如果在当前词法环境中没有找到想要查找的变量,JavaScript引擎可以在外部环境中查找它们。
-
this绑定(This binding.)
在此组件中,this的值被确定或设置(determined or set)。
在全局执行上下文中,this的值指向全局对象(在浏览器中,它指的是Window对象)或未定义(在严格模式下)。
在函数执行上下文中,this的值取决于函数的调用方式。
变量环境 (Variable Environment)
它也是一个词法环境,它的环境记录器(EnvironmentRecord)保存由VariableStatements 在执行上下文中创建的绑定。
如上所述,变量环境也是一个词法环境,因此它具有上述定义的词法环境的所有属性和组件。
在ES6中,词法环境(LexicalEnvironment)组件和变量环境(VariableEnvironment)组件之间的一个区别是,前者用于存储函数声明和变量(let和const)绑定,而后者仅用于存储变量(var)绑定。
二、执行阶段
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
- 当执行上述代码时(the above code is executed),JavaScript引擎创建一个全局执行上下文来执行全局代码。所以在创建阶段,全局执行上下文看起来像这样:
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
a: < uninitialized > ,
b: < uninitialized > ,
multiply: < func >
}
outer: < null > ,
ThisBinding: < Global Object >
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
c: undefined,
}
outer: < null > ,
ThisBinding: < Global Object >
}
}
- 在执行阶段(During the execution phase),完成变量赋值。因此,在执行阶段,全局执行上下文将类似于以下内容。
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
a: 20,
b: 30,
multiply: < func >
}
outer: <null>,
ThisBinding: <Global Object>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
c: undefined,
}
outer: <null>,
ThisBinding: <Global Object>
}
}
- 当遇到对function multiply(20,30)的调用时,将创建一个新的函数执行上下文来执行函数代码。所以在创建阶段,函数执行上下文看起来像这样:
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
g: undefined
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>
}
}
- 在此之后,执行上下文将经历执行阶段(the execution phase),这意味着完成对函数内变量的赋值。所以在执行阶段,函数的执行上下文看起来像这样:
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
g: 20
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>
}
}
变量提升原理
你可能已经注意到let和const定义的变量在创建阶段没有任何关联的值,但是var定义的变量被设置为undefined。
这是因为,在创建阶段,代码被扫描以查找变量和函数声明,而函数声明被完整地存储在环境中,变量最初被设置为未定义(对于var)或保持未初始化(对于let和const)。
这就是为什么你可以在声明之前访问var定义的变量(虽然未定义),但在声明之前访问let和const变量时会得到引用错误的原因。
顶层对象的属性
顶层对象在浏览器环境中指的是window对象,在Node环境中指的是global对象。在ES5中,顶层对象的属性与全局变量是等价的。
顶层对象的属性与全局变量相关,被认为是JavaScript语言中最大的设计败笔之一。这样的设计带来了几个很大的问题:
- 首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);
- 其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);
- 最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6为了改变这一点,
- 一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;
- 另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从ES6开始,全局变量将逐步与顶层对象的属性脱钩。
global对象
ES5的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。
- 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
- 浏览器和 Web Worker 里面,self也指向顶层对象,但是Node没有self。
- Node 里面,顶层对象是global,但其他环境都不支持。
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。
- 全局环境中,this会返回顶层对象。但是,Node模块和ES6模块中,this返回的是当前模块。
- 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
- 不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了CSP(Content Security Policy,内容安全政策),那么eval、new Function这些方法都可能无法使用。