一文搞懂原型和原型鏈

前瞻

JavaScript是面對對象編程,可是它又跟其餘編程語言不同,不一樣之處是JavaScript面對對象並非依賴於抽象類,而是經過原型鏈。在C++Java使用new命令時,都會調用"類"的構造函數。而在Javascript語言中,new命令後面跟的不是類,而是構造函數。可是,用構造函數生成實例對象,有一個缺點,那就是沒法共享屬性和方法。爲了解決這個問題,因而在構造函數設置了prototype屬性,也就是原型。前端

從一個構造函數提及

用構造函數生成實例對象,有一個缺點,那就是沒法共享屬性和方法。從而形成對系統資源的浪費。具體例子以下:es6

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.meow = function () {
    console.log('喵喵');
  };
}

var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');

cat1.meow === cat2.meow
// false
複製代碼

從上面的代碼,能夠cat1cat2meow方法是獨立的可是實現的功能都是同樣的,這既沒有必要,又浪費系統資源,由於全部meow方法都是一樣的行爲,徹底應該共享。這個問題的解決方法,就是JavaScript構造函數中添加prototype屬性。編程

prototype是什麼?

只有函數纔有prototype屬性,又稱爲顯式原型。prototype屬性包含一個對象,全部實例對象須要共享的屬性和方法,都放在這個對象裏面;那些不須要共享的屬性和方法,就放在構造函數裏面。瀏覽器

JavaScript規定,每一個函數都有一個prototype屬性,指向一個對象。bash

function f() {}
typeof f.prototype // "object"
複製代碼

對於普通函數來講,該屬性基本無用。可是,對於構造函數來講,生成實例的時候,該屬性會自動成爲實例對象的原型。前端工程師

function Animal(name) {
  this.name = name;
}
Animal.prototype.color = 'white';

var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color // 'white'
cat2.color // 'white'
複製代碼

上面代碼中,構造函數Animalprototype屬性,就是實例對象cat1cat2的原型對象。原型對象上添加一個color屬性,結果,實例對象都共享了該屬性。編程語言

原型對象的屬性不是實例對象自身的屬性。只要修改原型對象,變更就馬上體如今全部實例對象上函數

Animal.prototype.color = 'yellow';

cat1.color // "yellow"
cat2.color // "yellow"
複製代碼

若是實例對象自身就有某個屬性或方法,它就不會再去原型對象尋找這個屬性或方法。學習

cat1.color = 'black';

cat1.color // 'black'
cat2.color // 'yellow'
Animal.prototype.color // 'yellow';
複製代碼

總結一下,原型對象的做用,就是定義全部實例對象共享的屬性和方法。這也是它被稱爲原型對象的緣由,而實例對象能夠視做從原型對象衍生出來的子對象。ui

注:Object.prototype中Object是一個全局對象,也是一個構造函數,以及其餘基本類型的全局對象也都是構造函數。

_proto_又是什麼?

_proto_是每一個對象都有的屬性,又稱爲隱式原型。可是,_proto_不是一個規範屬性,只是部分瀏覽器實現了此屬性,對應的標準屬性是[[Peototype]]

大多狀況下,_proto_能夠理解爲‘構造函數的原型’,是實例和它的構造函數之間創建的連接,即_proto_ ===constructor.prototype

例子:

// 字面量方式
var a = {};
console.log(a.prototype);  //undefined
console.log(a.__proto__);  //Object {}

//構造器方式
var b = function(){}
console.log(b.prototype);  //b {}
console.log(b.__proto__);  //function() {}

// Object.create方式
var a1 = {}
var a2 = Object.create(a1)
console.log(b.prototype);  //undefined
console.log(b.__proto__);  //Object a1
複製代碼

ES6中的_proto_使用建議

本段摘自阮一峯ES6入門,具體解析能夠點擊查看。

一、_proto_屬性沒有寫入ES6的正文,而是寫入了附錄,

二、緣由是__proto__先後的雙下劃線,說明它本質上是一個內部屬性,而不是一個正式的對外的API,只是因爲瀏覽器普遍支持,才被加入了ES6

