1. 程式人生 > >徹底理解JS原型以及建構函式

徹底理解JS原型以及建構函式

什麼時候建立了原型物件

我們建立的每一個函式,不光是建構函式,都會有一個prototype屬性,當我們建立了一個函式/聲明瞭一個函式,它就會根據一組特定的規則為該函式建立一個prototype屬性指向的物件。

原型物件

原型物件就是我們建立了一個新函式的時候,自動建立的一個物件,當我們僅僅是建立了函式的時候,這個原型物件是這樣的:

這裡寫圖片描述

原型物件是會自動獲得一個constructor屬性,這個屬性指向我們建立的函式。
原型物件還有很多從Object繼承來的屬性和方法。

但是當我們建立了一個新例項後,該例項內部將包含一個指標,指向建構函式的原型物件~ [[prototype]]

[[prototype]]

這個是一個指標,這指標指向原型物件。 指令碼沒有標準的方式訪問這個[[prototype]],但FireFox Safari Chrome 都支援一個屬性稱為__proto__

ppt2.__proto__

真正重要的是:連線存在於例項和原型物件之間,而不是例項與建構函式。

Person–> Person.prototype <–person1
這裡寫圖片描述

建構函式的prototype屬性指向原型物件,原型物件的constructor屬性又指回了建構函式!

我們訪問原型中的方法是通過搜尋查詢物件屬性的過程實現的。

isPrototypeOf

判斷某個例項和某個原型的對應關係。

Person.prototype.isPrototypeOf(person1);//true

它就是判斷例項的[[prototype]]是否指向呼叫這個方法的物件。

Object.getPrototypeOf

ES5新方法,用來獲取一個例項的原型。

Object.getPrototypeOf(person1)==Person.prototype;

返回的原型,可以訪問這個原型中的屬性方法值。
我們在使用原型繼承的時候會用到這個方法。

原型鏈

這裡寫圖片描述
原理: 我們每次讀取某個物件的某個屬性時,都會執行一次搜尋,目標是具有給定名字的屬性,從物件的這個當前例項開始,如果找到這個屬性就停止。
否則繼續搜尋它的原型物件,如果搜尋到了屬性就停止。仍然搜尋不到,就搜尋這個原型物件的上一級的原型物件,直到Object.prototype,這個是所有物件的根原型物件,它的上一級是null。

弊端

我們不能通過物件例項重寫原型中的屬性。
只能是覆蓋 遮蔽,因為原型鏈搜尋會因此停止。
它搜尋到同名的屬性就不會繼續搜尋了。

delete 這個屬性,可以恢復原型鏈的搜尋

使用hasOwnProperty

這個方法可以區分 原型鏈上的屬性和 例項的屬性
例項屬性才返回true

原型與for in 與 in操作符

for in迴圈返回的是包括原型鏈上的所有屬性!
用hasOwnProperty可以返回所有例項屬性

如果要判斷所有原型鏈上的屬性呢?

function hasProtoProperty(object,name){
    return !object.hasOwnProperty(name)&&(name in object);
}

還有就是for - in 迴圈中的所有屬性是能過通過物件訪問,並且是可列舉的屬性!!!也就是說有些瀏覽器內建的屬性無法訪問,Object.defineProperty改為不可列舉的屬性也是。

但是 in 操作符
判斷這個屬性是否存在,不管是否是可列舉的,只要是原型鏈或例項屬性中存在!

BUG
但是一般定義的屬性都是可列舉的,IE8及更早的除外。

ES5將constructor和prototype屬性的 [[Enumerable]]設定為false~!

取得物件上所有可列舉例項屬性

Object.keys()方法。
僅僅返回所有可列舉的例項屬性

var p1=new Person();
p1.name='sd';
pa.age=23;
var keys=Object.keys(p1);
//name age 

如果返回所有例項屬性不管是否可列舉:

var keys=Object.getOwnPropertyNames(Person.prototype);

你可以把此處的Person.prototype理解為一個它上級原型物件的例項屬性,它返回//constructor ,name,age
也就是說把不可列舉的屬性也包含了

ES5兩個新方法 IE9+

原型的動態性

任何對原型物件的修改都會在例項物件上立即反映出來。
JS多型的天然性!!!!
建立了例項之後,再修改原型!!!也是一樣反映

