avatar

目錄
JavaScript 的一些觀念整理

JavaScript 觀念整理

塊級作用域

JavaScript 在 ES6 新增了塊級作用域,而盡可能不使用全域作用域。
優缺點如下:

全域作用域:

  1. 削弱了程式的彈性,盡量避免使用

塊級作用域:
1.用大括號 {} 的情況下成立,沒有的話會報錯
2.外層無法讀取內層作用域的變數

變數宣告

ES5 var

1.函式作用域(function scope)。
2.有變數提升(hoisting),就是在宣告之前可以使用,但值為 undefined。

Code
1
2
console.log(a);  //undefined
var a = 1;

3.透過 var 宣告 變數 i,在全域範圍內都有效,所以每次的循環,新的值 i 都會覆蓋掉舊值,導致輸出有問題。

Code
1
2
3
4
5
for (var i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i); // 5,5,5,5,5
}, 100);
}

4.區塊語句(if、else、 for、 while等)裡面用 var 宣告的變數,會洩漏到全域中。

ES6 let 與 const

1.不可重複宣告。
2.只在宣告的塊級作用域內有效,只要是被所屬{ }包起來的let變數,都不會跑到外面(全域)。
3.不存在變數提升,只能宣告後使用。
4.若在定義變數之前使用該變數則會拋出 ReferenceError 錯誤。

Code
1
2
p = 3;  // ReferenceError
let p;

let 變數

1.變數值會產生變動。
2.let 強調在 {} 的區塊執行環境,每次 let 都會產生一個新的作用域。
3.會在每次迭代時重新宣告變數,並將上一次迭代的結果作為這一次的初始值。

迭代:電腦程式雖然亦有反覆運算的含義,但一般指的是迴圈解。其目的通常是為了接近所需求的目標或結果。每一次對過程的重複被稱為一次”迭代”,每一次迭代得到的結果通常會被用來作為下一次迭代的初始值。
遞迴:通常指函式推疊呼叫。

以下情況會因為 let 特性,重新綁定值,產生正確的輸出

Code
1
2
3
4
5
for (let i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i); // 0, 1, 2, 3, 4
}, 100);
}
Code
1
2
3
4
5
6
7
8
9
10
11
12
可以看做
let i = 0
setTimeout(function () {
console.log(0); // 0
}, 100);

let i = 1
setTimeout(function () {
console.log(1); // 1
}, 100);

...

const 常數

1.宣告時就立即給予值,不可之後再改變值(修改會報錯)。
實際上並不是變數的值不能改變,而是變數指向的內存位址不能被改變。

Code
1
2
3
const a = [];
a.push('Java'); // 可執行,此时a為['Java']
a = ['script']; // error

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

2.可以用來宣告常數。為了方便區分哪些是常數那寫是變數,我們可以把常數在宣告的時候用大寫來表示。

Function(函式)

函式指的是將一或多段程式指令包裝起來,可以重複使用,也方便維護。而函式通常會有回傳值,使用 return 作為回傳值輸出,如果 return 後面還有程式碼,則不會被執行。因此,用 return 回傳空值也具有「中止」程式碼的功能。並非每種函式都有回傳值的需要,也有可能透過輸出的方式來將結果輸出。

函式是物件的一種,雖然用 typeof 去檢查的時候,得到 “function” 的結果,,但實際上它仍屬於 Object 的一種。

函式組成

1.函式的名稱(也可能沒有名稱)
2.在括號 ( ) 中的部分,稱為「參數 (arguments) 」,參數與參數之間會用逗號 , 隔開
3.在大括號 { } 內的部分,內含需要重複執行的內容,是函式功能的主要區塊。

Ps. arguments 是 JavaScript 預設的參數,可直接帶入,這種參數不須預先設定,所有函式都內建此參數,他會將呼叫函式所帶入的參數一並透過陣列的方式傳入。但 arguments 是類陣列,無法使用許多陣列相關的方式。

e.g.

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const originCash = 1000;
function updateEasyCard() {
let cash = 0;
console.log(arguments); // 這裡可以看到 arguments 的結構
for (let i = 0; i < arguments.length; i++) {
cash += arguments[i];
}
let money = cash + originCash;
console.log('我有 ' + money + ' 元');
}

