# ES6 基础

# 基础语法

# let 和 const

因为javascript中,变量默认是全局的,只存在函数级的作用域。ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效

let不像var那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。

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

# 箭头函数

ES6允许使用“箭头”(=>)定义函数。箭头函数可以扩大函数的作用域

如果是简单表达式的可以写为:

var f = v => v;

等价于:

var f = function(v) {
  return v;
};

实际上=> 是扩大了this 的作用域

# 函数参数的默认值

在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 Eg:

values = value || []

ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

# rest参数

ES6引入rest参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。 rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

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

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

  return sum;
}

add(2, 5, 3) // 10

rest 和 arguments 是有区别的:

  • rest是一个真正的参数组成的数组,但是arguments是一个对象

# 扩展(展开)运算符

扩展运算符(spread)是三个点(...),好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列

主要的用途,该运算符主要用于

# 函数调用

eg1:
function add(x, y, z) {
  return x + y + z;
}

var numbers = [4, 38, 5];
add(...numbers) // 47

eg2:

function test(...arges){
    console.log(typeof arguments)
    console.log(arguments)
    console.log(arges)
}

var argus1 = [1,2,3];
test(...argus1)
//object
//{ '0': 1, '1': 2, '2': 3 }
//[ 1, 2, 3 ]

var argus2 = [1,2,3];
test(argus2)
//object
//{ '0': [ 1, 2, 3 ] }
//[ [ 1, 2, 3 ] ]

# 用于数组字面常量

没什么用

# 对象的扩展/展开运算符(Object rest spread transform)

这个是ES7的提案,目前node的7.2版本还不支持,需要使用babel进行转换 babel:https://babeljs.io/docs/usage/cli/

Object rest spread transform:https://babeljs.io/docs/plugins/transform-object-rest-spread/

const mike = {
    name: 'kitty',
    size: 'big'
}

var factory = {
    ...mike,
    people: 'china'
}

console.log(factory)
//{ name: 'kitty', size: 'big', people: 'china' }

# 模板字符串 template string

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

  • 模板字符串中嵌入变量,需要将变量名写在${}之中。
  • 模板字符串的空格和换行,都是被保留的
  • 使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中
const place = 'Beijing'
var msg = `Hello, ${place}`;

# 类class

在javascript中,是没有传统类的概念的,使用原型链的方式来完成继承。 ES6中添加了class这个语法糖。

class Animal {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  shout() {
    return `My name is ${this.name},age is${this.age}`;
  }
  static foo() {
    return 'Here is a static method'
  }
}

const cow = new Animal('betty', 2);
cow.shout();
//My name is betty , age is 2 

Animal.foo();
//Here is a static method

class Dog extends Animal {
  constructor(name, age = 2, color = "black") {
    //在构造函数中,可以直接调用super方法
    super(name, age);
    this.color = color;
  }

  shout() {
    //在非构造函数中不可以直接调用super方法
    //但是可以采用super(). + 方法名字调用父类的方法
    return super.shout() + `, color is ${this.color}`;
  }
}

const jackTheDog = new Dog('jack');
jackTheDog.shout();
//"My name is jack, age is 2, color is black"

# 函数绑定*

函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。

<span className="control-btn" onClick={ ::this.onSubmitContract }>提交</span>

# 变量的解构赋值 Destructuring

数组的解构赋值

var [a, b, c] = [1, 2, 3];
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

解构赋值允许指定默认值

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

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

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

解构特性的用途:

  • 结合 for of 语法 遍历map解构 Notice: for of 语法 不能用于 普通的对象
var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

如果只想获取键名,或者只想获取键值,可以写成下面这样。

// 获取键名
for (let [key] of map) {
  // ...
}

// 获取键值
for (let [,value] of map) {
  // ...
}
  • 输入模块的指定方法 加载模块时,往往需要指定输入那些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");
  • 可以用于函数的传参 特别是对于js 的编程,如果在发送请求时需要的仅仅是封装对象的一部分参数,那么可以直接用于参数传递:
//index.js

var  params = {
 'contractNo':     1234567890,
 'contractId':    12,
 'contractName': 'important',
 'cointractType':   '主合同',
 'city':    'baoji',
 'district':   'A区',
 'districtId': '12'
}
this.props.actions.editContract(params)

//如果仅仅需要其中重要的部分
//action.js
function editContract({ contractId,  contractNo}){
    console.log( contractId)    //12
    console.log( contractNo)   //1234567890
}

# 标签模板

模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)

alert`123`
// 等同于
alert(123)

标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。

# 属性的简洁表示法

var foo = 'bar';
var boo = 'har'
var baz = {
foo,
boo
};
baz // {foo: "bar",boo: 'har'}

// 等同于
var baz = {foo: foo,boo:boo};

# Symbol

why?

ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。

ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

let s = Symbol();

