ES2015 是在2015年6月正式發布的一項重大更新

但是在命名方面有點混亂,很容易讓人產生誤解, 所以,還是必須先來認識一下:

ES2015 == ECMAScript 2015 == ES6 == ECMAScript 6

並且,ECMA委員會決定,每年6月都來發布新版本,並以該年作為版本標記 所以 2016年,發布了 ECMAScript 2016 (ES2016) 可視為ES6.1版本,僅小幅新增如 includes 方法及指數運算等功能

但普遍仍會以 ES6來描述 ES2015

所以,懂了嗎?以後聽到有人在說 ES2015或ES6,那都是在講同一個事情

ES2015 被稱為 Next Generation Javascript 也就是,他是未來 Javascript 重要的標準

雖然,目前各大瀏覽器開發商都在努力支援ES2015的標準,但是仍還未全面完成,但由於功能非常好用, 因此就有了 Babel 等編譯器,可以將 ES2015 的功能轉換,讓舊瀏覽器可以支援。

這裡解釋一下,Babel還未全面支援到ES2016,但有提供一個可以將 ES2016轉為 ES2015的工具

【Javascript Thread 執行緒】

Javascript 是一個**單一執行緒( single thread )**中執行, 原因是,一開始設計的目的是用於單一使用者的瀏覽器,非常合理。

當主要的 single thread 執行起來之後, 就可以接續在環境中增加輔助其他執行

同步(Synchronous, sync)與異步(Asynchronous, async)

Sync 一般語句都是用sync的方式執行 Async 例如,SetTimeout (在指定時間執行callback)、AJAX (XMLHttpRequest)、WebSocket,以及ES6的Promise、Generators

注意! callback function 不一定都是異步執行或同步執行,要看上方使用時機而定

Callback Function 的用法正式稱呼為: 延續傳遞風格(Continuation-passing style, CPS)

延續傳遞風格(Continuation-passing style, CPS)

會用另一個函式作為傳入參數,來寫程式,將原本要回傳的值,傳給下一個函式繼續執行

在異步執行中,最常使用CPS來確保異步回調流程能繼續執行

Callback 第一個參數會保留給 Error, Callback 第二個參數會保留給成功回應的資料並且成功時會把第一個參數設為null

另外,return用在callback上是個不對的寫法 要先執行callback,再return 並且,最後一行應該就是callback,而不必再寫 if..else..

function foo(callback) {
		...
    if (x==false) {
        callback(err, contents)
        // or callback(err)
        return
    }
    callback(null, contents)
}

【關於 let 以及 const】

一般javascript會使用var定義變數,ES6則使用 let 或 const。

var 屬於函式作用域(function scope)

function myfn(){
	var xfn = 1;
}

if(true) {
	var x = 1;
}

console.log(xfn);// undefined
console.log(x);// 1

let 跟 const 都屬於 區塊作用域(block-scoped),只在宣告的程式代碼區塊內有效

if(true) {
	var x = 1;
	let y = 2;
  const z = 3;
}

console.log(x);// 1
console.log(y);// undefined
console.log(z);// undefined

let 跟 const 只能宣告一次
(var 同一個變數可以多次宣告) 可以參考這裡

var a = 1;
let b = 1;
const c = 1;

let

let 也可以同時定義多個變數

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

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

var 會產生溢出 function 或作用區域的問題 (作用域的問題會在後面的this部分談到), let 則可以避免掉這種情況 如果 let 變數已經定義,可以變更值,但是不能再重複被定義

let b=1;
b=2;//OK
let b=3;//Error

另外,舉一個例子 使用 var 定義的變數都是屬於全局變數, 例如這範例中,每次回圈都會變更 i 都會儲存到同一個全局變數i 所以在後續呼叫陣列中的function ,function 返回的內容都會是最後一個 i 所賦予的值 一般var

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
i=32;//可以試著把這個移除
a[1](); // 32
a[2](); // 32
a[6](); // 32

如果改成 let,由於let只能在他使用的迴圈中有效, 因此,陣列儲存完所有function之後,每一個function中的子作用域的值都會是獨立存在 所以在呼叫時,才能返回對的答案 使用 let

var a = [];//這裡的a先不使用 let
for (let i = 0; i < 10; i++) {//父作用域
	//子作用域
  a[i] = function () {
    console.log(i);
  };
}
i=32;//如果使用 babel,可以查看註解本行前後差異

a[1](); // 1
a[2](); // 2
a[6](); // 6

const

ES6 const 宣告變數

在ES6,常數,一般是必須先宣告且賦予值,並且不能再更動值也不能再重新賦予值

const a = 10;
a=20 //錯誤error  unknow a is read only
const a=20 //錯誤error unknown: Duplicate declaration "a"

ES6 const 宣告陣列或物件

在ES6裡,如果用常數來宣告一個物件或陣列,這時系統會將這物件/陣列放在記憶體中 對常數而言,只要物件/陣列仍在同一個記憶體位置就可以 後續對物件/陣列的內容進行增減,都不會有問題

const a = {title:'2'}
console.log(a)
a.main = 2
console.log(a)

但是,如果再次賦予一個物件格式/陣列格式,或賦予變數值 就等於新建一個內容,會將原本的內容變更到記憶體另一個位置 這時,常數就會出現值已經變更的錯誤

const a = {title: '2'}

a = {other:'3'} //error unknown: "a" is read-only

By Value & By Reference

我們通常會使用不同類型來儲存值及應用,例如布林、字串、數字、物件、陣列、函式…等 這些格式主要可分成兩個觀念- 值以 By value方式儲存, 物件以 By reference方式儲存 (事實上,Javascript 是以 by sharing ,類似 By Vallue, By Reference方式儲存) 這裡我們將來探討他們之間的差異 可以參考這裡

By Value (primitive type)

首先, primitive type (原始型別) 所宣告的變數都是屬於 By Value 也就是宣告的變數會直接對應值 下面列的都是屬於 primitive type 的變數:

Boolean, String, Number, null, undefined

primitive type 所宣告的變數都會把值放在獨立的記憶體位置 例如,我們宣告了 a 變數,當我們指定b為a,b會將值放在另一個記憶體位置 也就是他們會個別獨立 直接看範例:

let a = 1  //a 會被放在記憶體 位置1
let b = a  //b 會被放在記憶體 位置2

a=2  //a變更,只會變更自己所屬記憶體 位置1 的值

console.log(a) //2
console.log(b) //1

By Reference (Objects type)

下面列的都屬於 Object type (物件型別)

Object, Array, Function

By Reference 也就是參照、參考的意思,

是在描述我們所宣告的變數如果是 Object type,就會以By Reference的方式來存取 Object type

這裡表示,當我們宣告了一個 Object type實體,會在記憶體建立一個位置 當其他物件指定這個實體,會同樣以By Reference的方式來存 Object type 這個實體

