顯示廣告
隱藏 ✕
看板 KnucklesNote
作者 Knuckles (站長 那克斯)
標題 [JS] 克服JS的奇怪部分 ch4 物件與函數(上)
時間 2016-11-23 Wed. 10:19:58


Udemy課程: JavaScript全攻略:克服JS的奇怪部分
https://www.udemy.com/javascriptjs/learn/v4/overview
上完第四章的心得筆記

章節4 物件與函數(上)

30. 物件與「點」

在其他的程式中,物件與函數是不一樣的東西
但在JS中,物件與函數非常的相關,很多情況下幾乎是一樣的

物件是多個名稱/值的組合,物件中也可以包含物件與函數
物件裡的成員變數又稱為物件的屬性(Property)
物件裡的成員函數又稱為物件的方法(Method)

例如建立一個物件 person
加入兩個屬性 firstname、lastname
var person = new Object();

person["firstname"] = "Knuckles";
person["lastname"] = "Huang";

console.log(person);
console.log(person["firstname"] + person["lastname"]);
執行結果:
[圖]



屬性除了使用中括弧[]來存取外,也可以使用點.來存取,例如
person.firstname = "Knuckles";
person.lastname = "Huang";

console.log(person.firstname + person.lastname); // 顯示 KnucklesHuang

除非要使用變數來當作屬性名稱,否則的話使用點運算子來存取屬性就好了

物件中再加上物件
person.address = new Object();
person.address.street = "111 Main St.";// 可以用兩個點運算子存取第二層物件的屬性
person["address"]["city"] = "Taipei"; //也可用兩個[]來存取
[圖]



31. 物件與物件實體(object literal)

上一節使用 new Object() 建立物件的方法其實不是很好
另一種使用物件實體的語法為
var person = {}; 
使用大括弧{}的結果與使用 new Object() 的結果相同

使用大括弧的方法還可以直接將屬性的名稱/值配對寫進去
var person = { firstname:"Knuckles", lastname:"Huang" };
這樣結果與上一節使用點運算子或中括弧[]運算子加入屬性的結果是一樣的

也可以在物件中加入物件,例如
var person = { 
	
firstname:"Knuckles", 
	
lastname:"Huang",
	
address: {
	
	
street: "111 Main St.",
	
	
city: "Taipei"
	
}
};

當有一個函數需要輸入一個物件時,也可以直接傳入物件實體,例如
var knuckles = { // <- 物件名稱改成人名
	
firstname:"Knuckles", 
	
lastname:"Huang",
	
address: {
	
	
street: "111 Main St.",
	
	
city: "Taipei"
	
}
};

//建立一個函數 greet,輸入值為一個物件,用來顯示輸入物件的屬性 firstname
function greet(person){
	
console.log('Hi ' + person.firstname);
}

//執行函數,將建立好的物件實體傳入
greet(knuckles); //顯示 Hi Knuckles

//執行函數時,直接將物件實體寫在輸入值
greet({ firstname: 'Mary', lastname: 'Doe' }); //顯示 Hi Mary


32. 框架小叮嚀:偽裝命名空間

命名空間(Namespace): 一群變數與函數的容器,用來將相同名稱的變數或函數分開

JS沒有命名空間的功能,但可以用物件的特性假裝出來

例如有兩種語言的Hello!,都想要使用greet做變數名稱
var greet = 'Hello!';
var greet = '哈囉!';

console.log(greet); // 顯示 哈囉!
這樣第一個英文的Hello!會被蓋掉

為了區分兩種語言的 greet,可以改為
var english = { greet: 'Hello!' };
var chinese = { greet: '哈囉!' };

console.log(english.greet); // 顯示 Hello!
建立兩個以語言為名稱的物件,將各自的 greet 變數放進去
就可以達到命名空間的效果了


33. JSON 與物件實體

JSON(JavaScript Object Notation): 受JS的物件實體語法啟發而產生的一種資料格式
雖然看起來很像,但有一點地方不一樣

例如一個物件實體為
var objectLiteral = { 
	
firstname: "Knuckles", 
	
isMember: true
}
他的JSON格式為
{
	
"firstname": "Knuckles",
	
"isMember": true
}
也就是在JSON中,成員的名稱必需加上引號變成字串的型式
而在物件實體中,成員的名稱可以加上引號,也可以不用加

JS有內建函式可以很容易的轉換兩種資料
var objectLiteral = { 
	
firstname: "Knuckles", 
	
isMember: true
}
// 將JS物件轉為JSON字串
var jsonString = JSON.stringify(objectLiteral);
console.log(jsonString);

//將JSON字串轉為JS物件
var jsObject = JSON.parse(jsonString);
console.log(jsObject);
執行結果
[圖]



34. 函數就是物件

JS的函數為一級函數

一級函數(First Class Functions): 可以對一般變數做的事,也可以對一級函數做
也就是說,可以指派一個變數為函數,可以把函數當輸入值傳入另一個函數
可以使用實體語法立刻建立函數

JS的函數就是物件

函數物件是一個特殊的物件,有自己的屬性和方法
他的成員可以包含各種變數、其他物件、其他函數

函數物件有一些特殊的屬性
函數名稱: 可以沒有名稱,沒名稱的函數稱為匿名函數
程式內容: 在函數裡寫的程式也會成為函數物件的一個屬性
          使用小括弧()可呼叫此屬性(Invocable)

舉個例子
function greet(){
	
console.log('hi');
}

//可以為函數加上屬性,因為函數就是物件
greet.language = 'english';
console.log(greet.language); //顯示 english
在這個函數物件中,特殊的屬性有
函數名稱: greet
程式內容: console.log('hi');


35. 函數陳述句與函數表示式

陳述句(Statement): 程式碼的單位,不會形成一個值
表示式(Expression): 程式碼的單位,會形成一個值,可以用指派給變數存起來

舉例來說
a = 3;  // 回傳 3,是一個表示式
1 + 2;  // 回傳 3,是一個表示式
a = { greeting: 'hi' } // 回傳一個物件,是一個表示式

if(a === 3){ } // a===3 會回傳布林值,但用if()包起來後沒回傳值了,是一個陳述句

函數的定義有分為陳述句與表示式兩種方法
// 函數陳述句,沒有回傳值
function greet(){
	
console.log('hi');
}
greet(); // 執行這個函數,顯示 hi

// 函數表示式,有回傳值
var anonymousGreet = function(){
	
console.log('hi');
}
anonymousGreet(); // 執行這個函數,顯示 hi

函數表示式,就是使用匿名函數的方法 function(){ ... }
建立並回傳一個函數物件,然後存到變數 anonymousGreet

函數表示式的提升(Hoistiog)

若是將函數表示式的執行移到建立函數前面
anonymousGreet(); // 顯示錯誤: undefined is not a function

var anonymousGreet = function(){
	
console.log('hi');
}
使用函數表示式時,因為是用 = 將函數物件指派給一個變數
程式內容不會一開始就載入記憶體
只有變數 anonymousGreet 會先載入記憶體,但沒有賦值,所以是 undefined
此時直接使用 anonymousGreet(); 就會出現 undefined is not a function

函數表示式可以用來當其他函式的輸入值

function log(a){
	
console.log(a);
}

log(function(){
	
console.log('hi');
});
建立一個一般的函數 log(),用來將輸入值顯示出來

執行log(),將匿名函數 function(){ console.log('hi'); }
當做輸入值傳入log()
log() 使用變數 a 儲存這個輸入的函數
並使用 console.log(a); 顯示出來

執行結果,就是顯示輸入的匿名函數
[圖]


也可以在函數 log() 中執行輸入的函數
function log(a){
	
a();
}

log(function(){
	
console.log('hi');
});
將匿名函數傳給 log() 後,log()使用變數 a 儲存
變數 a 即變為一個函數物件,加上小括號()即可執行這個函數物件

執行結果為顯示 hi


36. 傳值和傳參考

傳值(by value): 若變數是一個純值,使用 = 指派給另一變數時,
會將該值複製一份存到另一個變數的的記憶體位置

傳參考(by reference): 若變數是一個物件,使用 = 指派給另一個變數時,
不會複製該物件,而是讓兩個變數共用相同的記憶體位置,
使得同一個物件,有兩個名稱,像是幫物件取了一個別名

// by value (純值)
var a = 3;
var b = a; // 將 a 的值 3 指派給 b

a = 2; // 改變 a 的值為 2

console.log(a); // a 顯示為 2
console.log(b); // b 顯示為 3 ,b的值不會因為改變a而跟著變

// by reference (所有的物件(包含函數物件))
var c = { greeting: 'hi' };
var d = c; // 將 c 的物件指派給 d

c.greeting = 'hello'; // 改變 c 物件的屬性值

console.log(c); // c 顯示為 Object {greeting: "hello"}
console.log(d); // d 顯示為 Object {greeting: "hello"}
                // d 物件隨著 c 物件一起變了,因為就是同一個物件

// by reference (物件傳給函數的輸入值時也一樣)
function changeGreeting(obj){  //建立一個函數
	
obj.greeting = 'Hola'; //將傳入物件的屬性值改為'Hola'
}

changeGreeting(d); // 傳入 d 物件給這個函數
console.log(c); // c 顯示為 Object {greeting: "Hola"}
console.log(d); // d 顯示為 Object {greeting: "Hola"}
                // 在函數裡將物件obj的屬性改變後,c跟d也變了

// 使用 = 指派物件產生了新的記憶體位置
c = { greeting: 'howdy' };
console.log(c); // c 顯示為 Object {greeting: "howdy"}
console.log(d); // d 顯示為 Object {greeting: "Hola"}
                // 使用物件實體語法建立新的物件給c後,
                // c和d不再是同一個物件了

在其他程式語言有語法可以決定要傳值還是傳參考
但在JS中沒有選擇,純值就是使用傳值,物件就是使用傳參考


37. 物件、函數與「this」

JS在建立執行環境時,會自動產生一個變數 this
用來指向目前所在的物件

在全域環境時,this 就是指向預設的全域物件 window

若是在函數建立的執行環境下,this是指向什麼呢
function a(){
	
console.log(this);
}

var b = function(){
	
console.log(this);
}

a(); // 顯示 window
b(); // 顯示 window
可知不管是用函數陳述句或函數表示式,
裡面的 this 還是指向全域環境的 window 物件

若是在物件的方法(Method)中呢?
var c = {
	
name: 'The c object',
	
log: function(){
	
	
console.log(this); // 這個 this 是什麼呢?
	
}
}

c.log(); // 顯示 Object {name: "The c object"}
建立一個物件 c,使用函數表示式在物件裡新增一個函數 log
物件中的函數就是物件的方法(Method),又叫成員函數
使用 c.log(); 執行這個成員函數,
可知在物件 c 的成員函數中的 this,是指向物件 c


在JS有一個常令人感到困惑,有些人認為是bug的地方
若是在物件的成員函數中,建立一個函數
那麼在這個成員函數中的函數裡,this指向什麼呢?
var c = {
	
name: 'The c object',
	
log: function(){
	
	
var log2 = function(){
	
	
	
console.log(this); // 這個this是什麼呢?
	
	
}
	
	
log2();
	
}
}

c.log(); // 顯示 window
在物件 c 的成員函數 log 中,建立一個函數 log2 並執行
可發現在 log2 中的 this,預期應該要指向物件 c
結果竟然是指向全域的 window

這是JS一個奇怪的地方,有個常用的模式用來解決這個問題
var c = {
	
name: 'The c object',
	
log: function(){
	
	
var self = this; //建立一個變數 self 把這邊的 this 存起來
	
	
var log2 = function(){
	
	
	
console.log(self); // 這個self是什麼呢?
	
	
}
	
	
log2();
	
}
}

c.log(); // 顯示 Object {name: "The c object"}
在成員函數 log() 中,一開始就先用個變數 self 把 this 存起來
因為這邊的 this 可確定是指向物件 c
使用 var self = this; 後 self 即代表物件 c

接著在函數 log2 中顯示 self,因為 log2 的執行環境沒有變數 self
他會到外部環境尋找 self,依函數 log2 定義的地方,外部環境為 log
找到 log 裡的變數 self,而 log 裡的 self 代表物件 c
所以顯示 self 為物件 c





--
※ 作者: Knuckles 時間: 2016-11-23 10:19:58
※ 編輯: Knuckles 時間: 2016-11-25 03:45:41
※ 看板: KnucklesNote 文章推薦值: 1 目前人氣: 0 累積人氣: 1791 
分享網址: 複製 已複製
( ̄︶ ̄)b gotop_hsu 說讚!
r)回覆 e)編輯 d)刪除 M)收藏 ^x)轉錄 同主題: =)首篇 [)上篇 ])下篇