溫故js系列(15)-原型&原型鏈&原型繼承

前端學習:教程&開發模塊化/規範化/工程化/優化&工具/調試&值得關注的博客/Git&面試-前端資源彙總前端

歡迎提issues斧正:原型&原型鏈&原型繼承git

JavaScript-原型&原型鏈&原型繼承

JavaScript的原型是一個重要的知識點,不少擴展應用都是從原型出發的。要說原型,咱們先簡單說一下函數建立過程。上一篇文章用閉包實現類和繼承中用的是原型繼承,今天就講一講原型繼承。更多繼承在後面的文章中更新。github

函數建立過程

function Xzavier() {};

1.建立一個對象(有constructor屬性及[[Prototype]]屬性),其中[[Prototype]]屬性不可訪問、不可枚舉。
2.建立一個函數(有name、prototype屬性),再經過prototype屬性 引用第1步建立的對象。
3.建立變量Xzavier,同時把函數的引用賦值給變量Xzavier。面試

構造函數

構造函數是用來新建同時初始化一個新對象的函數,因此,任何一個函數均可以是構造函數。只是咱們在寫代碼的時候通常首字母大寫以便區分使用。閉包

原型

每一個函數在建立的時候js都自動添加了prototype屬性,這就是函數的原型,原型就是函數的一個屬性,相似一個指針。原型在函數的建立過程當中由js編譯器自動添加。模塊化

function Xzavier() {
    this.name = 'xzavier';
    this.sex = 'boy';
    this.job = "jser";
}
//給A添加屬性
Xzavier.age = 23;
//給A的原型對象添加屬性
Xzavier.prototype.sports = function() {console.log('basketball')}
Xzavier.prototype = {
    hobbit1: function() {console.log('basketball');},
    hobbit2: function() {console.log('running');}
};

原型鏈

在JavaScript中,每一個對象都有一個[[Prototype]]屬性,其保存着的地址就構成了對象的原型鏈
[[Prototype]]屬性是js編譯器在對象被建立時自動添加的,其取值由new運算符的右側參數決定。字面量的方式可轉化爲new Obejct();函數

var x = new Xzavier();
vae o = {};  //var o = new Obejct();

經過對象的[[Prototype]]保存對另外一個對象的引用,經過這個引用往上進行屬性的查找,這就是原型鏈查找機制工具

對象在查找某個屬性的時候,會首先遍歷自身的屬性,若是沒有則會繼續查找[[Prototype]]引用的對象,若是再沒有則繼續查找[[Prototype]].[[Prototype]]引用的對象,依次類推,直到[[Prototype]].….[[Prototype]]undefined學習

var str = new String('xzavier');
str

圖片描述

Object.prototype[[Prototype]]就是undefined優化

function Xzavier() {
    this.name = 'xzavier';
}
var x = new Xzavier();
x.age = 23;

console.log(x.job);  // 獲取x的job屬性 undefined

一、遍歷x對象自己,結果x對象自己沒有job屬性
二、找到x的[[Prototype]],也就是其對應的對象Xzavier.prototype,同時進行遍歷。 Xzavier.prototype也沒有job屬性
三、找到Xzavier.prototype對象的[[Prototype]],指向其對應的對象Object.prototype。Object.prototype也沒有job屬性
四、尋找Object.prototype的[[Prototype]]屬性,返回undefined。

函數的變量和內部函數

說了函數的原型鏈,這裏須要說一下的變量和內部函數。

私有變量和內部函數

私有成員,即定義函數內部的變量或函數,外部沒法訪問。

function Xzavier(){
    var name = "xzavier"; //私有變量
    var sports = function() {console.log('basketball')}; //私有函數 
}
var x = new Xzavier();
x.name;  //undefined

若是要訪問,須要對外提供接口。

function Xzavier(){
    var name = "xzavier"; //私有變量
    var sports = function() {console.log('basketball')}; //私有函數
    return{
        name: name,
        sports: sports
    }
}
var x = new Xzavier();
x.name;  //"xzavier"

靜態變量和內部函數

用點操做符定義的靜態變量和內部函數就是實例不能訪問到的變量和內部函數。只能經過自身去訪問。

function Xzavier(){
    Xzavier.name = "xzavier"; //靜態變量
    Xzavier.sports = function() {console.log('basketball')}; //靜態函數 
}
Xzavier.name; //"xzavier"
var x = new Xzavier();
x.name;  //undefined

實例變量和內部函數

經過this定義給實例使用的屬性和方法。

