# 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}
# 参考资料
- 30分钟掌握ES6/ES2015核心内容: (opens new window)http://www.jianshu.com/p/ebfeb687eb70
- ECMAScript6入门- 阮一峰: (opens new window)
- ES6 new feature (opens new window)
← JavaScript 类型 ES6 类 →