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.複製繼承(知道就好)
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.多重繼承
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點:
關於繼承更多知識參考:面向對象在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
作法是將須要獨立的屬性方法放入構造函數中,而能夠共享的部分則放入原型中,這樣作能夠最大限度節省內存而又保留對象實例的獨立性。