updateEasyCard(0); // 我有 1000 元
// arguments = [];

updateEasyCard(10, 50, 100, 50, 5, 1, 1, 1, 500); // 我有 1718 元
// arguments = [10, 50, 100, 50, 5, 1, 1, 1, 500];

語法:

function name(){}

函式(Function) 分成 Function Statement(函式陳述句)與Function Expression(函式表達式、表示式)。

Function Statement(函式陳述句)

程式碼的單位,這段程式碼不會產生一個值。

e.g.

Code
1
2
3
4
5
6
7
function greet(name){
console.log("Hello " + name);
}

if(a === 3){
console.log(a);
}

Function Expression(函式表達式、表示式)。

會產生(回傳)一個值,但值不一定會被賦予變數。

e.g.

Code
1
2
3
4
5
a = 3;  //  console.log 會回傳 3 

var greetFunc = function(name){
console.log("Hello " + name);
}

有時函式需要一些特定資訊才能完成它的工作,因此宣告函式時需要再給予它參數。在這樣的函式中,參數的功能就有如變數。參數放置的位置,在函式名稱後面的小括號裡。參數可一至多個。

函式也可以當作參數。

e.g.

Code
1
2
3
4
function calc(a,b) {
return a+b
}
console.log(calc(1,2)) //3

有加上名稱的函式

函式呼叫方式是: 函式名稱加上括號(),以及在括號中傳入對應的參數值,即可呼叫這個函式執行。例如:

Code
1
2
3
4
function calc(a,b) {
return a+b
}
console.log(calc(1,2)) //3

匿名函式

通常會另外指定給一個變數/常數,被指定後這些變數/常數就成了函式的名稱。通常用在:

1.當作其他函式的值傳入參數
2.一次性執行

語法:

function() {}

Code
1
2
3
4
// 函式表達式(FE)
const calc = function(a,b) {
return a+b
}

立即函式(IIFE)

立即執行程式碼,省略多餘的呼叫,用來分隔作用範圍,還可以用來避免汙染全域執行環境的東西,減少開發時因相同命名相衝的 bug。而且每次立即函式的執行環境都不一樣。

語法:

Code
1
2
3
// 擇一使用就好
(function () {...})()
(function () {...}())

e.g.

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var hello = function(name){
console.log('Hello ' + name);
}('Simon'); // Hello Simon


(function(food){
console.log('大俠愛吃' + food)
}('漢堡包'));

// 當 IIFE 和 全域環境有宣告相同變數名稱時,IIFE不會蓋掉全域的變數
var seafood = '波士頓龍蝦';
(function(name){
var seafood = '漢堡包';
console.log(name + '好~想吃' + seafood);
}('Simon')); // Simon好~想吃漢堡包

Callback(回調)

Callback(回調)其實跟一般函式沒什麼不同,差別在於被呼叫執行的時機。其實 Callback 就是把函式當作另一個函式的參數,然後在外部函式中調用該函式來完成某些事情

e.g.

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
function a(callback) {
setTimeout(function(){
console.log('a');
callback();
},1000)
}
function b() {
console.log('b');
}
a(b); //把b()傳入a();

//a
//b

純粹函式(Pure Functions)與副作用(Side Effects)概念

思考函式在使用上對外部環境造成的影響及效果。

副作用

代表著可能更動到外部環境,或者更動到傳入的參數值。不代表好、壞,看產生的結果而定。

e.g.

Code
1
2
3
4
5
6
7
8
9
10
11
12
// 有副作用 會改變值
counter++
x += 3
y = "Hello " + name

// 沒副作用 不改變值
3 + 5
true
1.9
x
x > y
'Hello World'

純粹函式

純粹函式(pure function)

1.給定相同的輸入(傳入值),一定會回傳相同輸出值結果(回傳值)。
2.不會產生副作用-不會改變原始輸入參數,或是外部環境。
3.不依賴任何外部的狀態(例如變數)。

Code
1
2
3
const sum = function(value1, value2) {
return value1 + value2;
}

