Javascript面向對象三大特性(封裝性、繼承性、多態性)詳解及建立對象的各類方法

Javascript基於對象的三大特徵和C++,Java面向對象的三大特徵同樣,都是封裝(encapsulation)、繼承(inheritance )和多態(polymorphism )。只不過實現的方式不一樣,其基本概念是差很少的。其實除三大特徵以外,還有一個常見的特徵叫作抽象(abstract),這也就是咱們在一些書上有時候會看到面向對象四大特徵的緣由了。javascript


1、封裝性html


    封裝就是把抽象出來的數據和對數據的操做封裝在一塊兒,數據被保護在內部,程序的其它部分只有經過被受權的操做(成員方法),才能對數據進行操做。java

  JS封裝只有兩種狀態,一種是公開的,一種是私有的。
    案例:編程

function Person(name,sal){
    this.name=name;         //公開
    var sal=sal;                 //私有
    this.showInfo=function(){ //公開
        window.alert(this.name+" "+sal);
    }
    function showInfo2(){      //把函數私有化
        window.alert("你好"+this.name+" "+sal);
    }
}
var p1 = new Person('Cece', 20, 10000); 
window.alert(p1.name + " is " +p1.age); //Cece is undefined
p1.showInfo();//Cece 20
p1.showInfo2();//VM302:1 Uncaught TypeError: p1.showInfo2 is not a function(…)

構造函數方式與原型方式給對象添加方法的區別:數組

//1.經過構造函數方式給對象添加方法
function Dog(name){
    this.name=name;
    this.shout=function(){
        window.alert("小狗尖叫"+this.name);
    }
}
var dog1=new Dog("aa");
var dog2=new Dog("bb");
if(dog1.shout==dog2.shout){
    window.alert("相等");
}else{
    window.alert("不相等");
}

//會輸出「不相等」
//2.經過原型方式給對象添加方法
function Dog(name){
    this.name=name;    
}
Dog.prototype.shout=function(){
    window.alert("小狗尖叫"+this.name);
}
var dog1=new Dog("aa");
var dog2=new Dog("bb");
if(dog1.shout==dog2.shout){
    window.alert("相等");
}else{
    window.alert("不相等");
}

//會輸出「相等」

說明經過構造函數來分配成員方法,給每一個對象分配一份獨立的代碼。這樣的弊端就是若是對象實例有不少,那函數的資源佔用就會很大,並且有可能形成內存泄漏。安全

而原型法是你們共享同一份代碼,就不會有那種弊端。app

所以,經過構造函數添加成員方法和經過原型法添加成員方法的區別:函數

1.經過原型法分配的函數是全部對象共享的;學習

2.經過原型法分配的屬性是獨立的;(若是你不修改屬性,他們是共享)測試

3.若是但願全部的對象使用同一個函數,最好使用原型法添加方法,這樣比較節省內存。

 

特別強調:咱們前面學習的經過prototype給全部的對象添加方法,可是這種方式不能去訪問類的私有變量和方法。案例:

function Person(){
    this.name="Cece";
    var age=18;
    this.abc=function(){    //公開
        window.alert("abc");
    }
    function abc2(){        //私有
        window.alert("abc");
    }
}
Person.prototype.fun1=function(){
    window.alert(this.name);//Cece
    //window.alert(age);//Uncaught ReferenceError: age is not defined(…)
    //abc2();           //Uncaught ReferenceError: abc2 is not defined(…)
    this.abc();         //abc
}
var p1=new Person();
p1.fun1();

 

2、繼承性


繼承能夠解決代碼複用,讓編程更加靠近人類思惟。當多個類存在相同的屬性(變量)和方法時,能夠從這些類中抽象出父類,在父類中定義這些相同的屬性和方法,全部的子類不須要從新定義這些屬性和方法,只須要經過繼承父類中的屬性和方法。
JS中實現繼承的方式:

1.類繼承:


(1)對象冒充
案例:

//1.把子類中共有的屬性和方法抽取出,定義一個父類Stu 
function Stu(name, age){ 
    this.name = name; 
    this.age = age; 
    this.show = function(){ 
        window.alert(this.name + " " + this.age); 
    } 
} 
function MidStu(name, age) { 
    this.stu = Stu; 
    // 經過對象冒充來實現繼承的 
    // 對象冒充的意思就是獲取那個類的全部成員,由於js是誰調用那個成員就是誰的,這樣MidStu就有了Stu的成員了 
    this.stu(name, age); 
    this.payFee = function(){ 
        window.alert("繳費" + money * 0.8); 
    } 
} 
function Pupil(name, age) { 
    this.stu = Stu; 
    // 經過對象冒充來實現繼承的 
    this.stu(name, age); 
    this.payFee = function(){ 
        window.alert("繳費" + money * 0.5); 
    } 
} 

