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 (作用域鏈),可以從函式內部取得函式外部的資源
因此,一個函式通常會有幾個作用域:
- 函式本身
- 外部函式內容(如果是函式中的函式,可存取外部函式內容)
- 全域
在不同的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