js面向對象編程——創建對象
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是一樣的。
用newStudent()創建的對象還從原型上獲得了一個constructor屬性,它指向函數Student本身:
xiaoming.constructor === Student.prototype.constructor;//true Student.prototype.constructor === Student;//true Object.getPrototypeOf(xiaoming) === Student.prototype;//true xiaoming.instanceof Student;//true
紅色箭頭是原型鏈,註意,Student.protorype指向的對象就是xiaoming、xiaohong的原型對象,這個原型對象自己還有個屬性constructor,指向Student函數本身。
另外,函數Student恰好有個屬性prototype指向xiaoming、xiaohong的原型對象,但是xiaoming、xiaohong這些對象可沒有prototype這個屬性,不過可以用_proto_這個非標磚用法來查看。
現在我們就認為xiaoming、xiaohong這些對象“繼承”自Student。
有個小問題:
xiaoming.name;//‘小明‘ xiaohong.name;//‘小紅‘ xiaoming.hello;//function:Student.hello() xiaoming.hello;//function:Student.hello() xiaoming.hello === xiaohong.hello;//false
xiaoming和xiaohong各自的name不同,這是對的,否則我們無法區分誰是誰了。
xiaoming和xiaohong各自的hello是一個函數,但它們是兩個不同的函數,雖然函數名稱和代碼都是相同的!
如果我們通過new Student()創建了很多對象,這些對象的hello函數實際上只需要共享同一個函數就可以了,這樣可以節省很多內存。
要讓創建的對象共享一個hello函數,根據對象的屬性查找原則,我們只要把hello函數移動到xiaoming、xiaohong這些對象的共同原型上就可以了,也就是Student.prototype:
修改代碼:
function Student(name) { this.name = name; } Student.prototype.hello = function () { alert(‘Hello,‘+this.name+‘!‘); }
用new創建基於原型的JavaScript對象,以上。
忘記寫new怎麽辦?
如果一個函數被定義為用於創建對象的構造函數,但是調用時忘記了寫new怎麽辦?
在strict模式下,this.name=name將報錯,因為this綁定為undefined,在非strict模式下,this.name=name不報錯,因為this綁定為window,於是無意間創建了全局變量name,並且返回undefined,這個結果更糟糕。
所以調用構造函數千萬不要忘記寫new。為了區分普通函數和構造函數,按照約定,構造函數首字母應該大寫,而普通函數首字母應該小寫,這樣,一些語法檢查工具如jslint將可以幫你檢測到漏寫的new。
最後我們可以編寫一個createStudent()函數,在內部封裝所有的new操作,一個常用的編程模式像這樣:
function Student(props){ this.name = props.name ||‘匿名‘;//默認值為‘匿名‘ this.grade = props.grade || 1;//默認值為1 } Student.prototype.hello = function(){ alert(‘Hello,‘+this.name+‘!‘); }; function createStudent(props){ return new Student(props || {}) }
這個createStudent()函數有幾個巨大的有點:一是不需要用new來調用,二是參數非常靈活,可以不傳,也可以這麽傳:
var xiaoming = createStudent({ name:‘小明‘ }); xiaoming.grade;//1
如果創建的對象有很多屬性,我們只需要傳遞需要的某些屬性,剩下的屬性可以用默認值。由於參數是一個Object,我們無需記憶參數的順序,如果恰好從JSON拿到了一個對象,就可以直接創建出xiaoming。
js面向對象編程——創建對象