JavaScript對每個建立的對象都會設定一個原型,指向它的原型對象。
當我們用
obj.xxx
通路一個對象的屬性時,JavaScript引擎先在目前對象上查找該屬性,如果沒有找到,就到其原型對象上找,如果還沒有找到,就一直上溯到
Object.prototype
對象,最後,如果還沒有找到,就隻能傳回
undefined
。
例如,建立一個
Array
對象:
var arr = [1, 2, 3];
其原型鍊是:
arr ----> Array.prototype ----> Object.prototype ----> null
Array.prototype
定義了
indexOf()
、
shift()
等方法,是以你可以在所有的
Array
對象上直接調用這些方法。
當我們建立一個函數時:
function foo()
return 0;
}
函數也是一個對象,它的原型鍊是:
foo ----> Function.prototype ----> Object.prototype ----> null
由于
Function.prototype
定義了
apply()
等方法,是以,所有函數都可以調用
apply()
方法。
很容易想到,如果原型鍊很長,那麼通路一個對象的屬性就會因為花更多的時間查找而變得更慢,是以要注意不要把原型鍊搞得太長。
構造函數
除了直接用
{ ... }
建立一個對象外,JavaScript還可以用一種構造函數的方法來建立對象。它的用法是,先定義一個構造函數:
function Student(name)
this.name = name;
this.hello = function ()
alert('Hello, ' + this.name + '!');
}
}
你會問,咦,這不是一個普通函數嗎?
這确實是一個普通函數,但是在JavaScript中,可以用關鍵字
new
來調用這個函數,并傳回一個對象:
var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
注意,如果不寫
new
,這就是一個普通函數,它傳回
undefined
。但是,如果寫了
new
,它就變成了一個構造函數,它綁定的
this
指向新建立的對象,并預設傳回
this
,也就是說,不需要在最後寫
return this;
。
新建立的
xiaoming
的原型鍊是:
xiaoming ----> Student.prototype ----> Object.prototype ----> null
也就是說,
xiaoming
的原型指向函數
Student
的原型。如果你又建立了
xiaohong
、
xiaojun
,那麼這些對象的原型與
xiaoming
是一樣的:
xiaoming ↘
xiaohong -→ Student.prototype ----> Object.prototype ----> null
用
new Student()
建立的對象還從原型上獲得了一個
constructor
屬性,它指向函數
Student
本身:
xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
xiaoming instanceof Student; // true
看暈了吧?用一張圖來表示這些亂七八糟的關系就是:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAnYldHL0FWby9mZvwFN4ETMfdHLkVGepZ2XtxSZ6l2clJ3LcV2Zh1Wa9M3clN2byBXLzN3btgHL9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsQTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SMxETN0EjMzImMhZDM3cTNzYzX0EzMxMTM1IzLcBTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
紅色箭頭是原型鍊。注意,
Student.prototype
指向的對象就是
xiaoming
、
xiaohong
的原型對象,這個原型對象自己還有個屬性
constructor
,指向
Student
函數本身。
另外,函數
Student
恰好有個屬性
prototype
指向
xiaoming
、
xiaohong
的原型對象,但是
xiaoming
、
xiaohong
這些對象可沒有
prototype
這個屬性,不過可以用
__proto__
這個非标準用法來檢視。
現在我們就認為
xiaoming
、
xiaohong
這些對象“繼承”自
Student
。
不過還有一個小問題,注意觀察:
xiaoming.name; // '小明'
xiaohong.name; // '小紅'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false
xiaoming
和
xiaohong
各自的
name
不同,這是對的,否則我們無法區分誰是誰了。
xiaoming
和
xiaohong
各自的
hello
是一個函數,但它們是兩個不同的函數,雖然函數名稱和代碼都是相同的!
如果我們通過
new Student()
建立了很多對象,這些對象的
hello
函數實際上隻需要共享同一個函數就可以了,這樣可以節省很多記憶體。
function Student(name)
this.name = name;
}
Student.prototype.hello = function ()
alert('Hello, ' + this.name + '!');
};