三、標準明確規定,只有瀏覽器必須部署這個屬性,其餘運行環境不必定須要部署,並且新的代碼最好認爲這個屬性是不存在的。

四、不管從語義的角度,仍是從兼容性的角度,都不要使用這個屬性,而是使用下面的Object.setPrototypeOf()(寫操做)、Object.getPrototypeOf()(讀操做)、Object.create()(生成操做)代替。

原型鏈是什麼?

每一個對象都會在其內部初始化一個屬性,就是prototype(原型),當咱們訪問一個對象的屬性時,若是這個對象內部不存在這個屬性,那麼他就會去prototype裏找這個屬性,這個prototype又會有本身的prototype,因而就這樣一直找下去,也就是咱們平時所說的原型鏈的概念。

原型鏈的終點

全部對象的原型最終均可以上溯到Object.prototypeObject.prototype對象有沒有它的原型呢?回答是Object.prototype的原型是nullnull沒有任何屬性和方法,也沒有本身的原型。所以,原型鏈的盡頭就是null

一、字符串原型鏈終點

let test = 'hello'

// 字符串原型
let stringPrototype = Object.getPrototype(test) 

// 字符串的原型是String對象
stringPrototype === String.Prototype  // true 

// String對象的原型是Object對象
Object.getPrototypeOf(stringPrototype) === Object.prototype // true 
複製代碼

二、函數原理鏈的終點

let test = function () {}
let fnPrototype = Object.getPrototype(test)
fnPrototype === Function.prototype // true
Object.getPrototypeOf(Function.prototype) === Object.Prototype // true

複製代碼

原型鏈是用來作什麼?

一、屬性查找

當JS引擎查找對象的屬性時,先查找對象自己是否存在該屬性,若是不存在,會去原型鏈上查找,但不會查找本自身的prototype。

用一個例子來形象說明一下:

let test = 'hello'

// 字符串原型
let stringPrototype = Object.getPrototype(test) 

// 字符串的原型是String對象
stringPrototype === String.Prototype  // true 

// String對象的原型是Object對象
Object.getPrototypeOf(stringPrototype) === Object.prototype // true 
複製代碼

二、拒絕查找原型鏈

hasOwnPrototype:指示對象自身屬性中是否具備指定的屬性。

語法:obj.hasOwnPrototype(prop)

參數prop:查找的屬性。

返回值:Boolean判斷是否存在。

let test = {'ABC': 'EDF' }
test.hasOwnPrototype('ABC') // true
test.hasOwnPrototype('toString') // false
複製代碼

該方法是在Object.prototype上,全部對象均可以使用,且會忽略掉那些從原型鏈上繼承的屬性。

擴展

constructor 屬性

prototype對象有一個constructor屬性,默認指向prototype對象所在的構造函數。

function P() {}
P.prototype.constructor === P // true
複製代碼

因爲constructor屬性定義在prototype對象上面,意味着能夠被全部實例對象繼承。

function P() {}
var p = new P();

p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false
複製代碼

上面代碼中,p是構造函數P的實例對象,可是p自身沒有constructor屬性,該屬性實際上是讀取原型鏈上面的P.prototype.constructor屬性。

constructor 屬性的做用

constructor屬性的做用是,能夠得知某個實例對象,究竟是哪個構造函數產生的。

舉個小栗子:

function F() {};
var f = new F();

f.constructor === F // true
f.constructor === RegExp // false
複製代碼

另外一方面,有了constructor屬性,就能夠從一個實例對象新建另外一個實例。

我再舉個小栗子:

function Constr() {}
var x = new Constr();

var y = new x.constructor();
y instanceof Constr // true
複製代碼

總結

天下無難事,只怕有心人。原型和原型鏈算是前端工程師的開發基礎了,原型雖然不太好理解,可是我相信只要有心學習和實踐,也沒有想象中的那麼難。但願,這篇文章對我本身幫組的同時,童鞋們看完以後也有必定的收穫。

相關文章
相關標籤/搜索