2020年1月13日 星期一

JS學習:函數進階使用技巧


代理函數物件

根據不同的條件,代理函數物件可以指向不同的函數來實現動態改變,如下:

function femaleFunction() {
    console.log('female');
}

function maleFunction() {
    console.log('male');
} 

let sex = 'female';

let sexFunction = sex === 'female' ? femalFunction : maleFunction;

sexFunction();    // female


上方倒數第二行的語句與下方相等,只是使用更為簡短的方式
if(sex === 'female') {
    sexFunction = femaleFunction;
}else {
    sexFunction = maleFunction;
}


函數執行佇列

針對某一個物件,可以根據不同的情況進行一系列的操作,如下:

function aFunc(obj) {
    console.log('aFunc');
    obj.aFunc = true;
}

function bFunc(obj) {
    console.log('bFunc');
    obj.bFunc = true;
}

function cFunc(obj) {
    console.log('bFunc');
    obj.cFunc = true;
}

let obj = {};

let functionAry = [];

functionAry.push(aFunc);
functionAry.push(bFunc);
functionAry.push(cFunc);

for( let i = 0 ; i < functionAry.length ;  i++) {
    functionAry[i](obj);
}

console.log(JSON.stringify(obj));  

// aFunc
// bFunc
// cFunc
// {"aFunc":true,"bFunc":true,"cFunc":true}


由上述的輸出結果可以得知函數依照 a 、 b 、 c 的順序執行,並且確實為 obj 物件增加了三個屬性,當然也可以有更多的變化,像是透過額外的陣列來儲存調用順序、動態控制參數等等

使用參數返回函數

有一個物件,我們希望依據該物件的內容或是型別來決定要使用什麼函數,傳統的方法可以在函數內部再調用其他函數,但是這樣會略顯冗長,如果使用函數來返回函數會比較簡潔,如下:

function chooseFunc(params) {
    // 利用型別來決定返回函數
    if(typeof params !== 'string'){
        return objFunction;
    }

    // 根據內容來決定返回函數
    switch(params) {
        case "A":
            return aFunc;

        case "B":
            return bFunc;

        default:
            return defaultFunc;
    }
}

函數動態添加實例屬性

假設有一個射擊函數,如果想知道總共射擊了幾次的話,可以在射擊函數裡面加上一個動態屬性次數,如果要引用的話,需要使用「函數名稱 + 屬性名稱」來存取,如下:

let shot = function(){
    shot.times ++;
    console.log(shot.times);
};

shot.times = 0;

shot();  // 1
shot();  // 2
shot();  // 3


函數物件動態添加實例方法

除了可以添加屬性之外,當然也可以添加方法,延續上面的例子,現在每射擊三次就要裝填彈藥一次,如下:

let shot = function(){
    shot.times ++;
    console.log(shot.times);
    shot.reload();
};

shot.reload = function() {
    if(this.times >= 3){
        this.times = 0;
    }
};

shot.times = 0;

shot();  // 1
shot();  // 2
shot();  // 3
shot();  // 1
shot();  // 2


由上方的例子可以知道函數是極其靈活的,但是也因為他的靈活性,在大專案使用更需要注意

2020年1月12日 星期日

JS學習:函數


函數定義

函數 ( Function ) 是執行特定任務並可以在程式中重複使用的程式碼區塊


函數有兩種定義方式,分別為函數語句 ( Function statement ) 和函數運算式 ( Function expression)


Statement 和 Expression 差別

Statement 是不會直接回傳值,因此也無法被定義成變數,像是 if

Expression 則是說輸入後可以直接回傳值的一段程式,可以被存成變數,像是 a === b

函數語句定義法

函數語句定義法與其他語言類似,使用 function 來宣告,如下:

function 函數名稱 ( 參數1 , 參數2 ... ) {
    內部語句
}

函數運算式定義法

函數運算式為建立匿名函數物件,並將引用賦值給左邊的函數型變數,如下

let log = function(log) {
    console.log(log);
}

兩者的區別

平常應盡量使用函數語句定義法,因為此種方法較為標準、簡潔,且函數語句定義法在執行的時候會被提升 ( hoisting ) 到最前面,也就是說只要該 function 有被定義,都可以被成功呼叫,而函數運算式定義法則是一定要先定義,否則會報錯,如下:


testA();    // 報錯,函數未定義
testB();    // B

let testA = function() { console.log('A');}
function testB() { console.log('B'); }


兩者還有一個差別,因為函數運算式並非像物件一樣獨立存在,等同於匿名函數,因此當引用這個函數的物件消失或不再使用的話,該函數就會被回收

參數

在 JavaScript 的世界裡面沒有分成傳值和傳引用,所以參數傳入都是引用,但是如果參數為基本資料型別的話,傳值和傳引用是一樣的;其他資料型別的話都是傳入引用,如下:

let a = 1;
let b = [1,2];
let c = {1:1, 2:2};

function testA(a, b, c) {
    a = a + 1;
    b.push(3);
    c[1] = 3;
}

console.log(a);    // 1
console.log(b);    // [1, 2, 3]
console.log(JSON.stringify(c));    // {1:3 , 2:2}


變數 a 為基本資料型別,因此只有在 function 裡面的 a 改變了,變數 b 和變數 c 都是傳入引用,因此不論內外都被改變了

預設參數

在宣告函數的時候,可以為傳入的參數設定預設值,當呼叫函數但是沒有帶入參數時,就會由預設值代替,如下:

function add(a=1, b=2) {
    console.log(a + b);
}

add();     // 3
add(2);    // 4
add(2,3);  // 5

存取參數資訊

傳入函數的參數都會被保留在一個自動建立的 arguments 物件中,並依照傳入的順序存入,如下:

function add(a=1, b=2) {
    console.log(JSON.stringify(arguments));
}

add();        // {}
add(1,2);     // {"0":1, "1":2}
add(1,2,3);   // {"0":1, "1":2, "2":3}


需要注意的是,雖然 arguments 看起來很像陣列,但是他僅具有索引、length 屬性和callee屬性,callee 為一個持有指向目前函數的引用,通常被用來建立遞迴,但是 callee 在嚴謹模式中已經被禁用了,如下:


function add(a=1, b=2) {
    console.log("a: "+a+" b: "+b);
    if(a > 1) { 
        arguments.callee(a-1,b-1); 
    }
}

add(1,2);   // a: 1 b: 2
            // a: 0 b: 1


除了 arguments 之外,其餘參數 ( Rest Operator ) 也可以達到同樣的效果,而且其餘參數是真的陣列,如下:


function add(a=1, b=2, ...params) {
    console.log(JSON.stringify(params));
}

add();       // []
add(2);      // []
add(2,3);    // []
add(2,3,4);  // [4]

由上面兩個範例可以發現說,如果函數有預先定義參數的話,對於其餘參數而言傳入的值會先給預先定義的參數,其餘的才會存入,而 arguments 則是傳入的值通通存入

展開運算子 ( Spread Operator )

如果將 ...params 用在 console 等情況下,會變成所謂的展開運算子,也就是把陣列中的值一個一個分開來,如下:

let show = [1,2,3,4];
console.log(...show); // 1 2 3 4 

函數的本質

在 JavaScript 的世界裡,所有東西都是物件,所以函數也是一種物件,為一種 Function 類型的物件如下:

function add(a=1, b=2) {
 console.log(JSON.stringify(arguments));
}

let functionType = Object.getPrototypeOf(add).constructor.name;
console.log(functionType);    // Function

functionType = Object.getPrototypeOf(Object.getPrototypeOf(add)).constructor.name;
console.log(functionType);    // Object


函數可以擁有獨立的屬性和方法,而函數一旦被執行的話,就會建立一種名為 Active Object 不可存取的特殊物件,同時每個函數都有內建的範圍鏈 ( Scope chain ) ,也可以稱為有效範圍,如下:

let a = 1;

let show = function(){
    let a = 10;
    a = a + 100
    return a;
}

console.log(show());   // 110
console.log(a);        // 1


因為在 show 裡面有定義 a ,因此 return 的 a 會先抓到內部定義的 a(區域變數),因此 console 的結果為內部的 10 + 100 ,但是這個變數 a 只存在於函數內,與外部定義的 a 為不同個、互不影響,所以第二次 console 的結果就直接是外部定義的 a

如果內部沒有定義的話,函數則會往外抓取變數(全域變數),如下:

let a = 1;

let showB = function(){
    a = a + 100;
    return a;
}

console.log(showB());  // 101
console.log(a);        // 101


所以如果抓取的是全域變數,而且在函數內更改的話,就會有所影響了


2020年1月8日 星期三

JS學習: This


本篇引用至 What-is-THIS-in-JavaScript ,主要為自己紀錄加深印象使用



This的定義

ECMAScript 對於 This 的定義為:

「The this keyword evaluates to the value of the ThisBinding of the current execution context.」

MDN 對於 This 的定義為:

「In most cases, the value of this is determined by how a function is called.」
 在大多數的情況下,this的值會由該 function 當時如何被呼叫而決定的


2020年1月5日 星期日

JS學習:流程控制 - 迴圈、Switch

迴圈

當碰到要重複許多相似操作的情況時,可慮使用的迴圈

迴圈三要素

迴圈的設計通常有以下的步驟,其中最重要的是確保迴圈三要素的正確

  1. 確定迴圈內容,也就是哪些事情要重複執行,重複的指令稱為循環體
  2. 確定迴圈變數,用來控制迴圈的開始與結束
  3. 迴圈三要素:
  • 迴圈初始化
          也就是需要先設置迴圈變數的初始值

  • 迴圈條件運算式
         也就是需要重複執行的條件,如果條件為 True ,則繼續執行下一個迴圈;否則,結束迴
         圈迴圈的條件除了限定執行的範圍外,更重要的是提供終止迴圈的條件,當一個迴圈無
         法達到終止條件,該迴圈會形成無窮迴圈,也稱之為閉環,在某些語言中會顯示錯誤

  • 循環體中必須要有更改迴圈變數值的語句
          如果迴圈變數值不改變,迴圈就無法終止,除非有外部其他程式碼改變迴圈變數值,否
          則就會是閉環