不純粹函式(impure function)

1.需要依賴外部的狀態值(變數值)

Code
1
2
3
4
5
let count = 1;

let increaseAge = function(value) {
return count += value;
}

箭頭函式

ES6的一種新語法,一般使用箭頭函式與 function 的用法大致一致,可以傳入參數、也有大括號包起來,除此之外箭頭函式也有更簡短的寫法,有以下幾種特性。

1.若只有單一行陳述可不加花括號{},也不用寫 return,預設就有,但如果有{}是需要自行加入 return,如果沒有傳入值則會出現 undefined。

let calc = (x,y) => (x+y);

2.只有一個參數可以不加括號。

let callSomeone = someone => someone + '吃飯了'

3.沒有參數時,一定要有括號。

let callSomeone = () => '小明' + '吃飯了'

4.沒有 arguments 參數
5.綁定的 this 不同

傳統函式:依呼叫的方法而定
箭頭函式:綁定到其定義時所在的物件,箭頭函式可以取代 ES5 使用var self = this 或 .bind(this)的情況,它可以綁定 this 變數。但有時情況較特殊,要視情況而定,而不是每種情況都可以用箭頭函式來取代。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var name = '全域阿婆'
var auntie = {
name: '漂亮阿姨',
callName: function () {
// 注意,這裡是 function,以此為基準產生一個作用域
console.log('1', this.name); // 1 漂亮阿姨
setTimeout(() => {
console.log('2', this.name); // 2 漂亮阿姨
console.log('3', this); // 3 auntie 這個物件
}, 10);
},
callName2: () => {
// 注意,如果使用箭頭函式,this 依然指向 window
console.log('4', this.name); // 4 全域阿婆
setTimeout(() => {
console.log('5', this.name); // 5 全域阿婆
console.log('6', this); // 6 window 物件
}, 10);
}
}

auntie.callName();
auntie.callName2();

一般函式在建立時是在 window 下,所以在 window 下使用箭頭函式自然會指向 window,要確實將箭頭函式宣告在物件內部,這樣 this 才會指向該物件。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var func = function () {
var func2 = function () {
setTimeout(() => {
console.log(this);
}, 10);
};
// 這裡才算真正的建立一個物件
// 因此要在此物件下的箭頭函式才會以此作為基準
var func3 = {
func: func2,
var4: 4
}
func2(); // this = window
func3.func(); // func3 Object
}
func();
// 就算在這裡新增一個 function,也不會影響到內層的箭頭函式
// func() 是最外層的函式,他對於內層的箭頭不會有影響。
// func2() 是包覆在內層的函式,但由於箭頭函式不是在物件內,所以沒有影響。
// func3() 是呼叫在物件內的函式,因此箭頭函式會是使用它所在的物件。

注意:如果 不是 建立在物件內的函式,並不會影響箭頭函示的 this

6.縮寫的函式

不可以使用箭頭函式的情況

1.call、apply、bind 這三個方法,無法覆蓋箭頭函式中的this值

this 在 Arrow function 中是被綁定的,所以套用 call 的方法時是無法修改 this。

Code
1
2
3
4
5
6
7
8
9
10
11
let family = {
ming: '小明'
}
const func = () => {
console.log(this);
}
const func2 = function () {
console.log(this);
}
func.call(family); // 箭頭函式的情況,this 依然是 window
func2.call(family); // 一般函示 this 則是傳入的物件

2.不能用在建構式

由於 this 的是在物件下建立,所以箭頭函式不能像 function 一樣作為建構式的函式,如果嘗試使用此方法則會出現錯誤。

Code
1
2
3
4
5
6
7
8
const PhoneTemplate = (brand, modal, withCamera) => {
this.brand = brand;
this.modal = modal;
// ...
}

const sonyPhone = new PhoneTemplate('Sony', 'Z100', true);
// 錯誤:PhoneTemplate is not a constructor

3.DOM 事件監聽

如果是用在監聽 DOM 上一樣會指向 window,所以無法使用在此情境。

4.Prototype 中使用 this

