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 為運算物件,加號為運算子

2019年12月29日 星期日

JS學習:基礎資料型別


布林值:Boolean

布林是用來表示真偽的資料型別,只有 True 和 False 兩個值,如果宣告一個布林型態變數,但是沒有賦予值的話,此時的值會是 False 


2019年12月25日 星期三

JS學習:變數的宣告與使用



所有資料都是物件

資料型別分成:基本資料型別、複雜資料型別

基本資料型別 ( primitive data type ):為語言的基本構成單元,Boolean、int、Number、String 等

複雜資料型別 ( complex data type ):通常由基本資料型別構成,Array、Function 等

變數命名

  1. 盡量使用有含意的英文單字
  2. 使用小駝峰命名 ( ex: maxWidth )
  3. 命名符合 最短長度、最富意義 原則
  4. 避免出現數字編號 ( ex: id_1、id_2 )

變數的本質

每個物件在建立時,會向記憶體請求一塊空間來儲存資料,而我們宣告的變數會持有引用
( Reference ),引用則是指向該物件所在的記憶體位置

2017年9月20日 星期三

Android Studio APK 安裝簽名衝突


有時候我們在寫Android APP 安裝到實體機測試的時候

會出現「未安裝應用程式。 發生衝突,安裝套件所使用的簽名與現有套件的簽名相同」

有別的文章有提到說,可能是因為先前安裝的程式沒有刪除,需要先把程式完全移除

但是今天遇到的問題是並非上述所提及的「同一個程式衝突」而是「不同程式衝突」

問題產生的流程是這樣

複製舊程式,修改程式碼與更改package名稱後,安裝程式發生衝突問題


原因可以出在更改名稱之後Gradle沒有同步更改

我們更改package名稱操作可能會如下圖所示


但是在Gradle底下卻不會一起更動



所以需要自己手動更改成新的名稱


另外一個問題是我們在產生APK的時候,可能會選擇「Build APK」這個選項



他會產生APK檔案沒有錯,但是Android在辨別APP的時候會比對「package名稱」跟「簽名」

使用這個選項是Android Studio 幫你使用預設的 Debug 簽名

在安裝的時候發現package名稱不一樣,但是簽名一樣,然後跳出錯誤訊息

所以當你有很多個不同的APP的時候,需要使用「Generate Signed APK」

原則上問題應該可以解決

至於簽名可不可以多個APP用同一個簽名,答案是可以的,而且Android官方也如此建議

為什麼要使用同一個簽名的原因如下

1. 升級:package名稱和簽名相同時,系統會判定兩者為同一個APP所以會更新程式

2. 模組:Android可以讓相同簽名的程式跑在同一個程序裡面,這樣可以個別升級

3. 共享:Android可以用相同簽名的程式在permission與許的情況下共享程式碼與數據

You should sign all of your APKs with the same certificate throughout the expected lifespan of your apps. There are several reasons why you should do so:
  • App upgrade: When the system is installing an update to an app, it compares the certificate(s) in the new version with those in the existing version. The system allows the update if the certificates match. If you sign the new version with a different certificate, you must assign a different package name to the app—in this case, the user installs the new version as a completely new app.
  • App modularity: Android allows APKs signed by the same certificate to run in the same process, if the apps so request, so that the system treats them as a single app. In this way you can deploy your app in modules, and users can update each of the modules independently.
  • Code/data sharing through permissions: Android provides signature-based permissions enforcement, so that an app can expose functionality to another app that is signed with a specified certificate. By signing multiple APKs with the same certificate and using signature-based permissions checks, your apps can share code and data in a secure manner.
                      出處:https://developer.android.com/studio/publish/app-signing.html

2015年9月2日 星期三

AP刷韌體 - tomato


原廠韌體並非不好,某些廠牌的韌體還有在更新,只是可能沒有需求的功能,所以才會刷成別的韌體。

刷韌體需要注意 -- 刷壞會被認定為人為損壞

以RT-N18U為範例

1. 至 Asus 官網下載韌體救援軟體
http://www.asus.com/tw/support


