進擊的 JavaScript(八) 之 繼承

前面講完原型鏈,如今來說繼承,加深理解下。數組

1、對象的相關知識

什麼是對象? 就是一些無序的 key : value 集合, 這個value 能夠是 基本值,函數,對象。(注意 key 和 value 之間 是冒號 : ,每一個 key : value 之間 是逗號 , )app

var person = {
    name: 'zdx',
    age: 18,
    get: function(){
        return '名字:' + this.name + '年紀:' + this.age;
    }
}

這時的 person 就是一個對象函數

讀取對象的屬性,有兩種方式:[' '],(使用中括號,裏面是有引號的)優化

person.name   //'zdx'
person['age']    //18

以前說過,對象的建立 有三種 方式: 字面量,new,Object.create;ui

可是,這種簡單的建立,並不能知足,咱們實際開發的要求。this

好比,每一個人的名字,年紀不同,咱們不可能,每一個人都寫一個對象,那不得炸了。prototype

var personA = {
    name: 'A',
    age: 28
}

var personB = {
    name: 'B',
    age: 22
}

因此,這時候,咱們就須要一些建立模式。code

<br/>對象

(一)、工廠模式

工廠模式呢,就是咱們寫一個方法,而後經過這個方法複製出咱們須要的對象。咱們須要多少個,就複製多少個。(注意這裏是用方法(函數) 來生成對象)繼承

var createPerson = function(name,age){
    var obj = {},
    obj.name = name,
    obj.age = age
}

而後咱們就能夠這樣使用了。

var personA = createPerson('A', 28);
var personB = createPerson('B', 22);

可是呢,這種模式,建立的對象,沒法歸類。也就是說,並不知道 personA,personB 等 是 屬於哪一個對象的(哪一個構造函數或者類)。

具備相同特性的一類事物,把它總結成一個 ;好比,人類,有男人,女人,小孩。把他們總結成了人類。

好比:

var obj = {};
obj instanceof Object;    //true
//instanceof  方法 能夠判斷 前者是不是 後者的實例對象

var arr = [];
arr instanceof Array;    //true

因而,構造函數模式來了。

<br/>

(二)、構造函數模式(類)

要想知道,男人,女人 屬於什麼類, 人類? 獸類? 這時候就須要 構造函數(類)了。

function Person(name, sex){     //注意,咱們一般把構造函數(類)的首字母大寫!
    this.name = name;
    this.sex = sex;
}
var personA = new Person('張三', '男');
var personB = new Person('李四', '女');

console.log( personA instanceof Person );    //true
console.log( personB instanceof Person );    //true

這時候 建立出來的對象, 就知道 它屬於哪一個構造函數(類)了。

<br/>

這裏爲啥用了 this 呢?new 一個函數, 爲啥會建立出 一個實例對象(咱們把 new 出來的對象 稱爲 實例對象,簡稱實例)呢?

  • 這步,以前的原型鏈中說過,若是你懂了,能夠簡單過一眼,加強記憶。

這個呢,就要理解new 到底幹了啥呢:

  1. 新建一個對象;
  2. 將該對象,即實例指向構造函數的原型;
  3. 將構造函數的this,指向該新建的對象;
  4. 返回該對象,即返回實例。下面,手寫一個方法 實現new 的功能。

<br/>

function New(func){    //func 指傳進來的構造函數
    var obj = {};   //這裏就直接新建一個空對象了,不用new Object了,效果同樣的,由於我感受這裏講實現new的功能   再用 new 就不太好。
    
    if(func.prototype != null){
        obj.__proto__ = func.prototype;
    }
  
    func.apply(obj,Array.prototype.slice.call(arguments, 1));
    //把func構造函數裏的this 指向obj對象(你能夠理解成func構造函數的this 替換成 obj對象);
    //Array.prototype.slice.call(arguments, 1);這個就是把New 函數,func 以後傳進來的參數,轉成數組。
   //把該參數數組,傳入func構造函數裏,並執行func構造函數,好比:屬性賦值。

    return obj;
    
}

驗證下:

