ECMAScript6

簡介

ES6目標,讓JavaScript變成一個企業級的開發語言,不只僅限制與前端頁面的腳本語言。javascript

標準(Standard): 用於定義與其餘事物區別的一套規則
實現(Implementation): 某個標準的具體實施/真實實踐

編譯

服務端使用 babel 編譯

構建工具fis使用
插件:fis-parser-babel2 編譯ES6前端

fis.match('**.ts', {
    
    parser: fis.plugin('babel2'),
    
    rExt: '.js'
    
});

TypeScript與babel編譯有區別:java

例如:對一個類的編譯TypeScript編譯的結果是一個閉包類,babel編譯的結果是一個安全類.node

function _classCallCheck( instance, Constructor ) { 
    if (!(instance instanceof Constructor)) { 
        throw new TypeError("Cannot call a class as a function"); 
    } 
}

var Person = function Person() {

    _classCallCheck(this, Person);

};
瀏覽器端編譯 traceur庫來編譯.

traceur庫,引入兩個庫:traceur.jstreaceur-bootstrap.js算法

寫<script>的ES6中,須要指定type類型爲module
注意:<script> 須要寫在引入庫的後邊。編程

<script src="../lib/traceur.js" type="text/javascript" charset="utf-8"></script>
<script src="../lib/traceur-bootstrap.js" type="text/javascript" charset="utf-8"></script>


<script type="module">
    
    class Person {
        
    }
    
    console.log(Person);
    
</script>

let&const

let

let聲明一個塊級做用域,而且能夠初始化該變量。用法與var相似。只能在所在的代碼塊內有效。json

塊做用域:{}構成的一個代碼庫處的做用域叫作塊做用域。bootstrap

let 容許吧變量的做用域限制在塊級域中。與var 不一樣的是:var聲明變量要麼是全局的,要麼是函數級的,而沒法是塊級的。數組

典型應用:for循環中的計數器
let定義的變量,會在塊做用域保留下來,訪問的就是當前循環的變量值。promise

for ( let j=0; j<4; j++ ) {
}

console.log(j); // error 報錯。

for(let i = 0; i < 5; i++){
  setTimeout(function(){
      console.log(i);
  }, i * 100);
}

做用域規則

用let定義的變量的做用域是定義它們的塊內,以及包含在這個塊中的子塊
與var區別,是否能夠有塊級概念,是否有存在變量提高

function test () {
    
    let a = 10;
    
    if ( true ) {
        
        let a = 20;
        
        console.log(a); // 20
        
    }
    
    console.log(a); // 10
    
}

test();

不存在變量提高

console.log(b); // undefined
console.log(a); // 報錯ReferenceError

let a = 10;
var b = 10;

function foo(){

  a = 20;  // 報錯 ReferenceError
  console.log(a);
  
  let a = 10;
  console.log(a);
  
}
foo();

let 的暫存死區

在同一個函數或同一個做用域中的let重複定義一個變量將引入TypeError

注意:switch中聲明的變量。

if ( true ) {
    
    let a;
    
    let a; // TypeError thrown.
    
}

若是塊中存在let 和const。 凡是在聲明以前就使用這些變量,就會報錯。
let聲明以前,該變量都是不可用的。稱之爲:暫時性死區(temproal dead zone簡稱 TDZ)

if (true) {
  // TDZ開始
  a = 10; // ReferenceError
  console.log(tmp); // ReferenceError

  let a; // TDZ結束
  console.log(tmp); // undefined

  a = 100;
  console.log(tmp); // 100
}

ES6中的TDZ 像是運行時的檢查而不是語法聲明上的概念.

if(true){
    
    setTimeout(function(){
        
        console.log(x); // 1
        
    });
    
    let x = 1;
    
}

let特色:

  • 具備塊級做用域
  • 沒有變量提高
  • 不容許重複聲明
  • 具備暫時性死亡區

爲了避免出發死亡區,必須,變量先聲明,後使用的規定。
let聲明的全局變量並非全局對象的屬性
let聲明的變量知道控制流到達該變量被定義的代碼行時纔會被裝載,因此在到達以前會使用該變量會觸發錯誤,也就是所說的暫時性死亡區。沒有分爲預解析階段。

塊級做用域的意義:
ES5中,只有全局做用域和函數做用域,沒有塊級做用域。

缺點:
1:內存變量可能會覆蓋外層變量。
2:用來計數的循環變量泄漏爲全局變量。(循環內變量過分共享)

const

const聲明一個只讀的常量。 一旦聲明,常量的值就不可更改。
意味着,const一旦聲明常量,就必須當即初始化,不可留到之後賦值。

語法:const key = value;

在定義常量的前面,不能訪問到常量,所以通常都將常量定義在文件的最前面。
例如:require() 模塊的時候使用。

const http = require('http');

常量不能被重複定義,爲了保證常量定義使用的時候的安全性。

const 和 let 的區別:
const具備let的全部特性,比let更加嚴格,不能夠被重複定義(賦值)。

const 只是保證變量名指向的地址不變,並不保證該地址的數據不變。因此將一個對象聲明爲常量必須很是當心。

const foo = {};

foo.k1 = 100;

console.log( foo.k1 );

foo = {}; // TypeError: "foo" is read-only
// 常量foo 儲存的是一個地址,這個地址指向一個對象。不可變的只是這個地址,即不能把foo指向另外一個地址,可是對象自己是可變的,因此依然能夠爲其添加新的屬性。

Objcet.freeze()
做用:凍結對象(不可爲對象添加屬性和和方法)
參數:被凍結的對象

const foo = {}
Object.freeze(foo);


// 常規模式時,下面一行不起做用, 忽略添加 
// 嚴格模式時,該行會報錯
foo.k1 = 100;

console.log(foo);

解構賦值

結構的做用是能夠快速取得數組或對象當中的元素或屬性,而無需使用arr[x]或者obj[key]等傳統方式進行賦值.

數組的結構賦值

解構:從數組和對象中提取值,對變量進行賦值。

基本用法:

let [a, b, c] = [1, 2, 3]; // 從數組中提取值,按照對應位置,對變量賦值。
// 這種寫法,本質上屬於「模式匹配」。 只要等號兩邊的模式相同,左右的變量就會賦予對應的值。

let [foo, [[bar], baz]] = [1, [[10], 100]];

let [x, y, ...z] = ['a', 'b', 'c', 'd'];

console.log(x,y,z); // a b ["c", "d"]

若是解構不成功,變量值爲undefiend。

let [d] = []; // undefined
let [e, f] = [1]; // 1 undefined

不徹底解構: 等號左邊的模式,只匹配一部分等號右邊的數組。

let [x, y] = [1, 2, 3];
console.log(x, y); // 1  2

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

console.log(a, b, c); // 1 2 4

等號的右邊不是數組,(嚴格的說,不是可遍歷的結構),會報錯。

// 報錯
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
// 轉爲對象之後不具有Iterator接口(前面5個表達式), 自己就不具有Iterator接口(最後一個表達式)

只要某種數據結構具備Iterator接口,均可以採用數組形式的解構賦值。 (例如:函數執行以後的返回值)

默認值
解構賦值容許設置默認值

let [a = 100] = [];

console.log(a); // 100

ES6內部使用嚴格相等 運算符(===),判斷一個位置是否有值。若是一個數組成員不嚴格等於undefeind,默認值不會生效。

let [a = 1] = [undefined];
let [b = 10] = [null];
let [c = 100] = [NaN];

console.log(a, b, c); // 1 null NaN

若是默認值是一個表達式,這個表達式是惰性求值(只有在用到的時候纔會求值)。

function fn () {
    console.log('a');
}

let [x = fn()] = [1];

console.log(x); // 1 
// fn(); 函數不會執行

默認值能夠引用其它解構賦值的變量
默認值能夠引用解構賦值的其它變量,但該變量必須已經聲明,若是不聲明,將會報錯。

let [x = 1, y = x] = []; // x = 1  y = 1
let [x = y, y = 1] = []; // ReferenceError 
// x用到默認值y時,y尚未聲明。

最爲經常使用的地方是:函數參數的arguments類數組對象的解構賦值。

對象的解構賦值

對象的解構與數組有一個重要的不一樣之處,數組的元素是按次序排序,變量的取值由它的位置決定的,而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。

let {a, b} = {b: 10, a: 100}; 

console.log(a, b); // 10  100

let { c } = {d: 1000, e: 100 }
console.log(c); // undefined

對象的解構賦值的內部機制:先找到同名屬性,而後再賦值給對應的變量。真被賦值的是後者,而不是前者。

let {foo: baz} = {foo: 'aaa', bar: 'bbb'}

console.log(baz); // aaa
console.log(foo); // ReferenceError
// 被賦值的是變量baz,而不是模式foo

默認值

默認值生效的條件:對象的屬性值嚴格等於undefined。

let {x = 3} = {x: undefined}
// x = 3;

let {y = 10} = {y: null}

console.log(x, y); // 3 null

若是解構失敗,變量值等於undefined

字符串的解構賦值

字符串的解構賦值本質:字符串被轉爲類數組對象,類數組對象中有length屬性,能夠有Iterator接口,能夠被遍歷。

const [a, b, c, d, e] = 'hello';

console.log(a, b, c, d, e); // h e l l o

數值和布爾值的解構賦值

須要指定包裝對象toString
解構規則:等號右邊的值不是對象,就先將其轉爲對象。 undefind 和 null 沒法轉爲對象,對其兩者解構賦值,都會報錯。

let {toString: s} = 10;

console.log( s === Number.prototype.toString ); // true

console.log(s);  // function toString() { [native code] }