實質原因: 原型與例項的連線不過是一個指標,而不是一個副本
關鍵還是搜尋原型鏈這個機制!!!

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
}
var friend =new Person();;
Person.prototype={
    constuctor:Person,
    name:"Nicolas",
    age:29,
    friend:["SH","WW"],
    job:"Soft Engineer"
    sayName:function(){

    }
};

如果我們修改了能夠立即在所有物件例項中反映出來

上面我們讓friend的原型物件為Person這個建構函式的prototype,但是後面我們又更新了這個Person的原型物件。重寫了原型物件,這樣就切斷了和friend這個例項的聯絡。
friend指向的是原來的,舊的那個原型物件!

重點例項中的指標僅指向原型,而不指向建構函式

原型的問題

對於共享的引用型別來說,改變其中一個,就會改變所有例項的這個屬性。

屬性設定和遮蔽

前面說過給例項屬性賦值,會遮蔽同名的原型中的屬性。即遮蔽~ 類似的[[GET]]操作總是選擇最底層的屬性

對於例項中不存在的屬性,而在原型鏈中存在的話
執行這個語句 myobj.foo=”bar”; (新增遮蔽屬性)
其實有三種情況
前提都是 如果在原型鏈上有這個屬性~

  • 如果這個屬性沒有被標記為writable:false,只讀的,那麼就會在這個物件例項中新增這個屬性,它是遮蔽屬性
  • 如果在[[prototype]]鏈上層存在這個屬性,但它只是被標記為只讀,writable:false,那麼無法修改已有屬性或在當前物件上建立遮蔽屬性。 嚴格模式下報錯
  • 如果它是一個setter,那麼就一定會呼叫這個setter

foo不會被新增到myobj,也不會重新定義foo這個setter
如果你希望第二第三種情況下也能遮蔽這個foo屬性,就要使用Object.defineProperty也不是 =

NEW操作與原型

function Foo(){}
var a=new Foo();
Object.getPrototypeOf(a)===Foo.prototype; //true

new操作會產生一個物件a,其中一步就是將a內部的[[prototype]]連結到Foo.prototype所指向的物件。

面向類的語言在那個,類可以被例項化多次,就像模具製作。(類把行為複製到物理物件中,對每個物件來說都是這樣)而JS,沒有類似的複製機制。你只能建立多個物件,但是它們的[[prototype]]關聯的是同一個物件。

我們通過原型把物件關聯起來,但是僅僅是關聯起來,不會複製,只是建立了一個關聯。

本質來說
在JS中,我們沒有建構函式,關鍵是 new呼叫它,會劫持這個普通函式並用構造物件的形式來呼叫它~!

function Nothing(){
    console.log('sss');
}

var a=new Nothing();
//a {} 

a是一個空物件,Nothing是一個普通的函式,但是被new呼叫的時候,它就會構造一個物件並賦值給a。這看起來像是new的一個副作用。

constructor的問題

之前討論constructor屬性時,其實對constructor的理解有點問題,因為我們可能認為這個屬性指向的函式是物件的建構函式。 這麼理解確實很容易。

constructor會被隔斷聯絡

Foo.prototype的.constructor 屬性只是Foo函式在宣告時的預設屬性。如果你建立了一個新物件並替換了函式預設的.prototype物件引用,那麼新物件不會自動獲得constructor屬性。

function Foo(){}
Foo.prototype={
//?
};//建立一個新的原型物件

var a1=new Foo();
a1.constructor===Foo; //false
a1.constructor===Object; //true!

這個新建的物件a1,看起來是用Foo構造的,但是呢,從上面的例子看出來,a1.constructor卻不是Foo。
其實a1並沒有constructor屬性,這個屬性是委託[[prototype]]鏈上的Foo.prototype來查詢的!
但是它上面也沒有constructor屬性(預設的Foo.prototype有這個屬性)

這裡寫圖片描述

這裡寫圖片描述
可以看出來,我們如果在聲明瞭
function Foo(){} 後去重寫它的prototype,那麼constructor屬性就會消失了

如何修復?

Object.defineProperty(Foo.prototype,"constructor",{
    enumerable:false,
    writable:true,
    configurable:true,
    value:Foo //讓這個.constructor指向FOO
})

實際上物件的.constructor會預設指向一個函式,這個函式可以通過物件的.prototype引用!!
constructor並不是一個不可變的屬性,它是不可列舉的,但是它的值可寫。
所以說這個屬性是非常隨意的,不可靠,不安全的引用。
我們要儘量避免使用這些引用!

它的行為非常難以捕捉,我們可以隨時修改,所以一般不要用這個引用。