function Xzavier(){
    this.name = "xzavier"; //實例變量
    this.sports = function() {console.log('basketball');}; //實例函數 
}
Xzavier.name; //undefined
var x = new Xzavier();
x.name;  //"xzavier"

原型鏈繼承

有了原型鏈,就能夠藉助原型鏈實現繼承。

function Xzavier() {
    this.name = 'xzavier';
    this.sex = 'boy';
    this.job = "jser";
}

function X() {};

X的原型X.prototype原型自己就是一個Object對象。F12打開控制檯輸入函數,再打印X.prototype:

Object {
    constructor: X()
    __proto__: Object
}

prototype自己是一個Object對象的實例,因此其原型鏈指向的是Object的原型。

X.prototype = Xzavier.prototype

X.prototype = Xzavier.prototype;

這樣至關於把X的prototype指向了Xzavier的prototype;
這樣只是繼承了Xzavier的prototype方法,Xzavier中的自定義方法則不繼承。

X.prototype.love = "dog";

這樣也會改變Xzavier的prototype,因此這樣基礎就很差。

X.prototype = new Xzavier()

X.prototype = new Xzavier();

這樣產生一個Xzavier的實例,同時賦值給X的原型,也即X.prototype至關於對象:

{
    name: "xzavier", 
    sex: "boy", 
    job: "jser",
    [[Prototype]] : Xzavier.prototype
}

這樣就把Xzavier的原型經過X.prototype.[[Prototype]]這個對象屬性保存起來,構成了原型的連接。
不過,這樣X產生的對象的構造函數發生了改變,由於在X中沒有constructor屬性,只能從原型鏈找到Xzavier.prototype,讀出constructor:Xzavier。

var x = new X;
console.log(x.constructor);

輸出:
Xzavier() {
    this.name = 'xzavier';
    this.sex = 'boy';
    this.job = "jser";
}

手動改正:

X.prototype.constructor = X;

這是X的原型就多了個屬性constructor,指向X。這樣就OK。

function Xzavier() {
    this.name = 'xzavier';
    this.sex = 'boy';
    this.job = "jser";
}

function X(){}
X.prototype = new Xzavier();
var x = new X()
x.name  // "xzavier"

[[Prototype]],__proto__,prototype

關於咱們常常遇到的[[Prototype]],__proto__,prototype

咱們在控制檯打印 var str = new String('xzavier'),展開查看屬性時,只會看到__proto__,因此起做用的是__proto____proto__是對象的內置屬性,是每一個對象都有的屬性,可是這個屬性使用不標準,因此不建議直接使用。可是,咱們的原型鏈就是基於 __proto__的。經過構造函數獲得的實例的 __proto__ 屬性,指向其對應的原型對象 String.prototype,這正如文中咱們打印 var str = new String('xzavier') 中看到的同樣。

[[Prototype]]是一個隱藏屬性,指向的是這個對象的原型。幾乎每一個對象有一個[[prototype]]屬性。

prototype是每一個函數對象都具備的屬性,指向原型對象,若是原型對象被添加屬性和方法,那麼由應的構造函數建立的實例會繼承prototype上的屬性和方法,這也是咱們在代碼中常常遇到的。構造函數產生實例時,實例經過其對應原型對象的 constructor 訪問對應的構造函數對象。因此,咱們繼承出來的實例每每沒有constructor,只是經過原型鏈查找,會讓咱們產生錯覺,可參見本系列原型鏈文章。

hasOwnProperty

hasOwnProperty是Object.prototype的一個方法,判斷一個對象是否包含自定義屬性而不是原型鏈上的屬性。
hasOwnProperty 是JavaScript中惟一一個處理屬性可是不查找原型鏈的函數。

function Xzavier() {
    this.name = 'xzavier';
    this.sex = 'boy';
    this.job = "jser";
}
//給A的原型對象添加屬性
Xzavier.prototype.sports = function() {console.log('basketball');};

var x = new Xzavier();
x.name; // 'xzavier'
'sex' in x; // true

x.hasOwnProperty('job'); // true
x.hasOwnProperty('sports'); // false

當檢查對象上某個屬性是否存在時,hasOwnProperty 是很是推薦的方法。

繼承在js中使用頻繁。ES6也設計了專門的CLASS語法糖供開發者使用。
更多繼承方法在新的文章中更新...

可貴週末,應該運動O(∩_∩)O~ 打打籃球,運動運動,有代碼,有籃球,有生活。。。長時間不動肩膀(其實身體各地方都是),還真疼啊。但願程序猿們都健健康康的!!!

相關文章
相關標籤/搜索