一樣是 this 的問題,如果原型上新增一個箭頭函式,並嘗試使用 this 的話會指向全域。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function PhoneTemplate (brand, modal, withCamera) {
this.brand = brand;
this.modal = modal;
// ...
}

PhoneTemplate.prototype.callSomeone = function (someone) {
console.log(this.brand + ' 打通電話給 ' + someone)
}
PhoneTemplate.prototype.callSomeone2 = (someone) => {
console.log(this.brand + ' 打通電話給 ' + someone)
}

const sonyPhone = new PhoneTemplate('Sony', 'Z100', true);
sonyPhone.callSomeone('小明'); // Sony 打通電話給 小明
sonyPhone.callSomeone2('杰哥'); // undefined 打通電話給 杰哥

ps.物件函式綁定 this,過去我們在寫物件內的函式時,為了確保 this 能夠正確運作會先將它賦予在另一個變數上 (that, self, vm…)。

Code
1
2
3
4
5
6
7
8
9
10
11
var auntie = {
name: '漂亮阿姨',
callName () {
// 先使用另一個變數指向 this,讓內層函式可以正確使用
var that = this;
setTimeout(function () {
console.log(that); // auntie 這個物件
}, 10);
}
}
auntie.callName();

箭頭函式本就會指向他所生成的物件上,所以可以不需要另外指向另一個物件。

Code
1
2
3
4
5
6
7
8
9
10
var auntie = {
name: '漂亮阿姨',
callName () {
setTimeout(() => {
// 箭頭函式中會自動指向生成的物件上
console.log(this); // auntie 這個物件
}, 10);
}
}
auntie.callName();

提升(Hoisting)

編譯器在編譯時期會先找出所有的變數並綁定所屬範疇,但不賦值,所以此刻變數所帶的值是 undefined;而在執行階段,JavaScript 引擎才會處理給值的事情。可以想成是把這些變數和函式「提升」到程式碼的最頂端。

變數與函式的拉升的不同之處在於,變數的拉升只有宣告部份,而函式的拉升是整個函式,因此函式在宣告前是可以執行的。

e.g.

變數

Code
1
2
console.log(a); // undefined
var a = 2;

編譯過程

Code
1
2
3
4
var a; // Hoisting,編譯時期確認 a 屬於全域範疇,但不賦值,所以此刻變數所帶的值是 undefined

console.log(a); // 執行時期 得到 undefined
a = ?; // 執行時期 才會知道 a 的值是什麼

函式

以下例子,因為 Hoisting 效果,會將 calc 函式拉到執行時期的前面,所以可以正常使用

Code
1
2
3
4
5
// 函式陳述式
calc(1,2) //3
function calc(x,y){
return (x+y)
}

編譯過程

Code
1
2
3
4
function calc(x,y){
return (x+y)
}
calc(1,2) //3

但若是使用函式表達式,就只有變數名稱被提升,並非整個函式提升,則會產生錯誤。

Code
1
2
3
4
5
// 函式表達式
calc(1,2) // calc is not a function
let calc = function(x,y) {
return (x+y)
}

編譯過程

Code
1
2
3
4
5
6
let calc;

calc(1,2) // TypeError
let calc = function(x,y) {
return (x+y)
}

重複宣告

若函式和變數同名,則函式會優先;若同時有多個函式同名,則後面的會覆寫前面的宣告。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
foo(); // 1

var foo;

function foo() {
console.log(1);
}

foo = 2;

看成

function foo() {
console.log(1);
}

foo(); // 1

foo = 2;
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
foo(); // 3

function foo() {
console.log(1);
}

function foo() {
console.log(2);
}

function foo() {
console.log(3);
}

解構賦值

ES6 的新特性之一,用於提取陣列或物件中的資料。

用來賦值的等號左邊寫變數或是常數,右邊要寫上對應的數值,就像鏡子ㄧ樣左右對應,若沒有對應的值時,則會得到 undefined。

陣列

基本使用

const [a,b] = [1,2] //a=1,b=2

先宣告後指定

Code
1
2
let a,b
[a,b] = [1,2]

略過

const [a,,b] = [1,2,3] //a=1,b=3

字串

