JavaScript中的面向對象其實就至關因而將需求抽象成一個對象,這個對象中包含屬性和方法,能夠用來描述或處理某一類實物,這個對象就叫作類。javascript
在Java中面向對象的三要素是:封裝、繼承、多態,在ES6之前雖然沒有class、extend這樣的關鍵字或方法,中一樣能夠實現這三要素。java
建立一個類安全
在ES6以前建立一個類通能夠用:聲明一個函數將其保存在變量中,內部經過對this來添加屬性和方法;也能夠在類的原型上添加屬性和方法。閉包
能夠看到當在控制檯中建立一個Book的類,Book的實例化對象book包含兩個屬性 id 和 name,它的隱身原型__proto__中包含一個constructor屬性,它又指向了Book這個原型對象。函數
若是在Book的原型上添加方法或屬性:比如 Book。prototype.display = function (){ ... } 每一個實例化對象都會經過原型鏈繼承到這個方法,但這並不是是她自有的方法,須要經過prototype訪問。this
現在ES6引入Class能夠這麼建立類,constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。spa
1 class Book { 2 constructor(id, name) { 3 this.id = id; 4 this.name = name; 5 } 6 }
封裝prototype
利用javascript的函數做用域能夠建立類的私有變量和私有方法,經過this建立類的共有屬性和共有方法。一下代碼是我理解中javascript封裝的精髓code
1 var Book = function (id,name) { 2 // 安全檢查 判斷執行過程當中是不是當前這個對象 3 // 避免誤建立全局對象 4 if (this instanceof Book) { 5 this.name = name; 6 } else { 7 return new Book(id,name); 8 } 9 // 私有屬性 10 var num = 1; 11 // 私有方法 12 function checkId() { 13 return num +'個'+ id; 14 } 15 // 公有屬性和公有方法 16 this.id = id; 17 this.copy = function () {} 18 // 特權方法 19 this.setName = function (name){} 20 this.getName = function() { 21 // 能夠訪問公有方法和屬性,也能夠訪問建立對象時的私有方法和屬性 22 } 23 // 構造器 24 this.setName = function (name) { 25 // 在對象建立時,使用特權方法初始化實例對象 26 } 27 // 閉包 28 return checkId; 29 }
繼承對象
繼承就是對原有對象的封裝,從中建立私有屬性、公有屬性等,對於每種屬性和方法特色都不同。能夠被繼承的方法屬性無外乎兩種,一種是在構造函數中,在對象實例化時複製一遍屬性和方法;一種是在類的原型對象中,這類方法和屬性能夠被全部實例化對象共有。
類式繼承:
類式繼承能夠理解爲把父類的實力賦值給子類的原型,經過原型鏈來實現繼承。
1 function SuperClass() { 2 this.books = ['父類book']; 3 } 4 function SubClass() {} 5 Sub.prototype = new SuperClass(); 6 7 var instance1= new SubClass(); 8 console.log(instance1.books); //['父類book']
用這種方式新建立的對象不只能夠訪問父類原型上的屬性和方法,還能訪問父類構造函數中賦值的屬性和方法,子類的原型還能夠訪問父類原型上的方法.
不足之處是,若是某一個實例化對象修改了books這個屬性,那麼全部實例化的對象books值都會被污染。
構造函數繼承:
構造函數式繼承能夠很好的解決類式繼承中所暴露的問題。它的精髓在於使用call() 這個函數
function SuperClass(id) { this.books = ['super']; this.id = id; } function SubClass(id) { SuperClass.call(this,id); } var instance1 = new SubClass(10); var instance2 = new SubClass(11); instance1.books.push('sub'); console.log(instance1.books) // ['super', 'sub'] console.log(instance2.books) // ['super', 'sub'] console.log(instance1.id) // 10 console.log(instance2.id) // 11
能夠看出 經過call()這個函數改變函數的做用環境,在子類中調用父類是將子類中的變量拿到父類中執行了一遍。這一類繼承不涉及到原型,因此父類原型的方法就不能被子類繼承。
組合繼承:
組合繼承就是結合了類式繼承中原型繼承,以及構造函數式繼承中在子類構造函數環境中執行一次父類 這兩點來繼承的
function SuperClass(id) { this.books = ['super']; this.id = id; } SuperClass.prototype.getId = function () { console.log(this.id); } function SubClass(id,name) { // 構造函數中繼承父類 id屬性 SuperClass.call(this,id); this.name = name; } // 類式繼承原型繼承父類 SubClass.prototype = new SuperClass(); // 新增子類原型方法 SubClass.prototype.getName = function () { console.log(this.name); } var instance1 = new SubClass(10,'sub1'); var instance2 = new SubClass(11,'sub2'); instance1.books.push('sub1-1'); console.log(instance1.books) // ['super', 'sub1-1'] console.log(instance2.books) // ['super'] instance1.getName() // 10 instance1.getTime() // sub2
寄生組合繼承 :
function inheritObject(o) { // 過渡對象 function F() {} // 原型繼承 F.prototype = o; // 返回一個原型繼承了父類對象的實例 return new F(); } function inheritPrototype(subClass, superClass) { // 複製一份父類的原型副本保存在變量中 var p = inheritObject(superClass.prototype); //修正由於重寫子類原型致使子類constructor屬性被修改 p.constructor = subClass; //設置子類的原型 subClass.prototype = p; } function SuperClass(id) { this.books = ['super']; this.id = id; } SuperClass.prototype.getId = function () { console.log(this.id); } function SubClass(id,name) { // 構造函數中繼承父類 id屬性 SuperClass.call(this,id); this.name = name; } inheritPrototype(SubClass, SuperClass) SubClass.prototype.getName = function () { console.log(this.name); } var instance1 = new SubClass(12,'sub12'); var instance2 = new SubClass(13,'sub13');
最大的改變就是子類的原型被賦予了父類的原型的一個引用,此時子類想要添加原型方法必須經過prototype添加。
以上是ES6以前,在使用javascript繼承時大多采用的方法,那麼在ES6當中,咱們能夠採用關鍵字 簡潔明瞭的達到繼承的目的
1 class ColorPoint extends Point {
2 constructor(x, y, color) { 3 super(x, y); // 調用父類的constructor(x, y) 4 this.color = color; 5 } 6 7 toString() { 8 return this.color + ' ' + super.toString(); // 調用父類的toString() 9 } 10 }
在子類的構造函數中必須使用 super()來調用父類的構造函數,這是由於子類本身的this對象,必須先經過父類的構造函數構造,獲得與父類一樣的實例屬性和方法,而後再對其進行加工,加上子類本身的實例屬性和方法。若是不調用super方法,子類就得不到this對象。所以只有在調用super之後,子類的this才能正常使用。
多態
多態能夠理解爲,在調用方法時,更具傳遞參數的數量、類型不一樣時,具備多種實現方式。 能夠說根據接口不一樣,呈現不同的結果。
對於ES6之前實現繼承,多態,封裝的原理,主要圍繞原型、原型鏈調用來實現,雖然說目前ES6能夠很好的實現這些靈魂玩法,包括目前大勢的TypeSScript更是把JS 引向了強類型語言的圈子,但萬變不離其宗,瞭解原理,才能更好的運用。