Skip to content

Latest commit

 

History

History
289 lines (218 loc) · 8.47 KB

01.md

File metadata and controls

289 lines (218 loc) · 8.47 KB

JavaScript 觀念整理


JavaScript 語言特性

JavaScript 是一個腳本語言 (Script Language),表示在開發時沒有 compiler,有著易於開發但是執行效率低的問題。 Script Language 易於快速開發一段程式碼,但是較不注重型別、完整性、易於出錯且效能較低,導致容易寫出不好的程式碼。 Node.js、Electron.js、GPU.js 這些語言的誕生,則幫助了 JavaScript 能在一般 OS 上面運行。


Call by Sharing

表示是將一個數值的位置 (址) 連結到變數,而非一個變數的位置 (call by reference),或是一個變數的值 (call by value)。

var a = 1,
b = a;
b = 2;
a //1
var a = {obj: 1},
b = a;
b.obj = 2;
a //{obj: 2}
var a = {obj: 1},
b = a;
b = {obj: 2};
a //{obj: 1}

例二中b值的變更,讓物件中 obj 參照到的址從 1 的位址變到 2 的址,所以 a 印出來時也跟著變化。 在例三中,則是先在記憶體中宣告了新的區塊,然後賦值這動作就讓b拿到了新的值 ({obj: 2}) 的位址,但這時候已經與a無關了。

function func(num){ num = 20; return num; } 
var result1 = 10; 
var result2 = func(result1); 
console.log(result1, result2); //10, 20
function beGirl(obj){ obj.sex = 'girl'; } 
var person = new Object(); 
beGirl(person); 
console.log(person.sex); //girl
function beBoy(obj){ obj = {sex: 'boy'}; } 
var person= {sex: 'not sure'}; 
beBoy(person); 
console.log(person.sex); //{set: 'not sure'}

從例二與例三看起來,情況變得有點詭異,明明例二中,像是把 ref 傳進 function 中,obj 的值變化,那位 person 的值也跟著變了。但在例三中,同樣的在 function 去改變值,但得到的結果卻與例二不一樣。 原因是把記憶體中的位置複製給了函數中的變數。 要注意的是,在例子中,一個 js object 是一個儲存位址的位址 - 他佔有一個位址,然後將該位址傳給變數 (person = {}),但同時,他也儲存著眾多變數的「值的位址」({sex: 'girl', name: 'JustinB'}),所以在值運作的判斷上,可以簡單區分成基本類型、引用類型。 簡單來說 JavaScript call by sharing 可以分成以下幾種

  • 基本類型像是 string、number... 等基本類別就是產生新的記憶體,將新址傳給變數。
  • 引用類型,js object、array,則區分成是否是對其中的屬性進行操作 (obj.sex = xxx、arr[0] = 1),在例二中就是如此操作,在函數外顯示出變化。
  • 另一個就是直接改變引用類型變數的址,這邊運作就像是基本類型一樣:產生新的記憶體,然後將新址傳給變數,如同例三中的情況。

變數 (Variable) vs. 屬性 (Property)

變數的宣告,相當於是在根物件 (瀏覽器中是window) 底下建立屬性,而只有用 var 宣告的才會是變數,其餘的,包含不加 var 的變數宣告都算是屬性的建立

var a = 1; //變數
window.a = 2; //屬性
a //2, 屬性

變數與屬性的運用方式幾乎一樣,差別是就是

1.delete 是否可以使用 2.存取一個不存在的屬性時:undefined;存取一個不存在的變數時:Uncaught ReferenceError

foo = 3; //相當於 window.foo = 3;
delete window.foo; //true
foo //undefined
goo //Uncaught ReferenceError: goo is not defined
var goo = 2;
delete window.goo; //false

Boolean

null 與 undefined 兩個比較時會產生 true。

null == null //true
null == undefined //true
undefined //true

其他時候,跟不管誰去比較都是 false。

null == false //false
null == true //false

NaN 則跟任何人,包括自己比,都是 false

NaN == null //false
NaN == NaN //false

唯一能判斷是 NaN 的方法就是 isNaN()

isNaN(NaN) //true

該表告訴我們:「==」的情況非常複雜,但「===」就相對單純,請愛用「===」、「!==」

new Boolean() 的使用,而只有下列 6 種值會出現 false:false本身、0、''(空字串)、null、undefined、NaN