例如,我們建立了 a 物件,接著指定 b 為 a,b會指向a在記憶體中的同樣位置 參考範例:

let a = {title:1}  //a 物件將儲存在記憶體 位置1
let b = a //b=a,b會同樣指定到記憶體 位置1

a.title = 2  //a 變更記憶體 位置1 中的值

console.log(a) //2
console.log(b) //2

let c = (obj)=>{return (obj.title=3)}  //建立 c 等於匿名函式,函式中會變更物件值
c(a) //用c函式執行a 物件,變更a物件中的值
console.log(a) //3
console.log(b) //3

【模組及導入功能】

ES6 可以自定義模組,使用的方式與 node 類似 可以只包含一種單一方法 module_name.js

export function hello() { ... }

也可以在模組中定義多組方法 module_name_muti.js

export default {
	a,
  b,
  c,
  d
}

ES6 擁有導入模組的功能,導入的方法有幾種:

import

將模組全部導入

import A from 'module_name'
A.hello();

僅導入局部內容

import { a, b, c } from 'module_name_muti'

a();
b();
c();

【Arrows (=>)】

在ES6用 Arrows(=>) 來表示 function Arrows function 跟一般 function不一樣的是 Arrow Function 最主要的功用就是某些情況下可以綁定this值 以前函式內部常見透過 self = this, .bind(this) 來綁定this。

Arrows 基本式

例如,一般javascript 寫函式 可以宣告有名稱或者匿名的函式 一般javascript

//直接宣告有名稱的函式
function myfn(foo) {
  return foo + ' world';
};
/* 或者可以用變數來儲存匿名函式 (二擇一)*/
var myfn = function(foo) {
  return foo + ' world';
};

myfn('hello');

在ES6則會是這樣寫 ES6

let myfn = (foo) => {
  return foo + ' world'
}
/* 或者可以這樣寫 */
let myfn = foo=>{
	return foo+' world'
}
/* 或者可以這樣寫  且不必寫return*/
let myfn = (foo =>  foo + ' world')


myfn('hello');

Arrows 夾帶多個參數

一般function可以傳入多個參數,寫法如:

function myfn(a, b){
	return a+b;
}

myfn(1,3);//輸出結果 4

ES6的寫法如下:

let myfn = (a,b)=>{
	return a+b;
}

myfn(1,3);//輸出結果4

解決內部函式的 this 問題

多年來,一般 function 內部函式 this 指向外部呼叫他的物件(通常都是 windows) 舉個例子 這裡我們建立一個 function,用call 將一個值傳入function 當作為 this。 這時,內部再建立一個 IIFE ,IIFE 裡面的 this 不知道是誰在呼叫他,所以就會指向外部 windows 物件

function myfn(){
  console.log(this); //hello
  (function(){
    console.log(this) //undefined or winodws obj
  })()
}

obj = 'hello';

myfn.call(obj)

以前的解法,是透握 var self = this 來處理,讓IIFE能直接使用這個儲存this的變數

function myfn(){
	var self = this;
  console.log(this); //hello
  (function(){
    console.log(self) //hello
  })()
}

obj = 'hello';

myfn.call(obj)

Arror function就解決了這個問題,

function myfn(){
  console.log(this); //hello
  ()=>{
  	console.log(this) //hello
  }
}

obj = 'hello';

myfn.call(obj)

特別提醒! 可以及無法使用Arrows的情況

function 裡可以使用 Arrow function

但是,如果是物件直接呼叫或存取時候,就要留意:

  • 在物件裡的屬性,必須使用function,而不能使用 Arrows function
{
	myfn: function(){},
}
  • 在物件的prototype屬性定義方法,必須使用 function
MyFunction.prototype.sayHello = function(){ }
  • addEventListener 裡面要用function
btn.addEventListener('click', function(){})

Map 使用 Arrows

Map 可以針對陣列中每一筆資料進行處理、轉型,再回傳一個轉型的陣列

var a = ['adam', 'joe', 'john', 'brown', 'alex'];
var content = a.map(function (title, index) {
  return index+title;
});
console.log(content);//["0adam", "1joe", "2john", "3brown", "4alex"]

在ES6,將上述內容以這樣的方式呈現。

var a = ['adam', 'joe', 'john', 'brown', 'alex'];
var content = a.map((title, index)=>{
  return index+title;
});
console.log(content);//["0adam", "1joe", "2john", "3brown", "4alex"]

Map reduce 使用 Arrows

map, 可以用來遍歷陣列中每一個元素,以及在過程中修改原始陣列 redue, 可以遍歷每個元素,並且統整為一個結果

先來看一個簡單的示範,看 map及reduce 的差異性 一般來說,Map 會用在調整陣列內容,Reduce會用在將陣列內容總和為一個值

var a = [1,2,3,4,5,6,7];

var result1 = a.map((title, index)=>{return 'num'+title;})

console.log(result1);//["num1", "num2", "num3", "num4", "num5", "num6", "num7"]

var result2 = a.reduce((total, value) => total+'num'+value);

console.log(result2);//1num2num3num4num5num6num7

filter, 可以遍歷每個元素,決定元素是否要保留(根據你提供的條件式),最後回傳新陣列 所以,一般來說,filter是用來篩選陣列內容 先來看一個簡單的示範案例:

var a = ['adam', 'joe', 'john', 'brown', 'alex'];

//Map
var result1 = a.map((title, index)=>{
  return index+title;
})
console.log(result1);//["0adam", "1joe", "2john", "3brown", "4alex"]


//Filter a
var result2 = a.filter(obj=>obj=='adam');
console.log(result2);//["adam"]


//Filter result1
var result3 = result1.filter(obj=>obj=='0adam');
console.log(result3);//["0adam"]

concat, 將陣列進行整併或字串進行串接

底下這例子,要取出偶數的值:

let news_data = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ]

let newSet = news_data
  .map((title, index) => {
    return {
      id: index,
      title: title
    }
  })
  .filter(news => news.id % 2 == 0)
  .map(news => [news.title])
  .reduce((t, b) => t.concat(b))

console.log(newSet) //[ 'A', 'C', 'E', 'G' ]

Reduce 特別說明

Reduce 有幾個參數可以使用

  • accumulator 前一個參數,若為陣列的第一個項目,會另外傳入值或初始化的值
  • currentValue 當前值(會跳過首個項目,從第二項目開始計算)
  • currentIndex 當前索引(會跳過首個項目,從第二項目開始計算)
  • array 原始陣列
var a = ['adam','joe','john','brown','alex'];

var result = a.reduce((accumulator, currentValue, currentIndex, array) =>{
  console.log(currentIndex);
  console.log(array);
  return accumulator+','+currentValue;
} );

