精讀JavaScript模式(三)
一、前言
上個月底,爸爸因為事故突然離世,說心裡話,現在看到'去世','爸爸'這樣的字眼,眼淚都會忍不住在眼眶打轉,還是需要時間治癒。最近也只是零碎的看了下東西,始終沉不下心去讀書,直到今天還是決定撿起之前看的JS模式。
前面兩篇部落格大概記錄了書中前兩章節我覺得一些好用的知識,從這篇開始就是第三章--直接量和構造函數了,難度也不算大,最近下班了在公司花點時間慢慢寫。
從第三篇開始,我想在介紹每個知識點前,先以概要的形式將這一部分介紹的東西以問題的形式列出來,方便大家帶著問題去讀,讀完了再回頭看問題,看自己能不能答出來,這樣也方便檢驗自己對於知識的掌握情況。
二、物件直接量
概要:什麼是物件,什麼是物件直接量寫法,空物件不是真正的空物件
我們可以將js中的物件簡單理解為名值對組成的散列表,其中值都是名的屬性,值可以是原始值(string,number...),也可以是物件,而當物件是函式時,我們一般稱之為方法。
js中自定義的物件任何時候都是可變的,內建本地物件的屬性也是可以修改的,比如你可以先建立一個空物件,然後在給它新增一些屬性或方法。而在建立物件時,物件直接量寫法是較為理想的方式。
var me = {}; me.name = "echo"; me.getName = function () { return me.name; };
你可以刪除物件的某個屬性或方法
delete me.name;
其實我們建立一個物件也沒必要像上面先建立空物件,再一步步新增屬性方式,在物件建立時可以同時把你需要定義的屬性同時新增好。
let me = { name = "echo", getName = function () { return this.name; } };
那麼這種直接用 = 建立物件的方式就是物件直接量的寫法,很直接不是嗎。物件直接量語法包括:
• 將物件主體包含在一對花括號內({ and })。
• 物件內的屬性或方法之間使用逗號分隔。最後一個名值對後也可以有逗號,但 在IE下會報錯,所以儘量不要在最後一個屬 性或方法後加逗 號。
• 屬性名和值之間使用冒號分隔
• 如果將物件賦值給一個變數,不要忘了在右括號}之後補上分號
我在上方提到的空物件可以說是算是一種簡稱,它們並不是真正的空物件,即便申明一個{},它也會從Object.prototype繼承很多屬性和方法,但是我們其實有方法建立嚴格意義上的空物件,我在上一篇文章中就列出了兩種方法,有興趣可以去看看。
三、通過建構函式建立物件
概要:為什麼推薦物件直接量寫法而不是建構函式寫法
儘管JS中沒有類的概念,但當你想快速建立多個具有共同特徵的例項時還是可以使用建構函式,JS中內建了不少建構函式,例如Object(),Date(),String()等等。
我們用建構函式的寫法來創造上面的物件:
var me = new Object(); me.name = "echo"; me.getName = function () { return me.name; }
相比之下,物件直接量的寫法與建構函式寫法相比程式碼更少,推薦直接量寫法的還有兩個原因
一是它可以強調物件是一個簡單的可變的散列表,而不必一定派生自某個類。
二是當你使用Object()建立物件時,解析器需要順著作用域鏈開始查詢,直到找到Object建構函式為止,而直接量的寫法是不會存在作用域解析行為。
四、自定義建構函式
概要:當你new一個建構函式時發生了什麼?
除了物件直接量和內建建構函式之外,我們還可以通過自定義的建構函式來建立例項物件,像這樣。
var Person = function () { this.name = "echo"; this.sayName = function () { console.log('my name is '+ this.name); }; } var me = new Person(); me.sayName();//my name is echo
說個小插曲,這裡我自定義的建構函式名Person的字母P其實可以小寫,但我們都知道,內建建構函式都是大寫開頭,所以為了讓建構函式更為醒目,推薦首字母大寫!
很奇怪對不對,我們new一個建構函式得到一個例項,這個例項就繼承了建構函式的屬性方法,那new這個過程中到底發生了什麼?
1.建立一個空物件,將它的引用賦給this,繼承函式的原型。
2.通過this將屬性和方法新增至這個物件。
3.最後返回this指向的新物件。
我們用程式碼模擬這三句話,就像這樣:
var Person = function () { // var this = {}; this.name = "echo"; this.sayName = function () { console.log('my name is '+ this.name); }; // return this; 這裡隱性返回的其實就是上面建立的空物件,這個空物件被賦予了name屬性和一個sayName方法 } var me = new Person(); me.sayName();//my name is echo
在這段程式碼中,sayName()方法被新增到了this中,但有個問題,不管我們執行幾次new Person(),sayName()方法會反覆的被新增到this中,且每次sayName()方法都會在記憶體中新開記憶體。
當我們所有例項中的sayName()方法都是一模一樣時,這種做法是很浪費記憶體的,推薦做法是將sayName()方法新增在Person的原型中。
var Person = function () { this.name = "echo"; }; Person.prototype.sayName = function () { console.log('my name is '+ this.name); }; var me = new Person(); me.sayName();//my name is echo
關於new一個建構函式到底發生了什麼,我在前面說會隱性的新建一個空物件賦予this,還是那句話,這裡的空物件並不是嚴格意義上的空,它還是繼承了Person的原型,準確來說應該是這樣。
// var this = Object.create(Person.prototype);
五、建構函式的返回值
概要:建構函式能返回什麼?預設返回什麼?
當我們new一個建構函式總是會返回一個物件,預設返回this所指向的物件。如果我們沒有在建構函式內為this賦予任何屬性,則會返回一個集成了建構函式原型,沒有自己屬性的'空物件'。(如果讀不懂這句話,請結合new發生的過程去理解)
儘管我們沒在建構函式內寫return語句,也會隱性的返回this,但其實我們可以返回自定義的物件。像這樣:
var Person = function () { this.name = "echo"; var that = {}; that.name = "wl"; return that; }; var me = new Person(); me.name;//wl
建構函式可以返回任意物件,只要你返回的是個物件。假設你返回的不是物件,程式也不會報錯,但這個返回值會被忽略,最終還是隱性的返回this所指向的物件。
var Person = function () { this.name = "echo"; var name = "wl"; return name; }; var me = new Person(); me.name; //echo
六、強制使用new 的模式
概要:不使用new呼叫建構函式會怎樣?建構函式內能自定義物件嗎?不使用new也能繼承建構函式原型的做法
建構函式與普通函式無異,只是呼叫需要使用new,當我們不使用new呼叫時,語法也不會出錯,但函式中的this會指向全域性物件(非嚴格模式是window)。
var Person = function () { this.name = "echo"; }; var me = Person(); window.name//echo
在這段程式碼中實際上建立了一個全域性物件屬性name,你可以通過window.name訪問到它。那麼說到這裡對於建構函式我們強調兩點。
1.建構函式名首字母大寫
2.呼叫建構函式使用new
遵守這些約定肯定是好的,但在實際開發的建構函式中,我們常常看看使用that等其它字面量代替this的做法。這麼做的目的是為了確保建構函式按照自己定義的方式執行,而不存建立空物件賦予this等隱性不可見的行為,更可預測。
var Person = function () { var that = {}; that.name = "echo"; return that; }; var me = new Person(); me.name;//echo
對於上述程式碼中,我們使用that代替了this,使用that只是一種命名約定,你可以使用self,me甚至任意非js語言保留字的欄位。
或者that都不建立,直接返回一個物件。
var Person = function () { return { name : "echo" }; }; var me = new Person(); var you = Person(); me.name//echo you.name//echo
這種寫法不管我們是否使用new去呼叫,都能得到一個例項,但這種模式丟失了原型,所有的例項都不會繼承Person()原型上的屬性。
var Person = function () { return { name:'echo' } }; Person.prototype.sayName = function () { console.log(1); }; me.sayName();//報錯,自定義物件未指向Person,沒繼承Person的方法 you.sayName();//報錯,同上
我們在前面說,不使用new時,this指向window(非嚴格模式),無法繼承Person的任何屬性。
var Person = function () { this.name = "echo" }; Person.prototype.sayName = function () { console.log(1); }; var me = new Person(); var you = Person(); me.name;//echo you.name;//報錯,此時的name是window的屬性 me.sayName();//1 you.sayName();//報錯,在例項化過程中this指向window,並未繼承Person的方法
那有辦法可以讓不使用new情況下例項也能繼承Person屬性的做法嗎,當然有,比如呼叫自身的建構函式:
var Person = function () { if(!(this instanceof Person)){ return new Person(); } this.name = "echo" }; Person.prototype.sayName = function () { console.log(1); }; var me = new Person(); var you = Person(); me.name;//echo you.name;//echo me.sayName();//1 you.sayName();//1
看到沒,沒使用new的例項you也繼承了Person的屬性和方法,如果看不懂那應該是對於instanceof運算子不太瞭解,這裡順帶說下
instanceof運算子用於測試建構函式的prototype屬性是否出現在物件的原型鏈中的任何位置---MDN
object instanceof constructor object:要檢測的物件. constructor:某個建構函式
function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } var auto = new Car('Honda', 'Accord', 1998); console.log(auto instanceof Car);//true console.log(auto instanceof Object);//true
那麼上述程式碼中,new Person()好理解,this就是隱性返回的例項,this instanceof Person為true跳過判斷,直接走new一個建構函式時發生的過程,得到例項自然會繼承Person的屬性和方法。
Person()呢,this指向window,很明顯this instanceof Person為false,假假為真,執行判斷內的程式碼new Person();同理,也走了new過程的三部曲,得到的例項也繼承了Person的屬性和方法。
有疑問或者錯誤也歡迎大家指出來。
最近總是覺得沒什麼值得開心的事情,趁著雙十二,給自己換了個鍵盤,畢竟每天都是要寫程式碼的,也算換一個心情。
儘管除了吃飯交房租就沒什麼開銷,還是挺心疼的。
那麼就先寫到這裡了。