let 和 const 命令
ES6 声明变量的六种方法
ES5 只有两种声明变量的方法:var
命令和function
命令。
ES6 除了添加let
和const
命令,还有另外两种声明变量的方法:import
命令和class
命令。所以,ES6 一共有 6 种声明变量的方法。
let 命令
基本用法
- let用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
- 定义的变量只在该作用区域内有效,所以不会影响父作用域
{
let a = 1
var b = 2
}
a // 报错:Uncaught ReferenceError: a is not defined at <anonymous>:1:1
b // 2
let 定义的变量只在该作用区域内有效,所以不会影响父作用域
所以很适合在for 循环中使用
var arr = []
for(var i =0; i< 10; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[1]() // 10
arr[5]() // 10
以上循环出的arr
内的方法 执行的都是console.log(i)
, 这里的i就是一个全局变量,输出始终输出全局的i
所以arr
的每一个成员输出结果都一样
要正确输出遍历的数字,应该使用let
来定义循环的变量
var arr = []
for(let i =0; i< 10; i++) {
arr[i] = function () {
console.log(i)
}
}
不存在变量提升
使用var
声明变量后变量变为全局,如果在声明之前调用变量,他的值为undefined
而不报错。
使用let
声明变量后,在声明之前调用变量就会报错,使逻辑看起来更合理,并可以正确的找到错误
// var 的情况
console.log(foo) // 输出:undefined
var foo = 2
// let 的情况
console.log(bar) // 报错:Uncaught ReferenceError: bar is not defined at <anonymous>:1:13
let bar = 2
let的这一特性在 块级作用域内同样适用
{
a = 1 //报错: Uncaught ReferenceError: a is not defined at <anonymous>:1:5
let a
}
这个功能会影响到typeof
的使用
typeof xxx // 一个不会声明的变量: undefined
typeof a // undefined
var a
typeof b //报错: Uncaught ReferenceError: b is not defined at <anonymous>:1:5
let b
类似以下代码也会报错
var x = x // undefined
let x = x // ReferenceError: x is not defined
不允许重复声明
let a = 1
let a = 2 // 报错:Uncaught SyntaxError: Identifier 'dsfsdf' has already been declared at <anonymous>:1:1
var a = 1
let a = 2 // 报错:Uncaught SyntaxError: Identifier 'dsfsdf' has already been declared at <anonymous>:1:1
块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
为什么需要块级作用域
如果不使用块级作用域 会出现以下情况
- 内层变量可能会覆盖外层变量
var tmp = new Date()
function f() {
console.log(tmp)
if (false) {
var tmp = 'hello world'
}
}
f() // undefined
- 用来计数的循环变量泄露为全局变量。
for (var i = 0; i < 10; i++) {
console.log(i)
}
console.log(i) // 10
ES6 的块级作用域
let为 JavaScript 新增了块级作用域。
function f1() {
let n = 5
if (true) {
let n = 10
}
console.log(n) // 5
}
块级作用域以下特点:
-
允许块级作用域的任意嵌套
-
外层作用域无法读取内层作用域的变量
-
内层作用域可以定义外层作用域的同名变量
{{{{
{
let insane = 'Hello World'
{let insane = 'Hello World'}
}
console.log(insane) // 报错
}}}}
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了
// IIFE 写法
(function () {
var tmp = ...
...
}())
// 块级作用域写法
{
let tmp = ...
...
}
块级作用域与函数声明
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
但是为了兼容部分旧代码,所以在块级作用域声明函数不会报错,可以运行
// 情况一
if (true) {
function f() {}
}
// 情况二
try {
function f() {}
} catch(e) {
// ...
}
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。
ES6 规定,块级作用域之中,函数声明语句的行为类似于let
,在块级作用域之外不可被引用。
但是,实际运行结果并不是这样,为了兼容部分老代码。仅对于ES6环境的浏览器运行中块级作用域之中声明的函数行为类似于var
,因此会存在变量提升。
function f() { console.log('I am outside!') }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!') }
}
f()
}())
像上面的代码理论上是是会执行外面的f()
的,实际上并不会,而且还会因为变量提升报错。
在开发过程中,应该避免在块级作用域中声明函数,如果实在需要,最好写成函数表达式,而不是函数声明语句。
// 函数声明语句
{
function f() {
return 11
}
}
// 函数表达式
{
let f = function () {
return 111
}
}
const 命令
基本用法
const
声明一个只读的常量。
- 一旦声明,常量的值就不能改变。
- 因为第一条的原因,
const
声明常量的时候就必须赋值。 const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。const
声明的常量,也与let
一样不可重复声明。
// 1
const a = 1
a = 111 // 报错:TypeError: Assignment to constant variable.
// 2
const b // 报错: Uncaught SyntaxError: Missing initializer in const declaration
// 3
{
const c = 33
}
c // 报错: Uncaught ReferenceError: c is not defined
const 本质
const
保证的并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。
对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。
const obj = {}
obj.a = 1 // obj = {a: 1}
obj = {a: 1} // 报错:Uncaught TypeError: Assignment to constant variable.
const arr = []
arr[0] = 'a' // arr = ['a']
arr = ['a'] // 报错:Uncaught TypeError: Assignment to constant variable.
如果想要一个无法更改的对象,可以使用Object.freeze
方法。
const obj = Object.freeze({a: 1})
// 常规模式时,下面一行不起作用
// 严格模式时,该行会报错
obj.a = 2
对象还有一些属性也应该冻结,下面是一个将对象彻底冻结的函数。
var constantize = (obj) => {
Object.freeze(obj)
for (let key of Object.keys(obj)) {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
}
}
顶层对象的属性
顶层对象,在浏览器环境指的是window
对象,在 Node
指的是global
对象
ES5 之中,顶层对象的属性与全局变量是等价的。
window.a = 1;
a // 1
a = 2;
window.a // 2
ES6 中:
var
命令和function
命令声明的全局变量,依旧是顶层对象的属性;let
命令、const
命令、class
命令声明的全局变量,不属于顶层对象的属性。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
global 对象
ES5 的顶层对象存在一些问题,各种环境下顶层对象不统一
- 浏览器里面,顶层对象是
window
和self
- Web Worker里面,顶层对象是
self
- Node 里面,顶层对象是
global
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this
变量,但是有局限性。
- 全局环境中,
this
会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。 - 函数里面的
this
,在严格模式下会返回undefined
- 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全政策),那么eval、new Function这些方法都可能无法使用。
综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};