const声明一个只读的常量,一旦声明,常量的值就不能被改变。python
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
上面代码代表改变常量的值会报错。git
const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须当即初始化,不能留到之后赋值程序员
const foo; // SyntaxError: Missing initializer in const declaration
上面代码表示,对于const
来讲,只声明不赋值,就会报错。github
const
的做用域与let
命令相同:只在声明所在的块级做用域内有效。编程
if (true) { const MAX = 5; } MAX // Uncaught ReferenceError: MAX is not defined
const
命令声明的常量也是不提高,一样存在暂时性死区,只能在声明的位置后面使用。数组
if (true) { console.log(MAX); // ReferenceError const MAX = 5; }
上面代码在常量MAX
声明以前就调用,结果报错,const
声明的常量,也与let
同样不可重复声明。浏览器
var message = "Hello!"; let age = 25; // 如下两行都会报错 const message = "Goodbye!"; const age = 30;
const
实际上保证的,并非变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,所以等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const
只能保证这个指针是固定的,至于它指向的数据结构是否是可变的,就彻底不能控制了。所以,将一个对象声明为常量必须很是当心。安全
const foo = {}; // 为 foo 添加一个属性,能够成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另外一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only
上面代码中,常量foo
储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo
指向另外一个地址,但对象自己是可变的,因此依然能够为其添加新属性。数据结构
const a = []; a.push('Hello'); // 可执行 a.length = 0; // 可执行 a = ['Dave']; // 报错
上面代码中,常量a
是一个数组,这个数组自己是可写的,可是若是将另外一个数组赋值给a
,就会报错。模块化
若是真的想将对象冻结,应该使用Object.freeze
方法。
const foo = Object.freeze({}); // 常规模式时,下面一行不起做用; // 严格模式时,该行会报错 foo.prop = 123;
上面代码中,常量foo
指向一个冻结的对象,因此添加新属性不起做用,严格模式时还会报错。
除了将对象自己冻结,对象的属性也应该冻结。下面是一个将对象完全冻结的函数。
var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); };
ES5 只有两种声明变量的方法:var
命令和function
命令。ES6 除了添加let
和const
命令,后面章节还会提到,另外两种声明变量的方法:import
命令和class
命令。
顶层对象,在浏览器环境指的是window
对象,在 Node 指的是global
对象。ES5 之中,顶层对象的属性与全局变量是等价的。
window.a = 1; a // 1 a = 2; window.a // 2
上面代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是无法在编译时就报出变量未声明的错误,只有运行时才能知道(由于全局变量多是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就建立了全局变量(好比打字出错);最后,顶层对象的属性是处处能够读写的,这很是不利于模块化编程。另外一方面,window
对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var
命令和function
命令声明的全局变量,依旧是顶层对象的属性;另外一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var a = 1; // 若是在 Node 的 REPL 环境,能够写成 global.a // 或者采用通用方法,写成 this.a window.a // 1 let b = 1; window.b // undefined
上面代码中,全局变量a
由var
命令声明,因此它是顶层对象的属性;全局变量b
由let
命令声明,因此它不是顶层对象的属性,返回undefined
。
ES5 的顶层对象,自己也是一个问题,由于它在各类实现里面是不统一的。
window
,但 Node 和 Web Worker 没有window
。self
也指向顶层对象,可是 Node 没有self
。global
,但其余环境都不支持。同一段代码为了可以在各类环境,都能取到顶层对象,如今通常是使用this
变量,可是有局限性。
this
会返回顶层对象。可是,Node 模块和 ES6 模块中,this
返回的是当前模块。this
,若是函数不是做为对象的方法运行,而是单纯做为函数运行,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'); };
如今有一个提案,在语言标准的层面,引入global
做为顶层对象。也就是说,在全部环境下,global
都是存在的,均可以从它拿到顶层对象。
垫片库system.global
模拟了这个提案,能够在全部环境拿到global
。
// CommonJS 的写法 require('system.global/shim')(); // ES6 模块的写法 import shim from 'system.global/shim'; shim();
上面代码能够保证各类环境里面,global
对象都是存在的。
// CommonJS 的写法 var global = require('system.global')(); // ES6 模块的写法 import getGlobal from 'system.global'; const global = getGlobal();
上面代码将顶层对象放入变量global
。