JavaScript物件導向編程

來源:互聯網
上載者:User

標籤: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 } 
  • 使用apply()綁定

 

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對象的代碼下面,都需要重新還原指向!!!

  • prototype模式改進
1 Cat.prototype = Animal.prototype;2 Cat.prototype.constructor = Cat;

看起來蠻不錯的寫法,卻會產生這樣的問題:第一行代碼執行完畢後,為了讓Cat.prototype指向還原,於是我們寫了第二行代碼,萬萬沒想到,第二行代碼執行完畢後,Animal.prototype的指向也變成了Cat!!

並且後續對Cat.prototype的任何修改都會影響到Animal.prototype,這樣顯然是不合理的。

但是我們可以把第二種和第三種寫法結合一下,產生下面這張寫法:

  • 利用Null 物件做中介
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物件導向編程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.