大家好,我是IT修真院北京分院25期的學員,一枚正直純潔善良的web前端程序員
今天給大家分享一下,修真院官網js任務4,深度思考中的知識點——JS的面向對象編程??
1.背景介紹
什么是面向對象編程?
“面向對象編程”(Object-Oriented Programming,縮寫為OOP)是目前主流的編程范式。它的核心思想是將真實世界中各種復雜的關系,抽象為一個個對象,然后由對象之間的分工與合作,完成對真實世界的模擬。
面向對象的語言有一個標志,就是類的概念,通過類可以創建任意多個具有相同屬性和方法的對象。ECMAScript中沒有類的概念,它的對象與基于類的語言中的對象有所不同。
2.知識剖析
如何把“屬性”(property)和"方法"(method),封裝成一個對象?
一、 生成實例對象的原始模式
假定我們把貓看成一個對象,它有"名字"和"顏色"兩個屬性。
var Cat = {
name : '',
color : ''
}
現在,我們需要根據這個原型對象的規格(schema),生成兩個實例對象。
var cat1 = {}; // 創建一個空對象
cat1.name = "大毛"; // 按照原型對象的屬性賦值
cat1.color = "黃色";
var cat2 = {};
cat2.name = "二毛";
cat2.color = "黑色";
好了,這就是最簡單的封裝了,把兩個屬性封裝在一個對象里面。但是,這樣的寫法有兩個缺點,一是如果多生成幾個實例,寫起來就非常麻煩;二是實例與原型之間,看不出有什么聯系。
二、 原始模式的改進
我們可以寫一個函數,解決代碼重復的問題。
function Cat(name,color) {
return {
name:name,
color:color
}
}
然后生成實例對象,就等于是在調用函數:
var cat1 = Cat("大毛","黃色");
var cat2 = Cat("二毛","黑色");
這種方法的問題依然是,cat1和cat2之間沒有內在的聯系,不能反映出它們是同一個原型對象的實例。
三、 構造函數模式
為了解決從原型對象生成實例的問題,Javascript提供了一個構造函數(Constructor)模式。
所謂"構造函數",其實就是一個普通函數,但是內部使用了this變量。對構造函數使用new運算符,就能生成實例,并且this變量會綁定在實例對象上。
比如,貓的原型對象現在可以這樣寫,
function Cat(name,color){
this.name=name;
this.color=color;
}
我們現在就可以生成實例對象了。
var cat1 = new Cat("大毛","黃色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.name); // 大毛
alert(cat1.color); // 黃色
四、構造函數模式的問題
構造函數方法很好用,但是存在一個浪費內存的問題。
請看,我們現在為Cat對象添加一個不變的屬性type(種類),再添加一個方法eat(吃)。那么,原型對象Cat就變成了下面這樣:
function Cat(name,color){
this.name = name;
this.color = color;
this.type = "貓科動物";
this.eat = function(){alert("吃老鼠");};
}
還是采用同樣的方法,生成實例
var cat1 = new Cat("大毛","黃色");
var cat2 = new Cat ("二毛","黑色");
alert(cat1.type); // 貓科動物
cat1.eat(); // 吃老鼠
表面上好像沒什么問題,但是實際上這樣做,有一個很大的弊端。那就是對于每一個實例對象,type屬性和eat()方法都是一模一樣的內容,每一次生成一個實例,都必須為重復的內容,多占用一些內存。這樣既不環保,也缺乏效率。
五、 Prototype模式
Javascript規定,每一個構造函數都有一個prototype屬性,指向另一個對象。這個對象的所有屬性和方法,都會被構造函數的實例繼承。
這意味著,我們可以把那些不變的屬性和方法,直接定義在prototype對象上。
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "貓科動物";
Cat.prototype.eat = function(){alert("吃老鼠")};
然后,生成實例。
var cat1 = new Cat("大毛","黃色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type); // 貓科動物
cat1.eat(); // 吃老鼠
這時所有實例的type屬性和eat()方法,其實都是同一個內存地址,指向prototype對象,因此就提高了運行效率。
3.常見問題
如何實現構造函數的繼承?
4.解決方案
比如,現在有一個"動物"對象的構造函數。
function Animal(){
this.species = "動物";
}
還有一個"貓"對象的構造函數。
function Cat(name,color){
this.name = name;
this.color = color;
}
怎樣才能使"貓"繼承"動物"呢?
一、 構造函數綁定
第一種方法也是最簡單的方法,使用call或apply方法,將父對象的構造函數綁定在子對象上,即在子對象構造函數中加一行:
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物
二、 prototype模式
第二種方法更常見,使用prototype屬性。
如果"貓"的prototype對象,指向一個Animal的實例,那么所有"貓"的實例,就能繼承Animal了。
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物
任何一個prototype對象都有一個constructor屬性,指向它的構造函數。
三、 直接繼承prototype
第三種方法是對第二種方法的改進。由于Animal對象中,不變的屬性都可以直接寫入Animal.prototype。所以,我們也可以讓Cat()跳過 Animal(),直接繼承Animal.prototype。
現在,我們先將Animal對象改寫:
function Animal(){ }
Animal.prototype.species = "動物";
然后,將Cat的prototype對象,然后指向Animal的prototype對象,這樣就完成了繼承。
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物
與前一種方法相比,這樣做的優點是效率比較高(不用執行和建立Animal的實例了),比較省內存。
缺點是 Cat.prototype和Animal.prototype現在指向了同一個對象,那么任何對Cat.prototype的修改,都會反映到Animal.prototype。
四、 利用空對象作為中介
由于"直接繼承prototype"存在上述的缺點,所以就有第四種方法,利用一個空對象作為中介。
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
F是空對象,所以幾乎不占內存。這時,修改Cat的prototype對象,就不會影響到Animal的prototype對象。
我們將上面的方法,封裝成一個函數,便于使用。
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
使用的時候,方法如下
extend(Cat,Animal);
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物
五、 拷貝繼承
上面是采用prototype對象,實現繼承。我們也可以換一種思路,純粹采用"拷貝"方法實現繼承。簡單說,如果把父對象的所有屬性和方法,拷貝進子對象,不也能夠實現繼承嗎?這樣我們就有了第五種方法。
首先,還是把Animal的所有不變屬性,都放到它的prototype對象上。
function Animal(){}
Animal.prototype.species = "動物";
然后,再寫一個函數,實現屬性拷貝的目的。
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
}
這個函數的作用,就是將父對象的prototype對象中的屬性,一一拷貝給Child對象的prototype對象。
使用的時候,這樣寫:
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物
5.編碼實戰
6.擴展思考
面向過程到面向對象思維如何轉變?
當我們習慣了面向過程編程時,發現在程序過程中到處找不到需要面向對象的地方,最主要的原因,是思維沒有轉變。程序員通常在拿到一個需求的時候,第一個反應就是如何實現這個需求,這是典型的面向過程的思維過程,而且很快可能就實現了它。而面向對象,面對的卻是客體,第一步不是考慮如何實現需求,而是進行需求分析,就是根據需求找到其中的客體,再找到這些客體之間的聯系。
7.參考文獻
參考:
8.更多討論
視頻鏈接:密碼: f47u
技能樹.IT修真院
“我們相信人人都可以成為一個工程師,現在開始,找個師兄,帶你入門,掌控自己學習的節奏,學習的路上不再迷茫”。
這里是技能樹.IT修真院,成千上萬的師兄在這里找到了自己的學習路線,學習透明化,成長可見化,師兄1對1免費指導??靵砼c我一起學習吧?!