Code
1
2
3
const str = "good";
const [a, b, c, d] = str
console.log(a,b,c,d) //g,o,o,d

其餘運算

const [a, ...b] = [1, 2, 3] //a=1, b=[2,3]

沒相對應的情況

const [, , , a, b] = [1, 2, 3] // a=undefined, b=undefined

物件

Code
1
2
3
4
5
let data = {
name: 'joyce',
height: '180',
sex: 'female'
}

直接對應相同名稱

let {name,height,sex} = data

基本用法

const { id:x } = {id:1} // x=1

屬性賦值

const { data1:data1,data2:data2 } = { data1:100,data2:200 } //data1=100,data2=200

屬性賦值縮寫

const { data1,data2 } = { data1:100,data2:200 } // {data1} = {data1:data1}

字串模板 String Template

ES6 加入,使用 ${ variable_name } 即可代入變數,而不需再用 + 與雙/單引號拼湊字串。

Code
1
2
3
let name = 'john';
let greetings_1 = 'Hello ' + name + '!'; // greetings_1 = "Hello Summer"
let greetings_2 = `Hello ${name}`; // greetings_2 = "Hello john"

擴展運算子 Spread Operator

ES6 加入,以 … 表示,將陣列展開成個別數值,像展示陣列內的元素。

Code
1
2
let list = ['apple', 'boy', 'dog'];
console.log(...list); // apple boy dog

陣列的複製,備註:多維陣列或有複雜物件結構的情況時,是以淺拷貝(Shallow Copy)的方式進行複製,以下為範例。

Code
1
2
3
4
5
6
let list = ['apple', 'boy', 'cat'];
let list2 = [...list];
let list3 = ['doll', ...list, 'fat'];

console.log(list2); // ["apple", "boy", "cat"]
console.log(list3); // ["doll", "apple", "boy", "cat", "fat"]

陣列的合併。

Code
1
2
3
4
5
6
let list = ['apple', 'boy', 'cat'];
let list4 = ['george', 'happy', 'ice cream'];
let list5 = [...list, ...list4];

console.log(list5); // ["apple", "boy", "cat", "george", "happy", "ice cream"]
展開字串成為個別元素。

當成參數,代入函式中。

Code
1
2
3
4
5
6
7
let student = ['john', 'boy'];

function sayHi(name, gender) {
console.log(`Hi, I am ${name}. I am a ${gender}.`);
}

sayHi(...student); // Hi, I am john. I am a boy.

其餘運算子 Rest Operator

以 … 表示,集合剩餘的數值並轉為陣列,可以想像是收集(多個元素至一個陣列)的功能。

Code
1
2
3
4
5
6
7
8
9
10
const concatenate = (...letters) => {
let result = '';
letters.forEach((element) => {
result = `${result}${element}`;
});
return result;
};

concatenate('f', 'i', 'v', 'e'); // "five"
concatenate('s', 'i', 'x'); // "six"

ps.
其餘參數只能有一個,並且只能放在最後。
其餘參數在沒有傳入值的時候會是空陣列。

預設傳入參數

在 ES6 可為參數設定初始值。

原來的方式

Code
1
2
3
4
5
6
7
function callMe(phone) {
var telNumber = phone !== undefined ? phone : '0912345678';
return telNumber;
}

callMe(); // "0912345678"
calMe('0987654321'); // "0987654321"

現在可以寫成

Code
1
2
3
const callMe = (phone = '0911111111') => phone;
callMe(); // "0911111111"
callMe('0922222222'); // "0922222222"

參考:
https://pjchender.blogspot.com/2017/01/const.html
https://ithelp.ithome.com.tw/articles/10192146
https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/arrow_function.html
https://ithelp.ithome.com.tw/articles/10193313
https://ithelp.ithome.com.tw/articles/10191549
https://ithelp.ithome.com.tw/articles/10185780
https://cythilya.github.io/2018/10/20/hoisting/
https://wcc723.github.io/javascript/2017/12/21/javascript-es6-arrow-function/
https://wcc723.github.io/javascript/2017/12/14/javascript-arguments/
https://cythilya.github.io/2018/04/02/es6-top-features-you-must-know/