const命令

基本用法

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] );
    }
  });
};

ES6声明变量的6种方法

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加letconst命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。

4.顶层对象的属性

顶层对象,在浏览器环境指的是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

上面代码中,全局变量avar命令声明,因此它是顶层对象的属性;全局变量blet命令声明,因此它不是顶层对象的属性,返回undefined

5.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,内容安全政策),那么evalnew 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

相关文章
相关标签/搜索