__proto__與constructor的使用
__proto__與constructor的使用
在 JavaScript 原型繼承結構裡面,規範中用 [[Prototype]] 表示物件隱式的原型,在 JavaScript 中用 __proto__ 表示,並且在 Firefox 和 Chrome 瀏覽器中是可以訪問得到這個屬性的,但是 IE 下不行。所有 JavaScript 物件都有 __proto__ 屬性,但只有 Object.prototype.__proto__ 為 null,前提是沒有在 Firefox 或者 Chrome 下修改過這個屬性。這個屬性指向它的原型物件。 至於顯示的原型,在 JavaScript 裡用 prototype 屬性表示
一、prototype
在JavaScript中,prototype物件是實現面向物件的一個重要機制。
每個函式就是一個物件(Function),函式物件都有一個子物件 prototype物件,類是以函式的形式來定義的。prototype表示該函式的原型,也表示一個類的成員的集合。
每個函式都有一個prototype屬性,這個屬性是指向一個物件的引用,這個物件稱為原型物件,原型物件包含函式例項共享的方法和屬性, 也就是說將函式用作建構函式呼叫(使用new操作符呼叫)的時候,新建立的物件會從原型物件上繼承屬性和方法。
在通過new建立一個類的例項物件的時候,prototype物件的成員都成為例項化物件的成員。
1、該物件被類所引用,只有函式物件才可引用;
2、在new例項化後,其成員被例項化,例項物件方可呼叫。
同時,函式是一個物件,函式物件若直接宣告成員,不用被例項化即可呼叫。
prototype
無論什麼時候,只要建立了一個新函式,就會根據一組特定的規則為該函式建立一個prototype屬性,預設情況下prototype屬性會預設獲得一個constructor(建構函式)屬性, 這個屬性是一個指向prototype屬性所在函式的指標,有些繞了啊,寫程式碼、上圖! 複製程式碼 程式碼如下:
function Obj(){ this.a=[]; //例項變數 this.fn=function(){ //例項方法 } } var o1=new Obj(); o1.a.push(1); o1.fn={}; console.log(o1.a); //[1] console.log(typeof o1.fn); //object var o2=new Obj(); console.log(o2.a); //[] console.log(typeof o2.fn); //function
在o1中修改了a和fn,而在o2中沒有改變,由於陣列和函式都是物件,是引用型別, 這就說明o1中的屬性和方法與o2中的屬性與方法雖然同名但卻不是一個引用,而是對Obj物件定義的屬性和方法的一個複製。 這個對屬性來說沒有什麼問題,但是對於方法來說問題就很大了,因為方法都是在做完全一樣的功能,但是卻又兩份複製,如果一個函式物件有上千和例項方法, 那麼它的每個例項都要保持一份上千個方法的複製,這顯然是不科學的
function Person(){
}
根據上圖可以看出Person物件會自動獲得prototyp屬性,而prototype也是一個物件,會自動獲得一個constructor屬性,該屬性正是指向Person物件。 當呼叫建構函式建立一個例項的時候,例項內部將包含一個內部指標(很多瀏覽器這個指標名字為__proto__)指向建構函式的prototype,這個連線存在於例項和建構函式的prototype之間, 而不是例項與建構函式之間
function Person(name){
this.name=name;
}
Person.prototype.printName=function(){
alert(this.name);
}
var person1=new Person('Byron');
var person2=new Person('Frank');
Person的例項person1中包含了name屬性,同時自動生成一個__proto__屬性,該屬性指向Person的prototype,可以訪問到prototype內定義的printName方法,大概就是這個樣子的
prototype內屬性、方法是能夠共享
function Person(name){
this.name=name;
}
Person.prototype.share=[];
Person.prototype.printName=function(){
alert(this.name);
}
var person1=new Person('Byron');
var person2=new Person('Frank');
person1.share.push(1);
person2.share.push(2);
console.log(person2.share); //[1,2]
實際上當程式碼讀取某個物件的某個屬性的時候,都會執行一遍搜尋,目標是具有給定名字的屬性,搜尋首先從物件例項開始,如果在例項中找到該屬性則返回, 如果沒有則查詢prototype,如果還是沒有找到則繼續遞迴prototype的prototype物件,直到找到為止,如果遞迴到object仍然沒有則返回錯誤。 同樣道理如果在例項中定義如prototype同名的屬性或函式,則會覆蓋prototype的屬性或函式。
function Person(name){
this.name=name;
}
Person.prototype.share=[];
var person=new Person('Byron');
person.share=0;
console.log(person.share); //0而不是prototype中的[]
構造簡單物件
當然prototype不是專門為解決上面問題而定義的,但是卻解決了上面問題。瞭解了這些知識就可以構建一個科學些的、複用率高的物件,如果希望例項物件的屬性或函式則定義到prototype中, 如果希望每個例項單獨擁有的屬性或方法則定義到this中,可以通過建構函式傳遞例項化引數。
function Person(name){
this.name=name;
}
Person.prototype.share=[];
Person.prototype.printName=function(){
alert(this.name);
}
二、constructor
在 Javascript 語言中,constructor 屬性是專門為 function 而設計的,它存在於每一個 function 的prototype 屬性中。這個 constructor 儲存了指向 function 的一個引用。
在定義一個函式(程式碼如下所示)時,JavaScript 內部會執行如下幾個動作:
1.為該函式新增一個原形(即 prototype)屬性
2. 為 prototype 物件額外新增一個 constructor 屬性,並且該屬性儲存指向函式F 的一個引用
這樣當我們把函式 F 作為自定義建構函式來建立物件的時候,物件例項內部會自動儲存一個指向其建構函式(這裡就是我們的自定義建構函式 F)的 prototype 物件的一個屬性proto,
所以我們在每一個物件例項中就可以訪問建構函式的 prototype 所有擁有的全部屬性和方法,就好像它們是例項自己的一樣。當然該例項也有一個 constructor屬性了(從 prototype 那裡獲得的),每一個物件例項都可以通過 constrcutor 物件訪問它的建構函式
我們可以利用這個特性來完成下面的事情:
物件型別判斷,如
var f = new F();
if(f.constructor === F) {
// do sth with F
}
其實 constructor 的出現原本就是用來進行物件型別判斷的,但是 constructor 屬性易變,不可信賴。我們有一種更加安全可靠的判定方法:instanceof 操作符。下面程式碼
仍然返回 true
if(f instanceof F) {
// do sth with F
}
原型鏈繼承,由於 constructor 存在於 prototype 物件上,因此我們可以結合
constructor 沿著原型鏈找到最原始的建構函式,如下面程式碼:
function Base() {}
// Sub1 inherited from Base through prototype chain
function Sub1(){}
Sub1.prototype = new Base();
Sub1.prototype.constructor = Sub1;
Sub1.superclass = Base.prototype;
// Sub2 inherited from Sub1 through prototype chain
function Sub2(){}
Sub2.prototype = new Sub1();
Sub2.prototype.constructor = Sub2;
Sub2.superclass = Sub1.prototype;
// Test prototype chain
alert(Sub2.prototype.constructor);// function Sub2(){}
alert(Sub2.superclass.constructor);// function Sub1(){}
alert(Sub2.superclass.constructor.superclass.constructor);// function Base(){}
上面的例子只是為了說明 constructor 在原型鏈中的作用,更實際一點的意義在於:一個子類物件可以獲得其父類的所有屬性和方法,稱之為繼承。
之前提到 constructor 易變,那是因為函式的 prototype 屬性容易被更改,我們用時下很流行的編碼方式來說明問題,請看下面的示例程式碼:
function F() {}
F.prototype = {
_name: 'Eric',
getName: function() {
return this._name;
}
};
初看這種方式並無問題,但是你會發現下面的程式碼失效了:
var f = new F();
alert(f.constructor === F); // output false
怎麼回事?F 不是例項物件 f 的構造函數了嗎?當然是!只不過建構函式 F 的原型被開發者重寫了,這種方式將原有的 prototype 物件用一個物件的字面量{}來代替。而新建的物件{}只是 Object 的一個例項,系統(或者說瀏覽器)在解析的時候並不會在{}上自動新增一個 constructor 屬性,因為這是 function 建立時的專屬操作,僅當你宣告函式的時候解析器才會做此動作。然而你會發現 constructor 並不是不存在的
既然存在,那這個 constructor 是從哪兒冒出來的呢?我們要回頭分析這個物件字面量
{}。因為{}是建立物件的一種簡寫,所以{}相當於是 new Object()。那既然{}是 Object
的例項,自然而然他獲得一個指向建構函式 Object()的 prototype 屬性的一個引用proto,又因為 Object.prototype 上有一個指向 Object 本身的 constructor屬性。所以可以看出這個constructor其實就是Object.prototype的constructo
一個解決辦法就是手動恢復他的 constructor,下面程式碼非常好地解決了這個問題:
function F() {}
F.prototype = {
constructor: F, /* reset constructor */
_name: 'Eric',
getName: function() {
return this._name;
}
};
var f = new F();
alert(f.constructor === F); // output true this time ^^
建構函式上怎麼還有一個 constructor ?它又是哪兒來的?細心的會發現,像 JavaScript 內建的建構函式,如 Array, RegExp, String,Number, Object, Function 等等居然自己也有一個 constructor:
alert(typeof Array.constructor != ‘undefined’);// output true
經過測試發現,此物非彼物它和 prototype 上 constructor 不是同一個物件,他們是共存的:
alert(typeof Array.constructor != 'undefined');// output true
alert(typeof Array.prototype.constructor === Array); // output true
在JavaScript中,每個具有原型的物件都會自動獲得constructor屬性。除了arguments、Enumerator、Error、Global、Math、RegExp、Regular Expression等一些特殊物件之外,其他所有的JavaScript內建物件都具備constructor屬性。例如:Array、Boolean、Date、Function、Number、Object、String等。所有主流瀏覽器均支援該屬性
不過這件事情也是好理解的,因為 建構函式也是函式。是函式說明它就是 Function 建構函式的例項物件,自然他內部也有一個指向 Function.prototype 的內部引用proto啦。因此我們很容易得出結論,這個 constructor(建構函式上的constructor 不是 prototype 上的)其實就是 Function 建構函式的引用:
alert(Array.constructor === Function);// output true
alert(Function.constructor === Function); // output true
建構函式(Constructor)在物件建立或者例項化時候被呼叫的方法。通常使用該方法來初始化資料成員和所需資源。構造器Constructor在js不能被繼承,因此不能重寫Overriding,但可以被過載Overloading
物件的constructor [1] 屬性用於返回建立該物件的函式,也就是我們常說的建構函式 [1] 。
在一個構造方法中可以使用super
關鍵字來呼叫一個父類的構造方法。
如果沒有顯式指定構造方法,則會新增預設的 constructor 方法。
如果不指定一個建構函式(constructor)方法, 則使用一個預設的建構函式(constructor)。
class Polygon {
// ..and an (optional) custom class constructor. If one is
// not supplied, a default constructor is used instead:
// constructor() { }
constructor(height, width) {
this.name = 'Polygon';
this.height = height;
this.width = width;
}
// Simple class instance methods using short-hand method
// declaration
sayName() {
ChromeSamples.log('Hi, I am a ', this.name + '.');
}
sayHistory() {
ChromeSamples.log('"Polygon" is derived from the Greek polus (many) ' +
'and gonia (angle).');
}
// We will look at static and subclassed methods shortly
}
// Classes are used just like ES5 constructor functions:
let p = new Polygon(300, 400);
p.sayName();
ChromeSamples.log('The width of this polygon is ' + p.width);
class Square extends Polygon {
constructor(length) {
// 在這裡, 它呼叫了父類的建構函式, 並將 lengths 提供給 Polygon 的"width"和"height"
super(length, length);
// 注意: 在派生類中, 必須先呼叫 super() 才能使用 "this"。
// 忽略這個,將會導致一個引用錯誤。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
set area(value) {
// 注意:不可使用 this.area = value
// 否則會導致迴圈call setter方法導致爆棧
this._area = value;
}
}
let s = new Square(5);
s.sayName();
ChromeSamples.log('The area of this square is ' + s.area);
js物件的constructor屬性返回建立該物件的函式的引用。
JavaScript的底層內部程式碼實現
// 字串:String()
var str = "張三";
document.writeln(str.constructor); // function String() { [native code] }
document.writeln(str.constructor === String); // true
// 陣列:Array()
var arr = [1, 2, 3];
document.writeln(arr.constructor); // function Array() { [native code] }
document.writeln(arr.constructor === Array); // true
// 數字:Number()
var num = 5;
document.writeln(num.constructor); // function Number() { [native code] }
document.writeln(num.constructor === Number); // true
// 自定義物件:Person()
function Person(){
this.name = "CodePlayer";
}
var p = new Person();
document.writeln(p.constructor); // function Person(){ this.name = "CodePlayer"; }
document.writeln(p.constructor === Person); // true
// JSON物件:Object()
var o = { "name" : "張三"};
document.writeln(o.constructor); // function Object() { [native code] }
document.writeln(o.constructor === Object); // true
// 自定義函式:Function()
function foo(){
alert("CodePlayer");
}
document.writeln(foo.constructor); // function Function() { [native code] }
document.writeln(foo.constructor === Function); // true
// 函式的原型:bar()
function bar(){
alert("CodePlayer");
}
document.writeln(bar.prototype.constructor); // function bar(){ alert("CodePlayer"); }
document.writeln(bar.prototype.constructor === bar); // true
constructor的建立
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*/
// Inspired by base2 and Prototype
(function(){
//initializing是為了解決我們之前說的繼承導致原型有多餘引數的問題。當我們直接將父類的例項賦值給子類原型時。是會呼叫一次父類的建構函式的。所以這邊會把真正的構造流程放到init函式裡面,通過initializing來表示當前是不是處於構造原型階段,為true的話就不會呼叫init。
//fnTest用來匹配程式碼裡面有沒有使用super關鍵字。對於一些瀏覽器`function(){xyz;}`會生成個字串,並且會把裡面的程式碼弄出來,有的瀏覽器就不會。`/xyz/.test(function(){xyz;})`為true代表瀏覽器支援看到函式的內部程式碼,所以用`/\b_super\b/`來匹配。如果不行,就不管三七二十一。所有的函式都算有super關鍵字,於是就是個必定匹配的正則。
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
// 超級父類
this.Class = function(){};
// Create a new Class that inherits from this class
// 生成一個類,這個類會具有extend方法用於繼續繼承下去
Class.extend = function(prop) {
//保留當前類,一般是父類的原型
//this指向父類。初次時指向Class超級父類
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
//開關 用來使原型賦值時不呼叫真正的構成流程
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
//這邊其實就是很簡單的將prop的屬性混入到子類的原型上。如果是函式我們就要做一些特殊處理
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
//通過閉包,返回一個新的操作函式.在外面包一層,這樣我們可以做些額外的處理
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
// 呼叫一個函式時,會給this注入一個_super方法用來呼叫父類的同名方法
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
//因為上面的賦值,是的這邊的fn裡面可以通過_super呼叫到父類同名方法
var ret = fn.apply(this, arguments);
//離開時 儲存現場環境,恢復值。
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// 這邊是返回的類,其實就是我們返回的子類
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// 賦值原型鏈,完成繼承
Class.prototype = prototype;
// 改變constructor引用
Class.prototype.constructor = Class;
// 為子類也新增extend方法
Class.extend = arguments.callee;
return Class;
};
})();