console.log(result);//adam,joe,john,brown,alex

但通常,我們只會使用前兩個參數, 先來看一下Reduce的簡單示範

//string
var a = ['adam','joe','john','brown','alex'];

var result = a.reduce((t, v) => t+','+v);

console.log(result);//adam,joe,john,brown,alex

//number
var b = [1,2,3,4,5,6,7];

var result2 = b.reduce((t, v) => t+v);

console.log(result2);//28

在有些場景,我們會需要再迴圈中彙整一些資訊 例如底下這個範例,將 items 的內容經過迴圈,並一個一個儲存在 total 裡面 最後再輸出:

var items = ['Hello','World','1','2','3'];
var total = '';
var words = '';

for(var i=0; i<items.length; i++){
	if(i==0){
		total+=items[i];//首次不需要加入空白
	}else{
		total += words.concat(' ').concat(items[i]);
	}
}

console.log(total);//Hello World 1 2 3

上面的狀況,使用Reduce會非常好解決,

var items = ['Hello','World','1','2','3'];

var result = items.reduce(function (total, value) {
  return total.concat(' ').concat(value);
});

console.log(result);//Hello World 1 2 3

這裡則是 ES6的寫法

var items = ['Hello','World','1','2','3'];

var result = items.reduce((total, value) => {
  return total.concat(' ').concat(value);
});

console.log(result);//Hello World 1 2 3

For … of

ES6 提供了 for of 來用來遍歷物件中每一個元素 這物件必須能被迭代(interable),因此像是 Array, String, Map, Set, TypeArray, arguments 等都可以 我們直接從範例來了解,這裡將舉我們最常使用的Array及String為例,詳細的參考可以直接看MDN web doc

陣列

let myArray = ['about','but','cart']

for(let value of myArray){
  console.log(value)
}
/*輸出結果
"about"
"but"
"cart"
*/

字串

let myString = 'abc'

for(let value of myString){
  console.log(value)
}
/*輸出結果
"a"
"b"
"c"
*/

【物件及函式內作用域 Closure 問題】

Javascript function

Javascript 的函式可以傳入原始資料類型(布林、數字、物件…)及物件(函式、陣列、物件),也可以返回原始資料類型或物件。

函式內部與外部有形成一個 scope chain (作用域鏈),可以從函式內部取得函式外部的資源

因此,一個函式通常會有幾個作用域:

  1. 函式本身
  2. 外部函式內容(如果是函式中的函式,可存取外部函式內容)
  3. 全域

在不同的scope都會包含scope本身獨有的環境變數: this 以及 arguments

奇妙地函式特性

函式屬於物件類型,但是在javascript設計的概念中, 所有函式,都有一個歸屬物件,要透過這個物件來呼叫他, 如果在預設形況下,這個歸屬物件會是全域 Global 或 windows物件

(所以我們也可說,其實所有函式都是物件的方法)

在函式中,this 會指向呼叫這個函式的歸屬物件(呼叫他的物件)

console.log(this) //Global or window物件
function myFn(){
	console.log(this) //Global or window物件
  function myFn2(){
  	console.log(this) //Global or window物件
  }
  myFn2()
}

透過 apply, call, bind 方法呼叫函式

函式除了可以用一般呼叫函式、物件呼叫方法、還可以透過 apply, call, bind 方法來呼叫 apply 跟 call 很像,差別在於, apply 除了第一個值同樣用 this,後續參數要用陣列的方式傳入 call 第一個值用this,後續值可值接帶入參數

在開始了解 call 跟 bind 之前,先了解他們呼叫的方式

call 可以提供this或參數值傳入原有函式,且立即呼叫 bind 則是建立一個新函式,在提供this或參數傳入這個新函式,但不會立即呼叫
call

使用 call 提供this或參數值傳入原有函式,會將第一個參數當作為 this,其次才是相關參數 並且在 call的當下會立即執行 以下範例: myFn call 所傳入的第一個參數,就會變成this 因此,就能在myFn使用this物件中的值 其他參數則傳入一般數值 這時再myFn就要定義相對應的變數來接這些參數

const myFn = function(x, y){
  console.log(this.a + this.b + x + y)
}

myFn.call({a:'a', b:'b'}, 'c', 'd') //abcd
bind

bind 與 call 也很類似,bind之後會建立一個新函式,再提供this或參數傳入這個新函式,但不會立即呼叫 例如:

const myFn = function(x, y){
  console.log(this.a + this.b + x + y)
}

const myExc = myFn.bind({a:'a', b:'b'}, 'c', 'd')

myExc(); //abcd

有趣的是,bind 的參數可以部份套用(Partially applied) 只要依照順序,分批傳入參數也可以成功執行

const myFn = function(x, y){
  console.log(this.a + this.b + x + y)
}

const myExc = myFn.bind({a:'a ', b:'b '}, 'c ')

myExc(); //a b c undefined
myExc('d'); //a b c d

Closure

在Javascript 建立函式時,函式本身會形成一個scope(作用域), 在這scope底下會同時建立一個 By Reference 的 Closure(封包),

Closure 內會包含函式及函式被建立時的環境以及this (但closure不會去記憶 this,而this會指向呼叫函式的物件)

closure 內所定義的變數,僅在 closure 內部可以使用。 但是在 closure 內部又可以取得外部的變數,請參考下圖:

其實只要記得這個概念,原則上都是在這個基礎上來運作~

Closure 資料結構以By Reference 的方式,包含了函式本身以及相關值 (注意! 封包是以 參照-By Reference 的方式而不是 複製-By Value) 進階可看這裡

而封包,就是在傳達一種封閉性,但同時又能獲取外界的資訊 在這closure封包內部所建立的新變數或物件都歸函式所有(以參照 reference 的方式), 只有函式內作用域範圍可以使用,外界無法直接取得

var a = 1

function myFn(){
  console.log('myFn get a:'+a)
  var b = 2
  console.log('myFn get b:'+b)
}
myFn()
console.log('global get a:'+a)
console.log('global get b'+b)
/*
myFn get a:1
myFn get b:2
global get a:1
Uncaught ReferenceError: b is not defined
*/

雖然 Javascript沒有物件導向程式的私有方法 Private, Protected , 但是可以透過closure封包的隔離特性來模擬私有方法

首先,我們先以**柯里化(Currying)**來說明封包的應用 柯里化(Currying) (通常都用柯里化來說明封包的應用) 柯里化源自數學的一種惰性延遲求值技巧, 這種技術可以透過javascript 函式的封包結構來實現, 首先,我們要看 myFn 部分,在一開始當程式執行但還未呼叫myFn時,myFn內部的匿名函式也不會被建立, 當我們呼叫 myFn之後,這時內部的匿名函式封包才會建立,匿名函式的封包結構才會生成 在 closure 裡面,可以透過scope chain取得外部函式內容及全域內容,這也是為什麼匿名函式可以直接在封包生成後,取得x, z