let { toString: t } = true;

console.log(t); // function toString() { [native code] }

函數參數解構賦值

能夠解構賦值,可使用默認值

[[1, 2], [3, 4]].map( ([a, b]) => a + b ); // 3 7
[[, 2], [, 4]].map(function ( [a = 10, b] ) {
    return a + b;
});

圓括號問題

對於編譯器來講,一個式子究竟是模式,,仍是表達式,沒有辦法從一開始就知道,必須解析到(或解析不到)等號才能知道。
問題:模式中出現的圓括號怎麼處理。
解析規則:只要有可能致使解構的歧義,就不得使用圓括號。

建議:只要有可能,就不要再模式中放置圓括號。能不使用,就不使用圓括號。

不能使用圓括號的狀況

變量聲明語句中,不能帶有圓括號
// 所有報錯
var [(a)] = [1];

var {x: (c)} = {};
var ({x: c}) = {};
var {(x: c)} = {};
var {(x): c} = {};}

var { o: ({ p: p }) } = { o: { p: 2 } };
// 都是變量聲明語句,模式不能使用圓括號。
函數參數中,模式不能帶有圓括號。
// 報錯
function f([(z)]) { return z; }
賦值語句中,不能將整個模式,或嵌套模式中一層,放在圓括號之中。
// 所有報錯
({ p: a }) = { p: 42 };
([a]) = [5];

可使用圓括號的狀況:
賦值語句的非模式部分,可使用圓括號。

[(b)] = [3]; // 正確  // 模式是取數組的第一個成員,跟圓括號無關
({ p: (d) } = {}); // 正確 // 模式是p,而不是d;
[(parseInt.prop)] = [3]; // 正確 // 模式是取數組的第一個成員,跟圓括號無關

用途

交換變量的值
[x, y] = [y, x]
從函數返回多個值

函數只能返回一個值,若是要返回多個值,只能將它們放在數組或對象裏返回。

// 返回一個數組

function example() {
  return [1, 2, 3];
}
var [a, b, c] = example();

// 返回一個對象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
var { foo, bar } = example();
函數參數定義

將一組參數與變量名對應。

// 參數是一組有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 參數是一組無次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
提取JSON數據

提取JSON對象中的數據

函數參數的默認值

遍歷Map結構

輸入模塊的指定方法

加載模塊時,每每須要指定輸入那些方法。

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

String的擴展

satrtsWith

str.starsWith(str1, num);

判斷字符串以指定參數開頭
參數1:開頭的字符
參數2:可選,指定開始查找的索引位置
返回布爾值

endWith

str.endWith(str1, num);

判斷字符串以指定參數結束
參數1:結束的字符子串
參數2:可選,指定開始查找的索引位置
返回布爾值

includes

str.includes(str1, num);

判斷指定的參數字符串是否包含在字符串中。
參數1:被查詢的字符串
參數2:可選,從那邊開始查找索引位置。
返回布爾值

標籤模板

``能夠緊跟一個函數名後邊,該函數將被調用來處理這個模板字符串。 稱之爲:「標籤模板」(tagged template);

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

標籤模板實質並非模板,而是函數的調用的一種特殊形式。「標籤」指的就是函數,緊跟在後面的模板字符串就是它的參數。

若是模板字符裏面有變量,就不在是簡單的調用,而是會將模板字符串先處理成多個參數,再調用函數。

模板字符串

語法:${表達式}
須要經過``來聲明字符串,纔會生效。
若是使用''單引號聲明沒有效果。並不會解析模板中的表達式。

//let num = `十`;
let num = `5`;
let intro = `<h1>笑一笑,${5 * 2}年少</h1>`;

repeat

重複字符串
參數:指定重複的次數

let num = '10';

let intro = `<h1>笑一笑,${num * 2}年少</h1>`;

let introT = intro.repeat(2);

console.log(intro);

console.log(introT);

raw

返回原始字符串,該方法不會將字符串中的轉義字符轉義
這個方法特殊,不須要使用()調用.
是String底下的一個方法,直接經過String調用便可。

console.log('success \n 1');

let str = String.raw`success \n 1`;

console.log(str);

Number的擴展

ES6在Number對象上,提供了Number.isFinite() 和 Number.isNaN();兩個方法。
用來檢查Infinite和NaN這兩個特殊值。

isFanite();

檢查是不是做爲數字存在,對於不存在或者是NaN返回值爲:false,對於數字返回值爲:true

Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false

// 實現方式
(function (global) {
  var global_isFinite = global.isFinite;

  Object.defineProperty(Number, 'isFinite', {
    value: function isFinite(value) {
      return typeof value === 'number' && global_isFinite(value);
    },
    configurable: true,
    enumerable: false,
    writable: true
  });
})(this);

isNaN();

當參數是NaN的時候,才返回true,其它狀況都返回false。

Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true'/0) // true
Number.isNaN('true'/'true') // true

// 實現方式
(function (global) {
  var global_isNaN = global.isNaN;

  Object.defineProperty(Number, 'isNaN', {
    value: function isNaN(value) {
      return typeof value === 'number' && global_isNaN(value);
    },
    configurable: true,
    enumerable: false,
    writable: true
  });
})(this);

與傳統的全局方法isFinte()方法isNaN()的區別:
傳統方法先調用Number()將非數值的值轉爲數值,再進行判斷。
Number.isFinte()和isNaN();只對數值有效,非數值一概返回false.

parseInt&parseFloat

ES6將全局方法parseInt();和parseFloat();方法 移植到Number對象上,行爲徹底保持不變。

目的:逐漸減小全局性方法,是的語言逐步模塊化。

isInteger

當參數是正整數,負整數,0,-0,的時候返回值爲:true。其它狀況下返回值爲:false
在JavaScript內部,整數和浮點數使用一樣的存儲方法,因此3和3.0被視爲同一個值。

Math的擴展

sign

Math.sign();用來判斷一個數究竟是整數,負數,仍是零。
返回值:

  • 參數爲整數,返回+1
  • 參數爲負數,返回-1
  • 參數爲0 ,返回0
  • 參數爲-0,返回-0
  • 參數爲其它值,返回NaN

    Math.sign(''); // NaN
    Math.sign('a'); // NaN
    Math.sign(NaN); // NaN
    Math.sign(false); // NaN
    Math.sign(undefined); // NaN
    Math.sign(0); // 0
    Math.sign(-0); // -0
    Math.sign(-2); // -1
    Math.sign(+2); // 1


// 模擬 Math.sign(); 方法
Math.sign = Math.sign || function ( x ) {
    
    x = +x;
    
    if ( x === 0 || isNaN(x) ) {
        return x;
    }
    
    return x > 0 ? 1 : 1;
    
}

trunc

Math.trunc(); 用於去除一個數的小數部分,返回 整數部分。

非數值,Math.trunc()內部先使用Number();將其轉爲數值。
Math.trunc(123.456); // 123
對於空值和沒法截取整數的值,返回NaN
Math.trunc(NaN); // NaN
Math.trunc('hello'); // NaN

// 模擬Math.trunc();
Math.trunc = Math.trunc || function ( x ) {
    return x < 0 ? Math.ceil(x) : Math.floor(x);
}

Array的擴展

form

做用:將類數組對象轉化成數組對象
參數1:類數組對象
參數2:可選,處理的回調函數。能夠處理類對象每一個成員
該回調函數中參數1:value值,(類數組的成員), 參數2:索引值。返回值會保留最終結果,若是沒有返回值,那麼from函數會返回由undefined構成的一個新數組

若是是轉一個真正的數組,Array.from();會返回一個如出一轍的新數組。
更常常的使用時:arguments 和 獲取的DOM元素。

// 類數組對象
let arrayLike = {
    0: 'a',
    1: 'b',
    length: 2
}

// ES5的寫法
let arr1 = [].slice.call(arrayLike); 

// ES6的寫法
let arr2 = Array.from(arrayLike);

console.log(arr1, arr2); // ['a', 'b']['a', 'b']

let arrayLike = {
    0: 'a',
    1: 'b',
    length: 2
}

// ES6的寫法
let arr2 = Array.from(arrayLike, function ( value, index ) {
    
    return value + '-xixi';
    
});

of

Array.of();將一組值,轉爲數組。
返回值:返回參數值組成的數組,若是沒有參數,就返回一個空數組。

做用:彌補數組構造函數Array()的不足。由於參數個數的不一樣,會致使Array()的行爲有所差別。
Array()若是是一個參數,則表示當前數組的長度。若是是二個或二個以上,則表示數組成員。

Array.of(1, 2, 3, 'xixi');

Array.of();基本上能夠用來替代Array()或new Array(); 而且不存在因爲參數不一樣而致使的重載問題。

Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]

// 模擬Arraay.of();方法
function ArrayOf () {
    return [].slice.call(arguments);
}

find

find();方法和findIndex();方法
find();
做用:獲取第一個符合條件的數組成員。
參數:回調函數。全部的數組成員一次執行該回調函數。知道找出一個返回值爲true的成員。若是沒有符合條件的成員,則返回false。
回調函數中的參數: 當前value值,當前索引值index(可選), 原數組arr(可選)

[1, 2, 23, 10].find( (n) => n > 10 ); // 23
[1, 2, 3].find( ( value, index, arr ) => value < 2  ) // 1

findIndex();
做用:返回第一個符合條件的數組成員的索引值,若是全部成員都不符合條件,則返回 -1.
參數:和find();參數同樣是一個回調函數。

['cyan', 'pink', 'tan', 'red'].findIndex( (value, index, arr) => Object.is('tan', value) );

提出的解決問題:
indexOf();沒法檢測出成員中是否有NaN

