鏈接:https://www.zhihu.com/question/34183746/answer/58155878
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
是時候拿出我珍藏多年的這張圖了:
初學javascript的時候也跟題主一樣搞不清楚,自己好好總結了一下:
首先,要明確幾個點:
1.在JS里,萬物皆對象。方法(Function)是對象,方法的原型(Function.prototype)是對象。因此,它們都會具有對象共有的特點。
即:對象具有屬性__proto__,可稱為隱式原型,一個對象的隱式原型指向構造該對象的構造函數的原型,這也保證了實例能夠訪問在構造函數原型中定義的屬性和方法。
2.方法(Function)
方法這個特殊的對象,除了和其他對象一樣有上述_proto_屬性之外,還有自己特有的屬性——原型屬性(prototype),這個屬性是一個指針,指向一個對象,這個對象的用途就是包含所有實例共享的屬性和方法(我們把這個對象叫做原型對象)。原型對象也有一個屬性,叫做constructor,這個屬性包含了一個指針,指回原構造函數。
好啦,知道了這兩個基本點,我們來看看上面這副圖。
1.構造函數Foo()
構造函數的原型屬性Foo.prototype指向了原型對象,在原型對象里有共有的方法,所有構造函數聲明的實例(這里是f1,f2)都可以共享這個方法。
2.原型對象Foo.prototype
Foo.prototype保存着實例共享的方法,有一個指針constructor指回構造函數。
3.實例
f1和f2是Foo這個對象的兩個實例,這兩個對象也有屬性__proto__,指向構造函數的原型對象,這樣子就可以像上面1所說的訪問原型對象的所有方法啦。
另外:
構造函數Foo()除了是方法,也是對象啊,它也有__proto__屬性,指向誰呢?
指向它的構造函數的原型對象唄。函數的構造函數不就是Function嘛,因此這里的__proto__指向了Function.prototype。
其實除了Foo(),Function(), Object()也是一樣的道理。
原型對象也是對象啊,它的__proto__屬性,又指向誰呢?
同理,指向它的構造函數的原型對象唄。這里是Object.prototype.
最后,Object.prototype的__proto__屬性指向null。
總結:
1.對象有屬性__proto__,指向該對象的構造函數的原型對象。
2.方法除了有屬性__proto__,還有屬性prototype,prototype指向該方法的原型對象。
鏈接:https://www.zhihu.com/question/34183746/answer/59043879
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
__proto__(隱式原型)與prototype(顯式原型)
1. 是什么- 顯式原型 explicit prototype property:
Note:通過Function.prototype.bind方法構造出來的函數是個例外,它沒有prototype屬性。(感謝 同學的答案讓我知道這一點)
NOTE Function objects created using Function.prototype.bind do not have a prototype property or the [[Code]], [[FormalParameters]], and [[Scope]] internal properties. ----- ECMAScript Language Specification
- 隱式原型 implicit prototype link:
Note: Object.prototype 這個對象是個例外,它的__proto__值為null
- 二者的關系:
隱式原型指向創建這個對象的函數(constructor)的prototype
2. 作用是什么- 顯式原型的作用:用來實現基於原型的繼承與屬性的共享。
ECMAScript does not use classes such as those in C++, Smalltalk, or Java. Instead objects may be created in various ways including via a literal notation or via constructors which create objects and then execute code that initialises all or part of them by assigning initial values to their properties. Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties.Objects are created by using constructors in new expressions; for example, new Date(2009,11) creates a new Date object. ---- ECMAScript Language Specification
- 隱式原型的作用:構成原型鏈,同樣用於實現基於原型的繼承。舉個例子,當我們訪問obj這個對象中的x屬性時,如果在obj中找不到,那么就會沿着__proto__依次查找。
Every object created by a constructor has an implicit reference (called the object’s prototype) to the value of its constructor’s “prototype” ---- ECMAScript Language Specification
3. __proto__的指向
__proto__的指向到底如何判斷呢?根據ECMA定義 'to the value of its constructor’s "prototype" ' ----指向創建這個對象的函數的顯式原型。所以關鍵的點在於找到創建這個對象的構造函數,接下來就來看一下JS中對象被創建的方式,一眼看過去似乎有三種方式:(1)對象字面量的方式 (2)new 的方式 (3)ES5中的Object.create() 但是我認為本質上只有一種方式,也就是通過new來創建。為什么這么說呢,首先字面量的方式是一種為了開發人員更方便創建對象的一個語法糖,本質就是 var o = new Object(); o.xx = xx;o.yy=yy; 再來看看Object.create(),這是ES5中新增的方法,在這之前這被稱為原型式繼承,
道格拉斯在2006年寫了一篇文章,題為 Prototypal Inheritance In JavaScript。在這篇文章中,他介紹了一種實現繼承的方法,這種方法並沒有使用嚴格意義上的構造函數。他的想法是借助原型可以基於已有的對象創建新對象,同時還不比因此創建自定義類型,為了達到這個目的,他給出了如下函數:
function object(o){ function F(){} F.prototype = o; return new F() }
----- 《JavaScript高級程序設計》P169
所以從實現代碼 return new F() 中我們可以看到,這依然是通過new來創建的。不同之處在於由 Object.create() 創建出來的對象沒有構造函數,看到這里你是不是要問,沒有構造函數我怎么知道它的__proto__指向哪里呢,其實這里說它沒有構造函數是指在 Object.create() 函數外部我們不能訪問到它的構造函數,然而在函數內部實現中是有的,它短暫地存在了那么一會兒。假設我們現在就在函數內部,可以看到對象的構造函數是F, 現在
//以下是用於驗證的偽代碼 var f = new F(); //於是有 f.__proto__ === F.prototype //true //又因為 F.prototype === o;//true //所以 f.__proto__ === o;
因此由Object.create(o)創建出來的對象它的隱式原型指向o。好了,對象的創建方式分析完了,現在你應該能夠判斷一個對象的__proto__指向誰了。
好吧,還是舉一些一眼看過去比較疑惑的例子來鞏固一下。
- 構造函數的顯示原型的隱式原型:
- 內建對象(built-in object):比如Array(),Array.prototype.__proto__指向什么?Array.prototype也是一個對象,對象就是由 Object() 這個構造函數創建的,因此Array.prototype.__proto__ === Object.prototype //true,或者也可以這么理解,所有的內建對象都是由Object()創建而來。
- 自定義對象
function Foo(){}
var foo = new Foo()
Foo.prototype.__proto__ === Object.prototype //true 理由同上
(1)
function Bar(){} //這時我們想讓Foo繼承Bar Foo.prototype = new Bar() Foo.prototype.__proto__ === Bar.prototype //true
//我們不想讓Foo繼承誰,但是我們要自己重新定義Foo.prototype Foo.prototype = { a:10, b:-10 } //這種方式就是用了對象字面量的方式來創建一個對象,根據前文所述 Foo.prototype.__proto__ === Object.prototype
注: 以上兩種情況都等於完全重寫了Foo.prototype,所以Foo.prototype.constructor也跟着改變了,於是乎constructor這個屬性和原來的構造函數Foo()也就切斷了聯系。
- 構造函數的隱式原型
既然是構造函數那么它就是Function()的實例,因此也就指向Function.prototype,比如 Object.__proto__ === Function.prototype
4. instanceofinstanceof 操作符的內部實現機制和隱式原型、顯式原型有直接的關系。instanceof的左值一般是一個對象,右值一般是一個構造函數,用來判斷左值是否是右值的實例。它的內部實現原理是這樣的:
//設 L instanceof R
//通過判斷
L.__proto__.__proto__ ..... === R.prototype ?
//最終返回true or false
Function instanceof Object // true Object instanceof Function // true Function instanceof Function //true Object instanceof Object // true Number instanceof Number //false