var midStu = new MidStu("zs", 13); 
midStu.show(); 
var pupil = new Pupil("ls", 10); 
pupil.show(); 

(2)經過call或者apply實現
案例:

//1.把子類中共有的屬性和方法抽取出,定義一個父類Stu 
function Stu(name,age){ 
    //window.alert("確實被調用."); 
    this.name=name; 
    this.age=age; 
    this.show=function(){ 
        window.alert(this.name+"年齡是="+this.age); 
    } 
} 
//2.經過call或者apply來繼承父類的屬性的方法 
function MidStu(name,age){ 
    //這裏這樣理解: 經過call修改了Stu構造函數的this指向, 
    //讓它指向了調用者自己. 
    Stu.call(this,name,age); 
    //若是用apply實現,則能夠 
    //Stu.apply(this,[name,age]); //說明傳入的參數是 數組方式 
    //能夠寫MidStu本身的方法. 
    this.pay=function(fee){ 
        window.alert("你的學費是"+fee*0.8); 
    } 
} 
function Pupil(name,age){ 
    Stu.call(this,name,age);//當咱們建立Pupil對象實例,Stu的構造函數會被執行,當執行後,咱們Pupil對象就獲取從 Stu封裝的屬性和方法 
    //能夠寫Pupil本身的方法. 
    this.pay=function(fee){ 
        window.alert("你的學費是"+fee*0.5); 
    } 
} 
//測試 
var midstu=new MidStu("zs",15); 
var pupil=new Pupil("ls",12); 
midstu.show(); 
midstu.pay(100); 
pupil.show(); 
pupil.pay(100); 

2.原型繼承

原型繼承是js中最通用的繼承方式,不用實例化對象,經過直接定義對象,並被其餘對象引用,這樣造成的一種繼承關係,其中引用對象被稱爲原型對象。

function A(){  
    this.color = 'red';  
}  
function B(){}  
function C(){}  
B.prototype = new A();  
C.prototype = new B();  
// 測試原型繼承  
var c = new C();  
console.log(c.color); // red  

原型繼承顯得很簡單,不須要每次構造都調用父類的構造函數,也不須要經過複製屬性的方式就能快速實現繼承。但它也存在一些缺點:

① 每一個類型只有一個原型,因此不支持多重繼承(即一個子類繼承自多個父類)。
② 不能很好的支持多參數或動態參數的父類,顯得不夠靈活。

③ 佔用內存多,每次繼承都須要實例化一個父類,這樣會存在內存佔用過多的問題。

3.複製繼承(知道就好)

複製繼承就是利用for in 遍歷對象成員,逐一複製給另外一個對象。經過這種方式來實現繼承。
function A(){  
    this.color = 'red';  
}  
A.prototype.say = function() {  
    console.log(this.color);  
}  
var a = new A();  
var b = {};  
// 開始拷貝  
for(var item in a) {  
    b[item] = a[item];  
}  
// 開始測試  
console.log(b.color); // red  
b.say(); // red  

封裝後:

Function.prototype.extend = function(obj){  
    for(item in obj){  
        this.constructor.prototype[item] = obj[item];  
    }  
}  
function A(){  
    this.color = 'green';  
}  
A.prototype.say = function(){  
    console.log(this.color);  
}  
// 測試  
var b = function(){};  
b.extend(new A());  
b.say(); // green  

複製繼承其實是經過反射機制複製類對象中的可枚舉屬性和方法來模擬繼承。這種能夠實現多繼承。但也有缺點:

① 因爲是反射機制,不能繼承非枚舉類型的屬性和方法。對於系統核心對象的只讀方法和屬性也沒法繼承。
② 執行效率差,這樣的結構越龐大,低效就越明顯。
③ 若是當前類型包含同名成員,這些成員會被父類的動態複製給覆蓋。
④ 多重繼承中,複製繼承不能清晰描述父類和子類的相關性。
⑤ 在實例化後才能遍歷成員,不夠靈活,也不支持動態參數

⑥ 複製繼承僅僅是簡單的引用賦值,若是父類成員包含引用類型,那麼也會帶來不少反作用,如不安全,容易遭受污染等。

4.混合繼承(構造+原型)

混合繼承是把多種繼承方式一塊兒使用,發揮各個優點,來實現各類複雜的應用。
最多見的就是把類繼承和原型繼承一塊兒使用。作法是將須要獨立的屬性方法放入構造函數中,而能夠共享的部分則放入原型中,這樣作能夠最大限度節省內存而又保留對象實例的獨立性。注意:
一、把方法寫在原型中比寫在構造函數中消耗的內存更小,由於在內存中一個類的原型只有一個,寫在原型中的行爲能夠被全部實例共享,實例化的時候並不會在實例的內存中再複製一份
而寫在類中的方法,實例化的時候會在每一個實例中再複製一份,因此消耗的內存更高。
因此沒有特殊緣由,咱們通常把屬性寫到類中,而行爲寫到原型中。
二、構造函數中定義的屬性和方法要比原型中定義的屬性和方法的優先級高,若是定義了同名稱的屬性和方法,構造函數中的將會覆蓋原型中的。
function A(x,y){  
    this.x = x;  
    this.y = y;  
}  
A.prototype.add = function(){  
    return (this.x-0) + (this.y-0);  
}  
function B(x,y){  
    A.call(this,x,y);  
}  
B.prototype = new A();  