[NaN].indexOf(NaN);  // -1

findIndex();搭配 Object.is(); 能夠檢測出來

[10,NaN].findIndex(  (value) => Object.is(NaN, value) ); // 1

fill

使用給定值,填充一個數組。

參數1:填充的值
參數2:可選,填充的起始位置。
參數3: 可選,填充的結束位置。

keys

keys() & values(); & entrices();

keys();
獲取數組全部成員的索引值。
返回的是一個迭代器對象。
須要經過 for-of遍歷數組迭代器

let colors = ['cyan', 'pink', 'tan', 'red'];

let Idx = colors.keys();

console.log( Idx );  // ArrayIterator {}

for ( let index of Idx ) {
    
    console.log(index);
    
}

values();
獲取全部成員的值,返回一個數組迭代器對象。

let colors = ['cyan', 'pink', 'tan', 'red'];

let vals = colors.values();

console.log( vals );  // ArrayIterator {}

for ( let val of vals ) {
    
    console.log(val);
    
}

entries();
對鍵值對的遍歷。

將數組成員的索引值以及數組成員的值,保留在一個新數組中。

let colors = ['cyan', 'pink', 'tan', 'red'];

let entri = colors.entries();

console.log(entri);

for ( let entris of entri ) {
    
    console.log( entris );
    
}
// 輸出:
// [0, "cyan"]
// [1, "pink"]
// [2, "tan"]
// [3, "red"]

若是不使用for-of遍歷,能夠手動調用遍歷器對象的next();方法,進行遍歷。

let colors = ['cyan', 'pink', 'tan', 'red'];

let entri = colors.entries();

console.log( entri.next().value ); // [0, "cyan"]
console.log( entri.next().value ); // [1, "pink"]
console.log( entri.next().value ); // [2, "tan"]
console.log( entri.next().value ); // [3, "red"]   

// next();方法返回:Object {value: Array[2], done: false}

空位

數組的空位:數組的某一個位置沒有任何值。
例如:Array構造函數返回的數組都是空位。

空位不是undefined,一個位置的值等於undefined,依然是有值的。空位是沒有任何值

注意:空位不是undefiend,一個位置的值等於undefeind。依然是有值。空位是沒有任何值。

0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
// 第一個數組的0號位置是有值的,第二個數組的 0 號位置沒有值。

ES5中對於空位的不一致:

  • forEach(), filter(), every() ,some()都會跳過空位。
  • map()會跳過空位,但會保留這個值
  • join()和toString()會將空位視爲undefined,而undefined和null會被處理成空字符串。

    // forEach();
    [,'a'].forEach((x,i) => console.log(i)); // 1

    // filter();
    ['a',,'b'].filter(x => true) // ['a','b']

    // every();
    [,'a'].every(x => x==='a') // true

    // some();
    [,'a'].some(x => x !== 'a') // false

    // map();
    [,'a'].map(x => 1) // [,1]

    // join();
    [,'a',undefined,null].join('#') // "#a##"

    // toString();
    [,'a',undefined,null].toString() // ",a,,"

ES6則明確將空位轉成undeinfed
因爲空位的處理規則很是不統一,因此應該儘可能避免出現空位。

Object的擴展

屬性的簡潔表示

ES6容許直接寫入變量和函數,做爲對象的屬性和方法。
在定義一個對象的時候,若是對象的屬性與屬性值對應的變量名同名,能夠簡寫屬性名。

let objName = 'bar'; 
let obj1 = {objName}

console.log( obj1 ); // Object {objName: "bar"}

// 等同於
let obj2 = {objName: objName} // Object {objName: "bar"}
console.log(obj2); 

// 在對象之中,只寫屬性名,不寫屬性值,這時候,屬性值等於屬性名所表明的變量。

// 參數簡寫
function Person ( name, age ) {
    return {name, age} // ES6 容許直接引入變量,不用對對象添加同名屬性
}

console.log( Person('cyan', 22) );  // Object {name: "cyan", age: 22}

// 等同於    
function Person(name , age) {
  return {name: name, age: age};
}

// 方法的簡寫
// 若是對象屬性名與對象屬性名對應的方法名稱相同,能夠省略屬性以及 function關鍵字, 像定義類中的方法同樣定義這個屬性方法。

let obj1 = {
    name: 'cyan',
    age: 22,
    sayAge: function () {
        console.log(this.age); // 22
    },
    sayName () { // ES6容許定義方法的時候,能夠省略方法對應的屬性名,和 fucntion 關鍵字
        console.log(this.name); // cyan
    }
}

obj1.sayName();
obj1.sayAge();    

// sayName等同於
    
var obj1 = {
  sayName: function() {
    console.log(this.name);
  }
}

// 這種屬性簡寫用於函數返回很是方便
function getPoint() {
  var x = 1;
  var y = 10;
  return {x, y};
}

getPoint();
// {x:1, y:10}

屬性名錶達式

JavaScript語言定義對象的屬性,有兩種方式。

// 方法一
obj.foo = true;  // 標識符做爲屬性名

// 方法二
obj['a' + 'bc'] = 123; // 表達式做爲屬性名

// 容許定義對象時候,對對象屬性應用表達式
let a = 'num';
let b = 'ber';

let obj = {
    [a + b]: 100
}

console.log( obj );

// 表達式也能夠用於方法名
let obj = {
  ['h'+'ello']() {
    return 'hi';
  }
};

obj.hello() // hi

注意:屬性名錶達式不能與簡潔表示法同時使用,會報錯。

// 報錯
let a = 'b';
let b = 'pink';
let color = { [a] }

console.log(color);

// 正確
let a = 'cyan';
let color = { [a]: 'pink' }

console.log(color);

方法的name屬性

函數的name屬性,返回函數名。
對象方法也是函數,,所以也有name屬性。

let obj = {
    name: 'cyan',
    sayName() {
        return this.name; 
    }
}

console.log( obj.sayName.name ); // sayName

特殊:
bind();方法創造的函數,name屬性返回"bound"加上原函數的名字。
Function構造函數創造的函數,name屬性返回"anonymous".

console.log( (new Function()).name ); // anonymous

let doSomething = function () {}

console.log( doSomething.bind().name ); //bound doSomething

is

Object.is();
做用判斷兩個參數是否相等。
能夠判斷0-0 ,是否相等。

ES5中,比較兩個值是否相等使用:==或者===
==缺點:自動轉換數據類型
===缺點:NaN不等於自身,以及0等於-0

ES6提出「Same-value equality」(同值相等)算法.
用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

assgin

Object.assgin();
做用:用於對象合併。
將源對象(source)的全部可枚舉屬性,複製到目標對象(target)。
參數1:目標對象。
後面的參數都是源對象。

只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false)。

若是目標對象與源對象有同名屬性,或多個源對象有同名屬性,則後面的屬性會覆蓋前面的屬性。
若是隻有一個參數對象,則返回該參數對象。

let obj1 = {
    x: 1,
    y: 2
}

let obj2 = {
    a: 'a',
    b: 'b'
}

console.log( Object.assign(obj1, obj2) );

若是參數不是對象,則會先轉成對象而後返回。
undefeind和null沒法轉成對象,做爲參數,會報錯。

Object.assign(2); // Number {[[PrimitiveValue]]: 2}
Object.assign(undefined) // 報錯
Object.assign(null) // 報錯

Object.assign();是淺拷貝,而不是深拷貝。若是源對象某個屬性的值是對象,那麼目標對象拷貝獲得的是這個對象的引用。

let obj1 = {
    x: 1,
    y: 2
}

let obj2 = {
    a: 'a',
    b: 'b'
}

let resObj = Object.assign(obj1, obj2);

console.log( resObj );
console.log( obj1 );
console.log( resObj == obj1 ); // true
console.log( obj2 );

assign用途

爲對象添加屬性
class Point {
    constructor (x, y) {
        Object.assign(this,{x, y});
    }
}

let p = new Point(10, 20);

console.log(p);  // Point {x: 10, y: 20}
// 經過Object.assign();將x屬性和y屬性添加到Point類的對象實例。
爲對象添加方法
Object.assign(Some.prototype, {
    someMethod1 ( arg1, arg2 ) {
        // ....
    }
    someMethod2 ( arg1, arg2 ) {
        // ....
    }
})


// 等價
Some.prototype.someMethod1 = function ( arg1, arg2 ) {
    // ....
}
Some.prototype.someMethod2 = function ( arg1, arg2 ) {
    // ....
}
克隆對象
function clone(origin) {
  return Object.assign({}, origin);
}    
// 將原對象拷貝到一個空對象,獲得了原始對象的克隆。
合併多個對象

將多個對象合併到某個對象,能夠是空對象中(合併以後返回新的對象)

const merge = (target, ...sources) => Object.assign({}, ...sources);

屬性遍歷

ES6中共有5中能夠遍歷屬性的方法:

  • for-in
    for-in循環遍歷對象吱聲和繼承的可枚舉的屬性(不含Symbol屬性)。
  • Object.keys(obj)
    Object.keys返回一個數組,包括對象自身的(不含繼承)全部可枚舉屬性(不含Symbol屬性)
  • Oject.getOwnPropertyNames(obj);
    Object.getOwnPropertyNames(); 返回一個數組,包含對象自身的全部屬性(不含Symbol屬性,可是包括不可枚舉屬性)
  • Object.getOwnPropertySymbols(obj);
    Object.getOwnPropertySymbols();返回一個數組,包含對象自身的全部Symbol屬性。
  • Reflect.ownKeys(obj);
    Reflect.ownKeys(obj);返回一個數組,包含對象自身的全部屬性,無論是屬性名Symbol或字符串,也無論是否可枚舉