2. 至 Tomato 網站查AP對應的版本 - help 的 Router List


3. 下載該版本的韌體



注意:比較新的AP在ROM上比較沒有這個問題,但是比較舊款的需要注意ROM大小,像是 Asus WL520GU 只有 4MB ,就不能下載超過 4MB的韌體

4. AP接LAN到電腦,AP預設IP為192.168.1.1,電腦IP設成同網段 ,但是不要設與AP預設IP一樣,Mask 設定 255.255.255.0 

note:Dns跟Gateway 可以不用設定,因為沒有要連到外部網路

note:Gateway 是連結不同網段之間的溝通橋樑,而刷機是在同網段,所以可以不用設定

note:Dns 是 對應 IP 跟網域名稱的服務

5. 開啟救援軟體,選好韌體

6. 確認IP同網段,ping AP (有回應為正常)

7. 關電源,同時按住 RESET 與 WPS 鍵 (ping值無回應)


8. 開啟電源,等到燈號閃爍後,放開WPS (ping值無回應)


9. 再次等到燈號變成閃爍,放開 RESET ,上傳韌體 (ping值無回應)

note:第二次的閃爍燈號會比第一次慢

10. 等到上傳完成且ping值穩定,在上傳完後,大概會重開機兩次左右,登入管理頁面(192.168.1.1)

如果無法登入,檢查一下有沒有步驟做錯,像我做的時候,有時候太快放開WPS,就會失敗。

11. 成功後就是設定AP

note:在過程中ping AP 其實是不必要的,但是會這麼做是讓ping值成為我們的眼睛,否則在整個過程中會無法得知 AP 的狀態。




2015年8月31日 星期一

掛載硬碟 - parted 應用

今天的情況是原本的東西都在500G的硬碟,要把home目錄單獨掛到一顆4T的硬碟底下

裝完硬碟後,可以先進BIOS查看有沒有讀到
或是等開機後使用  dmesg 這個指令

note: dmesg - 查看開機過程中 kernel 所偵測到的硬體資訊

note:如果有加插Ram的話 free -h 可以查看

步驟如下

看新硬碟代號
dmesg | grep sd 

切割我們掛載的硬碟(假設代號為 sdb)
parted /dev/sdb

note:rm分割區代號可以移除分割區

先告訴硬碟我們的分割表類型
mklabel gpt

新增分割區
mkpart
part-type[]? primary
fs-type[ext2]? xfs
start? 0
end? -1  
Warning : The resulting partition is not properly aligned for best performance. Ignore/Cancel? i

note:  fdisk 只能支援到 2T 所以超過的話就要使用 parted 

note:-1代表全部

切割完之後 p 可以查看,確認ok後 q 離開

更新分割表資訊
partprobe

然後格式化分割區
mkfs.xfs -f /dev/sdb1

接下來先將舊的home改名字,不然改 fstab的時候會有衝突發生,並新增home目錄
mv /home /home.bak
mkdir /home

將我們新硬碟寫到fstab裡頭
blkid /dev/sdb1 
查看分割區的UUID
如果有圖形化介面可以右鍵複製貼上,沒有的話
blkid /dev/sdb1 >> /etc/fstab 
把顯示的所有資訊寫到 fstab 裡頭,再到 fstab 裡頭做修改
UUID=xxx /home     xfs    defaults  0 0

note:UUID是該分割區的唯一值,所以如果重新分割的話,UUID也會不一樣喔

然後就可以掛載上去了
mount /home
用 df -lh 查看

最後備份原本home底下的檔案
rsync -av 來源 目的
檢查檔案權限
reboot後確定一切正常就完成囉!

-----------------------------------------分隔線--------------------------------------

dmesg | grep sd

parted /dev/sdb

mklabel gpt
mkpart primary xfs 0 -1 

partprobe
mkfs.xfs -f /dev/sdb1

mv /home /home.bak

blkid /dev/sdb1 >> /etc/fstab
vim /etc/fstab

mount /home

rsync -av source goal

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

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