typeof s
// "symbol"

Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

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

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

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

上面代码中,s1和s2是两个Symbol值。如果不加参数,它们在控制台的输出都是Symbol(),不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。

注意,Symbol函数的参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的。

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

s1 === s2 // false

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

s1 === s2 // false

上面代码中,s1和s2都是Symbol函数的返回值,而且参数相同,但是它们是不相等的。

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

作为属性名的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值作为对象属性名时,不能用点运算符

var mySymbol = Symbol();
var a = {};

a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

上面代码中,因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个Symbol值。

同理,在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。

let s = Symbol();

let obj = {
  [s]: function (arg) { ... }
};

obj[s](123);

上面代码中,如果s不放在方括号中,该属性的键名就是字符串s,而不是s所代表的那个Symbol值。

采用增强的对象写法,上面代码的obj对象可以写得更简洁一些。

let obj = {
  [s](arg) { ... }
};

实用:消除魔术字符串

魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,该由含义清晰的变量代替。

function getArea(shape, options) {
  var area = 0;

  switch (shape) {
    case 'Triangle': // 魔术字符串
      area = .5 * options.width * options.height;
      break;
    /* ... more code ... */
  }

  return area;
}

getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串

上面代码中,字符串“Triangle”就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。

常用的消除魔术字符串的方法,就是把它写成一个变量。

var shapeType = {
  triangle: 'Triangle'
};

function getArea(shape, options) {
  var area = 0;
  switch (shape) {
    case shapeType.triangle:
      area = .5 * options.width * options.height;
      break;
  }
  return area;
}

getArea(shapeType.triangle, { width: 100, height: 100 });

上面代码中,我们把“Triangle”写成shapeType对象的triangle属性,这样就消除了强耦合。

如果仔细分析,可以发现shapeType.triangle等于哪个值并不重要,只要确保不会跟其他shapeType属性的值冲突即可。因此,这里就很适合改用Symbol值。

const shapeType = {
  triangle: Symbol()
};

上面代码中,除了将shapeType.triangle的值设为一个Symbol,其他地方都不用修改。

# 模块

在ES6之前,javascript并没有对模块做出任何定义,于是先驱者们创造出了各种各样的规范来完成这个任务。伴随着require.js的流行,AMD格式成为首选。 之后,随之而来的是CommonJS格式,在之后browerify的诞生,让浏览器也能使用这种格式。知道ES6出现,模块这个概念才真正有了语言特性的支持。

描述:本质上而言,es6有些像commonjs 的方式,但是有着本质的区别, CommonJs是使用了export导出的东西挂在了一个空对象上,使用时是从这个对象上找值,这种加载称为“运行时加载”。而ES6的模块在设计上是不同的, ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。

模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

如何使用:

  • export
  • import

# export 命令

基本用法: 使用export xx 导出么,使用import { xx } 导入

//file.js
export const config = {
   name: 'white'
}

function hello(){
   //这么做好像是不行的
}
export hello;

//index.js
import { config } from 'file.js'
import { hello } from 'file.js'

深入用法: 如果指定了default,那么本质上他就是最外层的默认的module导出对象

//file.js
export default const name = 'hehe'

//index.js
import  name from 'file.js'
//file.js
const name = 'hehe'
function test(){
}
export {name, test}

//index.js
import {name, test} from 'file.js'

或者
import * as people from 'file.js'
people.name
people.test()

# 扩展API

# 字符串方法

# includes(), startsWith(), endsWith()

传统上,js提供了indexOf方法用于判断一个字符串是否包含另一个字符串,并且返回匹配字符串的下标,我们往往时候该api用于判断字符串的包含问题但往往是不需要这个下标的,仅仅需要一个布尔值,所以可以使用es6的includes 方法

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
var s = 'Hello world!';

s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true

这三个方法都支持第二个参数,表示开始搜索的位置。

var s = 'Hello world!';

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

# padStart(), padEnd()

ES7推出了字串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart用于头部补全,padEnd用于尾部补全。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

上面代码中,padStart和padEnd一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。

如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串

# 数值的扩展

# Math对象的扩展

Math.trunc() Math.trunc方法用于去除一个数的小数部分,返回整数部分。

Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc(-4.9) // -4
Math.trunc(-0.1234) // -0
Math.trunc('foo');    // NaN
Math.trunc();         // NaN

# 工具方法扩展

# repeat()

repeat方法返回一个新字符串,表示将原字符串重复n次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

# Object.is()

ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。

ES6提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

# Object.assign()

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)

var target = { a: 1 };

var source1 = { b: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

# 参考资料

  1. 30分钟掌握ES6/ES2015核心内容: (opens new window)http://www.jianshu.com/p/ebfeb687eb70
  2. ECMAScript6入门- 阮一峰: (opens new window)
  3. ES6 new feature (opens new window)
陕ICP备20004732号-3