遍歷規則:

  • 首先遍歷全部屬性爲數值的屬性,按照數字排序。
  • 其次遍歷全部屬性名爲字符串的屬性,按照生成時間排序。
  • 最後遍歷全部屬性名爲Symbol值的屬性,按照生成時間排序。

keys&values&entries

Object.keys();
做用: 參數對象自身的(不含繼承的) 全部可遍歷屬性的鍵名。
返回數組

Object.values();
做用:參數對象自身的(不含繼承的) 全部可遍歷屬性的鍵值。
返回數組

Object.entries();
做用:參數對象自身的(不含繼承的) 全部可遍歷屬性的鍵值對數組。
返回數組

let obj = { a: 1, b: 2 };

console.log( Object.entries(obj) );  [ ["a", 1], ["b", 2] ]

Symbol

ES6引入Symbol的緣由:根本上防止屬性名的衝突。

ES6引入一種新的原始數據類型Symbol,表示獨一無二的值。
它是JavaScript語言的第七種中數據類型。前六中是:Undefiend,Null,布爾值(Boolean),字符串(String),數值(Number),對象(Object)

let s = Symbol();
typeof s // symbol

Symbol值經過Symbol函數生成。
對象的屬性名能夠有兩種類型,一種是:string。 一種是:Symbol。(凡是屬性名屬於Symbol類型,就是獨一無二,能夠保證不會與其它屬性名產生衝突)

注意:Symbol函數前不能使用new關鍵字,不然會報錯。由於:Symbol是一個原始類型的值,不是對象。因爲Symbol值不是對象,因此不能添加屬性。基本上,它是一種相似於字符串的數據類型。

Symbol函數能夠接收一個字符串做爲參數,表示對Symbol實例的描述,
做用:控制檯顯示或轉爲字符串時,比較容易區分。

返回值不相等

注意:Symbol函數的參數只是表示對當前Symbol值的描述,所以相同參數的Symbol函數的返回值是不相等的。

// 沒有參數
let s1 = Symbol();
let s2 = Symbol(); 

console.log(s1 == s2);  // false 

// 參數
let s3 = Symbol('a');
let s4 = Symbol('b');

console.log(s3 == s4); // false

不能與其它類型值進行運算

let sym = Symbol('My Symbol');

console.log('your symbol is' + sym); // TypeError: Cannot convert a Symbol value to a string

console.log(`your symbol is` + sym);  // TypeError: Cannot convert a Symbol value to a string

能夠顯示轉換爲String,Boolean。(注意:不可以轉爲數值類型)

let sym = Symbol('My Symbol');

console.log(String(sym)); 
console.log(sym.toString());

console.log(Boolean(sym));
console.log(!sym);

console.log(Number(sym)); // TypeError: Cannot convert a Symbol value to a number
console.log(sym + 2);  // // TypeError: Cannot convert a Symbol value to a number

Symbol與字符串的區別:

  • 獨一無二
  • 值不相等
  • 不可與其它類型值進行運算

做爲屬性名Symbol

因爲每一個Symbol值都是不相等的,意味着Symbol值能夠做爲標識符。
用於對象的屬性名,就能保證不會出現同名的屬性。 對於一個對象由多個模塊構成的模塊狀況,能防止某一個鍵被不當心改寫或覆蓋。

Symbol值做爲屬性名時,是公開屬性,而不是私有屬性。

let mySymbol = Symbol('a');

// 第一種方法
let a = {};
a[mySymbol] = 'hello';

// 第二種寫法
let b = {
    [mySymbol]: 'hell2'
};
// 在對象的內部,使用Symbol值定義屬性時,Symbol值必須放在方括號之中。

// 第三種寫法
let c = {};
Object.defineProperty(c, mySymbol, {
    value: 'hello3'
});

console.log(a[mySymbol], b[mySymbol], c[mySymbol]);

注意:Symbol值做爲對象的屬性名,不能使用點語法。

let mySymbol = Symbol('a');

let obj = {};

obj[mySymbol] = 'hello';

console.log( obj[mySymbol] ); // hello
console.log( obj.mySymbol ); // undefind

定義一組常量,保證這組常量值是不相等。

log.levels = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  WARN: Symbol('warn')
};

log(log.levels.DEBUG, 'debug message');
log(log.levels.INFO, 'info message');

消除魔術字符串

魔術字符串:在代碼之中出現屢次,與代碼造成強耦合的某一個具體的字符串或者數值。
缺點:不利於未來的維護和修改。

消除魔術字符串的方法:把它寫成一個變量,或者常量。
變量或者常量等於那個值並不重要,只要確保不會跟其它的屬性衝突便可。

const shapeType = {
    triangle: Symbol(),
    triangleTow: Symbol()
}

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

let a = getArea( shapeType.triangleTow, { width: 20, height: 20 } );
let b = getArea( shapeType.triangle, { width: 10, height: 10 } );

console.log( a,b ); // 280 , 50

屬性名的遍歷

Symbol做爲屬性名,該屬性不會出如今for-infor-of循環中,也不會被Object.kes();. Object.getOwnProperTyNames();返回。
Symbol並非私有屬性,ES6中提供Object.getOwnPropertySymbols();

Object.getOwnPropertySymbols();
做用:獲取指定對象的全部Symbol屬性名。
返回一個數組。成員是當前對象的全部用做屬性名的Symbol值。

let obj1 = {};
let c = Symbol('c');
let d = Symbol('d');

obj1[c] = 'cyan';
obj1[d] = 'tan';

let objectSymbol = Object.getOwnPropertySymbols(obj1);

console.log( objectSymbol ); // [Symbol(c), Symbol(d)]

Reflect.ownKeys();
做用:返回全部類型的鍵名,包括常規鍵名和Symbol鍵名。

let obj = {
    [Symbol('my_key')]: 1,
    enum: 2,
    nonEnum: 3
};

console.log( Reflect.ownKeys(obj) ); // ["enum", "nonEnum", Symbol(my_key)]

以Symbol值做爲名稱的屬性,不會被常規方法遍歷獲得。能夠爲對象定義一些非私有的,但又但願只用於內部的方法。形成一種非私有話的內部的效果。

Symbol.for(),Symbol.keyFor()

Symbol.for();
做用:使用同一個Symbol的值。
參數:一個字符串。
過程:接收一個字符串後,搜索有沒有以該參數做爲名稱的Symbol值。若是有,就返回這個Symbol值,不然就新建並返回一個以該字符串爲名稱的Symbol值。(具備登記機制,被登記的會在全局環境中供搜索)

let s1 = Symbol.for('a');
let s2 = Symbol.for('a');

console.log( s1 === s2 ); // true

Symbol.keFor();
返回:一個已經登記的Symbol類型值的key。

let s1 = Symbol.for('a');
let s2 = Symbol.for('b');
let s3 = Symbol('b');

console.log( Symbol.keyFor(s1), Symbol.keyFor(s2), Symbol.keyFor(s3) ); // a b undefined

模塊的Singleton模式

Signleton模式:指的是調用一個類,任什麼時候候返回的都是同一個實例。

對於 Node 來講,模塊文件能夠當作是一個類。怎麼保證每次執行這個模塊文件,返回的都是同一個實例呢?

很容易想到,能夠把實例放到頂層對象global。

對於Node來講,模塊文件能夠當作是一個類。怎麼保證每次執行這個模塊文件,都是返回同一個實例呢? 能夠把實例掛載到頂層對象global。

// mod.js
function A () {
    this.foo = 'hello';
}

if ( !global._foo ) {
    global._foo = new A();
}

module.exprots = global._foo;


// 加載mod.js

let mod = require('./mod.js');
console.log( mod.foo );

存在問題: 掛載到global的屬性,是可寫的,任何文件均可以修改。
防止這種狀況,可使用Symbol.

// mod.js
const FOO_KEY = Symbol.foo('foo');

function A () {
    this.foo = 'hello';
}

if ( !global[FOO_KEY] ) {
    global[FOO_KEY] = new A();
}

module.exprots = global[FOO_KEY];

Proxy&Reflect

Proxy概述

Proxy做用:修改某些操做的默認行爲。 在目標對象以前設置屏障,外界對該對象的訪問,都必須先經過這層攔截,來保護該對象(內部的屬性,狀態,方法,行爲等等)。(Proxy的操做等同於在語言層面作出修改,因此屬性一種「元編程」,即對編程語言進行編程)

提供一種機制,能夠對外界的訪問進行過濾和改寫。
Proxy這個詞的原意是代理,這裏表達hi由它來「代理」某些操做,能夠譯爲「代理器」。

語法: new Proxy(代理對象, 屏蔽對象);

屏障的方法,比較特殊,並非任意定,只能經過get來獲取代理對象的信息,set來設置原始對象的額信息。可是它們不是直接處理對象的,在處理以前會過濾一些人爲操做。屏蔽對象配置攔截行爲(對於每個被代理的操做,須要提供一個對應的處理函數,該函數將攔截對應的操做。)

get();
參數:target,key,receiver

set();
參數:target,key,value,receiver

let Start = {
    count: 0
}

let obj1 = new Proxy(Start, {
    
    get: function ( target, key ) {
        
        console.log(`getting ${key}!`);
        
        return target[key];
        
    },
    
    set: function ( target, key, value ) {
        
        console.log(`setting ${key}!`);
        
        return target[key] = value;
        
    }
    
});

console.log( obj1.count );

obj1.count = 1;

console.log( obj1.count );

