探索 proto & prototype 與繼承之間的關係

原型

原型的背景

首先,你應該知道javascript是一門面向對象語言。javascript

是對象,就具備繼承性。java

繼承性,就是子類自動共享父類的數據結構和方法機制。git

而prototype 和 __proto__ 的存在就是爲了創建這種子類與父類間的聯繫。github

咱們將prototype稱做原型,將經過__proto__來創建起來的對象與對象的關係稱做原型鏈。數據結構

下面,經過建立一個簡單對象,來探索原型和原型鏈究竟是什麼。ide

原型與原型鏈

首先,建立一個最簡單的對象函數

function Foo(){}
var o = new Foo();

ps:這是剛從java轉入JavaScript時,最令我費解的一段代碼,憑什麼一個function就能夠用來建立一個對象。下面就是new 關鍵字的分解動做。。。這個關鍵字究竟作了什麼,能建立一個對象。。this

這個建立的過程,能夠分解成下面代碼spa

function Foo(){}
 // 建立一個基本對象
 var o = new Object();
 // 建立對象和父類原型之間的連接 
 o.__proto__ = Foo.prototype;
 // 執行構造函數
 Foo.call(o);

爲了更好的理解這段代碼,咱們先理解這幾個概念prototype

  • 什麼是構造函數constructor

    構造函數就是對象建立時,自動調用的方法

  • prototype

    prototype,長這樣

    {
        constructor: f foo()
        __proto__: Object
    }

    它是一個對象,存儲着一類事物的基本信息,因此能夠將它稱做類。

  • __proto__

    __proto__,這個屬性用來創建對象和類之間的關係。

有了這些概念,咱們來分析建立對象的過程當中,究竟作了些什麼.
建立一個對象,會作以下三件事。

  • 建立一個基本對象 new Object()
  • 創建新對象與原型(我把它理解爲類)之間的鏈接
  • 執行構造函數

小結:prototype能夠理解爲類,也就是存儲一類事物的基本信息。__proto__能夠理解爲一條線索,用來創建原型(類)和對象之間的關係。

原型、原型鏈和繼承之間的關係。

繼承,須要知足以下三個要求。

  • 子類繼承父類的成員變量
  • 子類繼承父類的方法
  • 子類繼承父類的構造器,若是父類的構造函數帶有參數,那麼子類中應該顯示調用

咱們該如何實現繼承呢?

// 建立一個構造函數,我認爲 a.prototype就是父類對象。
function a(x,y) {
    a.prototype.x = x;
    a.prototype.y = y
}
// 爲父類對象添加一個method 
a.prototype.console = function() {
    console.log(this.x);
    console.log(this.y);
}
//建立子類構造函數
function b(x,y) {
    // 子類顯示的調用父類構造方法
    a.call(this,x,y);
}
// 子類繼承父類的成員變量以及父類的方法
b.prototype = Object.create(a.prototype); = b.prototype.constructor = b;
// 建立對象
var c = new b(1,2);
// 這裏Object.create 是用來建立一個新的prototype,用來記錄新類的信息,並與父類創建聯繫
 Object.create = function() {
  //建立一個基本對象
  var temp = new Object();
  //與父類的的原型創建聯繫
  temp.__proto__ = proto;
  //返回新類的原型
  return temp;
 }

小結:繼承關係的實現,作了以下兩件事情

  • 子類顯示的調用父類的構造函數
  • 子類經過原型來與父類創建聯繫,從而能讓子類擁有父類的成員變量和方法。

原型就是類,原型鏈就是來創建子類和父類之間的聯繫。

原型鏈的實際表現

先建立一個類

function people() {}
// 爲父類對象添加一個method
people.prototype.run = function() {
   console.log("I am running");
}

經過類來建立一個對象

var p = new people();
p.run();
// i am running

這裏p對象長這樣

{
__proto__: Object
}

很顯然,這個對象之中並無run方法。

可是它卻能調用run,由於它會經過__proto__(原型鏈)來尋找類中的方法。

常常有人這麼問proto 和prototype有什麼區別?

