JavaScript的面向?qū)ο缶幊毯痛蠖鄶?shù)其他語言如Java辽幌、C#的面向?qū)ο缶幊潭疾惶粯印?br>
類和實例是大多數(shù)面向?qū)ο缶幊陶Z言的基本概念涌攻。
不過山橄,在JavaScript中墙歪,這個概念需要改一改听系。JavaScript不區(qū)分類和實例的概念,而是通過原型prototype來實現(xiàn)面向?qū)ο缶幊?br>
原型是指 當我們想要創(chuàng)阿金xiaoming這個具體的學生時,我們并沒有一個Student類型可用,那怎么辦,恰好有這么一個現(xiàn)成的對象:
var robot = {
name: 'Robot',
height: 1.6,
run: function () {
console.log(this.name + ' is running...');
}
};
我們看robot對象有名字,有身高,還會跑,有點像小明,干脆就根據(jù)它來創(chuàng)建小明得了.
于是我們把它改名為Student,然后創(chuàng)建出xiaoming:
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};
var xiaoming = {
name: '小明'
};
xiaoming.__proto__ = Student
注意最后一行代碼把xiaoming的原型指向了對象Student,看上去xiaoming放佛是從Student繼承下來的
xiaoming.name; // '小明'
xiaoming.run(); // 小明 is running...
xiaoming有自己的name屬性,但并沒有定義run()方法虹菲。不過靠胜,由于小明是從Student繼承而來,只要Student有run()方法,xiaoming也可以調(diào)用:
JavaScript的原型鏈和Java的Class區(qū)別就在浪漠,它沒有“Class”的概念陕习,所有對象都是實例,所謂繼承關系不過是把一個對象的原型指向另一個對象而已址愿。
要注意:上述代碼僅用于演示目的,在編寫JavaScript代碼時,不要直接用obj.proto去改變一個對象的原型,并且,低版本的IE也無法使用proto
Object.create()方法可以傳入一個原型對象,并創(chuàng)建一個基于該原型的新對象,但是新對象什么屬性都沒有,因此,我們可以編寫一函數(shù)來創(chuàng)建xioaming
原型對象:
var Student = {
name:'Robot',
height:1.2,
run: function () {
console.log(this.name + 'is running...')
}
};
function createStudent(name){
//基于Student原型創(chuàng)建一個新對象
var s = Object.create(Student);
//初始化新對象
s.name = name;
return s;
}
var xiaoming = createStudent('小明');
xiaoming.run();
xiaoming.__proto__ === Student;//true
創(chuàng)建對象
JavaScript對每個創(chuàng)建的對象都會設置一個原型该镣,指向它的原型對象。
當我們用obj.xxx訪問一個對象的屬性時必盖,JavaScript引擎先在當前對象上查找該屬性拌牲,如果沒有找到,就到其原型對象上找歌粥,如果還沒有找到,就一直上溯到Object.prototype對象拍埠,最后失驶,如果還沒有找到,就只能返回undefined枣购。
容易想到嬉探,如果原型鏈很長,那么訪問一個對象的屬性就會因為花更多的時間查找而變得更慢棉圈,因此要注意不要把原型鏈搞得太長涩堤。
構造函數(shù)
除了直接用{...}創(chuàng)建一個對象外,JavaScript還可以用一種構造函數(shù)的方法來創(chuàng)建對象。它的用法是分瘾,先定義一個構造函數(shù):
function Student(name) {
this.name = name;
this.hello = function () {
alert('hello, ' + this.name + '!');
}
}
這不是一個普通函數(shù)嗎胎围?這確實是一個普通函數(shù),但是在JavaScript中德召,可以用關鍵字new來調(diào)用這個函數(shù)白魂,并返回一個對象:
var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
注意: 如果不寫new,這就是一個普通的函數(shù),它返回undefined.但是如果寫了new,它就變成了一個構造函數(shù),它綁定的this指向新創(chuàng)建的對象,并默認返回this,
也就是說,不需要再最后寫return this;
新創(chuàng)建的xiaoming的原型鏈是:xiaoming ----> Student.prototype ----> Object.prototype ----> null
用new Student()創(chuàng)建的對象還從原型上獲得了一個constructor屬性,它指向函數(shù)Student本身.
紅色箭頭是原型鏈上岗。注意福荸,Student.prototype指向的對象就是xiaoming、xiaohong的原型對象肴掷,這個原型對象自己還有個屬性constructor敬锐,指向Student函數(shù)本身。
另外呆瞻,函數(shù)Student恰好有個屬性prototype指向xiaoming台夺、xiaohong的原型對象,但是xiaoming栋烤、xiaohong這些對象可沒有prototype這個屬性谒养,不過可以用proto這個非標準用法來查看。
現(xiàn)在我們就認為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不同丰泊,這是對的,否則我們無法區(qū)分誰是誰了始绍。
xiaoming和xiaohong各自的hello是一個函數(shù)瞳购,但它們是兩個不同的函數(shù),雖然函數(shù)名稱和代碼都是相同的亏推!
如果我們通過new Student()創(chuàng)建了很多對象学赛,這些對象的hello函數(shù)實際上只需要共享同一個函數(shù)就可以了,這樣可以節(jié)省很多內(nèi)存吞杭。
要讓創(chuàng)建的對象共享一個hello函數(shù)盏浇,根據(jù)對象的屬性查找原則,我們只要把hello函數(shù)移動到xiaoming芽狗、xiaohong這些對象共同的原型上就可以了绢掰,也就是Student.prototype:
修改代碼如下:
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};
忘記寫new怎么辦
如果一個函數(shù)被定義為用于創(chuàng)建對象的構造函數(shù),但是調(diào)用時忘記了寫new怎么辦童擎?
在strict模式下滴劲,this.name = name將報錯,因為this綁定為undefined顾复,在非strict模式下班挖,this.name = name不報錯,因為this綁定為window芯砸,于是無意間創(chuàng)建了全局變量name萧芙,并且返回undefined,這個結(jié)果更糟糕乙嘀。
所以末购,調(diào)用構造函數(shù)千萬不要忘記寫new。為了區(qū)分普通函數(shù)和構造函數(shù)虎谢,按照約定盟榴,構造函數(shù)首字母應當大寫,而普通函數(shù)首字母應當小寫婴噩,這樣擎场,一些語法檢查工具如jslint將可以幫你檢測到漏寫的new。
最后几莽,我們還可以編寫一個createStudent()函數(shù)迅办,在內(nèi)部封裝所有的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()函數(shù)有幾個巨大的優(yōu)點:一是不需要new來調(diào)用章蚣,二是參數(shù)非常靈活站欺,可以不傳,也可以這么傳:
var xiaoming = createStudent({
name: '小明'
});
xiaoming.grade; // 1
如果創(chuàng)建的對象有很多屬性,我們只需要傳遞需要的某些屬性矾策,剩下的屬性可以用默認值磷账。由于參數(shù)是一個Object,我們無需記憶參數(shù)的順序贾虽。如果恰好從JSON拿到了一個對象逃糟,就可以直接創(chuàng)建出xiaoming。