注意:是的Proxy起做用,必須針對Proxy實例進行操做, 而不是針對目標對象進行操做。
若是屏蔽對象沒有進行任何攔截操做,那就等同於直接經過原對象,操做原對象

let a = {};
let b = {};

let proxy = new Proxy(a, b);

proxy.tag = 'javascript';

console.log(a.tag); // javascript

將Proxy對象,能夠設置到Object.proxy屬性上,從而達到能夠從Object對象上調用。
Proxy實例能夠做爲其餘對象的原型對象。

// 放在object.proxy 屬性上
let object = {
    proxy: new Proxy(target, handler)
}

// 其它對象的原型上
let proxy = new Proxy({}, {
    get: function ( target, property ) {
        console.log( target[property] );
        return 10;
    }
});

let obj = Object.create(proxy);

console.log(obj.time); // 10

同一個攔截器函數,能夠設置多個操做。

var handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },

  apply: function(target, thisBinding, args) {
    return args[0];
  },

  construct: function(target, args) {
    return {value: args[1]};
  }
};

var fproxy = new Proxy(function(x, y) {
  return x + y;
}, handler);

fproxy(1, 2) // 1
new fproxy(1,2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo // "Hello, foo"

Proxy攔截器操做函數

對於能夠設置,但沒有設置攔截的操做,則直接落在目標對象上,按照原先的方式產生結果。

get()
攔截對象屬性的讀取

set()
攔截對象屬性的設置

has(target, propKey)
攔截propKey in proxy的操做,以及對象的hasOwnProperty();
返回值:布爾值。

判斷對象是否具備某個屬性時,方法纔會生效。典型的操做就是in運算符。
使用has方法隱藏某些屬性,不被in運算符發現

let headler = {
    
    has ( target, key ) {
        
        if ( key[0] === '_' ) {
            return false;
        }
        
        return key in target;
        
    }
    
}

let target = { _prop: 'aaa', prop: 'bbb' }

let proxy = new Proxy(target, headler);

console.log( '_prop' in proxy ); // false

deleteProperty(target, propKey)
攔截delete proxy[propKey]的操做。
返回值:布爾值。 表示是否刪除成功。

apply(target, object, args)
參數args:目標對象的參數數組。
攔截Proxy實例做爲函數調用的操做,好比proxy(...ars) ,proxy.call(object, ...args), proxy,apply(...)

let target = function () {
    return 'target';
}

let handler = {
    apply: function () {
        console.log('apply');
        return 'handler';
    }
}

let p1 = new Proxy(target, handler);

console.log(p1()); // hanlder

construct(target, args)
攔截做爲構造函數調用的操做。 好比: new Proxy(...args)

攔截函數和PHP中的魔術方法有相同的原理,當必定條件下,會觸發該聲明的函數。

Reflect概述

Reflect對象與Proxy對象同樣
爲了操做對象而提供的新API

設計目的:

  • 將Object對象的一些明顯屬性語言內部的方法(好比Object.defiendProerty),放到Reflect對象上,現階段,某些方法同時在Object和Reflect對象上部署,將來的新方法將只部署在Reflect對象上。
  • 修改某Object方法的返回結果,讓其變得更合理。
  • 讓Object操做都變成函數行爲
    好比: delete obj[name] 和 name in obj 的指令式操做
  • Reflect對象的方法與Proxy對象的方法一一對應,只要是Proxy對象的方法,就能在Reflect對象上找到對應的方法。

函數的擴展

參數的默認值

ES5的默認參數作法

若是沒有if判斷,參數是否被賦值,若是沒有,再等於默認值
緣由:若是參數賦值了,可是對應值是false,則該賦值就不該該起做用。 例如:調用的時候log('hello', ''); 若是參數是空字符串,就不該該被改成默認值。

function log ( x, y ) {
    
    if ( typeof y === undefined ) {
        
        y = y || 'world';
        
    }
    
    console.log(x, y);
    
}

log('Hello'); // Hello world
log('Hello', 'hi'); // Hello hi
log('Hello', ''); //   Hello

ES6使用默認參數,直接寫在參數定義的後邊。
好處:
1:易閱讀代碼
2:利於將來的代碼優化。將來的版本在對外接口中,測底拿掉這個參數,也不會致使之前的代碼沒法執行。

特色:參數變量時默認聲明的,不能用let或const再次聲明

function Point ( x, y=10 ) {
    this.x = x;
    this.y = y;
}

let p = new Point(100);
console.log(p);  // Point {x: 100, y: 10}

參數默認的位置

通常的,定義默認值的參數,應該是函數的尾參數。這樣能夠比較容易看出來,到底省略了哪些參數。

若是非尾參數設置默認值,這個參數是無法省略。(想使用默認值,設置爲undefeind)

function fn ( x = 1, y ) {
    
    return [x ,y];
    
}

console.log( fn() );  // [1, undefined]
console.log( fn(2) );  // [2, undefined]
console.log( fn(undefined, 1) ); // [1, 1] // 若是傳入undefeind,將觸發該參數等於默認值, 
console.log( fn(null, 1) ); // [null, 1] // 若是傳入null,則會輸出。
console.log( fn( , 10) ); // error

function fn2 ( x, y = 1, z ) {
    
    return [x, y, z];
    
}

console.log( fn2() ); // [undefined, 1, undefined]
console.log( fn2(2) ); // [2, 1, undefiend]
console.log( fn2(2, undefined, 3) ); // [2, 1, 3]
console.log( fn2(2, , 3) ); // error

函數的length屬性

length屬性的含義:該函數預期傳入的參數個數。

指定了默認值後,length屬性將失真。將返回沒有指定默認參數的個數。
某個參數指定了默認值之後,預期傳入的參數個數就不包括這個參數了。rest參數不會計入length屬性個數中。

let len1 = (function (a) {}).length;

let len2 = (function (a = 5) {}).length; 

let len3 = (function ( a, b, c = 1 ) {}).length;  

let len4 = (fucntion (...args) {}).length;

console.log(len1); // 1
console.log(len2); // 0
console.log( len3 ); // 2
console.log( len4 ); // 0

做用域

若是參數默認值是一個變量,則該變量所處的做用域,與其它變量的做用規則是同樣的,即先是當前函數的做用域,而後纔是全局做用域。

var x = 1;

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

fn(2); // 2

若是參數默認值是一個函數
該函數的做用域是其聲明時所在的做用域

let foo = 'outer';

function bar ( func = x => foo ) {
    
    let foo = 'inner';
    
    console.log( func() ); // outer
    
}

bar();
// 函數bar的參數func的默認值是一個匿名函數,返回值爲變量foo。這個匿名函數聲明時,bar函數的做用域尚未造成,因此匿名函數裏面的foo指向外層做用域foo

應用
利用參數默認值,能夠指定某一個參數不得省略,若是省略就拋出一個錯誤。

function throwMsg () {
    throw new Error('error');
}

function foo ( pro1 = throwMsg()  ) {
    
    return pro1;
    
}

foo(); // 拋出錯誤

能夠將參數默認值設置爲unfeined,代表這個參數是能夠省略的。

function foo ( options = undefined ) {

}

rest參數

語法:...變量名
做用:把逗號隔開的值序列組合成一個數組

獲取過來的是數組,將制定的多餘變量放入數組中。

使用rest參數就不須要使用arugments對象

function add ( ...nums ) {
    
    let sum = 0;
    
    for ( let val of nums ) {
        
        sum += val;
        
    }
    
    return sum;
    
}

console.log( add(1, 2, 3) );  // 6

注意:rest參數以後不能再有其它參數(即只能是最後一個參數)

擴展運算符

擴展運算符...
比如rest參數的逆運算。

做用:把數組或類數組對象展開成一系列用逗號隔開的值(將一個數組轉爲用逗號分隔的參數序列).

console.log(...[1, 2, 3]);
// 1 2 3

console.log(1, ...[2, 3, 4], 5 );
// 1 2 3 4 5

通常的,主要用於函數調用:

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

let nums = [10, 20];

console.log( add(...nums) ); // 30

擴展運算符與正常的函數參數,rest參數能夠配合使用,體現靈活性。

function fn ( x, y, z, n, m, ...nums ) {
    
    console.log(...nums); // 5 ,6
    
}

let args = [1, 2];

fn(-1, ...args, 3, ...[4, 5], 6);

擴展運算符的應用

合併數組
let moreArr = [3,4];

let arr1 = [1, 2].concat(moreArr);

console.log(arr1);

console.log( [1, 2, ...moreArr] );


let arr2 = ['a', 'b'];

let arr3 = ['c', 'd'];

let arr4 = ['e', 'f'];

// ES5的寫法
console.log( arr2.concat(arr3, arr4) );

// ES6的寫法
console.log( [...arr2, ...arr3, ...arr4] );
與解構賦值結合

擴展運算符與解構賦值結合起來使用,用於生成數組。
注意:擴展運算符只能放在參數的最後一位。(不然將會報錯)

const [first, ...rest] = [1, 2, 3, 4, 5];   
const [...butLast, last] = [1, 2, 3, 4, 5]; // SyntaxError: Rest element must be last element in array

const [first, ...middle, last] = [1, 2, 3, 4, 5];  // SyntaxError: Rest element must be last element in array
函數返回值

JavaScript的函數只能返回一個值,若是須要返回多個值,只能返回數組或對象。擴展運算符提供變通方法。

字符串

擴展運算符能夠將字符串轉爲數組

console.log( [...'hello'] ); // ["h", "e", "l", "l", "o"]
實現了Iterator接口的對象

任何Iterator接口對象,均可以使用擴展運算符轉爲真正的數組
例如:arguments對象,nodeList對象

let arrList = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
}