我想看到這裏,你應該很明白了。

  • prototype 一個存儲類信息的對象,只存在function中.(下圖中綠塊)
  • proto 單純是對象用來指向上級的一連接。(看下圖黃線)

那麼又有人會問function中__proto__又是什麼關係呢?

function 自己是對象,因此固然也有本身原型。function繼承於Function.(看下圖藍線)。

clipboard.png

下面介紹一下原型繼承和類繼承的關係。

原型繼承和類繼

An example that shows the difference between creating a JavaScript class and subclass in ES5 and ES6.

ES5

'use strict';

/**
 * Shape class.
 * 
 * @constructor
 * @param {String} id - The id.
 * @param {Number} x  - The x coordinate.
 * @param {Number} y  - The y coordinate.
 */
function Shape(id, x, y) {
    this.id = id;
    this.setLocation(x, y);
}

/**
 * Set shape location.
 * 
 * @param {Number} - The x coordinate.
 * @param {Number} - The y coordinate.
 */
Shape.prototype.setLocation = function(x, y) {
    this.x = x;
    this.y = y;
};

/**
 * Get shape location.
 * 
 * @return {Object}
 */
Shape.prototype.getLocation = function() {
    return {
        x: this.x,
        y: this.y
    };
};

/**
 * Get shape description.
 * 
 * @return {String}
 */
Shape.prototype.toString = function() {
    return 'Shape("' + this.id + '")';
};

/**
 * Circle class.
 * 
 * @constructor
 * @param {String} id     - The id.
 * @param {Number} x      - The x coordinate.
 * @param {Number} y      - The y coordinate.
 * @param {Number} radius - The radius.
 */
function Circle(id, x, y, radius) {
    Shape.call(this, id, x, y);
    this.radius = radius;
}
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;

/**
 * Get circle description.
 * 
 * @return {String}
 */
Circle.prototype.toString = function() {
    return 'Circle > ' + Shape.prototype.toString.call(this);
};

// test the classes
var myCircle = new Circle('mycircleid', 100, 200, 50); // create new instance
console.log(myCircle.toString()); // Circle > Shape("mycircleid")
console.log(myCircle.getLocation()); // { x: 100, y: 200 }

ES6

'use strict';

/**
 * Shape class.
 * 
 * @constructor
 * @param {String} id - The id.
 * @param {Number} x  - The x coordinate.
 * @param {Number} y  - The y coordinate.
 */
class Shape(id, x, y) {
    constructor(id, x, y) { // constructor syntactic sugar
        this.id = id;
        this.setLocation(x, y);
    }
    
    /**
     * Set shape location.
     * 
     * @param {Number} - The x coordinate.
     * @param {Number} - The y coordinate.
     */
    setLocation(x, y) { // prototype function
        this.x = x;
        this.y = y;
    }
    
    /**
     * Get shape location.
     * 
     * @return {Object}
     */
    getLocation() {
        return {
            x: this.x,
            y: this.y
        };
    }
    
    /**
     * Get shape description.
     * 
     * @return {String}
     */
    toString() {
        return `Shape("${this.id}")`;
    }
}

/**
 * Circle class.
 * 
 * @constructor
 * @param {String} id     - The id.
 * @param {Number} x      - The x coordinate.
 * @param {Number} y      - The y coordinate.
 * @param {Number} radius - The radius.
 */
function Circle extends Shape {
    constructor(id, x, y, radius) {
        super(id, x, y); // call Shape's constructor via super
        this.radius = radius;
    }
    
    /**
     * Get circle description.
     * 
     * @return {String}
     */
    toString() { // override Shape's toString
        return `Circle > ${super.toString()}`; // call `super` instead of `this` to access parent
    }
}

// test the classes
var myCircle = new Circle('mycircleid', 100, 200, 50); // create new instance
console.log(myCircle.toString()); // Circle > Shape("mycircleid")
console.log(myCircle.getLocation()); // { x: 100, y: 200 }

這段代碼,本身體會。。。。

相關文章
相關標籤/搜索