const z = 1 //全域
function myFn(x){ //外部函式
	return function(y){ //內部函式
  	return x+y+z //內部函式中,可以取得外部函式及全域函式值
  }
}
const a = myFn(1)(2)
console.log(a) //4
console.log(x) //not defined
console.log(y) //not defined
console.log(z) //1

但是始終要清楚知道,並不是只有柯里化這類巢狀函式才能產生封包 每個函式建立,都會產生封包

IIFE (Immediately Invoked Function Expression, 立即呼叫函式表達式) IIFE 是一種運用封包與匿名函式來達成立即執行函式的作法

(function(){ ... }())

IIFE 常用來建立 Module (模組), 因此,在IIFE所建立的模組內,就有類似物件的私有屬性、方法 我們常見jQuery Plugin或其他模組功能的程式設計,都可以見到IIFE

const myModule = (function(){
  
  var private2 = function(x){
    return x+100
  }
  
  var private1 = function(n){
    return private2(n)  
  }
  
  //public
  return {
    myPublic: private1
  }
}())

const a = myModule.myPublic(1);
console.log(a) //101

封包通常屬於較高消費的語法。

封包結構可能會變成高耗能的語法,因為他需要彙整的作用域鏈龐大繁瑣,也會固定占用一些記憶體,因此如果有包括迴圈或定時執行功能,就要留意。

但是,在正常使用情況下則不必太多顧慮,因為Javascript有很好的垃圾回收機制,可以回收不需要使用的記憶體。

Scope 作用域

在Global環境,整體區域就能稱為一個 global scope 全域作用域 在我們呼叫一個函式時,函式內部會自己形成一個 local scope 以上面的封包來說, 當我們呼叫函式,函式本身就自己形成一個local scope(作用域),而scope內部第一層就會包含一個 Closure(封包/閉包)的變數 Closure 會記憶當時建立的環境, Closure 裡面也包含 this 資訊,但不會去自動記憶,this會指向呼叫函式的物件,通常是 windows object

透過 chrome console 中斷點來查看一個範例,看這裡面所顯示的 scope與closure關係

function myFn(x){ 
	return () => console.log(x++)
}   

const newFn = myFn(1)
newFn()
newFn()

封包內的資料屬於 free (自由)變數 或 independent (獨立)變數,封裝在scope中,並且可以在函式中使用。

簡單聊 this

在我們建立函式或物件時,會包含了this 這個 this包含了上下文,通常就稱為this Context

前面也提到,函式中,this 會指向呼叫這個函式的歸屬物件(呼叫他的物件) 所以,在不同的情況執行,this所代表的對象都會不同

  • 一般情況,this都代表著最外層 global 物件 (瀏覽器預設為window物件)
  • 函式裡面如果宣告 this,this 指向呼叫他的物件
  • 物件裡面的方法如果宣告 this,this 會代表物件本身
  • 物件的建構式(constructor) 裡宣告 this,當物件被 new 運算符宣告成新的物件,this就會代表新物件

雖然函式裡的this不是函式本身,而是屬於呼叫他的物件 但仍可以透過函式的apply, call, bind 來傳入this

function myFunc(){...}
//請在非strict mode下執行
//下方三者的執行結果都會相等
myFunc()
window.myFunc()
this.myFunc()
const objA2 = {
  test(){
    console.log(this)
	function func1(){
		console.log(this)
	}
	func1();
  }
}
objA2.test()
//objA2.test()
//Window object

如果開啟 strict mode 嚴格模式,就無法取 global 物件(undefined)

EC - Execution Context, 執行上下文

javascript中的 EC (Execution Context, 執行上下文)代表的是已經被執行的內文, EC可以分成三種結構,這些結構都可以分別被呼叫

- 全域呼叫 : 通常一開始會先執行整個javascript (全域程式碼)
- 函式呼叫 : 根據函式被呼叫時,來執行函式封包內的內容
- eval

(eval 較少用,且不建議使用,在此不說明)

上面提到的是,上下文執行的結構

在概念上,當全域或函式的EC一被執行後,就會在結構中包含三種東西

Variable object
Scope chain
this

也就是說,一開始執行全域且產生全域 this

函式被呼叫且執行時,就會同時產生this,且這個this 會指向呼叫這個函式的歸屬物件

如果是函式內的函式,就會發生 this 為 undefined 的情況

VO - Variable object, 變數物件

首先,EC 執行內文後,全域所包含的變數函式都會集中在 VO (Variable object, 變數物件)

Scope chain (作用域鏈)

Scope chain 會將上層VO與自己的VO組成作用域鏈

變數執行 scope chain EC 執行的內文中,全域裡如果定義變數,此變數又同時被用在某個函數中 函式有自己的作用域,這裡面的 VO 會指向上層父母層的 VO ,這個連結會一值串到最上層的全域 VO,就稱為 Scope chain (作用域鏈) Scope chain 跟 propotype chain 非常相似,所以原理大致相同。

AO Scope chain

函式執行 scope chain 在函式中,因為還需要帶入參數,因此在函式中,會用 AO (Activation Object, 啟動物件) 來扮演VO的角色,AO包含參數值,AO 會有一個屬性會用來指向父母層的AO,如果你新增很多函式,這每一層函式都會環環相扣,直到指向最上層的全域 VO,這就是為什麼在函式內部還能使用外部的變數及相關內容

另外還有更深入的執行上下文 Execution Context(EC) ,在探討執行整個 javascript與函式的時機,可以參考這裡

物件內部,頂層作用域請以一般function建構

通常在初始化物件時, 頂層作用域會用一般的function型態, 這樣的話,在物件封裝的情況下,內層的function 才會代表著最外層物件本身,才能透過this來呼叫此物件, 例如,底下 obj.get() 就是用一般的function來建立,因此可成功呼叫物件,取得 this.title

let obj = { 
  //頂層作用域
  title: 'Hello world',
  get: function() {
    return this.title
  }
}

console.log(obj.get()) //Hello world
obj.title = 'Hello World2!'
console.log(obj.get()) //Hello World2!

如果,在初始物件內,就先使用 arrows 來建立function,則這個 function 內呼叫的 this 則會是 windows object 因為Arrows function 會取得建立時環境中的this作為自己的this,在初始物件中找不到this,就會往外,使用全域this

let obj = {
  //頂層作用域
  title: 'Hello world',
  //一開始就用 ES6 Arrows
  get: () => {
    return this.title
  }
}

console.log(obj.get());//錯誤

可以同時參考上面語法的等價代碼:

let obj = {
  title: 'Hello world',
  get: (function() {
      return this.title// Warning!
    }).bind(this)
}