function Person(name, sex){
    this.name = name;
    this.sex = sex;
    this.getName = function(){
        return this.name;
    }
}

var p = New(Person,"周大俠啊", "男");

console.log( p.getName() );   //"周大俠啊"

console.log( p instanceof Person );    //true

去掉new 的寫法就是這樣:

function Person(name, sex){
    var obj = {};
    obj.__proto__ = Person.prototype;
    obj.name = name;
    obj.sex = sex;
    obj.getName = function(){
        return obj.name;
    }
    return obj;
}

var p = Person("周大俠啊", "男");

console.log( p.getName() );   //"周大俠啊"

console.log( p instanceof Person );    //true

<br/>

使用 new 就簡化了步驟。

這種模式,也有弊端,你看出來了嗎? 有木有發現,每一個實例對象 都有 相同 的 getName 方法,這樣,是否是就但佔資源了呢,100個實例對象,就新建了 100 個 getName 方法。

原型模式開始!

<br/>

(三)、原型模式

以前的原型鏈說過,屬性、方法的讀取是沿着原型鏈查找的,也就是說,在構造函數的原型對象上 建立的 屬性、方法,它的實例對象都可以使用。

function Person(){};
Person.prototype.name = "周大俠啊";
Person.prototype.sex = "男";
Person.prototype.getName  = function(){
    return Person.prototype.name;
}

var p = new Person();
console.log( p.getName() );   //"周大俠啊"
console.log( p.hasOwnProperty("getName") );   //false   getName 不是 p實例的屬性

這樣,建立的實例對象 都能使用 getName ,而且,也不會給每一個實例對象,添加該屬性。

不過,你發現了嗎,這樣建立的實例對象,都是固定的屬性值, 一更改,全部的實例對象獲取的值,也都改變了。

那麼說了半天,這都是啥呢,別急,正由於上面作的鋪墊,纔有更好的 方法。我以爲,眼尖的同窗,可能已經知道了。

<br/>

構造函數模式 與 原型模式 相結合的模式。

構造函數模式,能夠建立 每一個 實例對象 本身的屬性, 而原型模式,能夠共享,同一個方法。

因此,咱們通常,把對象本身的屬性、方法 ,用構造函數模式建立; 公共的方法,用原型模式 建立。

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.getName = function(){
    return this.name;
}

var p = new Person("周大俠啊", 22);
p.getName();

<br/>

這樣就能夠完美的建立對象了。

要是你有餘力,能夠想一想,這裏的 this.namethis 是怎麼回事。

可是,上面這個還能夠優化,要是 Person.prototype 上有不少方法, 這種寫法就很很差,通常,咱們用這種寫法:

function Person(name, age){
    this.name = name;
    this.age = age;
}
//新建一個對象,給該對象添加 方法, 而後把該對象 賦值給Person.prototype ,該對象就成爲 Person (構造函數)的原型對象了。

Person.prototype = {
    constructor : Person,   //給新建的對象,添加constructor 屬性,創建與構造函數之間的聯繫。
    getName : function(){
        return this.name;
    },
    getAge : function(){
        return this.age;
    }
}

var p = new Person("周大俠啊", 22);

p.getName();

<br/>

這裏的 this 又看懂是怎麼回事了嗎? 那我就簡單說一下 this 的知識吧。

關於 this 估計都據說過 誰調用它,this就指向誰,這種說法,不嚴謹,this 的指向,是 在函數調用(運行)時肯定的

上面的:

Person.prototype = {
    constructor : Person, 
    getName : function(){
        return this.name;
    },
}
var p = new Person("周大俠啊", 22);
p.getName();

thisgetName 函數裏,而getName 真正運行 的地方 是 p.getName,p 是調用者,因此, this 就指向 p(換句話說,這時的this 就是 p)。

你要理解 精髓, 函數調用(運行)時 肯定的調用者 之間的關係。

看這個:

var a = 10;
var b = {
    a : 20,
    say : function(){
        console.log(this.a);
    }
}

var c = b.say;
c();    //10   非嚴格模式下,獨立調用(無調用者)this 指向全局對象