// 測試  
var b = new B(2,1);  
console.log(b.x); // 2  
console.log(b.add()); // 3 

5.多重繼承

繼承通常包括單向繼承和多向繼承,單向繼承模式較爲簡單,每一個子類有且僅有一個超類,多重繼承是一個比較複雜的繼承模式。一個子類可擁有多個超類。JavaScript原型繼承不支持多重繼承,但可經過混合模式來實現多重繼承。下面讓類C來繼承類A和類B:
function A(x){  
    this.x = x;  
}  
A.prototype.hi = function(){  
    console.log('hi');  
}  
function B(y){  
    this.y = y;  
}  
B.prototype.hello = function(){  
    console.log('hello');  
}  
// 給Function增長extend方法  
Function.prototype.extend = function(obj) {  
    for(var item in obj) {  
        this.constructor.prototype[item] = obj[item];  
    }  
}  
// 在類C內部實現繼承  
function C(x,y){  
    A.call(this,x);  
    B.call(this,y);  
};  
C.extend(new A(1));  
C.extend(new B(2));  

// 經過複製繼承後,C變成了一個對象,再也不是構造函數了,能夠直接調用  
C.hi(); // hi  
C.hello(); // hello  
console.log(C.x); // 1  
console.log(C.y); // 2  

在js中實現類繼承,須要設置3點:

① 在子類構造函數結構體內,使用函數call()調用父類構造函數,把子類的參數傳遞給調用函數如上面的例子:A.call(this,x) 這樣子類能夠繼承父類的全部屬性和方法。
② 在子類和父類之間創建原型鏈,如上例:B.prototype = new A() 爲了實現類的繼承必須保證他們原型鏈上的上下級關係。即設置子類的prototype 屬性指向父類的一個實例便可。
③ 恢復子類原型對象的構造函數, 如上例:B.prototype.constructor = B
在類繼承中,call() 和 apply() 方法被頻繁使用,它們之間的功能和用法都是相同的,惟一區別就是第2個參數類型不一樣。若是深刻,參考:http://blog.csdn.net/tyro_java/article/details/51020720
類的構造函數中的成員,通常稱之爲本地成員,本地成員繼承能夠用call 和 apply。而類的原型成員就是類的原型中的成員。

 

關於繼承更多知識參考:面向對象在javascript中的三大特徵之繼承

 

3、多態性


JS的函數重載
這個是多態的基礎,在以前的Javascript入門已經說過了,JS函數不支持多態,可是事實上JS函數是無態的,支持任意長度,類型的參數列表。若是同時定義了多個同名函數,則以最後一個函數爲準。
案例1:js不支持重載

/*****************說明js不支持重載*****/
function Person(){ 
    this.test1=function (a,b){ 
        window.alert('function (a,b)');  
    } 
    this.test1=function (a){ 
        window.alert('function (a)'); 
    } 
} 
var p1=new Person(); 
//js中不支持重載. 
//可是這不會報錯,js會默認是最後同名一個函數,能夠看作是後面的把前面的覆蓋了。 
p1.test1("a","b"); 
p1.test1("a");

案例2:js如何實現重載

//js怎麼實現重載.經過判斷參數的個數來實現重載 
function Person(){ 
    this.test1=function (){ 
        if(arguments.length==1){ 
            this.show1(arguments[0]); 
        }else if(arguments.length==2){ 
            this.show2(arguments[0],arguments[1]); 
        }else if(arguments.length==3){ 
            this.show3(arguments[0],arguments[1],arguments[2]); 
        } 
    } 
    this.show1=function(a){ 
        window.alert("show1()被調用"+a); 
    } 
    this.show2=function(a,b){ 
        window.alert("show2()被調用"+"--"+a+"--"+b); 
    } 
    function show3(a,b,c){ 
        window.alert("show3()被調用"); 
    } 
} 
var p1=new Person(); 
//js中不支持重載. 
p1.test1("a","b"); 
p1.test1("a"); 

一、多態基本概念
多態是指一個引用(類型)在不一樣狀況下的多種狀態。也能夠理解成:多態是指經過指向父類的引用,來調用在不一樣子類中實現的方法。
案例:

// Master類 
function Master(name){ 
    this.nam=name; 
    //方法[給動物餵食物] 
} 
//原型法添加成員函數 
Master.prototype.feed=function (animal,food){ 
    window.alert("給"+animal.name+" 喂"+ food.name); 
} 
function Food(name){ 
    this.name=name; 
} 
//魚類 
function Fish(name){ 
    this.food=Food; 
    this.food(name); 
} 
//骨頭 
function Bone(name){ 
    this.food=Food; 
    this.food(name); 
} 
function Peach(name){ 
    this.food=Food; 
    this.food(name); 
} 
//動物類 
function Animal(name){ 
    this.name=name; 
} 
//貓貓 
function Cat(name){ 
    this.animal=Animal; 
    this.animal(name); 
} 
//狗狗 
function Dog(name){ 
    this.animal=Animal; 
    this.animal(name); 
} 
//猴子 
function Monkey(name){ 
    this.animal=Animal; 
    this.animal(name); 
} 
var cat=new Cat("貓"); 
var fish=new Fish("魚"); 

var dog=new Dog("狗"); 
var bone=new Bone("骨頭"); 

var monkey=new Monkey("猴"); 
var peach=new Peach("桃"); 

//建立一個主人 
var master=new Master("zs"); 
master.feed(dog,bone); 
master.feed(cat,fish); 
master.feed(monkey,peach); 

多態利於代碼的維護和擴展,當咱們須要使用同一類樹上的對象時,只須要傳入不一樣的參數就好了,而不須要再new 一個對象。

以上就是Javascript基於對象三大特性。

 

附錄:js中建立對象的各類方法(如今最經常使用的方法是組合模式)。

1)原始模式

//1.原始模式,對象字面量方式
var person = {
    name: 'Jack',
    age: 18,
    sayName: function () { alert(this.name); }
};

//1.原始模式,Object構造函數方式
var person = new Object();
person.name = 'Jack';
person.age = 18;
person.sayName = function () {
    alert(this.name);
};

顯然,當咱們要建立批量的person一、person2……時,每次都要敲不少代碼,資深copypaster都吃不消!而後就有了批量生產的工廠模式。

2)工廠模式

//2.工廠模式,定義一個函數建立對象
function creatPerson (name, age) {
    var temp = new Object();
    person.name = name;
    person.age = age;
    person.sayName = function () {
        alert(this.name);
    };
    return temp;
}

工廠模式就是批量化生產,簡單調用就能夠進入造人模式。指定姓名年齡就能夠造一堆小寶寶啦,解放雙手。可是因爲是工廠暗箱操做的,因此你不能識別這個對象究竟是什麼類型(instanceof 測試爲 Object),另外每次造人時都要建立一個獨立的temp對象,代碼臃腫。

3)構造函數

//3.構造函數模式,爲對象定義一個構造函數
function Person (name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        alert(this.name);
    };   
}
var p1 = new Person('Jack', 18); //建立一個p1對象
Person('Jack', 18);    //屬性方法都給window對象,window.name='Jack',window.sayName()會輸出Jack

構造函數與C++、JAVA中類的構造函數相似,易於理解,另外Person能夠做爲類型識別(instanceof 測試爲 Person 、Object)。可是全部實例依然是獨立的,不一樣實例的方法實際上是不一樣的函數。這裏把函數兩個字忘了吧,把sayName當作一個對象就好理解了,就是說張三的 sayName 和李四的 sayName是不一樣的存在,但顯然咱們指望的是共用一個 sayName 以節省內存。

4)原型模式

//4.原型模式,直接定義prototype屬性
function Person () {}
Person.prototype.name = 'Jack';
Person.prototype.age = 18;
Person.prototype.sayName = function () { alert(this.name); };

//4.原型模式,字面量定義方式
function Person () {}
Person.prototype = {
    name: 'Jack',
    age: 18,
    sayName: function () { alert(this.name); }
};
var p1 = new Person(); //name='Jack' var p2 = new Person(); //name='Jack'

這裏須要注意的是原型屬性和方法的共享,即全部實例中都只是引用原型中的屬性方法,任何一個地方產生的改動會引發其餘實例的變化。

5)混合模式(構造+原型)

//5. 原型構造組合模式,
function Person (name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype = {
    hobby: ['running','football'];
    sayName: function () { alert(this.name); },
    sayAge: function () { alert(this.age); }
};

var p1 = new Person('Jack', 20);
//p1:'Jack',20; __proto__: ['running','football'],sayName,sayAge

var p2 = new Person('Mark', 18);
//p1:'Mark',18;__proto__: ['running','football'],sayName,sayAge

作法是將須要獨立的屬性方法放入構造函數中,而能夠共享的部分則放入原型中,這樣作能夠最大限度節省內存而又保留對象實例的獨立性。

 

參考:Javascript基於對象三大特性(封裝性、繼承性、多態性)

   簡單粗暴地理解js原型鏈--js面向對象編程

相關文章
相關標籤/搜索