物件內部,子作用域需使用arrows或者額外用變數來帶入this

在javascript中,一個函數內如果新增多個內層作用域,就要多加注意

首先舉例,物件中如果有obj.get存取且執行的匿名函數,所以this就代表物件本身 但是子作用域中的 inner function 被不同的函式呼叫時,this就會不同,所以會變成 Window object or undefined

因為函式中的this,會指向外部呼叫他的物件,所以在這裡 this 等於 obj

var obj = {
	
	title : 'Hello World',
	get : function() {
      console.log(this);//obj.get()
      console.log(this.title);//Hello World
        //Inner Function and called
        function inner(){
          console.log(this)//Window object or undefined
        }
        inner()
	}
};

所以,通常都會透過一個 scope chain 來處理, scope chain 可以把外面的變數帶到內部, 所以,可以透過一個變數存取外層的this,再把他帶到內部 (變數名稱常見的有 self, tg..,無論選哪一種名稱,建議固定使用一個,讓系統設計能一致)

var obj = {
	title : 'Hello World',
	get : function() {
		var self = this;//closure defined 
		console.log(self.title);//Hello World
    console.log(this.title);//Hello World
    //Inner Function and called
    function inner(){
			console.log(self.title);//Hello World
			console.log(this.title);//undefined
    }
    inner()
	}
};

obj.get();

在ES6,inner透過arrows來建構函式,都能夠呼叫到物件本身 (如果將 ES6結果進行 browserify - babel 轉譯,會發現this會經過 closure處理為 _this)

因為 arrows function 透過 bind.(this) 來綁定他外面function 中的this (obj), 所以這裡的arrows function 內部 this 就會等同於 obj

