1.let和constlet和const
共同点:声明的变量仅在块级做用域内有效;不存在变量提高;暂时性死区;不容许重复声明;全局变量将逐步与顶层对象的属性脱钩。let和const
不一样点:const
一旦声明变量,就必须当即初始化,不能留到之后赋值。
代码示例一:html
for(let i=1;i<5;i++){ setTimeout(function(){ console.log(i) },1000) }
代码示例二:正则表达式
var a = []; for (var i = 0; i < 10; i++) { var c = i; a[i] = function () { console.log(c); }; } a[6](); // 9
若是使用let,声明的变量仅在块级做用域内有效,最后输出的是6。数组
var a = []; for (var i = 0; i < 10; i++) { let c = i; a[i] = function () { console.log(c); }; } a[6](); // 6
代码示例三:数据结构
块级做用域:app
function f() { console.log('I am outside!'); } (function () { if(false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }());
上面代码在ES5中运行,会获得“I am inside!”,可是在ES6中运行,会获得“I am outside!”。这是由于ES5存在函数提高,无论会不会进入if代码块,函数声明都会提高到当前做用域的顶部,获得执行;而ES6支持块级做用域,无论会不会进入if代码块,其内部声明的函数皆不会影响到做用域的外部。ide
function f1() { console.log('I am outside!!!!'); } (function () { if(true) { // 重复声明一次函数f function f1() { console.log('I am inside!!!!'); } } f1(); // I am outside!!!! }());
2.变量的解构赋值模块化
(1)数组的解构赋值(若是等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错):函数
let [a, [b], d] = [1, [2, 3], 4]; a // 1 b // 2 d // 4 var [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4]
对于Set结构,也可使用数组的解构赋值。测试
[a, b, c] = new Set(["a1", "b1", "c1"]) a // "a1" b // "b1" c // "c1"
解构赋值容许指定默认值。this
let [x, y = 'b'] = ['a']; // x='a', y='b'
(2)对象的解构赋值(只要等号右边的值不是对象,就先将其转为对象。好比undefined和null没法转为对象,因此对它们进行解构赋值,就会报错):
数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb"
对象的解构赋值是下面形式的简写:
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
若是变量名与属性名不一致,必须写成下面这样。
var { foo: baz } = { foo: "aaa", bar: "bbb" }; baz // "aaa"
对象的解构也能够指定默认值。
var {x, y = 5} = {x: 1}; x // 1 y // 5
下面代码将整个解构赋值语句,放在一个圆括号里面,就能够正确执行,不然JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。
({x} = {x: 1});
解构赋值代码示例:
// 返回一个数组 function example() { return [1, 2, 3]; } let [a, b, c] = example(); // 返回一个对象 function example() { return { foo: 1, bar: 2 }; } let { foo, bar } = example();
3.字符串的扩展
(1)Unicode字符表示法: JavaScript容许采用“uxxxx”形式表示一个字符,其中“xxxx”表示字符的码点。超过0xFFFF的数值(好比u20BB7),将码点放入大括号,就能正确解读该字符。
"\u{20BB7}" // "?"
(2)codePointAt(),fromCodePoint()
对于须要4个字节储存的字符(Unicode码点大于0xFFFF的字符),codePointAt()
可以正确处理,返回一个字符的码点。
var s = '?a'; for (let ch of s) { console.log(ch.codePointAt(0).toString(16)); } // 20bb7 // 61
codePointAt()
是测试一个字符由两个字节仍是由四个字节组成的最简单方法。
function is32Bit(c) { return c.codePointAt(0) > 0xFFFF; } is32Bit("?") // true is32Bit("a") // false String.fromCodePoint(0x20BB7) // "?"
(3)正则表达式的u修饰符
--点字符
--Unicode字符表示法
--量词
--i修饰符
--预约义模式:由此能够写出一个正确返回字符串长度的函数。
function codePointLength(text) { var result = text.match(/[\s\S]/gu); return result ? result.length : 0; } var s = "??"; s.length // 4 codePointLength(s) // 2
(4)includes(), startsWith(), endsWith(),repeat(),padStart(),padEnd()
var s = "Hello world!"; s.startsWith("world", 6) // true s.endsWith("Hello", 5) // true s.includes("Hello", 6) // false 'x'.repeat(3) // "xxx" '12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
注:endsWith()
的行为与includes(), startsWith()
两个方法有所不一样。它针对前n个字符,而其余两个方法针对从第n个位置直到字符串结束。
(5)正则表达式的y修饰符
ES6为正则表达式添加了y修饰符,叫作“粘连”(sticky)修饰符。它的做用与g修饰符相似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始,不一样之处在于,g修饰符只确保剩余位置中存在匹配,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
var r = /hello\d/y; r.sticky // true
(6)模板字符串
传统的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! `);
4.数值的扩展
(1)二进制和八进制表示法
ES6提供了二进制和八进制数值的新的写法,分别用前缀0b和0o表示。
0b111110111 === 503 // true 0o767 === 503 // true
(2)Number.isFinite(), Number.isNaN()
:只对数值有效,非数值一概返回false。
(3)Number.parseInt(), Number.parseFloat()
:行为彻底保持不变,逐步减小全局性方法,使得语言逐步模块化。
(4)Number.isInteger(),Number.EPSILON,Number.MAX_SAFE_INTEGER,Number.MIN_SAFE_INTEGER,Number.isSafeInteger()
5.数组的扩展
(1)Array.from()
:用于将两类对象转为真正的数组:相似数组的对象(array-like object)和可遍历(iterable)的对象(包括数据结构Set和Map)。
代码示例:
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
Array.from()
还能够接受第二个参数,做用相似于数组的map方法,用来对每一个元素进行处理,将处理后的值放入返回的数组。
Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
(2)Array.of()
:用于将一组值,转换为数组。
Array.of(3) // [3]
6.函数的扩展
(1)函数参数的默认值:
ES6容许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') { console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello
利用参数默认值,能够指定某一个参数不得省略,若是省略就抛出一个错误:
function throwIfMissing() { throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; } foo() //Uncaught Error: Missing parameter
参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(即函数名以后有一对圆括号),这代表参数的默认值不是在定义时执行,而是在运行时执行(即若是参数已经赋值,默认值中的函数就不会运行)。
若是传入undefined,将触发该参数等于默认值,null则没有这个效果:
function foo(x=5, y=6){ console.log(x,y); } foo(undefined, null) // 5 null
指定了默认值之后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真:
(function(a){}).length // 1 (function(a=5){}).length // 0 (function(a, b, c=5){}).length // 2
注:定义了默认值的参数,必须是函数的尾部参数,其后不能再有其余无默认值的参数。这是由于有了默认值之后,该参数能够省略,只有位于尾部,才可能判断出到底省略了哪些参数。
(2)rest参数:
ES6 引入 rest 参数(形式为“...变量名”),用于获取函数的多余参数,这样就不须要使用arguments
对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) { console.log(values); let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // [2,5,3] // 10
注:rest参数以后不能再有其余参数,不然会报错。
函数的length属性,不包括rest参数:
(function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
(3)扩展运算符:
扩展运算符(spread)是三个点(...)。它比如 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 // ES6的写法 Math.max(...[14, 3, 77]) // 等同于 Math.max(14, 3, 77); // ES6的合并数组 [...arr1, ...arr2, ...arr3]
注:扩展运算符内部调用的是数据结构的Iterator接口,所以只要具备Iterator接口的对象,均可以使用扩展运算符。
(4)箭头函数:
function(x, y) { x++; y--; return x + y; } (x, y) => {x++; y--; return x+y}
应用示例:
class Animal { constructor(){ this.type = 'animal' } says(say){ setTimeout( () => { console.log(this.type + ' says ' + say) }, 1000) } } var animal = new Animal() animal.says('hi') //animal says hi
并非由于箭头函数内部有绑定this的机制,实际缘由是箭头函数根本没有本身的this,它的this是继承外面的,所以内部的this就是外层代码块的this。
箭头函数有几个使用注意点:
函数体内的this对象,绑定定义时所在的对象,而不是使用时所在的对象。 不能够看成构造函数,也就是说,不可使用new命令,不然会抛出一个错误。 不可使用arguments对象,该对象在函数体内不存在。 因为this在箭头函数中被绑定,因此不能用call()、apply()、bind() 这些方法去改变this的指向。
7.对象的扩展
(1)属性的简洁表示法:
function f(x, y) { return {x, y}; } // 等同于 function f(x, y) { return {x: x, y: y}; } f(1, 2) // Object {x: 1, y: 2}
方法简写:
var o = { method() { return "Hello!"; } }; // 等同于 var o = { method: function() { return "Hello!"; } };
(2)属性名表达式:
ES6 容许字面量定义对象时,用表达式做为对象的属性名,即把表达式放在方括号内。
var lastWord = 'last word'; var a = { 'first word': 'hello', [lastWord]: 'world' }; a['first word'] // "hello" a[lastWord] // "world" a['last word'] // "world"
(3)Object.is()
:用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo') // true Object.is({}, {}) // false Object.is(NaN, NaN) // true
(4)Object.assign()
:用于对象的合并,将源对象(source)的全部可枚举属性,复制到目标对象(target)。
var target = { a: 1, b: 1 }; var source1 = { b: 2, c: 2 }; var source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
为对象添加属性:
class Point { constructor(x, y) { Object.assign(this, {x, y}); } }
为属性指定默认值:
const DEFAULTS = { logLevel: 0, outputFormat: 'html' }; function processContent(options) { let options = Object.assign({}, DEFAULTS, options); }
8.Symbol()
(1)Symbol()
函数的参数只是表示对当前 Symbol 值的描述,所以相同参数的Symbol函数的返回值是不相等的。
var s1 = Symbol('foo'); var s2 = Symbol('foo'); typeof s1 // "symbol" s1 === s2 // false
(2)做为属性名的Symbol:
因为每个Symbol值都是不相等的,这意味着Symbol值能够做为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的状况很是有用,能防止某一个键被不当心改写或覆盖。(注:Symbol值做为对象属性名时,不能用点运算符)
var mySymbol = Symbol(); var a = { [mySymbol]: 'Hello!' };
(3)Symbol.for(),Symbol.keyFor()
:Symbol.for()
方法接受一个字符串做为参数,而后搜索有没有以该参数做为名称的Symbol值。若是有,就返回这个Symbol值,不然就新建并返回一个以该字符串为名称的Symbol值。
var s1 = Symbol.for('foo'); var s2 = Symbol.for('foo'); s1 === s2 // true
Symbol.keyFor()
方法返回一个已登记的 Symbol 类型值的key。
Symbol.keyFor(s1) // "foo" var s3 = Symbol("foo"); Symbol.keyFor(s3) // undefined
9.Set和Map数据结构
(1)Set(相似于数组,可是成员的值都是惟一的,没有重复的值):
var set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4] let set = new Set(['red', 'green', 'blue']); var array = Array.from(set); console.log(array); // ["red", "green", "blue"]
Set数据结构有如下方法:
add(value):添加某个值,返回Set结构自己。 delete(value):删除某个值,返回一个布尔值,表示删除是否成功。 has(value):返回一个布尔值,表示该值是否为Set的成员。 clear():清除全部成员,没有返回值。
(2)set遍历操做:
let set = new Set(['red', 'green', 'blue']);
keys()
、values()
、entries()
:
for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue //等同于 for (let item of set) { console.log(item); } Set.prototype[Symbol.iterator] === Set.prototype.values // true for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"]
forEach()
:
let set = new Set([1, 2, 3]); set.forEach((value, key) => console.log(value * 2) ) // 2 // 4 // 6
数组的map()
和filter()
也能够用于Set结构了:
let set = new Set([1, 2, 3]); set = new Set([...set].map(x => x * 2)); // Set {2, 4, 6} let set = new Set([1, 2, 3, 4, 5]); set = new Set([...set].filter(x => (x % 2) == 0)); // Set {2, 4}
使用Set()
,能够很容易地实现并集(Union)和交集(Intersect):
let a = new Set([1,2,3]); let b = new Set([4,3,2]); let union = new Set([...a, ...b]); // Set {1, 2, 3, 4} let intersect = new Set([...a].filter(x => b.has(x))); // Set {2, 3}
(3)Map(相似于对象,也是键值对的集合,可是“键”的范围不限于字符串,各类类型的值(包括对象)均可以看成键):
var map = new Map([ ['name', '张三'], ['title', 'Author'] ]);
注:只有对同一个对象的引用,Map结构才将其视为同一个键:
var map = new Map(); map.set(['a'], 555); map.get(['a']) // undefined
上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不同的,所以get方法没法读取该键,返回undefined。由上可知,Map的键其实是跟内存地址绑定的,只要内存地址不同,就视为两个键。
Map数据结构有如下属性和方法:
size:返回成员总数。 set(key, value):设置key所对应的键值,而后返回整个Map结构, 若是key已经有值,则键值会被更新,不然就新生成该键。 get(key):读取key对应的键值,若是找不到key,返回undefined。 has(key):返回一个布尔值,表示某个键是否在Map数据结构中。 delete(key):删除某个键,返回true。若是删除失败,返回false。 clear():清除全部成员,没有返回值。
(4)map遍历操做:keys()
、values()
、entries()
:
let map = new Map([ ['F', 'no'], ['T', 'yes'], ]); for (let key of map.keys()) { console.log(key); } // "F" // "T" for (let value of map.values()) { console.log(value); } // "no" // "yes" for (let item of map.entries()) { console.log(item[0], item[1]); } // "F" "no" // "T" "yes" // 等同于 for (let [key, value] of map.entries()) { console.log(key, value); // 等同于 for (let [key, value] of map) { console.log(key, value); } Map.prototype[Symbol.iterator] === Map.prototype.entries // true
forEach()
:
let map = new Map([ ['F', 'no'], ['T', 'yes'], ]); map.forEach(function(value, key, map) { console.log("Key: %s, Value: %s", key, value); }); // Key: F, Value: no // Key: T, Value: yes
Map结构转为数组结构,比较快速的方法是结合使用扩展运算符(...)。
let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); [...map.keys()] // [1, 2, 3] [...map.values()] // ['one', 'two', 'three'] [...map.entries()] // [[1,'one'], [2, 'two'], [3, 'three']] [...map] // [[1,'one'], [2, 'two'], [3, 'three']]
10.Iterator和for...of循环
任何部署了Iterator接口的对象,均可以用for...of循环遍历。
在ES6中,有三类数据结构原生具有Iterator接口:数组、某些相似数组的对象、Set和Map结构。
let arr = [4,5,6,0]; let iter = arr[Symbol.iterator](); iter.next(); // Object {value: 4, done: false} iter.next(); // Object {value: 5, done: false} iter.next(); // Object {value: 6, done: false} iter.next(); // Object {value: 0, done: false} iter.next(); // Object {value: undefined, done: true}
对于这三类数据结构,不用本身写遍历器生成函数,for...of循环会自动遍历它们。
for(let i of [4,5,6,0]){ console.log(i); } // 4 5 6 0
除此以外,其余数据结构(主要是对象)的Iterator接口,都须要本身在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。
下面是为对象添加Iterator接口的例子:
let obj = { data: [ 'hello', 'world' ], [Symbol.iterator]() { const self = this; let index = 0; return { next() { if (index < self.data.length) { return { value: self.data[index++], done: false }; } else { return { value: undefined, done: true }; } } }; } };
11.Generator()
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true } for (let v of hw) { console.log(v); } // hello // world
yield*语句:若是yield命令后面跟的是一个遍历器,须要在yield命令后面加上星号,代表它返回的是一个遍历器。
let delegatedIterator = (function* () { yield 'Hello!'; yield 'Bye!'; }()); let delegatingIterator = (function* () { yield 'Greetings!'; yield* delegatedIterator; yield 'Ok, bye.'; }()); for(let value of delegatingIterator) { console.log(value); } // "Greetings! // "Hello!" // "Bye!" // "Ok, bye."
12.class
class Animal { constructor(){ this.type = 'animal' } says(say){ console.log(this.type + ' says ' + say) } } let animal = new Animal() animal.says('hello') //animal says hello class Cat extends Animal { constructor(){ super() this.type = 'cat' } } let cat = new Cat() cat.says('hello') //cat says hello
子类必须在constructor方法中调用super方法,不然新建实例时会报错。这是由于子类没有本身的this对象,而是继承父类的this对象,而后对其进行加工。若是不调用super方法,子类就得不到this对象。
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '('+this.x+', '+this.y+')'; } } class ColorPoint extends Point { constructor(x, y, color) { super(x, y); this.color = color; } toString() { return this.color+' '+super.toString(); } } var colorPoint = new ColorPoint(3,6,'purple'); colorPoint.toString() "purple (3, 6)"
13.Module(import
和export
命令只能在模块的顶层,不能在代码块之中)
(1)export
命令:
export命令后面,使用大括号指定所要输出的一组变量。
var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export {firstName, lastName, year};
下面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2能够用不一样的名字输出两次。
function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion };
export命令规定的是对外的接口,必须与模块内部的变量创建一一对应关系。
// 写法一 export var m = 1; // 写法二 var m = 1; export {m}; // 写法三 var n = 1; export {n as m};
一样的,function和class的输出,也必须遵照这样的写法。
// 报错 function f() {} export f; // 正确 export function f() {}; // 正确 function f() {} export {f};
(2)import
命令:
import {firstName, lastName, year} from './profile';
import命令要使用as关键字,将输入的变量重命名。
import { lastName as surname } from './profile';
export default class
命令用于指定模块的默认输出。
export default class Person{ constructor(name, age){ this.name = name; this.age = age; } say(){ return `我是${this.name},今年${this.age}岁了。`; } }
import命令后面才不用加大括号,由于只可能对应一个方法。
import Person from './second.js';
export class
:
export class Fun { constructor(color){ this.color = color; } sayHello(){ console.log(`Hello,${this.color}`); } } import { Fun } from './second.js';
正是由于export default命令其实只是输出一个叫作default的变量,因此它后面不能跟变量声明语句。
// 正确 export var a = 1; // 正确 var a = 1; export default a; // 错误 export default var a = 1; // 正确 export default 42; // 报错 export 42;
示例:
var hobby = 'tour'; export default hobby; import hobby from './second.js';