js構造函數(繼承方法及利弊)

js構造函數

*前言:上篇文章介紹了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 打印以下:
原型鏈簡介.png
原來經過 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

ES5繼承方式

首先,咱們定義一個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  父類實例化的對象不擁有子類的原型屬性/方法;

優勢: 完美實現繼承;
缺點:實現相對複雜

附YUI庫實現繼承

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是一個德語詞,意思是"向上"、"上一層"。)這等於在子對象上打開一條通道,能夠直接調用父對象的方法。這一行放在這裏,只是爲了實現繼承的完備性,純屬備用性質。

ES6繼承方式

class Animal{                                                // 父類
    constructor(name){                                       // 構造函數
        this.name=name;
    }
    eat(){                                                   // 實例方法
        return 'hello world';
    }
}
class Cat extends Animal{                                    // 子類
  constructor(name){
      super(name);                                           // 調用實現父類的構造函數
      this.pName = name;            
  }
  sing(){
     return '吹呀吹呀,個人驕傲放縱~~';
  }
}
相關文章
相關標籤/搜索