console.log( [...Array.from(arrList)] ); // ["a", "b", "c"]
console.log( [...arrList] );  // TypeError: arrList[Symbol.iterator] is not a function

name屬性

函數的name屬性,返回該函數的屬性名。
屬性被瀏覽器支持,到ES6才寫入規範中。

ES5中匿名函數,name屬性會返回空字符串。
ES6中匿名函數,name屬性會返回實際的函數名

ES5,ES6中,有名的匿名函數,name屬性返回具備函數名的名字

Function 構造函數返回函數實例, name 屬性值爲:anonymouse

let fn1 = function () {};

// ES5
console.log( fn1.name ); // ''

// ES6
console.log( fn1.name ); // fn1


const fn = function fn2 () {}

// ES5
console.log( fn.name );  // fn2

// ES6
console.log( fn.name ); // fn2

// Function 構造函數
console.log( (new Function).name ); // 'anonymouse';

箭頭函數

基本用法

使用 => 定義函數
() => 表達式
參數 => 表達式

var f = v => v;
// 等價
var  f = function ( v ) {
    return v;
}

若是箭頭函數須要多個參數或者不須要參數,可使用()表明參數。

() => { 函數體 }

var fn = () => 5;

// 等同於
var fn = function () { return 5; }



var sum = (num1, num2) => num1 + num2;

// 等同於
var sum = function ( num1, num2 ) { return num1 + num2 }

箭頭函數返回對象,須要在對象外加()

var getTemp = id => ({ id: id, name: 'cyan' });

箭頭函數與變量解構賦值,rest參數搭配使用

let fn = (...s) => {
    console.log(...s);
}

const full = ( first, last ) => {
    return first+ ' ' + last;
}

// 等同於
function full( first, last ) {
    return first+ ' ' + last;
}

箭頭函數使用注意點

  • 函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。
  • 不能夠看成構造函數,也就是不可使用new關鍵字。不然會拋出一個錯誤。(names is not a constrtucor)
  • 不可使用arguments對象,由於arguments對象不存在函數體內
  • 不可使用yield命令, 所以箭頭函數不能做用Generator函數。
this固定

this對象在箭頭函數中,是固定的。

this的指向固定化,有利於封裝回調函數。
this指向固定化,並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this。致使內部的this就是外層代碼塊的this。正是由於它沒有this,因此也就不可以做爲構造函數。

箭頭函數的this永遠指向定義的時候的做用域,在各對象中定義,就指向那個對象。
當使用call()和apply()方法更改做用於時候,箭頭函數this是沒有更改爲功。

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

    setTimeout(function () {
        console.log('id: ' ,this.id); //41
    }, 1000);
        
}

var id = 21;

foo.call({ id: 42 });
// 箭頭函數致使this老是隻想函數定義生效時所在的對象

對比箭頭函數的setTimeout和ES5版本的普通回調函數:

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

foo();

//ES5
function fn2 () {
    let _this = this;
    
    setTimeout(function () {
        console.log('id:', _this.id);
    }, 100);
}

fn2();

// fn2中清楚的說明了,箭頭函數裏面根本沒有本身的this,而是引用外層的this。

function foo() {
    
  return () => {
      
      console.log(this); // Object {id: 1}
      
    return () => {
        
        console.log(this); // Object {id: 1}
        
      return () => {
          
          console.log(this); // Object {id: 1}
          
        console.log('id:', this.id); // id: 1

      };

    };

  };

}

var f = foo.call( {id: 1} );

f()()();

箭頭函數中不存在:this,argumetns, supernew.target.相應的指向外層函數的對應 this,argumetns, supernew.target

function foo () {
    
    console.log('args:', arguments); // args: [1, 2, 3, 4]
    
    setTimeout( () => {
        
        console.log('args:', arguments); // args: [1, 2, 3, 4]
        
    },100 );
    
}

foo(...[1, 2, 3, 4]);

箭頭函數嵌套

箭頭函數的嵌套:具備管道機制(前一個函數的輸出是後一個函數的輸入)。

let insert = (value) => ({
    
    into: (array) => ({
        
        after: (afterValue) => {
            
            array.splice( array.indexOf(afterValue) + 1, 0, value );
            
            return array;
            
        }
        
    })
});

console.log( insert(2).into([1, 3]).after(1) ); //[1, 2, 3]

尾調用優化

尾調用

尾調用:指某個函數的最後一步是調用另外一函數。
尾調用是函數式編程的重要概念。

function fn (x) {
    return g(x); // 函數的最後一步調用函數 g() ,就是指的尾調用
}

// 不屬於爲調用的情形
function f1 ( x ) {
    let y = g(x);
    return y;    
}
// 調用函數  g(x) 以後,還有賦值操做。

function f2 (x) {
    return g(x) + 1; 
}
// 調用以後還有其它操做

function f3 ( x ) {
    g(x);
}
// f3 最後是返回 undefined


// 尾調用不必定要出如今函數的尾部

function f ( x ) {
    
    if ( x > 0 ) {
        return m(x);
    }
    
    return n(x);
    
}

尾調用優化

尾調用特殊之處:與其它調用不一樣的,在於它特殊的調用位置。

函數調用會在內存造成一個「調用記錄」,又稱「調用幀」 (call frame),保存調用位置和內部變量等信息。
若是在函數A的內部調用函數B,那麼在A的調用幀上方,還會造成一個B的調用幀。等到B運行結束,將結果返回到A,B的調用幀纔會消失。若是函數B內部還調用函數C,那就還有一個C的調用幀,以此推類。全部的調用幀,就造成一個「調用棧」(call stack)

尾調用因爲是函數的最後一步操做,因此不須要保留外層函數的調用幀,由於調用位置,內部變量等信息都不會再使用到。只要直接用內層函數的調用幀,取代外層函數的調用幀就能夠。

尾調用優化:只保留內層函數的調用幀

function fn () {
    let m = 1;
    let n = 2;
    return g(m+n); // 執行到這一步,徹底能夠刪除fn()的調用幀,只保留 g(3)  的調用幀
}

fn();

// 等同於
function fn () {
    return g(3);
}

fn();

// 等同於
g(3);

// 「尾調用優化」: 只保留內層函數的調用幀。

若是全部函數都是尾調用,那麼徹底能夠作到每次執行時,調用幀只有一項,效果就大大節省內存。

注意:只有再也不用到外層函數的內部變量,內層函數的調用幀纔會取代外層函數的調用幀,不然就沒法進行「尾調用優化」

尾遞歸

函數調用自身,稱爲遞歸,若是尾調用自身,就稱之爲:尾遞歸。

遞歸很是消耗內存,由於須要同時保存成千上百個調用幀,很容易發生"棧溢出"錯誤(stack voerflow).可是對於尾遞歸來講,因爲只存在一個調用幀,因此不會發生"棧溢出"錯誤。

// 對比階乘  使用尾遞歸 和 不使用尾遞歸的情形

function factorial ( n ) {
    
    if ( n === 1 ) {
        return 1;
    }
    
    return n * factorial(n-1); 
    
}


// 尾遞歸
function factorial2 ( n, total ) {
    
    if ( n === 1 ) {
        return total;
    }
    
    return factorial2( n-1, n * total );
    
}

factorial2( 5, 1 ); // 120

遞歸函數的改寫

尾遞歸的實現,每每須要改寫遞歸函數,確保最後一步只調用自身。作到這一點的方法,就是把全部用到的內部變量改寫成函數的參數。

方法1:是在尾遞歸函數以外,再提供一個正常形式的函數。

function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

function factorial(n) {
  return tailFactorial(n, 1);
}

factorial(5) // 120

方法2:函數柯里化
柯里化:將多參數的函數轉換成單參數的形式。(函數式編程的重要概念)

方法3:ES6的函數默認值

function factorial ( n, total = 1 ) {
    
    if ( n === 1 ) return total;
    
    return factorial(n-1, n * total);
    
}

console.log( factorial(5) ); // 120;

遞歸本質上是一種循環操做。純粹的函數式編程語言沒有循環操做命令,全部的循環都是使用遞歸實現。
對於支持「尾調用優化」的語言,只須要知道循環能夠用遞歸代替。
若是一旦使用遞歸,就最好使用尾遞歸。

尾遞歸只在嚴格模式下有效果

正常模式下:函數內部有兩個變量,能夠跟蹤函數的調用棧。

func.argumetns : 返回調用時函數的參數
func.caller: 返回調用當前函數的那個函數。

尾遞歸優化生效的時候,函數的調用棧會改寫,所以argumetns,caller會失真。嚴格模式下禁用這兩個變量。 因此尾遞歸模式僅在嚴格模式下生效。

Set和Map數據結構

Set

基本用法

Set對象是ES6中新增的一種表示集合的數據結構。它相似於數組,可是成員的值都是惟一的。沒有重複的值。

Set自己是一個構造函數,須要經過new關鍵字來生成。

Set函數能夠接受一個數組(或相似數組的對象)做爲參數,用來初始化。
獲得的實例化對象是去重複的Set對象。

let s2 = new Set([123, 23, , 345, 345, 23]);

console.log(s2); // Set {123, 23, undefined, 345}
console.log(...s2); // 123 23 undefined 345

console.log( s2.size ); // 4

Set實例的屬性和方法

Set結構的實例屬性:

Set.prototype.constructor:構造函數,默認就是Set函數
Set.prototype.size: 返回Set實例的成員總數

Set實例方法分爲:操做方法(用於操做數據) 和遍歷方法(用於遍歷成員)

