JavaScript-JSON與物件實體

JSON的發展

JSON(JavaScript Object Notation)是被JavaScript物件實體語法(Object literal syntax)所啟發的。

多年前,網路資料的傳輸格式很多種,像是XML。透過tag標籤,伺服器會解析它的資訊,可是當要下載資料時,因為過多不必要的tag符號,導致資料便很龐大,佔據很多頻寬。

1
2
3
4
5
//xml格式範例
<object>
<firstname>Mary</firstname>
<isAProgrammer>true</isAProgrammer>
</object>

後來發現JavaScript物件實體語法(Object literal syntax)的格式更簡易。

1
2
3
4
var object = {
firstName:'Mary',
isAProgrammer:true
}

因此,發展出一串字串的JSON格式,現今普遍都是使用JSON格式傳輸資料。

JSON格式有一些規範,屬性命名一定要被包在引號””中

1
2
3
4
{
"firstName":"Mary",
"isAProgrammer":true
}

轉換JSON的方法

在JavaScript中,有兩個內建的函式可以轉化JSON格式。

  • 將物件轉化為JSON字串

    1
    JSON.stringify(object)
  • 將JSON字串轉化回物件

    1
    2
    3
    4
    JSON.parse('{
    "firstName":"Mary",
    "isAProgrammer":true
    }');
1
2
3
4
5
6
7
8
console.log(JSON.stringify(object));

var jsonValue = JSON.parse('{
"firstName":"Mary",
"isAProgrammer":true
}');

console.log(jsonValue);

上述程式碼會印出下方所示的結果,先是印出一個JSON字串,接著再印出一個物件

JavaScript-物件

創建物件

宣告一個物件,有以下兩種寫法。
當JavaScript解析到大括號”{}”時,會認為這是在創造一個物件,因此下述第二行寫法,等同於創建一個空的物件。

1
2
var psrson = new Object();
var person ={};

初始化物件

在創建物件的同時,也可以在大括號”{}”中同時創建其中的屬性及方法。

1
2
3
4
5
6
7
8
var Tony = {
firstName:'Tony',
lastName:'Alicea',
address:{
country:'Taiwan',
city:'Taipei'
}
}

1
2
3
function greet(person){
console.log("Hi"+person.firstname);
}
1
greet(Tony);//Hi Tony

呼叫函式時同時建立物件

1
2
3
4
greet({
firstName:'Mary',
lastName:'Doe'
});//Hi Mary

JavaScript-偽命名空間

命名空間為何?

在現代程式語言中,「命名空間(Namespace)」是承裝變數與函式的一個容器,通常會將變數跟函式的名稱分別開來。
可是,在JavaScript中,並沒有命名空間。

1
2
3
4
var greet = 'Hello';
var greet = 'Hola';

console.log(greet);//Hola

偽裝命名空間

在JavaScript中為了避免衝突,可以使用物件當作容器,偽裝成命名空間的作用。
這樣子也可以避免在不同的框架中,若想給變數或函式取名字時,可以避免重複命名而被覆蓋的問題。

1
2
3
4
var english = {};
var spanish = {};
english.greet = 'Hello';
spanish.greet = 'Hola';

JavaScript-創建、取用物件中的屬性

1
2
3
4
5
6
7
8
var person = new Object();

person['firstName']='Tony';
person['lastName']='Alicea';

var firstNameProperty = 'firstName';

console.log(person);

上述結果會印出person物件,如下圖所示

若要取出物件中特定屬性的值,可以使用”[]”運算子

1
console.log(person[firstNameProperty])//Tony

簡化程式碼的寫法,可以使用”.”運算子,會自動理解”.”後述的為屬性名稱,並將該屬性名稱傳入物件中做參照查找,取出它的值。

1
console.log(person.firstName);//Tony

也可以用”.”去設定值。
下述程式碼,會先去address中查找country、city屬性,若找不到則會自動建立這個屬性,並且賦值給它。

1
2
3
person.address = new Object();
persone.address.country = 'Taiwan'
persone.address.city = 'Taipei'

若要取值,可以用”.”也可以用”[]”去取出屬性的值,兩種寫法做的事情都一樣。

