JavaScript 是一個腳本語言 (Script Language),表示在開發時沒有 compiler,有著易於開發但是執行效率低的問題。 Script Language 易於快速開發一段程式碼,但是較不注重型別、完整性、易於出錯且效能較低,導致容易寫出不好的程式碼。 Node.js、Electron.js、GPU.js 這些語言的誕生,則幫助了 JavaScript 能在一般 OS 上面運行。
表示是將一個數值的位置 (址) 連結到變數,而非一個變數的位置 (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),在例二中就是如此操作,在函數外顯示出變化。
- 另一個就是直接改變引用類型變數的址,這邊運作就像是基本類型一樣:產生新的記憶體,然後將新址傳給變數,如同例三中的情況。
變數的宣告,相當於是在根物件 (瀏覽器中是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
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,則結果為第一個,否則就是第二個
}
NaN 無法做計算,但依舊是個數字型態
typeof(NaN) // number
要轉換文字成為數字時,會用 parseInt()、parseFloat(),而這方法會由左到右依序辨認,若是無法辨認,則以已辨認的部分為準:
parseInt('100,000') //100, 因為無法辨認逗點
parseInt('$100,000') //NaN, 因為第一個值就無法辨認
+'9527' //9527 快速轉換法:前面放個「+」號
若是用以下的方式產生新日期: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
中文翻譯叫做提升,大部分直接用英文,是一種「把宣告提升到其所在區域內頂端的行為」。 而這邊要提到,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() {...}
當我們要做一個可以記錄目前狀態的函數的時候,常常會使用全域變數來做這件事情。
舉例來說:紀錄點擊數,並呈現出來 直覺解法:寫一個 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