包含 this 的函數 正在調用(運行) 的時候 是 c(); 此時,無調用者,指向全局對象。

<br/>
<br/>

2、繼承

上面作了辣麼多鋪墊,終於要開始繼承的講解啦!什麼是繼承呢,其實呢,就是你 想要使用 別人的屬性,或者方法; 這時候就要利用繼承來實現。

既然是 獲得別人的屬性,方法,就是繼承,那麼不就是 除了 Object.prototype 其餘都是繼承了? dui ! 你說的沒錯...哈哈

因此,繼承的一種就是基於 原型鏈的了 ,就是屬性的查找,讀取。

另外一種就是 構造函數繼承了。

首先來一個須要被繼承的目標

//父類:
function Person(name) {
    this.name = name;
}

Person.prototype = {
    constructor: Person,
    getName : function() {
        return this.name;
    }
}

<br/>

(一)、構造函數的繼承

//子類:
function Son(name, age) {   
    Person.call(this, name);    //call 方法,把前面函數的this 指向 給定的對象,第二個參數開始,傳入 參數,並執行函數。
    this.age = age;
}

//就至關於
function Son(name, age) {
    //Person(name); 此時 Person 裏的 this  等於當前的this
    this.age = age;
}

//等同於
function Son(name, age) {
    this.name = name,
    this.age = age
}

下面檢驗一下:

var s1 = new Son('周大俠啊', 22);
console.log(s1.name);  //'周大俠啊' 
console.log(s1.getName());   //報錯,這是Person原型對象上的方法

<br/>

(二)、原型鏈的繼承

一、經過new一個父類實例

這種是,new 一個父類實例,而後把son.prototype 添加到原型鏈上去。

//new 一個 Person 實例 賦給 Son.prototype
Son.prototype = new Person();

//給子類的原型加方法:
Son.prototype.get = function() {
        return this.name + this.age;
}

上面的 Son.prototype = new Person() 實際上就是這樣的

//內部就是
var obj = {};

obj.__proto__ = Person.prototype;

Son.prototype = obj

//把 obj 賦給 Son.prototype 。而obj 這個實例  又指向了Person的原型對象,
//這樣,Son 就添加到了 Person 的原型鏈上了。

<br/>

因此:

//給子類的原型加方法:
Son.prototype.get = function() {
        return this.name + this.age;
} 

//就是:
obj.get = function(){    //經過給obj添加方法 而後賦值給Son的原型對象
    return this.name + this.age;
};

驗證:

var s2 = new Son('周大俠啊', 22);

console.log(s2.get());  //'周大俠啊22' 

console.log(s2.getName());   //'周大俠啊'

<br/>

二、因而乎,咱們就能夠本身封裝一個繼承方法

function create(proto, options){     //proto表示父類的原型對象,options表示給子類添加的屬性
    var obj = {};
    obj.__proto__ = proto;
    return Object.defineProperties(obj,options);    //Object.defineProperties方法直接在一個對象上定義新的屬性或修改現有屬性,並返回該對象。因此這裏就直接return了
    }
    
//繼承  就這樣寫了
Son.prototype = create(Person.prototype,{
    constructor: {    //這種格式的賦值寫法, 是 defineProperties 方法規定的寫法。
        value: Son
    },
    get: {
        value: function(){
            return this.name + this.age;
        }
    }
})

//驗證一下
var s3 = new Son("zdx",22);

console.log(s3.getName());     //'zdx'

console.log(s3.get());         //'zdx22'

Object.defineProperties具體用法點這裏

<br/>

三、ES5中就有個Object.create 方法 它就實現了剛剛封裝的create 方法

這裏是它具體用法

能夠直接使用它來完成繼承:

Son.prototype = Object.create(Person.prototype,{
    constructor: {
        value: Son
    },
    get: {
        value: function(){
            return this.name + this.age;
        }
    }
})

//驗證一下
var s4 = new Son("zdx",22);

console.log(s4.getName());     //zdx

console.log(s4.get());         //zdx22
相關文章
相關標籤/搜索