1. 程式人生 > >Javascript基礎之-原型(prototype)

Javascript基礎之-原型(prototype)

首先呢,prototype是物件裡的一個內建屬性,並且呢,這個屬性是對於其他物件的一個引用。所以呢,思考下面的例子:

var obj = {
  a: 2
}
var myObj = Object.create(obj);
console.log(myObj.a); // 2
console.log(myObj === obj);  // false
console.log(Object.getPrototypeOf(myObj) === obj);  // true
Object.getPrototypeOf(myObj).a = 4
console.log(obj.a);  // 4

這裡可以看到,實際上Object.create()是新建了一個物件,並且這個物件的prototype是obj的一個引用,所以呢,如果咱們直接修改prototype裡面的值,原物件也就跟著變了。

很簡單吧,那麼如果執行如下程式碼的話,會發生什麼呢?

myObj.a = 10;

是不是認為,還跟上面那個一樣的,obj.a也變成10了呢?實際上不是的,他的執行機制要比咱們想的稍微複雜一點點。

實際上要分三種情況來看:

如果在prototype鏈上存在這個屬性,並且沒有標記為只讀,那麼就會在本物件上新建一個新的同名屬性。

如果在prototype鏈上存在這個屬性,並且標記為只讀,那麼將無法修改已有屬性或在本物件上新建一個同名屬性,如果是嚴格模式的話,還會報錯。

如果在prototype鏈上只是存在此setter,那麼一定會呼叫此setter,並不會新增屬性到物件上,更不會重新定義這個setter

很枯燥是吧,來看例子,對照著上面的情況,好好的理解一下:

var obj = {
  a: 2,
  set c(num) {
    console.log('exec it');
  }
}
var myObj = Object.create(obj);
myObj.a = 10;
console.log(obj.a);  // 2
console.log(myObj.a);  // 10
Object.defineProperty(obj, 'b', {
  value: 3,
  writable: false
})
myObj.b = 10;
console.log(myObj.b); // 3
myObj.c = 20;  // "exec it"
console.log(myObj.c);    // undefined

假如上面的已經理解了,那麼可以思考下下面的執行結果:

var obj = {
  a: 2
}
var myObj = Object.create(obj);
console.log(++myObj.a);  // 3
console.log(obj.a);  // 2

這個在咱們實際的編碼中時有發生,看程式碼是想把a改成3,但是由於上面第一種情況的影響,實際上是新建了一個同名屬性3,並且賦值給了myObj。

上面我們談論的都是普通物件的prototype的一些特性,接下來,咱們就要講關於new關鍵字相關的一些知識點了,思考下面的例子

function Foo() {}
var a = new Foo();
console.log(Object.getPrototypeOf(a) === Foo.prototype);  // true
var b = new Foo();
Object.getPrototypeOf(b).saySomething = function () {
  console.log('say something');
}
a.saySomething();  // "say something"

很明顯,在new的過程中呢,生成了一個新物件,並且把Foo.prototype引用到了新物件的prototype。那麼因為是引用,所以通過b改變其原型上的prototype的值,Foo.prototype裡也會跟著改變。

那麼new的過程,是不是一定引用的是函式的prototype呢?也不一定,比如說下面的例子。

function Foo() {
  return {
    a: 3
  }
}
var a = new Foo();
console.log(Object.getPrototypeOf(a) === Foo.prototype);  // false
console.log(Object.getPrototypeOf(a) === Object.prototype); // true
console.log(a.a); // 3

在這個例子中,由於new的時候,返回的是一個物件,所以最後實際上a最終引用的是Foo最後返回的那個小物件,所以其prototype就是Object.prototype,而不是Foo.prototype

甚至說,Foo.prototype也是可以被改變的,不過在這時候,new出來的物件,其prototype就是被改過的那個物件。

var protoObj = {
  b: 10
}
function Foo() {}
Foo.prototype = protoObj;
var a = new Foo();
console.log(Object.getPrototypeOf(a) === Foo.prototype);  // true
console.log(Object.getPrototypeOf(a) === protoObj); // true
console.log(a.b); // 10

你看,如果prototypeObj修改了預設的Foo.prototype,所以最後,實際上形成了這麼一個引用鏈:a.prototype => foo.prototype => protoObj=>Object.prototype。

所以說結論吧,在new的時候,實際上執行會包含這麼幾步,

如果有return並且返回的是一個物件的話,則直接返回return後的那個物件。

反之,則新建一個物件。

並且吧函式的prototype引用到新建物件的prototype中。

所以說,原型,可以理解為我本來物件有一個prototype,引用著其他的物件,當我這個物件的prototype引用了另一個物件的prototype,一般情況會到Object.prototype為止,這樣就組成了一個原型鏈,原型鏈也就是互相引用的引用鏈。而這個引用鏈是可以根據自己的需求去改。

好了,簡短的一小節就完事了,如果有不明白的,或者有疏漏的地方,或者有什麼地方想和我討論的,可以留言給我哦。

本文轉載自http://www.lht.ren/article/8/