var obj = {
	title : 'Hello World',
	get : function() {
		var self = this;//closure defined 
		console.log(self.title);//Hello World
		console.log(this.title);//Hello World
    //Inner Function and called
    function inner(){
			console.log(self.title);//Hello World
			console.log(this.title);//undefined
    }
    inner(){
    //ES6 and auto called
		(() => {
			console.log(self.title);//Hello World
			console.log(this.title);//Hello World
		})();
	}
};

obj.get();

當然,也可以用bind 將外面的this帶入,表示為 inner 的this

【模板字符(`) Template strings】

在javascript可以用單引號或雙引號來包裹文字

var string = 'hi';

模板字符是使用反引號(`)來包裹字符,基本概念與單雙引號相同,

var templat_string = `Hello`;

但是,模板字符還有一些額外的特性,接下來會介紹:

透過模板字符注入變數

透過 ${變數名稱} 就可以將變數注入在模板字符中

let name = 'Adam'
let country = 'Taiwan'

let msg = `My Name is ${name}, I live in ${country}`
console.log(msg) //My Name is Adam, I live in Taiwan

模板字符內支援換行

一般javascript文字不能直接換行,必須透過 + 或 array.join(' ‘) 來達成

var msg = 'This is first line.'+
		  'This is second line.'+
		  'This is Third line.';

console.log(msg)
//或者
var msg = [
			"This is first line.",
			"This is second line.",
			"This is Third line."
		  ].join(' ');

console.log(msg)

在模板字符裡直接換行即可

let msg = `
This is first line.
This is second line.
This is Third line.
`;
console.log(msg)

動態賦予屬性

在ES6的屬性可以用變數的方式來產生,其中包括結合模板字符來產生屬性, 用法如下:

let ary = [1,2,3];

let ajust_ary = ary.map(n=>{
  return {
    [n]:n,
    [`${n*10}`]: n*10
  }
});

console.dir(ajust_ary);
/*
[ { '1': 1, '10': 10 },
  { '2': 2, '20': 20 },
  { '3': 3, '30': 30 } ]
*/

5. Class

ES6 實現了類似C的語法,其中包括類別(Class)的定義方式 這裡的寫法須留意的是,ES6 Class 內部屬性不需要加逗號 (,)

Class 基本格式

class A {
  constructor() {
    this.title = 'Hello World'
  }
  get_title() {
    console.log(this.title)
  }
}

Class Extends

另外,ES6也支援繼承語法: extends

//A Class
class A {
  get_title() {
    console.log('Hello World');
  }
}

//B Class Extends A
class B extends A{
  get_name(){
    console.log('My name is B');
  }
}


let b_class = new B();
b_class.get_title();//Hello World
b_class.get_name();//My name is B

Super 函式

應用時,除了要能呼叫父類別的方法,我們也會希望能傳送參數、取得或變更父類別的屬性值, 這時建構子可以使用 super 函式來呼叫父類別的建構子, 呼叫 super 之後,就能夠操作父類別的 constructor 使用繼承語法,例如底下範例中的B,如果想要操作A的constructor, 就必須在B宣告一個 super();

//A Class
class A {
  constructor(name){
    this.title = name;
  }
  get_title() {
    console.log('Hello World '+this.title);
  }
}


class B extends A{
  constructor(name){
    super() //must call `super` before using `this` if this class has a superclass
    this.title = name;
  }
  get_name(){
    console.log('My name is B');
  }
}

let b_class = new B('BBB');
b_class.get_title();
b_class.get_name();

【prototype】

原型(prototype)物件能被別的物件繼承

所有JavaScript中的函式都有一個內建的prototype屬性,指向一個特殊的(prototype物件)物件,物件的contructor屬性,指向原來的函式

myFunction(){...}

myFunction  === myFunction.prototype.constructor //true

【proto】

proto 是每一個JavaScript物件都有的內部屬性,他所代表的就是最…上層的源頭

Function.prototype

Function Prototype: 所有函式最上層的起源

MyFun1.__proto__ === Function.prototype //true
MyFun2.__proto__ === Function.prototype //true

Object.prototype

具有物件屬性的實體,他的 proto 指向的是Javascript最上層的物件"Object.prototype"

前面提到,所有函式最上層的物件就是 Function.prototype,而Function.prototype屬性也是個物件, 所以 Function.prototype也同樣會再往上指向Object.prototype

Function.prototype.__proto__ === Object.prototype //true

上面的關係都可以從這張圖去理解

函式內部與最上層物件的關係

Object.prototype 是JavaScript最上層的物件 在每一個Javascript 函式中, myFunction.__proto__.prototype.__proto___

深入閱讀 推薦參考

New 物件 該歸屬於何方

我們前面提到,函式具有內建的prototype屬性,並且可以再透過內部constructor指向函式本身

CVT2HUGO: 就等同於最上層的 Object.prototype
myFunction  === myFunction.prototype.constructor //true

當我們用 new運算符來產生物件實體

myFunc(){...}

const myNewFunc = new myFunc

這個透過 new 建立的物件實體是屬於物件 (不是函數) 所以new 物件實體不會有prototype這個屬性,

myNewFunc.prototype //undefined

並且他的 proto 指向的是他來源的那個函式

const myNewFunc = new myFunc

myNewFunc.__proto__ === myFunc.prototype //true

透過這樣的方式,就能讓物件的原型鏈串接起來

Object.prototype 是唯一例外

在前面,我們提到Function.prototype也同樣會再往上指向Object.prototype,但是沒有提到 Object.propotype再往上…

原因就是,

Object.prototype: 是最上層的起源

Object.prototype(Object的原型) 沒有 prototype,所以在 Object 裡面的所有物件或函式,proto都會指向該 Object

可以仔細觀察,上面的圖例中,Object.protype的 proto都是 null

proto 與 prototype 在實際環境執行概況

現在,來實際透過 chrome console 來執行一個一段js scipt,並且在呼叫函式時候給予中斷點 來查看這個函式的結構

function myFn(x){ 
	console.log(x++)
}

const myNewFn = myFn
myNewFn(1)

ES6 的 proto

ES6 提供了__proto__可以用來獲取目標

這個功能在模組設計上,相當便利, 就直接透過下方例子來看,使用方式:

CVT2HUGO: prototype 類別的proto
//------------------------------------
//建立一個A類別
//A Class
class A {
  constructor() {
    this.title = 'Hello World'
  }
  get_title() {
    console.log(this.title);
  }
}

//------------------------------------
// B拓展A類別
//B __proto__ A Class

let B = {
  __proto__:new A()
}

B.get_title();//Hello World

//------------------------------------
// C拓展A類別並修改A類別設定
//C __proto__ A Class

let C = {
  __proto__:new A(),

  constructor() {
    this.title = "Hello C Change The World!"
  }, 
  Btitle: "I am C",
  get_c_title(){
    console.log(this.Btitle);
  }
}

C.get_title();
C.get_c_title();

附註:__proto__只有較新的瀏覽器(ie11以上)才支援

【Generator】

ES6新功能中,Generator受到許多矚目,能用簡單的方式實現流程控制, Generator 能夠讓函式設定 yield 閘道 例如下方實例,首先要透過 * 修式符號 來宣告為Generator函數 接下來就能透過 yield 來設定閘道

當我們第一次呼叫Generator函式,一開始並不會執行動作, 要等到使用 next() 方法之後,才會開始執行函式,並且在抵達 yield 閘道會先暫停執行,且返回 valule 及 done 屬性 value 包含一個返回值, done 則判斷Generator是否執行完成 再次呼叫 next(),就會繼續呼叫下一段直到遇到 yield先暫停且返回值,以此類推 當Generator全數執行完畢,就會得到 done 為 false 此時,執行完畢後,如果再呼叫 next 方法,就會拋出例外

Generator最大的好處就是,每次執行都是非同步, 所以在Generator未全數執行完畢,也不會影響其他函數而產生效能不佳的問題, 當每次呼叫 next() —-> yeild ,就會結束該次運算,不會卡住 進階可參考這裡

require('babel-polyfill');
//宣告一個 * 修飾符的Generator函式
function * gen(){
  console.log('1');
  yield "2";
  console.log('3');
  yield "4";
}

//取得gen Generator
let b = gen();
console.log('before');

//呼叫next,正式執行函式,並執行到 yield 暫停執行
var x = b.next(); //1

//用value取得 yield 返回的物件值
console.log(x.value);//2

//呼叫next,正式執行函式,並執行到 yield 暫停執行
var y = b.next(); //3

//用value取得 yield 返回的物件值
console.log(y.value); //4

//Generator會包含一個done屬性,判斷是否執行完畢
if(y.done){ console.log('end'); }else{ console.log('not yet'); }

b.next(); //結束後在呼叫 next,在Node會拋出例外

Generator需要的 Babel 安裝擴充

ES6 Generator會使用到的async,在網頁端會顯示出錯誤:

Generator regeneratorRuntime is not defined

因此,除了使用polyfill

require('babel-polyfill');

也要再額外安裝babel 的 transform-runtime 擴充

npm install --save-dev babel-plugin-transform-runtime

package.json

{
  "name": "browserify",
  "version": "1.0.0",
  "babel": {
    "plugins": [
      [
        "transform-runtime",
        {
          "helpers": false,
          "polyfill": false,
          "regenerator": true,
          "moduleName": "babel-runtime"
        }
      ]
    ]
  },
  ...
}

【Promise】

關於promise的進階介紹,可以參考這裡 在javascript常會使用到非同步操作(ajax、載入檔案、等待上傳….等) 在非同步操作結束後,需要判斷成功或失敗,進行各別狀況處理, 這時就能透過 Promise 來達成,

絕對是 async

Promise 已經被ES6及新的瀏覽器支援,透過 Promise 可以讓我們確保某件事情做完之後,才進行後續動作 所以它本質上都是使用callback(回呼函式),並且都是async(異步執行)

throw

在ES6中,實作 promise 只需要 then 及 catch 方法,並且有幾個靜態函式可以使用: Promise.resolve、Promise.reject、Promise.all、Promise.race

onFulfilled和onRejected

當我們實際執行一個 promise,他會先將狀態切換為 pending(等待中),並且依照 onFulfilled函式(即resolve), onRejected函式(即reject) 順序來初始化傳入的回呼函式

且onFulfilled和onRejected必須是函式

Promise 執行完畢,會回傳兩個物件,

  • promise 物件: 給有支援 Promise 的環境使用
  • thenable物件: 給沒有支援 Promise 的環境使用

thenable

當執行完畢,如果成功取得值,返回 resolve 結果,會包含 promise物件(值),以及 thenable 物件 thenable物件是為了讓有些沒有符合promise標準的框架也能使用 then 大概機制如下:

//thenable物件
const thenable = {
    then: function(resolve, reject) {
        if(/*成功*/){
	        //onFulfill
          resolve()
        }else{
        	//onReject
          reject()
        }
    }
}

關於 promise 的執行原理

特別一提的是,promise建構函式中,主要是使用try…catch來執行,因此不會在瀏覽器中直接產生錯誤,當throw(try…catch,發生例外時的throw(拋出)動作),則會呼叫 reject, 所以,在promise執行過程,發生throw就會立刻執行 reject

//參考自es6-promise:Promise物件建構函式相關
function initializePromise(promise, resolver) {
  try {
    resolver(function resolvePromise(value){
      resolve(promise, value);
    }, function rejectPromise(reason) {
      reject(promise, reason);
    });
  } catch(e) {
    reject(promise, e);
  }
}

promise 使用方式

這裡先了解Promise 正常使用時的基本架構:

//建立 Promise
//最後會返回新的 promise 及 thenable 物件
var my_promise = new Promise(function(resolve, reject){
	//非同步操作之後再根據情況決定返回  resolve或 reject
  //原則上,就算HTTP返回的是404或500等狀態,也是算是成功,而是後續再用resolve, reject來決定成功與否
  if(/*非同步成功取得資料*/){
    resolve('Success Message or data');
  }else{
    /*非同步無取得資料或發生404,500..狀態*/
    reject('Error Message or no data');
  }
});

//處理 Promise Callback (有成功接受跟失敗拒絕兩種狀況)
my_promise.then(function(v){
	//當執行成功,會回傳新的"promise物件
  console.log(v);
}, function(error){
	//在一般情況下,then 都會接收到返回的訊息,即使HTTP返回的是404或500等狀態,
  // then 還是會接收並回傳"新的"promise物件,並且回傳promise reject
  console.log('error');
}).catch((err) => {
	//這裡的 catch 只有當then無法回傳promise時才會進行
  //例如,無法連線、請求被阻擋,導致返回 
  console.log(err.message)
});

Promise通常會用在承諾監控一件非同步請求,而不會進行太多邏輯處理 這裡示範,使用 promise實現一個 ajax 請求資源

var getJSON = function(url) {
  var promise = new Promise(function(resolve, reject){
    var client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();
    function handler() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
  });
  return promise;
};

getJSON("data.json").then(function(json) {
  console.log(json);
}, function(error) {
  console.error('Error Happened', error);
}).catch((err) => {
	//這裡的 catch 只有當then無法回傳promise時才會進行
  //例如,無法連線、請求被阻擋,導致返回 
  console.log(err.message)
});

then..catch 連鎖處理

當我們有一系列的流程希望按順序執行,並且有些執行過程要進行錯誤處理 就能使用 then 與 catch 搭配組合成一個連鎖流程 可直接參考下面的官方範例:

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don't worry about it");
}).then(function() {
  console.log("All done!");
});

