解讀js原型prototype_proto_constructor

關鍵字:
1.構造函數
2.prototype的使用
3.原型鏈
4. constructor
5.Object.create()
6. Object.prototype._ proto _
7.獲取原型對象方法的比較
寫作原因:理清原型的諸多概念


1.什么是構造函數?

構造函數形如??:
function Cat (name, color) {
 this.name = name;
 this.color = color;
}

var cat1 = new Cat('花花', '黑色');

cat1.name // '花花'
cat1.color // '黑色'

構造函數的缺點:
這樣做是對系統資源的浪費,因為同一個構造函數的對象實例之間,無法共享屬性.

2.為什么有prototype這個屬性?

JavaScript 的每個對象都繼承另一個對象,后者稱為“原型”(prototype)對象。只有null除外,它沒有自己的原型對象。

原型對象上的所有屬性和方法,都能被派生對象共享。這就是 JavaScript 繼承機制的基本設計。

通過構造函數生成實例對象時,會自動為實例對象分配原型對象。每一個構造函數都有一個prototype屬性,這個屬性就是實例對象的原型對象。

function Animal (name) {
  this.name = name;
}

Animal.prototype.color = '黑色';

var cat1 = new Animal('花花');
var cat2 = new Animal('喵喵');

cat1.color // '黑色'
cat2.color // '黑色'

上面代碼中,構造函數Animal的prototype對象,就是實例對象cat1和cat2的原型對象。在原型對象上添加一個color屬性。結果,實例對象都能讀取該屬性。

原型對象的屬性不是實例對象自身的屬性。只要修改原型對象,變動就立刻會體現在所有實例對象上。
當實例對象本身沒有某個屬性或方法的時候,它會到構造函數的prototype屬性指向的對象,去尋找該屬性或方法。這就是原型對象的特殊之處。
如果實例對象自身就有某個屬性或方法,它就不會再去原型對象尋找這個屬性或方法。
總結一下:原型對象的作用,就是定義所有實例對象共享的屬性和方法。這也是它被稱為原型對象的原因,而實例對象可以視作從原型對象衍生出來的子對象。

Animal.prototype.walk = function () {
  console.log(this.name + ' is walking');
};

上面代碼中,Animal.prototype對象上面定義了一個walk方法,這個方法將可以在所有Animal實例對象上面調用。

由于 JavaScript 的所有對象都有構造函數(只有null除外),而所有構造函數都有prototype屬性(其實是所有函數都有prototype屬性),所以所有對象都有自己的原型對象。

3.原型鏈是什么?有什么作用?

對象的屬性和方法,有可能是定義在自身,也有可能是定義在它的原型對象。由于原型本身也是對象,又有自己的原型,所以形成了一條原型鏈(prototype chain)。比如,a對象是b對象的原型,b對象是c對象的原型,以此類推。

如果一層層地上溯,所有對象的原型最終都可以上溯到Object.prototype,即Object構造函數的prototype屬性指向的那個對象。那么,Object.prototype對象有沒有它的原型呢?回答可以是有的,就是沒有任何屬性和方法的null對象,而null對象沒有自己的原型。

Object.getPrototypeOf(Object.prototype)// null

上面代碼表示,Object.prototype對象的原型是null,由于null沒有任何屬性,所以原型鏈到此為止。

“原型鏈”的作用是:
讀取對象的某個屬性時,JavaScript 引擎先尋找對象本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。如果直到最頂層的Object.prototype還是找不到,則返回undefined。

如果對象自身和它的原型,都定義了一個同名屬性,那么優先讀取對象自身的屬性,這叫做“覆蓋”(overriding)。

需要注意的是,一級級向上,在原型鏈尋找某個屬性,對性能是有影響的。所尋找的屬性在越上層的原型對象,對性能的影響越大。如果尋找某個不存在的屬性,將會遍歷整個原型鏈。

下面的代碼可以找出,某個屬性到底是原型鏈上哪個對象自身的屬性。

function getDefiningObject(obj, propKey) {
  while (obj && !{}.hasOwnProperty.call(obj, propKey)) {
    obj = Object.getPrototypeOf(obj);
  }
  return obj;
}
//對象實例的hasOwnProperty方法返回一個布爾值,用于判斷某個屬性定義在對象自身,還是定義在原型鏈上。
Date.hasOwnProperty('length')// true

4.constructor

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

function P() {}
P.prototype.constructor === P// true

由于constructor屬性定義在prototype對象上面,意味著可以被所有實例對象繼承。

image.png
function P() {}
var p = new P();

p.constructor
// function P() {}

p.constructor === P.prototype.constructor
// true

p.hasOwnProperty('constructor')
// false

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

constructor屬性的作用,是分辨原型對象到底屬于哪個構造函數。

function F() {};
var f = new F();
f.constructor === F // true
f.constructor === RegExp // false
上面代碼表示,使用constructor屬性,確定實例對象f的構造函數是F,而不是RegExp。

