*前言:上篇文章介紹了js中經過構造函數來實例化對象的各類方法js構造函數,這篇文章主要介紹構造函數的繼承(類的繼承),一樣包括 ES5 和 ES6 兩部分的介紹,能力所限,文中不免有不合理或錯誤的地方,還望各位大神批評指正~segmentfault
首先簡單介紹一下實例屬性/方法 和 原型屬性/方法,以便更好理解下文數組
function Persion(name){ this.name = name; // 屬性 this.setName = function(nameName){ // 實例方法 this.name = newName; } } Persion.prototype.sex = 'man'; // 向 Persion 原型中追加屬性(原型方法) var persion = new Persion('張三'); // 此時咱們實例化一個persion對象,看一下name和sex有什麼區別
在控制檯查看 persion 打印以下:
原來經過 prototype 添加的屬性將出如今實例對象的原型鏈中,
每一個對象都會有一個內置 proto 對象,當在當前對象中找不到屬性的時候就會在其原型鏈中查找(即原型鏈)函數
咱們再來看下面的例子
注意:在構造函數中,通常不多有數組形式的引用屬性,大部分狀況都是:基本屬性 + 方法。this
function Animal(n) { // 聲明一個構造函數 this.name = n; // 實例屬性 this.arr = []; // 實例屬性(引用類型) this.say = function(){ // 實例方法 return 'hello world'; } } Animal.prototype.sing = function() { // 追加原型方法 return '吹呀吹呀,個人驕傲放縱~~'; } Animal.prototype.pArr = []; // 追加原型屬性(引用類型)
接下來咱們看一下實例屬性/方法 和 原型屬性/方法的區別
原型對象的用途是爲每一個實例對象存儲共享的方法和屬性,它僅僅是一個普通對象而已。而且全部的實例是共享同一個原型對象,所以有別於實例方法或屬性,原型對象僅有一份。而實例有不少份,且實例屬性和方法是獨立的。spa
var cat = new Animal('cat'); // 實例化cat對象 var dog = new Animal('dog'); // 實例化狗子對象 cat.say === dog.say // false 不一樣的實例擁有不一樣的實例屬性/方法 cat.sing === dog.sing // true 不一樣的實例共享相同的原型屬性/方法 cat.arr.push('zz'); // 向cat實例對象的arr中追加元素;(私有) cat.pArr.push('xx'); // 向cat原型對象的pArr中追加元素;(共享) console.log(dog.arr); // 打印出 [],由於cat只改變了其私有的arr console.log(dog.pArr); // 打印出 ['xx'], 由於cat改變了與狗子(dog)共享的pArr
固然,原型屬性爲基本數據類型,則不會被共享
在構造函數中:爲了屬性(實例基本屬性)的私有性、以及方法(實例引用屬性)的複用、共享。咱們提倡:
一、將屬性封裝在構造函數中
二、將方法定義在原型對象上prototype
首先,咱們定義一個Animal父類code
function Animal(n) { this.name = n; // 實例屬性 this.arr = []; // 實例屬性(引用類型) this.say = function(){ // 實例方法 return 'hello world'; } } Animal.prototype.sing = function() { // 追加原型方法 return '吹呀吹呀,個人驕傲放縱~~'; } Animal.prototype.pArr = []; // 追加原型屬性(引用類型)
function Cat(n) { this.cName = n; } Cat.prototype = new Animal(); // 父類的實例做爲子類的原型對象 var tom = new Cat('tom'); // 此時Tom擁有Cat和Animal的全部實例和原型方法/屬性,實現了繼承 var black = new Cat('black'); tom.arr.push('Im tom'); console.log(black.arr); // 打印出 ['Im tom'], 結果其方法變成了共享的,而不是每一個實例所私有的,這是由於父類的實例方法/屬性變成了子類的原型方法/屬性了;
優勢: 實現了子對象對父對象的實例 方法/屬性 和 原型方法/屬性 的繼承;
缺點: 子類實例共享了父類構造函數的引用數據類型屬性。對象
function Cat(n) { this.cName = n; Animal.call(this, this.cName); // 核心,把父類的實例方法屬性指向子類 } var tom = new Cat('tom'); // 此時Tom擁有Cat和Animal的全部實例和原型方法/屬性,實現了繼承 var black = new Cat('black'); tom.arr.push('Im tom'); console.log(black.arr); // 打印出 [], 其方法和屬性是每一個子類實例所私有的; tom.sing(); // undefind 沒法繼承父類的原型屬性及方法;
優勢:
一、實現了子對象對父對象的實例 方法/屬性 的繼承,每一個子類實例所繼承的父類實例方法和屬性都是其私有的;
二、 建立子類實例,能夠向父類構造函數傳參數;
缺點: 子類實例不能繼承父類的構造屬性和方法;繼承
function Cat(n) { this.cName = n; Animal.call(this, this.cName); // 核心,把父類的實例方法屬性指向子類 } Cat.prototype = new Parent() // 核心, 父類的實例做爲子類的原型對象 Cat.prototype.constructor = Cat; // 修復子類Cat的構造器指向,防止原型鏈的混亂 tom.arr.push('Im tom'); console.log(black.arr); // 打印出 [], 其方法和屬性是每一個子類實例所私有的; tom.sing(); // 打印出 '吹呀吹呀,個人驕傲放縱~~'; 子類繼承了父類的原型方法及屬性
優勢:
一、建立子類實例,能夠向父類構造函數傳參數;
二、父類的實例方法定義在父類的原型對象上,能夠實現方法複用;
三、不共享父類的構造方法及屬性;
缺點: 調用了2次父類的構造方法原型鏈
function Cat(n) { this.cName = n; Animal.call(this, this.cName); // 核心,把父類的實例方法屬性指向子類 } Cat.prototype = Parent.prototype; // 核心, 將父類原型賦值給子類原型(子類原型和父類原型,實質上是同一個) Cat.prototype.constructor = Cat; // 修復子類Cat的構造器指向,防止原型鏈的混亂 tom.arr.push('Im tom'); console.log(black.arr); // 打印出 [], 其方法和屬性是每一個子類實例所私有的; tom.sing(); // 打印出 '吹呀吹呀,個人驕傲放縱~~'; 子類繼承了父類的原型方法及屬性 tom.pArr.push('publish'); // 修改繼承於父類原型屬性值 pArr; console.log(black.pArr); // 打印出 ['publish'], 父類的原型屬性/方法 依舊是共享的, // 至此簡直是完美呀~~~ 然鵝! Cat.prototype.childrenProp = '我是子類的原型屬性!'; var parent = new Animal('父類'); console.log(parent.childrenProp); // 打印出'我是子類的原型屬性!' what? 父類實例化的對象擁有子類的原型屬性/方法,這是由於父類和子類使用了同一個原型
優勢:
一、建立子類實例,能夠向父類構造函數傳參數;
二、子類的實例不共享父類的構造方法及屬性;
三、只調用了1次父類的構造方法;
缺點: 父類和子類使用了同一個原型,致使子類的原型修改會影響父類;
function Cat(n) { this.cName = n; Animal.call(this, this.cName); // 核心,把父類的實例方法屬性指向子類; } var F = function(){}; // 核心,利用空對象做爲中介; F.prototype = Parent.prototype; // 核心,將父類的原型賦值給空對象F; Cat.prototype = new F(); // 核心,將F的實例賦值給子類; Cat.prototype.constructor = Cat; // 修復子類Cat的構造器指向,防止原型鏈的混亂; tom.arr.push('Im tom'); console.log(black.arr); // 打印出 [], 其方法和屬性是每一個子類實例所私有的; tom.sing(); // 打印出 '吹呀吹呀,個人驕傲放縱~~'; 子類繼承了父類的原型方法及屬性; tom.pArr.push('publish'); // 修改繼承於父類原型屬性值 pArr; console.log(black.pArr); // 打印出 ['publish'], 父類的原型屬性/方法 依舊是共享的; Cat.prototype.childrenProp = '我是子類的原型屬性!'; var parent = new Animal('父類'); console.log(parent.childrenProp); // undefind 父類實例化的對象不擁有子類的原型屬性/方法;
優勢: 完美實現繼承;
缺點:實現相對複雜
function extend(Child, Parent) { var F = function(){}; F.prototype = Parent.prototype; hild.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; } // 使用 extend(Cat,Animal);
Child.uber = Parent.prototype;
的意思是爲子對象設一個uber屬性,這個屬性直接指向父對象的prototype屬性。(uber是一個德語詞,意思是"向上"、"上一層"。)這等於在子對象上打開一條通道,能夠直接調用父對象的方法。這一行放在這裏,只是爲了實現繼承的完備性,純屬備用性質。
class Animal{ // 父類 constructor(name){ // 構造函數 this.name=name; } eat(){ // 實例方法 return 'hello world'; } } class Cat extends Animal{ // 子類 constructor(name){ super(name); // 調用實現父類的構造函數 this.pName = name; } sing(){ return '吹呀吹呀,個人驕傲放縱~~'; } }