2017年1月17日

[筆記] JavaScript ES6 中的物件的擴展(object literal extension)


在 ES6 中,對於物件的使用有了更彈性的應用,除了撰寫物件的方式變得更為精簡之外,更允許將屬性名稱指定為變數,以達到動態賦予屬性名稱的效果,讓我們來看看在 ES6 中物件的擴展吧。

物件的擴展(object literal extension)


在 ES6 中允許在物件中直接給變量,這時候物件的屬性名稱為變數的名稱,物件的屬性值為變數的值,讓我們來看一下這個例子:


let website = "pjchender";
let obj = {website};
console.log(obj);    //[Object]{website: "pjchender"}


這時候變數的名稱(website)會變成物件的屬性名稱,變數的值("pjchener")會變成物件的屬性值。實際上,let obj = {website} 也是一種縮寫,完整的寫法會是 let obj = {website:website} 它實際上對應的關係如下圖所示:


也因此,如果我們輸入如下的程式碼:


let website = "pjchender";
let obj = {abc:website};
console.log(obj); // [object]{abc: "pjchender}


建立出來的物件,他的屬性名稱就會是 "abc" 而不是 "website"。

我們可以用同樣的方式為多個物件的屬性賦值:


let website = "pjchender";
let country = "Taiwan";

let obj = {
  website,
  country
};
console.log(obj); // [object]{country: "Taiwan", website: "pjchender"}


在 object literal 中所賦予的值,會覆蓋掉在更上 let 的宣告:


let name = "PJCHEN";
let country = "Taiwan";

let obj_es6 = {
  name: "Aaron",
  country,
}

console.log(obj_es6); // [Object]{name: "Aaron", country: "Taiwan"}


從這裡我們可以看到最後 name 這個屬性值會是 Aaron 而不是 PJCHEN。

物件中的方法也可以簡寫


除了物件中的屬性可以簡寫外,物件中的方法也可以透過這樣的方式簡寫(第8-10行),例如:


let name = "PJCHEN";
let country = "Taiwan";

let obj = {
  name,
  country,
  location(){
    console.log(this.name + ' lives in ' + this.country);
  }
}

obj.location();  // PJCHEN lives in Taiwan


這樣方法的簡寫,原本的寫法如下:

let obj = {
  name: name,
  country: country,
  location: function(){
    console.log(this.name + ' lives in ' + this.country);
  }
}


另外,還有一點需要注意的是,這樣簡寫的方法,預設的屬性名稱會是字串的型態,也因此原本的簡寫 location( ){...} 等同於 'location'( ){...}

在 ES6 中允許將表達式作為屬性名稱,以達到動態賦值的效果


在 ES6 中,允許將表達式作為屬性的名稱,只需要使用 [ ] 就可以了,方法如下:


let obj_es = {
  ["web"+"site"]: "pjchender"
}
console.log(obj_es); // [Object]{website: "pjchender"}


透過這樣的方式,我們更可以去動態賦予屬性名稱:



let websiteName = "pjchender";
let a = 2;
let b = 3;

let obj_es = {
  [websiteName]: "welcome",
  [a+b]: "sumNumber"
}
console.log(obj_es);  // [Object]{5: "sumNumber", pjchender: "welcome"}



透過 [ ] 的方式,我們的屬性名稱就可以放入變數,以達到動態給予屬性名稱的效果。

2017年1月16日

[筆記] JavaScript ES6 中的物件解構賦值(object destructuring)


在上一篇文章中我們說明了 ES6 當中如何使用陣列解構賦值([筆記] ES6 中的陣列解構賦值(array destructuring)),這篇我們一樣著重在解構賦值的部分,進一步說明如何以物件解構賦值(object destructuring)。

物件解構賦值的基本使用


物件解構賦值的基本使用方法如下:


let obj = {
  website: "pjchender",
  country: "Taiwan"
}

let {website, country} = obj;
console.log(website);  // pjchender
console.log(country);  // Taiwan


