es6要點

1.let和const
let和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(importexport命令只能在模塊的頂層,不能在代碼塊之中)

(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';
相關文章
相關標籤/搜索