2.let和const命令(学习笔记)

2017-05-08  本文已影响0人  我才不是稻草人

所有内容基于阮一峰ECMAScript 6 入门


1.let命令

只在命令所在的代码块内有效。
for循环有一个特别之处,循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

不存在变量提升

暂时性死区(temporal dead zone,简称TDZ)
ar tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}
// 报错
let x = x;
// ReferenceError: x is not defined

在变量x的声明语句还没执行完成前,就去取x的值导致报错“x未定义”。


不允许重复声明

不能在函数内部重新声明参数

function func(arg) {
  let arg; // 报错
}

function func(arg) {
  {
    let arg; // 不报错
  }
}

2.块级作用域

为什么需要块级作用域

1.内层变量可能会覆盖外层变量
2.用来计数的循环变量泄露为全局变量

ES6的块级作用域
function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

外层作用域无法读取内层作用域的变量。
内层作用域可以定义外层作用域的同名变量。
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不必要了。

// IIFE 写法
(function (){
    var tmp = ...;
}());
// 块级作用域写法
{
  let tmp = ...;
  ...
}

块级作用域与函数声明

ES6中允许在块级作用域中声明函数。
ES6中块级作用域内声明的函数类似与let,对作用域外没有影响。
但是为了减轻因此产生的不兼容问题,浏览器的实现可以不遵守上面的规定,有自己的行为方式。

  • 允许在块级作用域内声明函数
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
  • 同时,函数声明还会提升到所在的块级作用域的头部。
    上面三条规则只对ES6的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当做let处理。
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

上面的代码在符合 ES6 的浏览器中,都会报错,因为实际运行的是下面的代码。

// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
  var f = undefined;
  if (false) {
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

考虑到环境导致的行为差异太大,应该避免在块级作用域中声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

// 函数声明语句
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 函数表达式
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}

另外,还有一个需要注意的地方,ES6允许块级作用域内声明函数的规则,只在使用大括号的情况下成立。

// 不报错
'use strict';
if (true) {
  function f() {}
}

// 报错
'use strict';
if (true)
  function f() {}

do 表达式

本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。
现在有一个提案,使得块级作用域可以变为表达式,也即是说可以返回值:

let x = do {
  let = f()
  t*t+1;
};

上面代码中,变量x会得到整个块级作用域的返回值。(只是提案,现在可能还用不了

3.const 命令

基本用法

const声明一个只读的常量,const一旦声明就必须立即初始化不能留到以后赋值。
const的作用域与let命令相同:只在声明所在块级作用域内有效。

if (true) {
  const MAX = 5;
}

MAX // Uncaught ReferenceError: MAX is not defined

const命令声明的常量也不是提升,同样存在暂时性死区,只能在声明的位置后面使用。
也不能重复声明。

本质

const实际上保证的,不是变量的值不变,而是变量指向的那个内存地址不得改变。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
const a = [];
a.push('Hello'); // 可执行
a.length = 0;    // 可执行
a = ['Dave'];    // 报错

想将对象冻结,应该使用Object.freeze方法。

const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
ES6声明变量的六种方法

var命令function命令。
ES6除了添加let和const命令,另外两种声明变量的方法:import命令和class命令。

4.顶层对象的属性

顶层对象,浏览器环境指的是window对象,在Node指的是global对象。
ES5之中,顶层对象的属性与全局变量是等价的。

window.a = 1;
a // 1

a = 2;
window.a // 2

顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。
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。

global对象

ES5的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。

  • 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
  • 浏览器和 Web Worker 里面,self也指向顶层对象,但是Node没有self。
  • Node 里面,顶层对象是global,但其他环境都不支持。

同一段代码为了在各种环境都能取到顶层对象,现在一般是使用this,但是有局限性。

  • 全局环境中,this会返回顶层对象,但是,node模块和ES6模块中返回的是当前模块。
  • 函数里面的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' &&
      typeor 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。

上一篇下一篇

猜你喜欢

热点阅读