ES6 语言基础 - archering/basic GitHub Wiki

//ES6的系统学习可以参考 这个文章 http://es6.ruanyifeng.com/#docs/symbol

ECMAScript2015 vs ES6

(1)标准委员会决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本 (2)ES6 的第一个版本在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。 (3)2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准。 (4)2017 年 6 月发布 ES2017 标准。

ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。

由于目前并不是所有浏览器都支持ES6,所以需要借助工具将ES6编写的程序转成ES5的代码。Babel是目前广泛使用的转码器,他是一个第三方开发的jvascript库,运行在node下。

在unix like的操作系统上,常常能看到.bashrc 或者shrc .bowerrc等 rc文件,这个rc的全称是 “run command”。 脚本程序在启动阶段通常会首先读取rc文件。

Babel 的配置文件是.babelrc,存放在项目的根目录下。使用 Babel 的第一步,就是配置这个文件。

该文件用来设置转码规则和插件,基本格式如下。

{
 "presets": [],
 "plugins": []
}

presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装。

最新转码规则

$ npm install --save-dev babel-preset-latest

react 转码规则

$ npm install --save-dev babel-preset-react

然后,将这些规则加入.babelrc。

  {
    "presets": [
      "latest",
      "react"
    ],
    "plugins": []
  }

注意,以下所有 Babel 工具和模块的使用,都必须先写好.babelrc。 Babel 6.0 开始,不再直接提供浏览器版本,而是要用构建工具构建出来.

ES6新增let,const 和块级作用域

const 常量是块级作用域;此声明创建一个常量,其作用域可以是全局或本地声明的块。 与var变量不同,全局常量不会变为窗口对象window(最顶层对象global)的属性。声明后必须立即初始化,初始化后不能修改。

let 声明一个作用域被限制在块级中的变量、语句或者表达式。与var关键字不同的是,它声明的变量只能是全局或者整个函数块的; 和var 不同,像if-else ,function, switch 这样的块都可以阻断let声明变量的延续,let并不会像var一样在全局对象上创造一个属性。不会声明前置。

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // 同样的变量!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // 不同的变量
    console.log(x);  // 2
  }
  console.log(x);  // 1

var 函数作用域,会声明前置会给全局对象window创建属性

块状作用域的可视化理解 :块语句(或其他语言的复合语句)用于组合零个或多个语句。该块由一对__大括号__界定,可以是labelled:

let x = 1;
{
  let x = 2;
}
console.log(x); // 输出 1

下面的代码如果使用var,最后输出的是10。

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域

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

上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。 箭头函数 Arrow Functions

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域凡是在声明之前就使用这些变量,就会报错

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

“暂时性死区”也意味着typeof不再是一个百分之百安全的操作 //会报错ReferenceError

typeof x; // ReferenceError
let x;

如果一个变量根本没有被声明,使用typeof反而不会报错

typeof undeclared_variable // "undefined"
// 不报错
var x = x;

// 报错
let x = x;
// ReferenceError: x is not defined

上面代码报错,也是因为暂时性死区。使用let声明变量时,只要变量在还没有声明完成前使用,就会报错


ES6 数组赋值 Destructuring

var arr = [];
a[0] = 1;
a[1] = 2;
a[2] = 3;

现在允许

let [a,b,c] = [1,2,3];

如果解构不成功,变量的值就等于undefined。

let [foo] = [];
let [bar, foo] = [1];

以上两种情况都属于解构不成功,foo的值都会等于undefined。

解构不仅可以用于数组,还可以用于对象。

let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。


ES6 的函数变化非常大

(1) ES6 允许为函数的参数设置默认值 ,定义了默认值的参数,应该是函数的尾参数

function log(x, y = 'World') {
  console.log(x, y);
}
//参数变量x是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。

使用参数默认值时,函数不能有同名参数。

// 不报错
function foo(x, x, y) {
  // ...
}

// 报错
function foo(x, x, y = 1) {
  // ...
}

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数

(2) ES6 引入 rest 参数(形式为...变量名), 不知道有几个具体的参数, 和Actionscript 一样;rest 参数之后不能再有其他参数,函数的length属性,不包括 rest 参数

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10

从 ES5 开始,函数内部可以设定为严格模式。

function doSomething(a, b) { 'use strict'; // code } ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错

(3) **箭头函数 ** ;ES6 允许使用“箭头”(=>)定义函数

var f = v => v;  
//f 函数名 
// v 参数
// =>  回调函数  function(){}
//箭头后的 v  返回值  相当于  return v;

上面的箭头函数等同于:

var f = function(v) {
  return v;
};
下面逐个说箭头函数的 “参数部分” 和 “代码块部分”

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

var sum = (num1, num2) => { return num1 + num2; }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象{ id: id, name: "Temp" },必须在对象外面加上括号,否则会报错。

// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

箭头函数导致this总是指向函数定义生效时所在的对象

如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。

let fn = () => void doesNotReturn();

正常的箭头函数怎么写

let hello = (a,b) => { 
  a = a*3;  
  b = a*b;   
  console.log(a + b);  
return a+b + 1;  
}


//执行
hello(1,2);// return 10 

ES6 class

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的_模板_。 ES6 的class可以看作只是一个语法糖(syntax sugar) 即换了一种形式的语法包装,实际还是function那一套。 例子说明,如果用class 实现编写同一段代码

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);
//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  //定义“class”的方法的时候,前面不需要加上function这个关键字
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}



//typeof Point // "function"   // 可以看到这个 class 顶一个point 就是function

ES6 的 module 系统和 import export

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取 ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西. CommonJS 和 AMD 加载都是在运行时,加载整个对象(模块即对象),然后在取对象的具体成员进行操作, ES6模块不是对象,而且ES6模块的是编译时加载,即在编译期决定需要模块的那部分内容,按需加载,在运行期这些模块已经加载好了。 ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。

export

export命令用于规定模块的对外接口 e.g

profile.js文件,保存了用户信息, ES6 将其视为一个模块,里面用export命令对外部输出了三个变量

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

export命令除了输出变量,还可以输出函数或类(class)。 只要是这个module的成员都可以输出

export function multiply(x, y) {
  return x * y;
};

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

// 报错
export 1;

// 报错
var m = 1;
export m;

上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出 1,第二种写法通过变量m,还是直接输出 1。> > > 1只是一个值,不是接口。正确的写法是下面这样。

// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

import 命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。