理解Javascript的原型和原型鏈

前言

本文2088字,閱讀大約須要5分鐘。

總括: 結合實例闡述了原型和原型鏈的概念並總結了幾種建立對象的方法,擴展原型鏈的方法。javascript

祿無常家,福無家門。html

正文

原型

Javascript中有一句話,叫一切皆是對象,固然這句話也不嚴謹,好比nullundefined就不是對象,除了這倆徹底能夠說Javascript一切皆是對象。而Javascript對象都有一個叫作原型的公共屬性,屬性名是_proto_。這個原型屬性是對另外一個對象的引用,經過這個原型屬性咱們就能夠訪問另外一個對象全部的屬性和方法。好比:前端

let numArray = [1, 2, -8, 3, -4, 7];

Array對象就有一個原型屬性指向Array.prototype,變量numArray繼承了Array.prototype對象全部的屬性和方法。java

這就是爲何能夠直接調用像sort()這種方法:數組

console.log(numArray.sort()); // -> [-4, -8, 1, 2, 3, 7]

也就是說:瀏覽器

numArray.__proto__ === Array.prototype // true

對於其餘對象(函數)也是同樣(好比Date(),Function(), String(),Number()等);sass

當一個構造函數被建立後,實例對象會繼承構造函數的原型屬性,這是構造函數的一個很是重要的特性。在Javascript中使用new關鍵字來對構造函數進行實例化。看下面的例子:函數

const Car = function(color, model, dateManufactured) {
  this.color = color;
  this.model = model;
  this.dateManufactured = dateManufactured;
};
Car.prototype.getColor = function() {
    return this.color;
};
Car.prototype.getModel = function() {
    return this.model;
};
Car.prototype.carDate = function() {
    return `This ${this.model} was manufactured in the year ${this.dateManufactured}`
}
let firstCar = new Car('red', 'Ferrari', '1985');
console.log(firstCar);
console.log(firstCar.carDate());

上面的例子中,方法getColor,carDate,getModel都是對象(函數)Car的方法,而Car的實例對象firstCar能夠繼承Car原型上的一切方法和屬性。性能

結論:每個實例對象都有一個私有屬性_proto_,指向它的構造函數的原型對象(prototype)。學習

原型鏈

在Javascript中若是訪問一個對象自己不存在的屬性或是方法,就首先在它的原型對象上去尋找,若是原型對象上也不存在,就繼續在原型對象的原型對象上去尋找,直到找到爲止。那麼原型對象有盡頭麼?全部對象的原型盡頭是Object.prototype,那麼Object.prototype這個對象的_proto_指向啥呢?答案是null。咱們平常開發中用到的絕大多數對象的_proto_基本不會直接指向Object.prototype,基本都是指向另外一個對象。好比全部的函數的_proto_都會指向Function.prototype,全部數組的_proto_都會指向Array.prototype

let protoRabbit = {
  color: 'grey',
  speak(line) {
        console.log(`The ${this.type} rabbit says ${line}`);
  }
};
let killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "assassin";
killerRabbit.speak("SKREEEE!");

上面代碼中變量protoRabbit設置爲全部兔子對象的公有屬性對象集,killerRabbit這隻兔子經過Object.create方法繼承了protoRabbit的全部屬性和方法,而後給killerRabbit賦值了一個type屬性,再看下面的代碼:

let mainObject = {
    bar: 2
};
// create an object linked to `anotherObject`
let myObject = Object.create( mainObject );
for (let k in myObject) {
  console.log("found: " + k);
}
// found: bar
("bar" in myObject);

如上變量myObject自己並無bar屬性,但這裏會循着原型鏈一層一層往上找,直到找到或者原型鏈結束爲止。若是到原型鏈盡頭仍是沒找到該屬性,那麼訪問該屬性的時候就會返回undefined了。

使用for...in關鍵字對對象進行迭代的過程,和上面訪問某個屬性循着原型鏈查找相似,會去遍歷全部原型鏈上的屬性(不論屬性是否可枚舉)。

let protoRabbit = {
  color: 'grey',
  speak(line) {
      console.log(`The ${this.type} rabbit says ${line}`);
  }
};
let killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "assassin";
killerRabbit.speak("SKREEEE!");

上面的代碼中訪問speak的效率很高,但若是咱們想建立不少個Rabbit對象,就必需要重複寫不少代碼。而這正是原型和構造函數的真正用武之地。