promise.all 與 promise.race

https://eyesofkids.gitbooks.io/javascript-start-es6-promise/content/contents/promise_all_n_race.html

promise.all

promise.all 可以用陣列的方式一次帶入多個promise, 若其中有一項被reject或發生錯誤,就會返回 reject 這些陣列若全部執行都解決 resolve 了,才會返回成功resolve , 並且,一定會等全部執行完,返回的順序會跟傳入陣列中的順序一致

底下這範例, p2 會先執行完,接下來是 p1, p3 但是回傳值仍會按照 all 帶入的順序 p1, p2, p3

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p1 resolve'), 200, )
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p2 resolve'), 100, 'p2')
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p3 resolve'), 500, 'p3')
});

Promise.all([p1, p2, p3]).then((value) => {
    console.log(value) //["p1 resolve", "p2 resolve", "p3 resolve"]
},(value2)=>{
  console.log(value2)
}).catch((err) => {
    console.log(err.message)
})

包含一個失敗的範例 當發現有一個 p2 有拋出錯誤時,就會停止整個流程,返回 rejected

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p1 resolve'), 200, )
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p2 resolve'), 100, 'p2')
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => reject('p3 reject'), 500, 'p3')
});

Promise.all([p1, p2, p3]).then((value) => {
    console.log(value) 
},(value2)=>{
  console.log(value2)//"p3 reject"
}).catch((err) => {
    console.log(err.message)
})

promise.race

promise.race 同樣可以用陣列的方式傳入多個 promise, 但是他執行的方式就像是在舉行競賽,只要有任何一個值完成,無論是resolve, reject 還是錯誤,都會返回結果

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p1 resolve'), 200, )
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => reject('p2 reject'), 100, 'p2')
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p3 resolve'), 500, 'p3')
});

Promise.race([p1, p2, p3]).then((value) => {
    console.log("Resolve:"+value) 
},(value2)=>{
  console.log("Reject:"+value2) //"Reject:p2 reject"
}).catch((err) => {
    console.log(err.message)
})

如果傳入的陣列同時完成所有promise時,也只會陣列順序,返回陣列中第一個 promise 的值

const p1 = new Promise((resolve, reject) => {
  resolve('p1 resolve')
});
const p2 = new Promise((resolve, reject) => {
  reject('p2 reject')
});
const p3 = new Promise((resolve, reject) => {
  resolve('p3 resolve')
});

Promise.race([p1, p2, p3]).then((value) => {
    console.log(value) //"p1 resolve"
},(value2)=>{
  console.log(value2)
}).catch((err) => {
    console.log(err.message)
})

【Symbol】

Symbol目前在瀏覽器支援仍未普及,但是特性相當有趣: 一般 Javascript 預設有6種型態, undefined、null、布林值(Boolean)、字串(String)、數值(Number)、物件(Object) Symbol被定義為Javascript第7種數據型態 它的特點就是,每一個值若定義為 symbol 型態,就可保證不會再重複

常用來設計索引(key)

現在,就先簡單了解一下這個黑科技

symbol(‘key string’)

首先,這裡有key1, key2,都使用 Symbol(‘my_private’)作為值 輸出也都顯示為 Symbol(‘my_private’) 但是當判斷兩者是否相等,卻輸出 false!

let key1 = Symbol('my_private');
let key2 = Symbol('my_private');

console.log(key1);//Symbol('my_private')
console.log(key2);//Symbol('my_private')

if(key1==key2){
  console.log('yes');
}else{
  console.log('no');// no !!!!!
}

從這裡可以了解,只要透過Symbol的對象,都會具有唯一性。 並且,這個唯一性具有絕對的機密性,理論上不可能取得。

let obj = {}

let key1 = Symbol('my_private');
let key2 = Symbol('my_private');

obj[key1] = 1;

console.log(JSON.stringify(obj));//{}
console.log(obj);//{}
console.log(obj[key1]);//1
console.log(obj[key2]);//undefined

Symbol.for(“key name”)

Symbol.for 就沒有隱密性, 除了會將儲存的值放在全域,還能夠透過Symbol.keyFor()取得

let a = Symbol.for("keyname");
console.log(Symbol.keyFor(a)); // "keyname"

let b = Symbol("keyname");
console.log(Symbol.keyFor(b)); // undefined

這裡做一個比較 [Symbol.for] 會將把值放在全域 因此,只要發現有相同值,就會視為相同 並且可以透過Symbol.keyFor()取得Symbol.for()值

Symbol.for("A") === Symbol.for("A") // true

[Symbol] Symbol則不會放在全域 因此,只要發現有相同值,都會建立為新的Symbol 無法透過Symbol.keyFor()取得Symbol.for()值

Symbol("A") === Symbol("A") // false

【展開運算符 與 其餘運算符】

這兩種特性原則上及寫法都一樣, 但特徵不同,所以使用情境不同

  • 展開運算符,是把一個陣列展開(expand)成個別值
  • 其餘運算符,是收集其餘的值,轉變成一個陣列