在這當中有一個很重要的點,陣列的解構賦值強調的順序,而物件的解構賦值強調的則是屬性名稱,屬性名稱必須相互對應才能夠取得到值。

物件解構賦值的寫法看似簡單,但實際上,在上面這段程式碼中 let {website, country} = obj ,實際上完整的寫法應該是像這樣子(也就是說上面那段程式碼是簡寫):


let {website:website, country:country} = obj;


它會根據前面的屬性名稱來對應要給的值,但值其實是給冒號(:)後面的變數,用圖來看像是這樣子:


所以真正被建立和賦值的 let{ } 當中,冒號(:)後的變數。

我們可以透過另一個例子更容易了解這個概念:


let obj = {
  website: "pjchender",
  country: "Taiwan"
}

let {website:wb, country:ct} = obj;

console.log(website, country);   //  Error:website in not defined
console.log(wb, ct)  // "pjchender", "Taiwan"



我們把冒號後的內容改寫成 wb 和 ct,接著我們會發現,當我們呼叫原本的變數 website 和 country 時,會回報 error;但是當我們呼叫 wb 和 ct 這兩個變數時,則能夠正確回傳變數值,也就是說,在物件解構賦值中,冒號前是用來對應物件的屬性名稱,冒號後才是真正建立的變數名稱和被賦值的對象

相對的,當冒號前的屬性名稱對應不到物件中的屬性名稱時,則會出現 undefined:


// website 和 wb 並沒有相對應
let{website} = {wb: "pjchender"};
console.log(website);    // undefined


物件解構賦值是以屬性名稱做對應


和陣列解構賦值不同的是,在陣列解構賦值中,我們可以接受下面這樣的寫法:


let [a, , c] = [1, 2, 3];
console.log(a, c); // 1, 3


但是在物件解構賦值中,我們不能像上面這樣寫:


let{a, ,c} = {a:1, b:2, c:3};
console.log(a,c); // Error


之所以會回報錯誤,主要的原因是在陣列的解構賦值強調的是順序,而物件的解構賦值強調的則是屬性名稱,屬性名稱必須相互對應才能夠取得到值

物件解構賦值同樣能給予預設值


和陣列的解構賦值一樣,物件的解構賦值也能賦予預設值,方法如下:

此範例擷取自阮一峰-ECMAScript 6 入門

物件解構賦值的用途


物件解構賦值的用途相當多(可參考阮一峰-ECMAScript 6 入門),其中在提取 JSON 數據時相當方便:


let data_JSON = {
  id: 74,
  website: "pjchender",
  country: "Taiwan",
  detail:{
    add: "Tainan",
    phone: "0933333333"
  }
}

let {id, website, country, detail} = data_JSON;
console.log(id, website, country, detail);


如此就能夠快速提領出 JSON 物件的屬性名和屬性值。

2017年1月15日

[筆記] JavaScript ES6 中的陣列解構賦值(array destructuring)

在 ES6 中過去的陣列和物件可以透過解構(destructuring)的方式來賦值。這篇文章中,我們會先說明如何透過陣列的方式來賦值。

陣列解構賦值的方法(array destructuring)

過去陣列內的元素在賦值的時候,只能透過直接給值的方式,像是下面這樣:
let numbers = [1, 2, 3];
let a = numbers[0];
let b = numbers[1];
let c = numbers[2];
console.log(a,b,c);   // 1, 2, 3

一般用法

然而在 ES6 中可以直接透過解構的方式賦值,像是下面這樣子:
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1, 2, 3
如此變數 a = 1, b = 2, c = 3,這是最基本的陣列解構賦值方式。

當輸入的變數多於所給的值

當變數的數量多於賦予的值時,多出來的那個變數會被賦予 undefined 的值(d = undefined):
// 當變數多於所給的值
let [a, b, c, d] = [1, 2, 3];
console.log(a, b, c, d); // 1, 2, 3, undefined

當輸入的變數少於所給的值

當輸入的變數少於所給的值的時候,只有被指定到的變數會有值,少掉的變數可以直接空過去,這時候 g = 1, i = 3:
// 當變數少於給的值
let [g, , i] = [1,2,3];
console.log(g, i);  // 1, 3