While

While 的意思是「當....的時候,做....」,也就說當條件符合時,執行循環體,如下:

while ( 迴圈條件 ) {
    循環體
}

如果以上一章的找書例子來實作的話,如下:

let i = 0;
while ( i < box.length ) {
  
    if ( box[i] == targetBookName ) {
        console.log( '找到了' );
    }
    i++;
}

do-while 

使用while時,首先會判斷是否符合迴圈條件,符合的話再執行循環體,而 do-while 則相反,先執行循環體,再判斷是否符合條件,符合的話再繼續執行,因此 do-while 一定會執行至少一次

do {
    循環體
} while ( 迴圈條件 )

for

相較於 while ,for 迴圈比較靈活,也是最常使用迴圈語法,如下:

for ( 初始化 ; 迴圈條件 ; 步進 ) {
    循環體
}

步進 ( Step ) 指的是每一次進行迴圈時,對迴圈變數值所做的變化

同樣的使用找書的例子:

for ( let i = 0 ; i < box.length ;  i++ ){
    if ( box[i] == targetBoxName ){
        console.log( ' 找到了 ' );
}

for 迴圈可以將括號內的初始化、迴圈條件、步進 通通為空,分別寫在迴圈內外,只要確保迴圈三要素正確即可,如下:

let i = 0;
for ( ; ; ) {
    if ( box[i] == targetBookName ) {
        console.log( ' 找到了 ' );
    }
    i++;
    if ( i >= box.length ) break;  // 跳出迴圈
}

也可以使用逗點 (,) 增加初始化、迴圈條件、步進,如下:
for ( 初始化1 , 初始化2 ; 條件1 , 條件2 ; 步進1 , 步進2 ) 

for...in

for...in  是用來列舉一個集合中的所有元素,集合為收集起來的一個或一堆東西,像是 Array 和 Object 都屬於集合

雖然 Array 的索引一定是由 0 開始,可以用 for 處理,但是對於 Object 而言,需要先知道 Key 才能存取,因此 for...in 是為了在不知道 Key 時,也可以存取 Object 成員,如下:

let testObj = {
    1 : '111',
    2 : '222',
    3 : '333'
}; 

for( let key in testObj){   console.log( 'Key = ' + key + ' ,value= ' + testObj[key]); }

break 和 continue 

break 和 continue 是用來控制迴圈流程,皆在循環體內使用,break 是直接跳出整個迴圈,而 continue 則是終止目前迴圈,直接進行下一次迴圈,兩者在使用時,位於 break 或是 continue 後方的語法皆不會執行,如果是在函數中,return 的效果與 break 是一樣

break 可以用來終止無窮迴圈、閉環的情況

在實務上對於多維陣列的處理上經常會使用迴圈裡加迴圈,這種時候的流程控制就很需要仰賴 break 和 continue,如下:

for(let i = 0 ; i < array.length ; i ++){
    for(let j = 0 ; j < array[i].length ; j++){
        if(array[i][j] == target) {
            console.log('找到了');
            break;  // 跳出子迴圈
        }
    }
}

switch

switch是用來處理選擇分支判斷,而且switch的判斷是使用全等於運算子 ( === ) ,因此需要值與型態都一致

舉個例子,在箱子裡拿一個水果,如果是蘋果則需要付5塊錢,如果是香蕉的話則是3塊錢,其他免費

switch(getFruit){
    case "蘋果":
        console.log('付5塊錢');
        break;
   case "香蕉":
        console.log('付3塊錢');
        break;
   default:
        console.log('免費');
        break;
}
 以上範例也可以使用 if-else 來完成,但是switch可以不用每一個分支加上 break ,如果該分支沒有 break 的話,則會往下執行下一個分支

現在多加兩種水果,如果是橘子的話也要付5塊錢,如果是柳丁的話,除了要付3塊錢,還有先準備購物袋

switch(getFruit){
    case "橘子":
    case "蘋果":
        console.log('付5塊錢');
        break;
    case "柳丁":
        console.log('準備購物袋');
    case "香蕉":
        console.log('付3塊錢');
        break;
    default:
        console.log('免費');
        break;
}

這種情況也可以使用 if-else 來完成,但是使用switch可以更為簡潔的表示

2020年1月4日 星期六

JS學習:流程控制、if - else

什麼是過程導向?

過程導向會將解決問題分成一個個步驟,並按照順序執行,包含程式語言中必備的三大要素:條件、迴圈和判斷

假設一個情況:要從一箱書裡找到一本名為「殿堂之路」的書,可以分成以下的步驟

2020年1月2日 星期四

JS學習:運算子、運算式


運算子、運算式?

運算子 ( Operator ) 一定要有運算物件/ 運算元 ( Operand ) ,才可以進行運算,兩者的組合就稱之為運算式 ( Expression ) 

1 + 2 = 3 ,就是一個運算式, 1、2 為運算物件,加號為運算子

JS學習:函數進階使用技巧

代理函數物件 根據不同的條件,代理函數物件可以指向不同的函數來實現動態改變,如下: function femaleFunction() { console.log('female'); } function maleFunction() { ...