new Boolean(false);         //產生 false
new Boolean(0);             //產生 false
new Boolean('');            //產生 false
new Boolean(null);          //產生 false
new Boolean(undefined);     //產生 false
new Boolean(NaN);           //產生 false

boolean 還有以下較為特別的語法

function something(setting){
  myDefault = setting || 2; //表示若 setting 沒有放入值 (undefined) 的話,就會給予 2 當作預設值
}
function something(callback) {
  callback && callback(); 
  // 常見、有點 tricky 的寫法,相當於 if(callback) callback()
  // 利用邏輯判斷:如果第一個為 false,則結果為第一個,否則就是第二個
}

Number

NaN 無法做計算,但依舊是個數字型態

typeof(NaN) // number

要轉換文字成為數字時,會用 parseInt()、parseFloat(),而這方法會由左到右依序辨認,若是無法辨認,則以已辨認的部分為準:

parseInt('100,000') //100, 因為無法辨認逗點
parseInt('$100,000') //NaN, 因為第一個值就無法辨認
+'9527' //9527 快速轉換法:前面放個「+」號

Date

若是用以下的方式產生新日期:new Date(year, month[, day[, hour[, minutes[, seconds[, milliseconds]]]]]),則會在month的地方出錯

new Date(2016, 10) //Tue Nov 01 2016 00:00:00 GMT+0800 (台北標準時間)
new Date(2016, 10, 30) //Wed Nov 30 2016 00:00:00 GMT+0800 (台北標準時間)
new Date(2016, 10, 31) //Thu Dec 01 2016 00:00:00 GMT+0800 (台北標準時間)

這是因為month的表示方法,相較於 1 ~ 12 月,對應的值是 0 ~ 11

日期轉換問題:

new Date('2016-11-30') //Wed Nov 30 2016 08:00:00 GMT+0800 (台北標準時間)
new Date('2016/11/30') //Wed Nov 30 2016 00:00:00 GMT+0800 (台北標準時間)

若用 yyyy-MM-dd 格式,由於台灣是 GMT+8 時區,因此會自動加 8 小時,可想見,若是有些地方是 GMT-8,則顯示日期可以能會是前一天的日期

ECMA 規格中定義的最正式寫法: YYYY-MM-DDTHH:mm:ss.sssZ 2016-09-25T02:24:39.385Z


Hoisting

中文翻譯叫做提升,大部分直接用英文,是一種「把宣告提升到其所在區域內頂端的行為」。 而這邊要提到,js 的變數,是 function scope:

if(true) {
  var a = 1;
}
 
console.log(a) //1

在其他語言中,a 值得顯示一定會出現 error 的,但在 js 中,卻可以成功執行;如此可知,限制變數的最大阻力來自於 function,而非大括號。

js 會將所有的 var 變數宣告以及 function 宣告提到最前面,為了避免 var 宣告變數會在程式運作中間產生差異,應該一開始就在函式的最前方把所有的變數宣告出來。

var i = 5;
 
function(){
  if(i < 10) {console.log('BOOOM!');} //這邊 i 是全域
}

可見到在上面的 function 中,一開始預期要取得全域 i 的值,然後做出判斷;但若是別人在不知道的情況,改寫了 code:

function(){
  if(i < 10) {console.log('BOOOM!');}
  // .... 
   
  var i = 5 //相當於在 function 最前方宣告 var i
  // ..... 
}

此時 i 的宣告會被拉升到該 function 的最上方,導致前面的判斷式失敗。

而雖然 function 的宣告也會被提到最前方,但由於 function 的敘述都很長,因此為了開發、閱讀維護之便利,一個js 檔的寫法可能會如下:

// 變數宣告
var a = 1, b = 2, c = setting || 3, d = function(){....};
 
// 主程式邏輯數十行
a++
b--
something(c);
 
// function 宣告
function something() {...}
function another() {...}

Closure

當我們要做一個可以記錄目前狀態的函數的時候,常常會使用全域變數來做這件事情。

舉例來說:紀錄點擊數,並呈現出來 直覺解法:寫一個 function,外頭放一個全域變數來記錄

var cnt;
function clickButton() { 
  //... 
  cnt++; 
}

但在同時,我們也不想要汙染到全域變數,因此,運用 closure 的概念來寫:

function clickButton() {
  var cnt = 0;
  return function(){
    return cnt++;
  }
}
 
var a = clickButton();
a() //0
a() //1
a() //2
...

Reference

  1. http://angelyeah.pixnet.net/blog/post/455372288
tags: Javascript Basic