操做方法
  • add(value): 添加某個值,返回Set結構自己。
  • delete(value):刪除某個值,返回一個布爾值,表示是否刪除成功。
  • has(value):返回一個布爾值,表示該值是否爲Set成員。
  • cleart(): 清除全部成員,沒有返回值。

Array.form()能夠將Set結構轉爲數組。

var items = new Set([1, 2, 3, 5, 6, 3, 4]);

console.log( Array.from(items) );  // [1, 2, 3, 5, 6, 4]
遍歷操做
  • keys(); 返回鍵名的迭代器。
  • values(); 返回鍵值的迭代器。
  • entries(); 返回鍵值對的迭代器。
  • forEach(); 使用回調函數遍歷每一個成員。

Set結構的遍歷順序是插入順序。 使用場景好比:使用Set結構保存一個回調函數列表,調用的時候,就可以保證按照添加順序調用。

因爲Set結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值)。 因此keys() 和values()是相同的。

let set = new Set(['pink', 'cyan', 'tan']);

for (let item of set.keys()) {
  console.log(item);
}
// pink
// cyan
// tan

for (let item of set.values()) {
  console.log(item);
}
// pink
// cyan
// tan

for (let item of set.entries()) {
  console.log(item);
}
// ["pink", "pink"]
// ["cyan", "cyan"]
// ["tan", "tan"]

Set結構的實例默承認遍歷,它的默認遍歷器生成函數就是它values方法。

console.log( Set.prototype[Symbol.iterator] === Set.prototype.values ); // true

遍歷的應用

  • 數組去重
  • 擴展運算符(...) 內部使用 for -of循環
  • 實現交集,並集,差集。
  • 與Array.from()搭配使用
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}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

WeakSet

WeakSet結構與Set結構相似,也是不重複的值的集合。

WeakSet是一個構造函數,須要經過new關鍵字。指定參數初始化。
參數:對象,或者 一個數組或相似數組的參數(具備Iterator接口的對象)

var ws = new WeakSet();

var a = [[1, 2], [3, 4]];

console.log( ws.add(a) ); // WeakSet {[Array[2], Array[2]]}
console.log( new WeakSet(a) );  // WeakSet {[1, 2], [3, 4]}


new WeakSet([1, 2]); // TypeError: Invalid value used in weak set
// 數組的成員成爲WeakSet的成員,而不是數組自己,意味着,數組的成員只能是對象。

區別:
1:WeakSet的成員只能是對象,而不能是其它類型的值。(具備Iterator接口的對象,均可以做爲WeakSet參數)
2:WeakSet對象中弱引用,即垃圾回收至機制不考慮WeakSet對該對象的引用。也就是說:若是其它對象都再也不引用該對象,那麼垃圾回收機制會自動回收該對象所佔的內存。(WeakSet存在內存中的新生代中,當Form空間和To空間的角色發生對換察覺沒有引用就回收),不考慮對象還存在於WeakSet中。這個特色意味着,沒法引用WeakSet成員,所以WeakSet是不可遍歷。

var ws = new WeakSet();

console.log( ws.add(window) ); // window對象
console.log( ws.add({a: 10}) ); //  Object {a: 10}} 
// console.log( ws.add(Symbol()) );  // TypeError: Invalid value used in weak set
// console.log( ws.add(1) ); //  TypeError: Invalid value used in weak set
// console.log( ws.add('abc') ); //  TypeError: Invalid value used in weak set

WeakSet結構方法

  • WeakSet.prototype.add(value): 向WeakSet實例添加一個新成員。
  • WeakSet.prototype.delete(value);:清除WeakSet實例的指定成員
  • WeakSet.prototype.has(value);返回一個布爾值,表示某個值是否在WeakSet實例之中。

    var ws = new WeakSet();

    ws.add(window);

    console.log( ws.has(window) ); // true

WeakSet結構的特色:

不能遍歷,緣由:成員都是弱引用,隨時均可能消失。遍歷機制沒法保證成員的存在,極可能剛剛遍歷結構,成員就取不到了。

典型的應用:
存儲DOM節點,不用可是這些節點充文檔移除時,會引起內存泄漏。

Map

Map結構的目的和基本用法

JavaScript的對象,本質上是鍵值對的集合(Hash結構).可是傳統上只能用字符串看成鍵

Map數據結構,相似對象,也是鍵值對的結合。可是「鍵」的範圍不只限於字符串。各類類型的值(包括對象)均可以看成鍵。
Object結構提供了一種「字符串-值」的對應。Map提供了一種「值-值」的對應。是一種更完善的Hash結構實現。

var m  = new Map();

var o = {p: 'hello'}

m.set(o, 'content');

console.log( m.get(o) ); // content

Map做爲構造函數,參數能夠是一個數組,該數組的成員是一個個表示鍵值對的數組。初始化函數。(注意是接受參數,二維數組)

var  m = new Map([['name', 'cyan']]);

console.log( m.get('name') );

若是對同一個鍵屢次賦值,後面的值將覆蓋前面的值。
若是讀取一個未知的鍵,返回undefined。

let colorsMap = new Map();

colorsMap.set(1, 'tan')
    .set(1, 'pink');
    
console.log( colorsMap.get(1) ); // pink    
new Map().get('asfddfsasadf'); // undefined

注意:只有同一個對象的引用 ,纔是 同一個鍵。

let m = new Map();

m.set(['a'], 111);

console.log( m.get(['a']) ); // undefeinds
// 表面是同一個鍵,但實際上,這是兩個值,內存地址是不同的,所以get()方法沒法讀取該鍵,返回undefiend

Map的鍵,其實是根內存地址綁定的,只要內存地址不同,就視爲兩個鍵。 解決了:同名屬性碰撞(clash) 問題。

若是Map的鍵是一個簡單類型的值(數字,字符串,布爾值), 則只要兩個值嚴格相等。Map將其視爲一個鍵,包括(0,-0) 雖然NaN不嚴格相等於自身,但Map將其視爲同一個鍵

let map = new Map();

map.set(NaN, 123);
map.get(NaN) // 123

map.set(-0, 123);
map.get(+0) // 123

實例的屬性和操做方法

屬性:
size屬性
返回Map結構的成員總數

操做方法:

  • set(key, value) :set()方法 設置key所對應的鍵值,返回正Map結構。若是key已經有值,則鍵值會被更新,不然新生成該鍵 (set返回的是Map結構,可使用鏈式寫法)
  • get(key): get()方法讀取key對應 鍵值,若是找不到key,返回undefined。
  • has(key): has();返回一個布爾值,表示某個鍵是否在Map數據結構中。
  • delete(key): delete(); 刪除某個鍵,返回布爾值,若是刪除失敗,返回false
  • clear(): 清除全部成員,沒有返回值。

遍歷方法:

三個遍歷器,一個遍歷方法
Map的遍歷順序就是插入順序。

  • keys(): 返回鍵名的遍歷器
  • values(): 返回鍵值的遍歷器
  • entries(); 返回全部成員的遍歷器
  • forEach(); 遍歷Map的全部成員

let m = new Map([
    ['f', false],
    ['t', true]
]);


for ( let item of m.keys() ) {
    console.log( item );
}
// f
// t

for ( let item of m.values() ) {
    console.log( item );
}
// false
// true


for ( let item of m.entries() ) {
    console.log( item );
}
// ["f", false]
// ["t", true]

m.forEach( ( val, idx, map ) => {
    
    console.log( val, idx, map );
    
} );

Map與其它類型轉換

Map轉爲數組

使用:擴展運算符...

let m = new Map([
    ['f', false],
    ['t', true]
]);

console.log(...m);  // ["f", false] ["t", true]
數組轉爲Map結構

把數組放入Map構造函數中,就能夠轉爲Map結構。

let m = new Map([
    ['f', false],
    ['t', true]
]);
Map結構轉爲對象

前提:全部Map結構中的鍵都是字符串,它能夠轉爲對象

function strMapToObj ( strMap ) {
    let obj = Object.create(null);
    for ( let [k, v] of strMap ) {
        obj[k] = v;
    }
    return obj;
}
let myMap = new Map().set('yes', true).set('no', false);
console.log(strMapToObj(myMap) );  // Object {yes: true, no: false}
對象轉爲Map結構
function objtoStrMap ( obj ) {
    let strMap = new Map();
    for ( let k of Object.keys(obj) ) {
        strMap.set(k, obj[k]);
    }
    return strMap;
}

console.log( objtoStrMap({yes: true, no: false}) ); // Map {"yes" => true, "no" => false}
Map結構轉爲JSON

Map轉爲JSON分爲兩種:
Map的鍵名都是字符串,能夠選擇JSON.stringify();
Map的鍵名有非字符串,能夠選擇轉爲數組JSON

// Map 轉爲對象
function strMapToObj ( strMap ) {
    let obj = Object.create(null);
    for ( let [k, v] of strMap ) {
        obj[k] = v;
    }
    return obj;
}


// Map 轉爲JSON
function strMapToJson ( strMap ) {
    return JSON.stringify( strMapToObj(strMap) );
}

let myMap = new Map().set('yes', true).set('no', false);

console.log( strMapToJson(myMap) ); // {"yes":true,"no":false}


function mapToArrayJson ( map ) {
    return JSON.stringify([...map]);
}

let myMap2 = new Map().set('a', 'tan').set('b', 'pink').set('c', 'tan');

console.log( mapToArrayJson(myMap2) ); // [["a","tan"],["b","pink"],["c","tan"]] 
// 轉爲二維數組
JSON轉爲Map

JSON轉爲Map,正常狀況下,全部鍵名都是字符串。

function objToStrMap ( obj ) {
    let strMap = new Map();
    for ( let k of Object.keys(obj) ) {
        strMap.set(k, obj[k]);
    }
    return strMap;
}

function jsonToStrMap ( jsonStr ) {
    return objToStrMap(JSON.parse(jsonStr));
}

let json = '{"a": "tan", "b": "pink", "c": "cyan"}';

console.log( jsonToStrMap(json) ); // Map {"a" => "tan", "b" => "pink", "c" => "cyan"}

WeakMap

WakMap結構與Map結構基本相似。
區別:只接受對象做爲鍵名(null除外),不接收其它類型的值做爲鍵名,並且鍵名所指向的對象,不計入垃圾回收機制。

WeakMap的設計目的在於,鍵名是對象的弱引用(垃圾回收機制不應將引用考慮在內),因此其對應的對象可能會被自動回收。當對象被回收後,WeakMap自動移除對應的鍵值對。
典型的: 一個對應的DOM元素的WeakMap結構,DOM節點做爲鍵名。當某個DOM元素被清除,其所對應的WeakMap記錄就會被自動被移出。基本上,WeakMap的使用場合,它的鍵所對應的對象,可能會在未來消失。WeakMap結構有助於防止內存泄漏

let wp = new WeakMap();

wp.set(document.body, 'body');

console.log( wp ); // WeakMap {body {} => "body"}

WeakMap的方法:

  • get()
  • set()
  • has()
  • delete()

Iterator和for-of循環

Itertor(迭代器,遍歷器)的概念

JavaScript原有的表示「集合」的數據結構,主要是數組和對象。ES6添加了Map,和Set。

需求:一種統一的接口機制,來處理全部不一樣的數據結構。

遍歷器(Iterator),是一種接口,爲個各類不一樣的數據結構提供統一的訪問機制。
任何數據結構只要部署了Iterator接口,就能夠完成遍歷操做。(即依次處理該數據接口的全部成員)

Iterator的做用:

  • 爲各類數據結構,提供一個統一的,簡便的訪問接口。
  • 使得數據結構的成員可以按照某種次序排列
  • ES6創造了一種新的遍歷命令for-of循環,Iterator接口主要供for-of消費

Iterator遍歷過程:

  1. 建立一個指針對象,指向當親啊數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。
  2. 第一次調用指針對象的next()方法,能夠將指針指向數據結構的第一個成員。
  3. 第二次調用指針對象的next()方法,指針就指向數據結構的第二個成員。
  4. 不斷調用指針對象的next()方法,直到它指向數據結構的結束位置。

每一次調用next()方法,都會返回數據結構的當前成員的信息。
具體:返回一個包含valuedone兩個屬性的對象。
value: 當前成員的值
done: 屬性是一個布爾值,表示遍歷是否結束。

let it = makeIterator(['a', 'b']);
    
    it.next();
    it.next();
    it.next();
    
    function makeIterator ( arr ) {
        
        var nextIndex = 0;
        return {
            next: function () {
                return nextIndex < array.length ? {  value: array[nextIdex++] } : { done: true }
            }
        }
        
    }
    // 定義一個函數,做用:遍歷器對象。對數組['a', 'b'] 執行這個函數,就會返回該數組的遍歷器對象(指針對象) it。

調用的指針對象的next方法,就能夠遍歷事先給定的數據結構。
因爲Iterator只是把接口規格加到數據結構之上,遍歷器與它所遍歷的那個數據結構,其實是分開的,徹底能夠寫出沒有對應數據結構的遍歷器對象,或者說用遍歷器對象模擬出數據結構。

在ES6中,有些數據結構原生具有Iterator接口(好比數組),即不用任何處理,就能夠被for-of循環遍歷。有些數據結構就不能夠直接被遍歷(好比對象).
緣由:數據結構原生部署了Symbol.iterator屬性。凡是部署了Symbol.iterator屬性的數據結構,就稱爲部署了遍歷器接口,調用這個接口,就會返回一個遍歷器對象。

數據結構的默認Iterator接口

Iterator接口的目的:爲全部的數據結構,提供一種統一的訪問機制。
當使用for-of循環遍歷某種數據結構時,該循環會自動尋找Iterator接口。

ES6規定,默認的Iterator接口部署在數據結構的Symbol.iterator屬性,或者說,一個數據結構只要具備Symbol.iterator屬性,就能夠認爲是「可遍歷的」
調用Symbol.iterator()方法,就會獲得當前數據結構的默認的遍歷器生成函數。Symbol.iterator自己是一個表達式,返回Symbl對象的iterator屬性。 這是一個預約義好的,類型爲Symbol的特殊值,因此要放在方括號內。

ES6中三類數據結構原生具有Iterator接口:

  • 數組
  • 某些相似數組的對象
  • Set結構和Map結構

其它數據(主要是Object)的Iterator接口,須要本身的Symbol.iterator屬性上面部署。
對象之因此沒有默認部署Iterator接口,緣由:對象的那個屬性先遍歷,那個屬性後遍歷是不肯定的。

本質上,遍歷器是一種線性處理,對於任何非線性的數據結構,部署遍歷器接口,就等於部署一種線性轉換。

對於相似數組的對象(存在數值鍵名和length屬性),部署Iterator接口,就是Symbol.iterator方法直接引用數組的Iterator接口。

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

相似對數的對象調用數組Symbl.iterator()

let iterable = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
    [Symbol.iterator]: Array.prototype[Symbol.iterator]
}

for ( let item of iterable ) {
    console.log(item);
}
// a
// b
// c

調用Iterator接口的場合

默認調用

解構賦值

對數組和Set結構進行解構賦值時,會默認調用Symbol.iterator()方法

let s1 = new Set().add('a').add('b').add('c');

let [x ,y] = s1;

let [first, ...rest] = s1;

console.log( first, ...rest ); // a b c
擴展運算符

擴展運算符... 會默認調用Iterator

let str = 'hello';

console.log( [...str] ); // ["h", "e", "l", "l", "o"]

let arr = ['b', 'c'];

console.log( ['a', ...arr, 'd'] ); // ["a", "b", "c", "d"]
yield*

yield*後面跟的是一個可遍歷的結構

其它場合

因爲數組的遍歷會調用遍歷器接口,因此任何接受數組做爲參數的場合,其實都調用了遍歷器接口。

  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]]))
  • Promise.all()
  • Promise.race()

字符串的Iterator接口

字符串是一個相似數組的對象,也原生具備Iterator接口。

let someString = 'hi';

console.log( typeof someString[Symbol.iterator] ); // function

let it = someString[Symbol.iterator](); // 指針對象

console.log( it.next() ); // Object {value: "h", done: false}
console.log( it.next() ); // Object {value: "i", done: false}
console.log( it.next() ); // Object {value: undefined, done: true}

for-of循環

for-of循環可使用的範圍包括數組,Set和Map結構,某些相似數組的對象(好比arguments對象,DOM NodeList對象),Generator對象,以及字符串。

JavaScript原有的for-in循環,只能得到對象的鍵名,不能直接獲取鍵值,ES6提供的for-of循環,容許遍歷得到鍵值。

var arr = ['a', 'b', 'c', 'd'];

for ( let a in arr ) {
    console.log(a) // 0 1 2 3
}

for (let a of arr) {
    console.log( a ); // a b c d 
}

相似數組的對象都具備Iterator對象,經過Array.from();轉爲數組。

let arrayLike = { length: 2, 0: 'a', 1: 'b' };

// 報錯
for (let x of arrayLike) {
  console.log(x);
}

// 正確
for (let x of Array.from(arrayLike)) {
  console.log(x);
}

其它遍歷語法比較

最原始的使用for循環。

缺點:寫法麻煩

提供forEach();

缺點:沒法中途跳出forEach循環,break,return,continue不能生效。

for-in循環

缺點:

  • 數組的鍵名是數字,可是for-in循環是以字符串做爲鍵名「0」,"1",「2」等等。
  • for-in循環不只遍歷數字鍵名,還會遍歷手動添加的其它鍵,甚至包括原型鏈上的鍵。
  • 某些狀況下,for-in循環會以任意順序遍歷鍵名。

for-in循環主要是遍歷對象,不適用遍歷數組。

for-of循環:

優勢:

  • 有着同for-in同樣的簡介語法。
  • 不一樣於forEach(), 能夠與break,continue和reutrn配合使用
  • 提供了遍歷全部數據結構的統一操做接口。

Promise對象

Promise是一種形式

Promise: 先知,(預先將你的將來告知,規劃好你繼續的路)
將異步操做轉換成更符合先知視角的形式展示

所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。
語法上:Promise是一個對象,從它能夠獲取異步操做的消息。Promise提供統一的API,各類異步操做均可以用一樣的方法進行處理。


Promise對象特色:

  • . 對象的狀態不受外界影響。Promise對象表明一個異步操做,有三種狀態。

Pending(進行中),resolved(已完成,又稱Fulfilled)rejected(已失敗)

只有異步操做的結果,能夠決定當前是哪種狀態,任何其它操做都沒法改變這個狀態。表示無其它手段改變。

  • . 一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。Promise 對象的狀態改變。只有兩種可能:Pending變爲resolved 和 **從

Pending變爲rejected** 任何一種均可以讓狀態凝固,就不會再繼續變化。

Promise 缺點:

  • 沒法取消Promise,一旦新建它就會當即執行,沒法中途取消
  • 若是不設置回調函數,Promise內部拋出錯誤,不會反應到外部。
  • 當處於Pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)

基本用法

var promise = new Promise(function ( resolve, reject ) {
    
    // some code

    if ( /* 異步操做成功 */ ) {
        resolve();
    } else {
        reject();
    }

});
相關文章
相關標籤/搜索