铭哥和佩佩的博客

铭哥和佩佩的博客分享Python、PHP、JavaScript、HTML5、CSS3等各种知识

ECMA6轻松掌握

ECMA6

1. 什么是ECMA6

ECMAScript 6.0(简称 ES6)是 JavaScript 语言的下一代标准,在2015年6月正式发布。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

2. ECMAScript 和 JavaScript 的关系

ECMAScript是JavaScript的规格,JavaScript是ECMAScript的一种实现。日常场合,这两个词是可以互换的。
另外,ActionScript也是 ECMAScript的实现

1996年11月,JavaScript 的创造者 网景公司,将 JavaScript 提交给国际标准化组织ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布262号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是1.0版。

该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有网景公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被网景公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是网景,这样有利于保证这门语言的开放性和中立性。

3. ECMA2015

ES6 的第一个版本,在2015年6月发布,正式名称是《ECMAScript 2015标准》(简称 ES2015)。
2016年6月,小幅修订的《ECMAScript 2016标准》(简称 ES2016)发布,这个版本可以看作是 ES6.1 版。
2017年6月会发布 ES2017 标准。

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

4. ES6的兼容性问题

目前各大浏览器对ES6的支持度已经越来越高了,超过90%的 ES6 语法特性都实现了。
具体可参考 http://kangax.github.io/compat-table/es6/ 查看各大浏览器的最新版本对ES6的支持情况

nodejs 微信开发 完全支持ES6的语法

5. Babel 转码器

该转码器可以吧ES6语法转码为ES5语法,意味着,可以借助于Babel转码器提高ES6的兼容性。

ES6常用语法

1. 变量扩展

1.1. let 关键字

用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效

{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

for循环的计数器,就很合适使用let命令

for (let i = 0; i < 10; i++) {}

console.log(i);
//ReferenceError: i is not defined

不存在变量提升

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

不允许重复声明

let a = 100;
let a = 200; //报错

let b = 100;
b = 200; //正确

1.2 const 关键字

const声明一个只读的常量。一旦声明,常量的值就不能改变。

const PI = 3.1415;
PI // 3.1415

PI = 3; //报错

同,let一样,也不存在变量提升

1.3 顶层对象和全局变量

ES6中将顶层对象(浏览器中是window对象,node中是Global对象)与全局变量不再挂钩。
但是,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

2. 对象的简化写法

2.1 属性的简化写法

var foo = 'name1';
var bar = 'name2';

var obj = {foo,bar};
//等同于
var obj = {foo:foo, bar:bar}

2.2 方法的简化写法

{
    fn:function(){
    }
}
//可简化为
{
    fn(){
    }
}

3. 变量解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

3.1 数组的解构赋值

以前,变量赋值,只能这么写

var a = 1;
var b = 2;
var c = 3;

现在,也可以这么写

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

上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

如果解构不成功,变量的值就等于undefined
如果等号左边的形式是数组,而右边不是数组,将会报错

解构变量可以有默认值

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

3.2 对象的解构

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

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

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

如果变量名与属性名不一致,必须写成下面这样。
var { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

这实际上说明,对象的解构赋值是下面形式的简写

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

和数组一样,解构也可以用于嵌套解构的对象

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

注意,这时p是模式,不是变量,因此不会被赋值。

对象的解构也可以指定默认值

var {x, y = 5} = {x: 1};
x // 1
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"

3.3 特殊对象的解构

JavaScript中一切皆对象,向字符串、布尔值、数值都可以被解构,而且字符串可以当做字符组成的数组

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

let {length : len} = 'hello';   //因为字符串具有属性length
len // 5

let {toString: s} = 123;
s === Number.prototype.toString // true

3.4 实际用途

交换变量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

提取JSON数据

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

函数参数的默认值

jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
}) {
  // ... do stuff
};

输入模块的指定方法

const { SourceMapConsumer, SourceNode } = require("source-map");

4. 扩展运算符(...)

扩展运算符(...)可以将某些数据结构转为数组。
可以将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。

// arguments对象
function foo() {
  var args = [...arguments];
}

// NodeList对象
[...document.querySelectorAll('div')]

5. for...of 循环

ES6 借鉴 C++、Java、C# 和 Python 语言,引入了for...of循环,作为遍历所有数据结构的统一的方法。
for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)以及字符串

const arr = ['red', 'green', 'blue'];

for(let v of arr) {
  console.log(v); // red green blue
}

6. Class关键字

6.1 定义类

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

JS传统方法中定义对象是通过构造函数,这种写法跟传统的面向对象语言(比如C++和Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。

class Bar {
  doStuff() {
    console.log('stuff');
  }
}

var b = new Bar();
b.doStuff() // "stuff"

如果你了解过Java或者PHP或者C++或者.net 你将会对这种写法非常熟悉

6.2 constructor 构造方法

class Bar{
    constructro() {
        
    }
}

在类被实例化的时候, 构造函数会被自动调用

6.3 extends 继承

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

class ColorPoint extends Point {}

上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。

6.4 super

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

7. 箭头函数

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

var f = v => 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) => { 
    let sum =  num1 + num2; 
    return sum; 
}

箭头函数的一个用处是简化回调函数
// 正常函数写法
var result = values.sort(function (a, b) {
return a - b;
});

// 箭头函数写法
var result = values.sort((a, b) => a - b);

函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42

上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到100毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。

箭头函数 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

箭头函数 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。

8. ES6函数参数问题

8.1 函数默认值

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。不必那ES5那样,采用些特殊手段

//ES6写法
function log(x, y = 'World') {
  console.log(x, y);
}

//ES5写法
function log(x, y) {
    if (y === undefined) {
        y = 'World';
    }
    console.log(x,y);
}

8.2 rest参数

ES6 引入 rest 参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了

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

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

  return sum;
}

add(2, 5, 3) // 10

9. 模板字符串

传统的JavaScript语言,输出模板通常是这样写的。

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

上面这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题

$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

模板字符串(template string)是增强版的字符串,用反引号 ` 标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

模板字符串中嵌入变量,需要将变量名写在${}之中。
大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。

`User ${user.name} is not authorized to do ${action}.`
`${x} + ${y} = ${x + y}`
`foo ${fn()} bar`

如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。

10. Symbol

ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)
Symbol值通过Symbol函数生成。
Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

let s = Symbol();

typeof s
// "symbol"

var s1 = Symbol('foo');
var s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。

任意两个Symbol类型的数据都不想等

// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();

s1 === s2 // false

// 有参数的情况
var s1 = Symbol('foo');
var s2 = Symbol('foo');

s1 === s2 // false

Symbol值不能与其他类型的值进行运算,会报错。

由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
var mySymbol = Symbol();

// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
var a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

注意,Symbol值作为对象属性名时,不能用点运算符。

11. Set 数据结构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成 Set 数据结构。

const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4

上面代码通过add方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值

Set 函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。

var set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]

// 例二
var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

12. Map 数据结构

ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。

var m = new Map();
var o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。

var map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

以上介绍的是ES6中最为常用的也较为容易掌握一些新语法。 查看更为详细的ES6语法,请访问阮一峰老师 ECMAScript6入门

(完)

仅有一条评论

  1. 支持

添加新评论