在陣列解構中賦予預設值

我們也可以在陣列解構中賦予預設值,像是下面這樣:
let [a, b, c = 4, d = 'Hello'] = [1, 2, 3];
console.log(a, b, c, d); // 1, 2, 3, "Hello"
如此就可以將變數 c 賦予 4,d 賦予 Hello 的預設值,輸出的結果 c 因為後面有給值,所以依然是 3 ,而 d 在後面沒有給值,就直接帶入了預設值,得到 “Hello"。

防雷須知

如果你使用的是 standardJS 當作你的 code style,那麼你應該很習慣在結尾不加分號,但是在使用陣列的結構賦值時,這麼做 有可能會噴錯誤。
例如這樣:
let x = 0
let y = 0
console.log(x, y)
[x, y] = [1, 2]
console.log(x, y)
這時候會得到 "TypeError: Cannot set property '0' of undefined 的錯誤。首先簡單說明一下之所以可以不用在結尾加分號是因為在 多數情況下,
在語句或一段代碼敘述後,加了Enter鍵(\n)後,JS剖析器會在執行期間自動幫你插入分號 @ eddyChang
上面提到是多數情況,但是在 某些情況下 JS 引擎是不會幫你加上分號的,其中像是這裡的開頭以 [ 開頭的語句。因此在這裡,請記得在 [ 的前面加上分號,寫起來會像是這樣 ;[x, y] = [1, 2]
更多關於分號的使用請參考 JavaScript裡的語句用分號結尾是個選項嗎 @ eddyChang

資料來源

[筆記] JavaScript ES6 中的函數預設值(default value)


在過去如過要在函式中建立預設值常常要利用到 JS 中強制轉換型別(coercion)的這種特性,但在 ES6 中則可以用相當簡易的方式就可以設定函式的預設值(default value),寫起來更簡潔方便,讓我們來看一下可以怎麼樣使用。

函式預設值的使用


函式預設值的使用非常簡單,只需要在函式中給予參數的地方用等號賦值就可以了,方式如下:

function add(x = 3, y = 5){
  console.log(x+y);
}

add();  // 8

也就是說,只要在 add 這個函式的參數位置寫 add(x =3, y=5) 就可以直接帶入預設值,因此雖然我在執行 add( ) 的時候沒有帶入任何的參數值,但它不會報錯,而是回傳 8 。

使用預設值的基本觀念


丟入的參數值會由前往後代入

假設我只給予後面的 y 預設值,如下:

function add(x, y = 5){
  console.log(x+y);
}

add(2);   // 7
add(2,8); // 10


這時候我可以只輸入一個參數是沒有問題的,這個值會先被丟到 x 這個參數。然而,如果我只給予前面的 x 預設值,如下:

function add(x = 3, y){
  console.log(x+y);
}

add(2);   // NaN
add(2,8); // 10


這時候當我輸入 add(2) 來執行這個函式時,因為 2 會先被帶入 x ,所以 x = 2;而 y 沒有被賦值,因此 y = undefined,這也使得最後回傳的結果會是 NaN。


2017年1月14日

[筆記] JavaScript ES6 中的箭頭函數(arrow function)及對 this 的影響


在這篇文章中要來說明一下在 ES6 中相當常見的箭頭函數(arrow function),讓我們來看一下可以怎麼樣使用。

什麼是箭頭函數(arrow function)


首先,我們來看一下過去我們撰寫函數的方法:



在 ES6 中,我們可以把它改成箭頭函數的寫法,它會變成下面這樣:


沒有參數的時候要記得加上空括號

要特別留意的地方是,在箭頭函數中如果沒有帶入參數時,一樣要加上空括號。



如果只是要回傳某個值,可以省略 return

如果我們的函式本身只是要回傳某個值的話,可以把 return 這個字省略掉:


箭頭函數帶入參數值


兩個以上的參數,需要使用括號

當我們的函式擁有兩個以上的參數時,我們一樣要使用括號來帶入參數,寫法像是下面這樣子:


當函數只有一個參數時,不需要使用括號

從上面的例子我們可以知道,當函數沒有參數或有兩個以上的參數時,我們都要加上括號( ),但是當函數只有一個參數時,可以省略括號不寫,因此,當我們的函數只有一個參數時,我們的函數長得像這樣:



箭頭函數當中的 this 是定義時的對象,而不是使用時的對象


在使用箭頭函數時,有一點要注意的是,在箭頭函數中,this 指稱的對象在所定義時就固定了,而不會隨著使用時的脈絡而改變。

讓我們來看一下這個例子:

在這個範例中,不論我們使用的是原本 function 的寫法或 ES6中的箭頭函式,都會回傳得到最外層的 window 物件(如果不清楚 this 的話可以先參考 [筆記] 談談JavaScript中的"this"和它的bug),這樣看起來似乎兩者沒有太大的差別。



然而,換個例子的情況就不一樣,讓我們來看看下面兩個不同的例子:

例子一(參考自 阮一峰 - ECMAScript 6 入門

我們分別用原本的寫法和箭頭函示的寫法建立了兩個 function:

// 原本的 function
let fn = function(){
  console.log(this.constructor.name);  // Object(data)
  setTimeout(function(){
    console.log(this.constructor.name) // Window
  },100);
}

// 箭頭函式 Arrow function
let fn_arr = function(){
  console.log(this.constructor.name);  // Object(data)
  setTimeout(() => {
    console.log(this.constructor.name) // Object(data)
  },100);
}

let id = 21;
let data = {
  id: 21
}

fn.call(data);     
fn_arr.call(data); 


setTimeout:裡面都分別帶入 setTimeout 的函式,我們知道 setTimeout 執行的時間會在整個 JS execution context 都被執行完後才會執行(如果對這概念不清楚的話可參考:[筆記] 談談JavaScript中的asynchronous和event queue),因此函式建立的時間和實際執行的時間是不同的,因此這也創造了兩個不同時間點的 this 所指稱的對象。

call:另一個要了解的是 call 的用法,在 call 當中,我們會帶入後面所指定的物件當作所指稱的 this 對象(對這個概念不清楚的話可參考:[筆記] 了解function borrowing和function currying ─ bind(), call(), apply() 的應用)。

this.constructor.name:這樣子的寫法只是避免在回傳出物件的時候把整個物件內容給傳出來,而是指示傳出該物件的名稱(參考自:stackoverflow)。

綜合上述,我們可以知道,因為有用 call(data) 的緣故,因此不論是使用傳統函式寫法(fn)或箭頭函式(fn_arr)時,在一開始函式裡面的 this 都指稱的是 data 這個物件。

然而不同的地方會在執行 setTimeout 中的函式,在使用傳統函式的寫法時,使用 fn.call(data) 時,因為它執行的時間點是在整個 JS execution context 執行完才執行,而當時的環境會變成是 global environment,因此使用傳統函式時,這個 this 指稱的對象會轉變成 window object 。但是,如果是使用新的箭頭函式(arrow function),這個 this 所指稱的對象則不會改變,依然是 data 這個 object

範例:https://jsbin.com/wodegu/edit?js,console

例子二(參考自 Accelerated ES6 JavaScript Training

第二個例子是使用 addEventListener 來達到示範,首先我們在 HTML 中建立一個 button element,然後利用 JS 來抓取這個 button,接著 JS 部分則如下所示:

var button = document.querySelector('button');
var fn_arr = () => {
  // 建立 function 時 this 指 Window
  console.log(this.constructor.name)  // 執行function時 this 指 Window
};
var fn = function(){
  // 建立 function 時 this 指 Window
  console.log(this.constructor.name)  // 執行function時 this 指 HTMLButtonElement

button.addEventListener('click', fn_arr);


和例子一中的 setTimeout 類似,我們使用的 addEventListener ,也會在整個 execution context 執行結束後,在網頁觸發事件時才執行。

因此不論在傳統的函式寫法(fn)或箭頭函式(fn_arr)的寫法,一開始建立 function 的時候 this 所指稱的都是  window 這個物件,然而,如果是使用傳統的寫法,在觸發這個事件時所指稱的對象會從原本的 window 變成 HTMLButtonElement;若使用的是箭頭函式,則會固定所指稱的對象,因此 this 依然指稱的是 window 這個物件。

範例:https://jsbin.com/kotetu/edit?js,output

2017年1月4日

[筆記] JavaScript ES6 中使用 const 宣告常數


在 ES6 中可以透過 const 來宣告變數,究竟 const 有什麼特別的地方呢?

首先 const 的意思是 constant ,也就是常數的意思,當我們宣告它之後,它是不能在被改變的,但實際上在使用時仍然有一些需要注意的地方,讓我們先來看一下下面的例子:

當我們使用 const 來宣告變數時,就像和使用 let 一樣,都可以得到 27 的結果:


可是當我對這個 const 重新指派值時,就會出現錯誤訊息:


也就是說 age 它在這裡其實是被設定成一個常數,而不是一個變數。 透過 const 我們可以宣告常數

因此,為了方便區分哪些是常數那寫是變數,我們可以把常數在宣告的時候用大寫來表示,像是這樣:


使用 const 在宣告陣列或物件時需要留意的地方


這麼看起來 const 似乎很容易理解,但是有幾種狀況必須非常小心,當 const 使用在陣列(array)或物件(object)的時候需要特別留意,讓我們來看一下使用在陣列的情況:

當我們已經使用 const 宣告好一個常數,接著在用 push 去推入一個陣列的值時,並不會有錯誤的情形產生。


之所以不會有錯誤,是因為在 JS 中陣列(array)和物件(object)都是屬於 reference type,因此實際上我們並沒有把這個常數指向(pointer)另一個東西,它仍然指稱到的是同一個記憶體位置。

如果不清楚 by reference 的意思,可以參考:[筆記] 談談JavaScript中by reference和by value的重要觀念

同樣的道理,如果我們使用的是物件(object),一樣不會有錯誤的情形產生:


然而,還有一點需要提醒的是,如果你是使用 object literal 的方式修改物件的內容,那對於 JS 引擎來說,就等於是建立了一個新的物件,也就是它會將這個物件存到另一個記憶體位置,意思就是這個常數的值改變了,如此的話,同樣會出現錯誤的訊息(陣列也是一樣的道理):


const 和 let 一樣都是block-scoped


最後一點是 const 和 let 一樣,所宣告的變數都是僅在代碼區塊內有效(block-scoped),也就是僅在限定的{ }內有效:





2017年1月3日

[筆記] JavaScript ES6 中使用block-scoped 的 let 宣告變數



最近開始在 Udemy 上學習和另一個與 JavaScript 有關的課程ㄧ Accelerated ES6 JavaScript Training,主要的內容是針對 ES6(ECMAScript 6)加以學習,因此接下來會整理這門課程當中的內容還有其他網頁資源作為自己的學習筆記,有想要一同學習的夥伴們也可以持續 follow ,另外,因為我也是在持續學習的過程中,所以如果有任何觀念或語法上的錯誤,也非常希望比較有經驗的大大們可以協助改正。

使用 let 宣告變數


在 ES6 中有一個新的關鍵字 let ,let 的用法和過去使用的 var 非常相似,都可以用來宣告變數:

在這裡我們可以看出,不論是用 var 或用 let 都能得到相同的結果。


然而,var 和 let 最主要的差別在於 let 所宣告的變項只有在代碼塊區域(block scope)內有效,讓我們繼續看下去:

當我們使用 var 來宣告變數時,因為它不會受限於在代碼區塊內,所以一樣可以輸出變數內容。


但是當我們使用的是 let 的時候,一切就不同了,因為 let 所宣告的變數只能做用到代碼塊區域(block scope)中,所以當我們在 { } 外面要呼叫用 let 所定義的 author 這個變數時,就會出現 error:


簡單一句話來說:用 let 所定義的變數只能作用在所屬的 { } 中有效