let protoRabbit = function(color, word, type) {
  this.color = color;
  this.word = word;
  this.type = type;
};
protoRabbit.prototype.getColor = function() {
    return this.color;
}
protoRabbit.prototype.speak = function() {
    console.log(`The ${this.type} rabbit says ${this.word}`);
}
let killerRabbit = new protoRabbit('grey', 'SKREEEEE!', 'assassin');
killerRabbit.speak();

如上代碼,使用構造函數的方式就能夠節省不少的代碼。

結論:每個實例對象都有一個私有屬性_proto_,指向它的構造函數的原型對象(prototype)。原型對象也有本身的_proto_,層層向上直到一個對象的原型對象爲null。這一層層原型就是原型鏈。

附贈一張原型鏈的圖:

建立對象的四種方法

字面量對象

這是比較經常使用的一種方式:

let obj = {};
構造函數建立

構造函數建立的方式更多用來在Javascript中實現繼承,多態,封裝等特性。

function Animal(name) {
    this.name = name;
}
let cat = new Animal('Tom');
class建立

class關鍵字是ES6新引入的一個特性,它實際上是基於原型和原型鏈實現的一個語法糖。

class Animal {
  constructor(name) {
    this.name = name;
  }
}
let cat = new Animal('Tom');

擴展原型鏈的四種方法

構造函數建立

上面例子有用到使用構造函數建立對象的例子,咱們再來看一個實際的例子:

function Animal(name) {
    this.name = name;
}
Animal.prototype = {
    run() {
    console.log('跑步');
    }
}
let cat = new Animal('Tom');
cat.__proto__ === Animal.prototype; // true
Animal.prototype.__proto__ === Object.prototype; // true
優勢:支持目前以及全部可想象到的瀏覽器(IE5.5均可以使用). 這種方法很是快,很是符合標準,而且充分利用JIST優化。

缺點:爲使用此方法,這個問題中的函數必需要被初始化。另外構造函數的初始化,可能會給生成對象帶來並不想要的方法和屬性。

Object.create

ECMAScript 5 中引入了一個新方法: Object.create()。能夠調用這個方法來建立一個新對象。新對象的原型就是調用 create 方法時傳入的第一個參數:

var a = {a: 1}; 
// a ---> Object.prototype ---> null
var b = Object.create(a);
b.__proto__ === a; // true
優勢: 支持當前全部非微軟版本或者 IE9 以上版本的瀏覽器。容許一次性地直接設置 __proto__ 屬性,以便瀏覽器能更好地優化對象。同時容許經過 Object.create(null) 來建立一個沒有原型的對象。

缺點:不支持 IE8 如下的版本;這個慢對象初始化在使用第二個參數的時候有可能成爲一個性能黑洞,由於每一個對象的描述符屬性都有本身的描述對象。當以對象的格式處理成百上千的對象描述的時候,可能會形成嚴重的性能問題。

Object.setPrototypeOf

語法:

Object.setPrototypeOf(obj, prototype)

參數:

參數名 含義
obj 要設置其原型的對象。
prototype 該對象的新原型(一個對象 或 null).
var a = { n: 1 };
var b = { m : 2 };
Object.setPrototypeOf(a, b);
a.__proto__ === b; // true
優勢:支持全部現代瀏覽器和微軟IE9+瀏覽器。容許動態操做對象的原型,甚至能強制給經過 Object.create(null) 建立出來的沒有原型的對象添加一個原型。

缺點:這個方式表現並很差,應該被棄用;動態設置原型會干擾瀏覽器對原型的優化;不支持 IE8 及如下的瀏覽器版本。

_proto_
var a = { n: 1 };
var b = { m : 2 };
a.__proto__ = b;
a.__proto__ === b; // true

使用_proto_也能夠動態設置對象的原型。

優勢:支持全部現代非微軟版本以及 IE11 以上版本的瀏覽器。將 __proto__ 設置爲非對象的值會靜默失敗,並不會拋出錯誤。

缺點:應該徹底將其拋棄由於這個行爲徹底不具有性能可言;干擾瀏覽器對原型的優化;不支持 IE10 及如下的瀏覽器版本。

以上。


能力有限,水平通常,歡迎勘誤,不勝感激。

訂閱更多文章可關注公衆號「前端進階學習」,回覆「666」,獲取一攬子前端技術書籍

前端進階學習

相關文章
相關標籤/搜索