1
2
3
4
console.log(person.address.country);//Taiwan
console.log(person.address.city);//Taipei
console.log(person['address']['country'];//Taiwan
console.log(person['address']['city'];//Taipei

JavaScript-函式中的預設值

在JavaScript的函式中,每當一個函式被呼叫,就會創建一個執行環境,在這個執行環境(execution context)被創造的同時,會同步先在記憶體中創建引數中的變數,預設值為undefined。
如果在呼叫函示時沒有帶入參數,並不會出錯,因為會被當作引數為undefined值做帶入。

1
2
3
4
5
function greet(name){
console.log('Hello'+name)
}

greet();//Hello undefined;

上方的例子,在程式執行時,新的執行環境建立,而在其中也會宣告出一個變數name,在變數倍賦值之前,預設值為undefined。

建立預設值

當沒有參數傳入時,如果希望自訂預設值,可以使用||(or運算子)來完成預設值設定。

當變數name為undefined時就會是false,接著就會繼續讀取”||”右邊的字串,並回傳該字串的值assign變數name。

1
name = name||'<default name here>'

查看Operator precedence(運算子 先後順序),因為”||”相較於”=”有更高的precedence,所以會先執行”||”,並且由左至右執行。

當”||”左邊是true的時候,若讀取到true,會直接回傳true;但若讀取到false,就會繼續判斷”||”右邊,當右邊是一個字串時,它會判斷是true,並回傳字串。

1
2
3
4
5
6
7
8
function greet(name){
name = name || '<default name here>'
console.log('Hello'+name)
}

greet(Tony);// Hello Tony;
greet();// Hello <default name here>;
greet(0);// Hello <default name here>;

但是這個寫法要注意,傳入參數的值不能為0,因為0會被當成false,所以會繼續執行判斷”||”右邊。

避免library重複,設定預設值

假設在同一個檔案中,引入三個library,lib1跟lib2的變數名可能會重複,程式碼會依照library引入的順序,由上而下執行,若變數有重複,後者會覆蓋前者變數的值。

1
2
3
4
5
6
7
8
//lib1.js中
var libraryName = 'lib1';

//lib2.js中
var libraryName = 'lib2';

//app.js中
console.log(libraryName);//lib2

為了解決變數重複,在使用之前可以先檢查該變數是非已經從存在於全域執行環境中。

1
window.libraryName = window.libraryName || "lib2";

上述程式碼,會先檢查”||”左邊的值,若已經有被定義賦值,則會被判斷為true,並將字串assign給window.libraryName;若尚未被賦值則會判斷為false,並將”||”右邊的字串回傳assign給window.libraryName。

1
2
3
4
5
6
7
8
//lib1.js中
var libraryName = 'lib1';

//lib2.js中
window.libraryName = window.libraryName || "lib2";

//app.js中
console.log(libraryName);//lib1

JavaScript-型別

動態型別(Dynamic Typing)

在JavaScript中,不需要宣告變數的型別,因為JavaScript是使用「動態型別」在處理變數。

僅需要宣告變數(type),不需要告訴JavaScript變數的型別(type),它會在執行程式時,自動判別變數的型別,而且一個變數在不同時候可能擁有不同型別。

JavaScript的六種純值(primitive)

純值是一種資料型別,代表基本型別,只是一個值,而非物件。

undefined

JavaScript給所有變數的初始值,變數會一直處於undefined,直到程式碼執行到設定變數的值

null

表示不存在,可以在程式宣告時,給與該變數的值為null,用以表示該變數沒有值。

boolean

表示對或錯,用true或false表示值

Number(數值)

在JavaScript中的數值皆為浮點數,不像其他程式語言還會細分整數或其他浮點數等,所以在JavaScript中也可以直接把他假裝為整數。

String(字串)

由一連串字符所組成,可以單引號或雙引號表示

Symbal(符號)

這是新的型別,被用在ES6中,尚未被全部的瀏覽器支援

強制型轉(Coercion)

JavaScript引擎自動將1轉換為字串’1’,才繼續做相加運算,自動避免出錯

1
2
var a = 1 + '2';
console.log(a);//12

JavaScript-執行緒、同步與非同步

單執行緒(Single Threaded)

在程式碼裡面都有很多指令,單執行緒代表一次只執行一個指令。

同步執行(Synchronous)

對程式語言來說,是一次一個,而且照出現順序一行一行執行。

非同步執行(Asynchronous)

在同一時間中,不只一個在執行。

JavaScript引擎

JavaScript引擎本身並非獨立存在,比如說在瀏覽器中,JavaScript並非唯一的東西,還會有其他的引擎處理別的程式,像是rendering engine會顯示畫面到螢幕中,或是也有負責處理HTTP請求資料的。

JavaScript引擎外部可以跟其他的引擎相互溝通,而且可以是非同步處理,也就是說JavaScript引擎、rendering engine、HTTP請求,這三者可以同時都在進行處理、執行。

然而,JavaScript引擎本身,是單執行緒和同步執行的。也就是說,在JavaScript引擎裡面,程式碼是一行一行被解讀。

總結,JavaScript引擎本身是單執行緒、同步執行,而向外的溝通、請求可以是非同步執行

事件佇列(Event Queue)

前面提到,JavaScript引擎本身是同步執行,但是當JavaScript引擎接收到從外部來的一個需要處理事件時,在JavaScript引擎中會先被放到等待列,也就是先放進事件佇列(Event Queue)。

JavaScript引擎還是會先處理完執行堆處理完,直到b()結束、a()結束、全域結束都從執行堆中pop掉,當執行堆是空的時候,JavaScript才會注意到Event Queue。

首先處理click事件,看到該事件觸發執行clickHandler(),所以就在執行堆中創建一個執行環境給它。當該事件處理完畢,被pop掉之後,執行堆中又被淨空了,才再繼續處理下一個佇列事件。

JavaScript-範圍鏈(scope chain)

外部環境參照

每個執行環境都會有一個外部環境參照,用意是當使用變數時,JavaScript不只會在當前的執行環境中尋找變數,也會到外部環境參眾中尋找變數

1
2
3
4
5
6
7
8
9
function b(){
console.log(myVar);
}
function a(){
var myVar = 2;
b();
}
var myVar = 1;
a();

範圍鍊

當在目前執行環境找不到變數或函式時,JavaScript會往外部環境參照找,若沒有找到,則再外繼續往下一層的外部環境找,一直往範圍鍊下面找,直到最底層的全域環境中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function c(){
console.log(myVAr);//1
}
function a(){
function b(){
console.log(myVAr);//2
}
var myVar = 2;
b();
c();
}

var myVar = 1;
a();

注意!
在範圍鍊中,每個執行環境的外部環境參照與「執行堆的上下位置」沒有關係,而是取決於「程式碼中的物理位置

JavaScript-執行堆

呼叫函數(Invocation)

當我們說到invocation function或是 function invocation,代表是去執行這個函式。

在JavaScript中,使用小括號()做「呼叫函式(invocation)」的動作

1
2
3
4
function a(){
//宣告、定義a函式內容
};
a();//呼叫a函式

執行堆(Execution Stack)

每一次在JavaScript呼叫函式,就會創造出一個新的執行環境,並且堆疊放進執行堆中(後者在上),這個執行環境中會有自己的記憶體空間,用以存取區域變數和函式,並獨立進行其中的區域創建與執行

1
2
3
4
5
6
7
function b(){

}
function a(){
b();
}
a();

上方的實際運作,當在全域creation創建階段時

  1. 會在全域記憶體中創建a()與b()函式

接著在全域execution執行階段時

  1. 讀到第7行時會呼叫a()函式,此時就會創造一個新的執行環境,放到執行堆中
  2. 當進入a()函式中呼叫到b()函式時,則會暫停當前的執行,另外創建一個新的執行環境,放到執行堆中
  3. 當b()函式執行完畢之後,該執行環境就會從之星堆被拿掉(pop)
  4. 接著回到a()函式中,執行完畢後,該執行環境再被拿掉(pop)
  5. 最後回到全域繼續執行完畢

變數環境

每當一個執行環境被創造時,他都會有獨立的記憶體空間,用以存取區域變數和函式。

變數環境指的是,創建變數的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
function b(){
var myVar;
console.log(myVar);//undefined
}
function a(){
var myVar = 2;
console.log(myVar);//2
b();
}
var myVar =1;
console.log(myVar);//1
a();
console.log(myVar);//1

上方的實際運作,當在全域creation創建階段時

  1. 會在全域記憶體中創建a()與b()函式,以及變數myVar初始值為undefined

接著在全域execution執行階段時

  1. 讀到第10行時,會在全域的記憶體中設定變數myVar的值為1
  2. 讀到第12行時會呼叫a()函式,此時就會創造一個新的執行環境,放到執行堆中。在其中的獨立記憶體,創建變數myVar,同樣先給預設值undefined,等到執行到第6行內容,才設定myVar的值為2
  3. 在a()函式中呼叫到b()函式時,則會暫停a()當前的執行,另外創建一個新的執行環境,放到執行堆中
  4. 在b()函式中,又再獨立的記憶體中創建變數myVar,同樣先給預設值undefined
  5. 當b()函式執行完畢之後,該執行環境就會從之星堆被拿掉(pop)
  6. 接著回到a()函式中,執行完畢後,該執行環境再被拿掉(pop)
  7. 最後回到全域繼續執行完畢

JavaScript-undefined vs. not defined

undefined值

undefined是JavaScript中一個特殊的值,用以表示這個變數僅被宣告出來,可是值尚未被設定。

實際的運作上,會在creation創建階段先在記憶體中宣告一個變數a,並且給予undefined值。而進入execution執行階段,因為在呼叫變數a之前,沒有定義a的值,所以印出來的值就是undefined值。

not defined錯誤

如果變數完全沒有被宣告,在creation創建階段並沒有任何變數被宣告出來,進入execution執行階段,當要呼叫變數b時,因為記憶體中完全沒有b這個變數及值,就會出現not defined錯誤。

補充:最好不要宣告值為undefined

undefined通常都是在creation創建階段,系統給予變數的一個預設值。雖然也可以人工宣告變數為undefined,可是會導致日後degub的困難。

© 2020 Leah's Blog All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero