[課程筆記]克服JS的奇怪部分-CH3 物件與函數(上)

Udemy課程連結
課程總目錄

本篇筆記目錄:

U13 物件與「點」
U14 物件與物件實體
框架小叮嚀:偽裝命名空間
U15 JSON 與物件實體
U16 函數就是物件
U17 函數陳述句與函數表示式
觀念小叮嚀:傳值和傳參考
U18 物件、函數與「this」
觀念小叮嚀:陣列——任何東西的集合
U19 'arguments' 與 spread
框架小叮嚀:重載函數
危險小叮嚀:自動插入分號
框架小叮嚀:空格

U13 物件與「點」

在 JS 中物件和函數是非常相關的

物件和點

物件裡面可以放(在物件中叫做):

  1. 純值-property(屬性)
  2. 物件-property(屬性)
  3. 函數-method(方法)

在記憶體中,核心物件會有一個記憶體的位址,然後可以參考到這些電腦記憶體中的屬性和方法的位址,屬性和方法的位址可能有關,也可能無關,但無論如何,物件能夠參考到這些位址空間,找到這些屬性、方法的存在。

有兩種取用成員的方法:

  1. []是計算取用成員的運算子,會去找物件裡面的屬性名字
var person =new Object();


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

var firstNameProperty="firstname"

console.log(person[firstNameProperty]);

//"Tony"

  1. .也是計算取用成員的運算子,會去找物件裡面的屬性名字
var person =new Object();

//
person["firstName"]="Tony";
person["lastName"]="Alicea";


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

實際運作的方法 person.’firstName’,但不要這樣打,因為語法解析器會幫我們判斷!

var person =new Object();
person.address=new Object();//物件中的物件
person.address.street="111 Main St." //用點運算子增加屬性給子物件

點運算子是左相依性的,會先看 person 這個物件,然後找屬性和方法的名字,在記憶體中找到person.address,下一步會再person.address 這個物件中,找到它的屬性或方法叫street

點、中括號都只是函數,一個取出資訊的方式

雖然是兩個不同的函數,不過做的事情一模一樣!

console.log(person.address.street)
console.log(person["address"]["street"])

但最好還是使用點運算子,它很簡潔、清楚,也很容易除錯!


U14 物件與物件實體

Object Literal syntax 物件實體語法

前面使用 new Object() 來建立物件,但有個捷徑叫做物件實體語法,要注意這不是運算子,這是當 JS 在解析語法時,看到大括號,而且不是當作 if 條件是或迴圈時,他會假設你在創造一個物件。

範例:

var person={
	firstName:'Tony',
	lastName:'Alicea',
	address:{
		street:'111 Main St.',
		city:'New York',
		state:'NY'
	}
}
console.log(person)

這還能夠更強大:
1.我們可以在任何地方建立物件

function greet(person){
	console.log('Hi'+person.firstName);
}

greet({
	firstName:'Mary',
	lastName:'Doe'}
	);

2.混合運用:用點運算法新增一個屬性,再用物件實體語法定義物件

Tony.adddress2={
	street:'333 Second St'
}

為什麼可以這樣運作?因為我們寫的程式碼,不是直接運作,是被轉換過的,對於JS 底層來說,使用點運算法或物件實體語法來建立物件都是一樣的。


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

Namespace 命名空間

在現代的程式語言中,命名空間是變數和函數的容器,通常這是用來維持變數和函數的名稱分開。

想像這兩個變數是被創造在兩個不同的JS檔案中,一個是英語打招呼,另一個西班牙

var greet ='Hello';//eng.js
var greet ='Hola';//span.js

console.log(greet);
//Hola

命名空間可以解決覆蓋的問題,因為命名空間可以分別有兩個容器裝著問候語,雖然 JS 中沒有命名空間,但我們可以用物件來做到。

我們可以藉由建立一個物件當作容器,來避免這樣的衝突:

var english={}
var spanish={}

english.greet='Hello'
spanish.greet='Hola'

這兩個變數,雖然都叫做 greet,但它們不會互相衝突,不會互相覆寫
我們可以在很多的框架都看到這樣的用法。

更深入的用法:把不同的容器物件分級

例如,我們想要 english 有打招呼的方法的命名空間在裡面,而裡面有不同的問候語

english.greetings.greet='Hello!';
//uncaught type error:cannot set property of undefined

為什麼會有錯誤呢?因為最左邊的會先執行,當english.greetings.這裡的點運算子被呼叫時,因為這是空的,所以他找不到,會傳入 undefined 給右邊的點運算子。

我們需要寫成這樣:

english.greetings={}
english.greetings.greet='Hello!';

或我們可以用物件實體語法初始化它

var english={
	greetings:{
		basic:'Hello'
	}
};

U15 JSON 與物件實體

JSON(Javascript Object Notation)

JSON 是被 JS 的物件實體語法啟發的,雖然很像物件實體語法,但有個常見錯誤是認為它們是同一個東西,然後遇到錯誤。

物件實體語法:

var objectLiteral={
	firstName='Mary',
	isAProgrammer:true,
}

幾年前,網路資料的傳輸格式很多,像是XML,但太多不必要的符號,浪費很多下載頻寬,所以大家使用JSON格式,如下:

{
	"firstName"='Mary',
	"isAProgrammer":true,
}

JSON 和 物件實體語法 的差異

屬性需要被包在引號裡面

技術上來說,JSON 是物件實體語法的子集合,所以 JSON 並不是 JS 的一部份,但因為他很受歡迎,JS 有一些內建的功能可以轉換這兩者:

物件轉 JSON 格式:.stringfy

var objectLiteral={
	firstName='Mary',
	isAProgrammer:true,
}

console.log(JSON.stringify(objectLiteral));

JSON 格式轉物件:.parse

var jsonValue=JSON.parse{'{"firstname":"Mary","isAProgranner":"true"}')


U16 函數就是物件

JavaScript的函數就是物件

一級函數(first class functions)

我們可以對別的型別,如、物件、字串、數值、布林做的事都可以對函數做

函數物件是一個特殊型態的物件,它有所有物件的特色還有一些其他屬性,所以我們可以將物件連結到純值到名稱/值配對、物件、其他函數,其他屬性中有兩個最重要的:

1.Name 名稱

JavaScript的函數不一定要有名字,但它可以有名字

2.code property 程式屬性

我們寫的程式就並非就是函數本身,只是其中一種屬性,這個屬性特別的是,它是可以呼叫的(Invocable),代表你可以執行這個程式碼。

function greet(){
	console.log('hi')
}

greet.lauguage='english'

console.log(greet)

//function greet(){
//	console.log('hi');
//}

console.log(greet.language)
//'english'

我們新增了一個函數的屬性,在其他程式語言中,這是不可能的,但在 JavaScript,函數就是物件,當這個 greet()被創造,這個函數物件會被放進記憶體,它的名稱是greet,然後有程式屬性,包含了我寫的程式碼,所以如果我用括號呼叫greet,這會呼叫函數,讓它執行,讓執行環境被創造。


U17 函數陳述句與函數表示式

expression 表示式

程式碼的單位,會形成一個值

var a;

//DevTools
a=3
//3 等號運算子會回傳一個值

1+2;
//3 加號運算子會回傳一個值

陳述句

不會會傳值

if (a===3){
	console.log('Hi')
}

1. 函數陳述句

當它被執行時,它不會回傳值

function greet(){
	console.log('Hi')
}

greet();

2. 函數表示式

var anonymousGreet=function(){
	console.log('hi')
}

anonymousGreet();

函數表示式可以馬上創造物件

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

log({greeting:'hi'});

代表我們也可以傳入函數(一級函數的觀念)

function log(a){
	a()
}

log(function(){
	console.log('Hi')
});

觀念小叮嚀:傳值和傳參考

By value 傳值

var a=3;
var b;
b=a;
  1. 變數 a 得到純值的記憶體位置,
  2. 執行 b=a 時,b 新變數指向一個新位址,
  3. 純值的拷貝被放到新的記憶體的位址

兩個變數互不影響例子例子:

var a=3;
var b;
b=a
a=2;

console.log(a)
console.log(b)

//2
//3
//a 不會影響b

By reference 傳參考

所有物件都是傳參考 (by reference)特性

就像一個物件擁有不同別名一樣

var a={gretting:'hi'};
var b
b=a;

兩個變數互相影響例子:

var c={greeting:'hi'};
var d;
d=c;
c.greeting='hello';//mutate(改變)

就算是參數,物件也經由參考傳入:

var c={greeting:'hi'};
var d;
d=c;
c.greeting='hello'
function changeGreeting(obj){
	obj.greeting='Hola';
}
changeGreeting(d);
console.log(c);
console.log(d);

//Object{greeting:"Hola"}
//Object{greeting:"Hola"}

我們可以使用等號運算子設定一個新的記憶體空間:

var c={greeting:'hi'};
var d;
d=c;
c.greeting='hello'

function changeGreeting(obj){
	obj.greeting='Hola';
}

changeGreeting(d);
c={greeting:'howday'};//設定一個新的記憶體空間給c,

console.log(c);
console.log(d);//d持續指向舊的記憶體
//greeting:'howday'
//greeting:'Hola'

U18 物件、函數與「this」

我們知道當函數被呼叫時,會創造執行環境,並放進執行堆。

this

this 會指向不同的物件,依據函數如何被呼叫的

在瀏覽器內的全域物件是 window物件:

console.log(this)
//window物件

其他情況:

1.函式

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

a();//window 物件

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

a()
b()
//window 物件
//window 物件

我們可以發現,不論用函數表示式或函數陳述句,this 都會指向全域物件。

2.物件方法

var c={
	name:'The c object',
	log:function(){
		this.name='Updated c object'
		console.log(this)
	}
}
c.log();

//Object{name:"Updated c object",Log:function}

當函數是連結到物件的方法時,this 指向包含他的物件。

以下是許多人都會認為這是一個 bug:

var c={
	name:'The c object',
	log:function(){
		this.name='Updated c object';
		console.log(this)
		
		var setname=function(newname){
			//這裡的名稱屬性被等號運算子新增到全域物件
			this.name=newname;
		}
		setname('Updated again!The c object');
		console.log(this);
	}
}

//Object{name:"Updated c object",Log:function}
//Object{name:"Updated c object",Log:function}

this.name這裡的名稱屬性被等號運算子新增到全域物件,表示當這個裡面的函數(function(newname)….),它的執行環境被創造時,this指向全域物件。

很多人認為他錯了,但在這個情況下JavaScript就是這樣運作,遇到這個情形時能夠怎麼辦?我們可以設定變數:

var c={
	name:'The c object',
	log:function(){
		var self=this;//self 指向整個物件

		self.name='Updated c object';
		console.log(self)
		
		var setname=function(newname){
			
			self.name=newname;
		}
		setname('Updated again!The c object');
		console.log(self);
	}
}

//Object{name:"Updated c object",Log:function}
//Object{name:"Updated again!The c object",Log:function}

我們知道物件是用傳參考設定,所以self會指向和this一樣的記憶體位置,當我們要使用this的時候,我們改用self ,雖然 self 沒有在函數裡被宣告,但 JS 會往範圍鏈裡面找,到函數的外部環境繼續尋找一個叫做 self 的變數。

let 也可以清除一些這樣的問題~~


觀念小叮嚀:陣列——任何東西的集合

在大部分的程式語言,陣列可以包含一連串相同型別的東西,但 JS 是動態型別,所以陣列裡面可以放不同型別的東西

var arr=[
1,
false,
{
	name:'Tony'
},
function(name){
	var greeting="Hello";
	console.log(greeting+name);
},
'hello'
]

console.log(arr);
arr[3](arr[2].name);

//Hello Tony 

U19 ‘arguments’ 與 spread

arguments 參數

包含了所有傳入到所呼叫的函數的值

一個執行環境被創造時,JS 會幫我們設定一點東西(如:變數環境、this、外部環境),另外還有參數(argument),它包含了所有傳入到所呼叫函數的值。

function greet(firstname,lastname,language){
	console.log(firstname);
	console.log(lastname);
	console.log(language)
	console.log(arguments)
}

greet('John')
//'John'
//undefined
//undefined
//[John]

argument不是陣列,是像陣列的(array-like),因為它只有一部分的陣列功能,我們可以用argument.length來檢查是否有參數。

Spread

Spread 是 ES6新加入的功能,如果我有要傳入函數的參數,我可以用…增加一個參數

function greet(firstname,lastname,language,...other){
	console.log(firstname);
	console.log(lastname);
	console.log(language)
	console.log(other)
}
greet('john','doe','11 main st','new york')
//

… 代表任何別的東西,然後包進名稱為other的陣列裡面


框架小叮嚀:重載函數

重載函數 function overloading

同一個函數,能夠有不同數量的參數

JavaScript 沒有重載函數,因為函數就是物件,所以沒有這個處理函數的功能,如果想要函數有兩種不同的版本,我們可以這麼使用:

function greet(firstname,lastname,language){
	console.log(firstname);
	console.log(lastname);
	console.log(language)
}
function greetEnlgish(firstname,lastname){
	greet(firstname,lastname,'en')
}

function greetSpanish(firstname,lastname){
	greet(firstname,lastname,'es')
}

greetEnglish("John","Doe");
greetSpanish("John","Doe");

危險小叮嚀:自動插入分號

為什麼 JS 中分號不是必要的?因為 JavaScript 幫我們自動插入分號

一般來講語法解析器在解析語法是一個字一個字去檢查,舉例來講 r-e-t-u-r-n,解析完畢後當我們按下換行(carriage return),它是個看不見的字元,當語法解析器讀到時,就會自懂幫我們補上分號,所以我們必須理解到一件事情,並不是它並不需要分號而是 JavaScript 幫我們補上了,所以我們永遠要記得打出分號!!!

尤其在回傳時,自動插入分號會導致很大的問題:

function getPerson(){

	return
	{
		firstname:'Tony'
	}
}

console.log(getPerson)
//undefined

為什麼顯示undefined?
因為關鍵字return 後面發現 carrige return,所以他會補上分號變成 return;

不過這樣是可以的:

function getPerson()
{
  ...
}

但為了避免一些奇怪的問題,我們還是會將 { 放在函數名稱的同一行。


框架小叮嚀:空格

JS 對於空格是非常自由,所以我們可以放一些註解

var 
	//firstname of the person
	firstname,

	//last name of the person
	lastname,

	//the language
	language;
var person={
	//註解
	firstname:'John',

	lastname:'Doe'
}
console.log(person)

//Object...

盡可能的讓你的程式碼易懂囉~~