接下來會個別介紹這兩者,後面再回頭理解這裏面的差異

【展開運算子 Spread Operator …】

Spread Operator(展開運算子) 也可以翻作 拓展運算子,或展開屬性(Spread Properties or Spread Attributes) 總之,直接稱 Spread 或者展開運算子即可, 展開運算子用三個點 ...

展開運算符,是把一個**可迭代(iterable)**的物件展開(expand)成個別值

可迭代(iterable)的物件包括: String、Array、Map、Set..等 通常都會使用在陣列或物件 例如:

CVT2HUGO: 來表示
//Data
const myData = [1,2,3]

console.log(...myData)
//1 2 3

這些值可以直接帶入其他陣列或函式..等用途~ 在支援ES6相關的框架,都會看到大量使用 Spread 的設計

Spread 帶入陣列

展開的值,可以帶入陣列中, 例如我們這裡,在myData2中,帶入 myData 展開值

//Data
const myData = [1,2,3]

//Spread Operator myData to myData2
const myData2 = [4,5, ...myData]

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

Spread 帶入函式

展開的值可以做為函式傳入值, 例如,我們將 myData Spread 傳入 myFn

//Data
const myData = [1,2,3]

//Function
function myFn(a,b,c){
  console.log(a+b+c)
}

//Spread myData to myFn
myFn(...myData)
//6

【其餘運算符 Rest Operator …】

前面提到 spread operator 的特性,如果用在函式的傳入變數時,這個三點…符號在這裡就稱為Rest Operator(其餘運算符) 而這個變數就稱為其餘參數(Rest parameters)

其餘運算符,是收集其餘的值,轉變成一個陣列 (主要差別就在這裡)

例如,下方範例myFn函式中,帶入 …data 其餘參數

//Data
const myData = [60, 72, 80]

//Function with spread variable
function myFn(name, ...data){
  console.log('Student Name:'+name);
  
  let result = 0;
  data.forEach(function(n){
    result+= parseInt(n);
  })
  console.log(result)
}

//Call Function
myFn('adam', myData)

spread會預設回傳空陣列,而不是 undefined 例如在上面的函式,如果完全沒帶入值 第一個name會顯示 undefined,但data會顯示為空陣列

//Function with spread variable
function myFn(name, ...data){
  console.log('Student Name:'+name);
  //"Student Name:undefined"
  console.log(data)
  //[]
}

//Call Function
myFn()

要記得只能放一個,並且只能放在最後一個位置

將 spread 應用在陣列解構賦值(destructuring)

ES6 可以在陣列中進行解構賦值(destructuring), 例如A陣列中為變數,來對應B陣列值 A陣列的變數就可以搭配 spread 例如:

//array to array
const [a,b,c] = [1,2,3]

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

//spread for second variable
const [x,...y] = [1,2,3,'a','b','c']

console.log(x);//1
console.log(y);//[2, 3, "a", "b", "c"]

要記得只能放一個,並且只能放在最後一個位置

物件中使用spread 仍為ES7草案

在 react, redux 可以看到物件中使用 spread, 但這個使用方法還是在草案階段,因此在使用時,要再額外使用babel外掛(babel-plugin-transform-object-rest-spread)幫助處理轉換

【Set】

ES6 包含了一個新的資料結構 Set,像陣列一樣,可以用來儲存資料, 但是 Set 裡面的都必須是唯一值,也就是不會存在重複的值 如果將重複的值傳入,Set會自動處理

Set 有一些方法可以使用,較常使用到的有:

  • size:傳回內容長度
  • has:布林值,確定指定元素是否存在
  • add: 添加值
  • delete: 刪除指定值
  • clear:清空所有值

並且使用(Spread Operator)展開運算子(…)來取得結構值

//Data
const myData = [1,2,3,4,5,6,6,6];
console.log(myData);//[1, 2, 3, 4, 5, 6, 6, 6]

//New Set
const mySet = new Set(myData);
console.log([...mySet]);//[1, 2, 3, 4, 5, 6]

//Size
console.log(mySet.size);//6

//Has
console.log(mySet.has(6));//true
console.log(mySet.has(7));//false

//Add
mySet.add(7).add(8).add(8);
console.log(mySet.size);//8
console.log([...mySet]);//[1, 2, 3, 4, 5, 6, 7, 8]

//Delete
mySet.delete(7);
console.log(mySet.size);//7
console.log([...mySet]);//[1, 2, 3, 4, 5, 6, 8]

//Clear
mySet.clear();
console.log(mySet.size);//0
console.log([...mySet]);//[]

可以直接透過這樣的方式來清除陣列中的重複值

[...new Set(myArray)]

遍歷 set

Set 結構裡沒有key值,只有value值,所以在遍歷set時,會發現輸出key跟value得到的都是同樣的值

//Data
const myData = [1,'a'];

//New Set
const mySet = new Set(myData);

//forEach
mySet.forEach((value, key)=> {
  console.log(key+":"+value)
})
/*
1:1
'a':'a'
*/

可以透過下列方法來取得 set 的物件值 keys,values,entries keys,values 取得的值都一樣, 而 entries 會取得 key value 物件, 原則上,僅使用values即可

//Data
const myData = [1,'a'];

//New Set
const mySet = new Set(myData);

//keys
for (let k of mySet.keys()) {
  console.log(k);
}
/*
* 1
* "a"
*/

//values
for (let v of mySet.values()) {
  console.log(v);
}
/*
* 1
* "a"
*/

//entries
for (let ent of mySet.entries()) {
  console.log(ent);
}
/*
* [1, 1]
* ["a", "a"]
*/

Set 的集合用法

這裡我們透過範例來說明 Set 如何在兩筆資料的集合用法 聯集 Union 交集 Intersect A減B差集 Difference A minus B

//New Set1, New Set2
const mySet = new Set([1,'a']);
const mySet2 = new Set([1, 2,'b']);


//聯集 Union of mySet and mySet2
//=============
//New Set3
const myGroupSet = new Set([...mySet, ...mySet2])

console.log([...myGroupSet]);
//[1, "a", 2, "b"]


//交集 Intersect of mySet and mySet2
//=============
//New Set3
const myGroupSet2 = new Set([...mySet].filter(v => mySet2.has(v)))

console.log([...myGroupSet2]);
//[1]


//A減B差集 Difference mySet minus mySet2
//=============
//New Set4
const myGroupSet3 = new Set([...mySet].filter(v => !mySet2.has(v)))

console.log([...myGroupSet3]);
//["a"]

【其他參考資源】

http://es6.ruanyifeng.com/#docs/intro https://gank.io/post/564151c1f1df1210001c9161 http://es6.ruanyifeng.com/#docs/let