有了constructor屬性,就可以從實例新建另一個實例。

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

var y = new x.constructor();
y instanceof Constr // true
上面代碼中,x是構造函數Constr的實例,可以從x.constructor間接調用構造函數。

這使得在實例方法中,調用自身的構造函數成為可能。

Constr.prototype.createCopy = function () {
  return new this.constructor();
};

由于constructor屬性是一種原型對象與構造函數的關聯關系,所以修改原型對象的時候,務必要小心。

function A() {}
var a = new A();
a instanceof A // true

function B() {}
A.prototype = B.prototype;
a instanceof A // false
上面代碼中,a是A的實例。修改了A.prototype以后,constructor屬性的指向就變了,導致instanceof運算符失真。
instanceof運算符用來比較一個對象是否為某個構造函數的實例

所以,修改原型對象時,一般要同時校正constructor屬性的指向。

//推薦寫法
C.prototype.method1 = function (...) { ... };

instanceof運算符返回一個布爾值,表示指定對象是否為某個構造函數的實例。

var v = new Vehicle();
v instanceof Vehicle // true
上面代碼中,對象v是構造函數Vehicle的實例,所以返回true。

instanceof運算符的左邊是實例對象,右邊是構造函數。它會檢查右邊構建函數的原型對象,是否在左邊對象的原型鏈上。

由于instanceof對整個原型鏈上的對象都有效,因此同一個實例對象,可能會對多個構造函數都返回true

var d = new Date();
d instanceof Date // true
d instanceof Object // true
上面代碼中,d同時是Date和Object的實例,因此對這兩個構造函數都返回true。

除了上面這種繼承null的特殊情況,JavaScript 之中,只要是對象,就有對應的構造函數。因此,instanceof運算符的一個用處,是判斷值的類型。
instanceof運算符只能用于對象,不適用原始類型的值。

此外,對于undefined和null,instanceOf運算符總是返回false。
undefined instanceof Object // false
null instanceof Object // false

利用instanceof運算符,還可以巧妙地解決,調用構造函數時,忘了加new命令的問題。

function Fubar (foo, bar) {
  if (this instanceof Fubar) {
    this._foo = foo;
    this._bar = bar;
  }
  else {
    return new Fubar(foo, bar);
  }
}
上面代碼使用instanceof運算符,在函數體內部判斷this關鍵字是否為構造函數Fubar的實例。如果不是,就表明忘了加new命令。

5.Object.create()

生成實例對象的常用方法,就是使用new命令,讓構造函數返回一個實例。但是很多時候,只能拿到一個實例對象,它可能根本不是由構建函數生成的,那么能不能從一個實例對象,生成另一個實例對象呢?

JavaScript 提供了Object.create方法,用來滿足這種需求。該方法接受一個對象作為參數,然后以它為原型,返回一個實例對象。該實例完全繼承繼承原型對象的屬性。

// 原型對象
var A = {
print: function () {
console.log('hello');
}
};

// 實例對象
var B = Object.create(A);
B.print() // hello
B.print === A.print // true
上面代碼中,Object.create方法以A對象為原型,生成了B對象。B繼承了A的所有屬性和方法。

6.Object.prototype._ proto _

_ proto _ 屬性(前后各兩個下劃線)可以改寫某個對象的原型對象。

var obj = {};
var p = {};

obj._ proto _ = p;
Object.getPrototypeOf(obj) === p // true
上面代碼通過_ proto _ 屬性,將p對象設為obj對象的原型。

根據語言標準,_ proto _ 屬性只有瀏覽器才需要部署,其他環境可以沒有這個屬性,而且前后的兩根下劃線,表示它本質是一個內部屬性,不應該對使用者暴露。因此,應該盡量少用這個屬性,而是用Object.getPrototypeof()(讀取)和Object.setPrototypeOf()(設置),進行原型對象的讀寫操作。

7.獲取原型對象方法的比較

_ proto _ 屬性指向當前對象的原型對象,即構造函數的prototype屬性。

var obj = new Object();

obj._ proto _ === Object.prototype
// true
obj._ proto _ === obj.constructor.prototype
// true
上面代碼首先新建了一個對象obj,它的_ proto _ 屬性,指向構造函數(Object或obj.constructor)的prototype屬性。所以,兩者比較以后,返回true。

因此,獲取實例對象obj的原型對象,有三種方法。

obj._ _proto_ _
obj.constructor.prototype
Object.getPrototypeOf(obj)

上面三種方法之中,前兩種都不是很可靠。最新的ES6標準規定,_ proto _ 屬性只有瀏覽器才需要部署,其他環境可以不部署。而obj.constructor.prototype在手動改變原型對象時,可能會失效。
推薦使用第三種Object.getPrototypeOf方法,獲取原型對象。


參考阮一峰的ES5高級教程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內容