標籤:inf xtend ati 產生 deepcopy 另一個 運算 attr 記憶體
今天學習的內容是JS物件導向編程,以下總結的所有內容都來自於阮一峰大大的部落格,感興趣的小朋友可以戳原連結
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html
真,通俗易懂,同時上一次面試的第一道題(JS繼承)也在我的腦海中逐漸立體起來。
JS,一門沒有Class類的物件導向編程(OOP)語言,我的第一個問題是如何產生執行個體化對象?
假設我們有一個對象Cat,現在需要執行個體出一隻具體的“貓”,很容易寫出下面的代碼:
1 var cat1 = {};2 cat1.name = "Tom";3 cat1.color = "black";
定義對象並給對象賦屬性的這種寫法就可以看作是 最原始的產生Cat執行個體對象 的方法。我們當然不會傻到需要多少個執行個體,就寫多少這樣類似的代碼,於是我們可以繼續寫成這樣:
1 function Cat(name, color){2 return {3 name: name,4 color: color5 }6 }7 var cat1 = Cat("Tom", "black");8 var cat2 = Cat("Lucy", "white");
這顯然是 調用函數 的寫法,但是這樣不行,看不出兩個執行個體對象的聯絡??(什麼意思啊喂)
然後順其自然的進入到 建構函式 的寫法(補充一下對建構函式的定義:普通函數中使用this,對建構函式new一下,就可以產生執行個體對象啦~)
1 function Cat(name, color){2 this.name = name;3 this.color = color;4 }5 var cat1 = new Cat("Tom", "black");6 var cat2 = new Cat("Lucy", "white");
說實話這種寫法跟上面調用函數的有點像呢,但是建構函式有屬性加持啊,這下可以通過“constructor”屬性以及“instanceof”運算子查看兩個執行個體對象的關係了,雖然我現在依然不知道幹嘛要這樣。
每一個執行個體化對象的constructor屬性都是指向原型對象的!!! // cat1.constructor == Cat => true
instanceof 同理咯 // cat1 instanceof Cat == true
但是建構函式裡有恒定的屬性或者方法會怎麼樣呢?通過每一次執行個體化建構函式,都會產出一個相同的屬性或方法。假設程式中需要對原型對象多次執行個體化,這樣就會產生佔用記憶體的弊端,於是下一個方法又要出場了!!
prototype屬性
1 function Cat(name, color){2 this.name = name;3 this.color = color; 4 }5 Cat.prototype.kind = "貓科動物";6 Cat.prototype.eat = function () {7 alert(‘我喜歡吃傑瑞‘); 8 }
引入官方術語,每一個建構函式都有一個prototype屬性,這個屬性指向另一個對象,而執行個體對象會繼承到這個對象上的屬性和方法,只要把不變的屬性以及方法綁定在這個對象上,它們就會指向同一個記憶體空間,你可以這樣測試:
1 var cat1 = new Cat("Tom", "black");2 var cat2 = new Cat("Lucy", "white");3 console.log(cat1.eat == cat2.cat); // true
而純粹使用建構函式的寫法,這個時候控制台會輸出 false ,說明它們實際指向不同的記憶體空間。
擴充:
感覺不太常用就不再手動敲一遍啦~
接下來是 in 運算子的相關使用,想必大部分人都是很熟悉這塊的,用來判斷對象是否含有某個屬性以及遍曆屬性。(但是上次我竟然沒回答對,哎)
1 console.log(name in cat1); // true2 3 for (var key in cat1) {4 console.log("attr-"+ key + ":" + cat1[key]);5 }
嗯,又深刻理解了一遍產生原型執行個體的四種方法!按照電腦語言的套路,完成了封裝自然要討論如何繼承。
建構函式的五種繼承方法
現在已經有Cat對象,新增父物件Animal,如何讓Cat對象繼承Animal對象?
1 function Cat(name, color){ //子物件2 this.name = name;3 this.color = color;4 } 5 function Animal(species){ //父物件6 this.species = "貓科動物";7 }
1 function Cat(name, color){2 Animal.apply(this, arguments);3 this.name = name;4 this.color = color; 5 }
- 使用prototype模式,將子物件的prototype對象指向父物件的執行個體
1 Cat.prototype = new Animal(); 2 Cat.prototype.constructor = Cat; // 修改了Cat的prototype對象後,也會把該對象原本對Cat的指向修改成Animal, 此時需要手動重新把指向還原
但是!執行個體化對象也是要佔用記憶體的不是,於是這種寫法也是不被提倡的,於是有了下面的改進寫法。
注意:以下所有修改了prototype對象的代碼下面,都需要重新還原指向!!!
1 Cat.prototype = Animal.prototype;2 Cat.prototype.constructor = Cat;
看起來蠻不錯的寫法,卻會產生這樣的問題:第一行代碼執行完畢後,為了讓Cat.prototype指向還原,於是我們寫了第二行代碼,萬萬沒想到,第二行代碼執行完畢後,Animal.prototype的指向也變成了Cat!!
並且後續對Cat.prototype的任何修改都會影響到Animal.prototype,這樣顯然是不合理的。
但是我們可以把第二種和第三種寫法結合一下,產生下面這張寫法:
1 function extend(child, parent){2 var f = function () {}3 f.prototype = parent.prototype;4 child.prototype = new f();5 child.prototype.constructor = child;6 }
執行個體化Null 物件是幾乎不佔記憶體的,因此排除了上述兩種寫法的弊端。
1 function copyExtend(child, parent) { 2 var c = child.prototype; 3 var p = parent.prototype; 4 for (var key in p){ 5 c[key] = p[key]; 6 } 7 c.uber = p; 8 } 9 10 function Animal(){}11 Animal.prototype.species = "貓科動物";12 13 //調用14 copyExtend(Cat, Animal);
講完建構函式的繼承,接下來是 非建構函式的繼承
其實普通函數的繼承原理和建構函式還是類似的,主要也是利用prototype屬性以及屬性拷貝的方法。
重新定義兩個對象:
var china = { // 父物件 nation: "中國";}var doctor = { // 子物件 career: "醫生"}
1、第一個方法:我們來定義一個object函數
function object(parent){ function f() {} f.prototype = parent; return new f();}
var doctor = object(country);
doctor.career = "醫生"
object() 的作用,我們都知道建構函式的prototype屬性都是指向另一個對象的(執行個體對象是不存在這個屬性的),第二句代碼相當於把父物件強行設定為這個“另一個對象”。而執行個體化對象(new f())會自動繼承"另一個對象"的所有屬性和方法,這個時候返回的執行個體對象實際已經具備父物件的屬性和方法,再補充上子物件的屬性不就剛好完成了對父物件的繼承?非常巧妙。
2、第二個方法,依舊是拷貝屬性,此處掠過淺拷貝的方法,直奔最完美答案——深拷貝
1 function deepCopy(child, parent){ 2 child = child || {}; 3 for (var key in parent) { 4 if (typeof(parent[key])) === ‘object‘) { // 判斷屬性是否是對象 5 child[key] = (parent[key].constructor === Array) ? [] : {}; // 判斷屬性對象是數組對象還是純對象 6 deepCopy(child[key], parent[key]); // 遞迴拷貝 7 } else { 8 child[key] = parent[key]; 9 }10 }11 return child;12 }
不同於淺拷貝,深拷貝後的子物件繼承了父物件的屬性以後,即使修改了繼承自父物件的屬性,也不會影響到原父物件屬性的內容。
我們來測試一下:
1 china.city = ["北京", "上海", "深圳"]; // 新增一個父物件的對象屬性2 3 // 調用深拷貝方法 4 var doc = deepCopy(doctor, china);5 doc.city.push("杭州");6 7 console.log(doc.city); // ["北京", "上海", "深圳", "杭州"]8 console.log(china.city); // ["北京", "上海", "深圳"]
以上,關於JavaScript物件導向編程的一些比較入門的知識點有了比較深刻的認識。寫博文實在是太累了~還是站在巨人的肩膀上,以後還是要多寫多總結啊!!!
JavaScript物件導向編程