最近做前端需求,發(fā)現(xiàn)JavaScript對類和對象的實現(xiàn)跟其他語言有很大不同,做了一些學(xué)習(xí)和了解。
JavaScript是一種基于原型的語言,它和基于類的語言有所不同。基于類的語言中對象是類的實例,類可以從另外一個類繼承。而JavaScript中,所有對象都是實例,繼承也是直接從其他對象繼承。
原型
JavaScript中的所有事物都是對象:字符串、數(shù)組、日期,函數(shù),正則表達(dá)式等。每一個對象都關(guān)聯(lián)一個原型對象,用proto屬性即可以訪問到,這就是對象的“原型”。
每個對象都從它的原型對象繼承屬性。也就是原型對象的屬性,通過該對象都可以直接訪問。
以O(shè)bject類型的對象為例,通過對象直接量{}和new Object()創(chuàng)建的對象都具有同一個原型,用proto屬性或通過Object.prototype都可以獲得原型對象的引用。這些對象從它的原型Object.prototype屬性繼承屬性和方法,這就是”原型繼承“。
大部分對象最終都從Object.prototype繼承,下圖列出這個原型對象內(nèi)的屬性。
看下面繼承的這段代碼,可以更容易理解。
let p = {a:'a1'};
let q = Object.create(p);
console.log('q.a=' + q.a);
q.a = 'aaa';
q.b = 'bbb';
console.log('q.a=' + q.a);
上面代碼創(chuàng)建p對象,創(chuàng)建一個指定原型為p的q對象。然后對屬性a和b分別賦值。打印兩個對象如下:
從打印出的結(jié)果得到如下幾點:
- q通過指定p為原型創(chuàng)建,q對象的__proto__屬性就是p對象,這就是原型繼承。
- q從p繼承了屬性,所以第三行訪問q.a的時候得到了p.a的值。因為取值時如果對象本身沒有這個屬性就會到他的原型上繼續(xù)查找,再到原型的原型上查找直到找到。
- 給q的a屬性賦值,在q對象上新增了a屬性,而未修改它原型上a屬性的值。因為賦值時原型上有這個屬性但不會去修改,而是給對象新增自由屬性來實現(xiàn),相當(dāng)于覆蓋原型鏈上相同名字的屬性。
- 給q的b屬性賦值,q創(chuàng)建時沒有b屬性所以新增b屬性。
接下來在最后增加一行代碼
p.a = 'a2';
再次打印兩個對象值
修改p對象的屬性,q的原型也被修改了。這是原型繼承的原因。原型對象的屬性改了,繼承它對象的原型屬性也修改了。
原型鏈
除Object.prototype外,幾乎所有的對象都有原型,他們的構(gòu)造函數(shù)都有一個繼承自O(shè)bject.prototype的原型。因此[]和new Array()創(chuàng)建的數(shù)組類型對象,繼承自Array.prototype,并且同時繼承自O(shè)bject.prototype。
其他類型比如函,日期,表達(dá)式這些JS內(nèi)置對象都是如此。這樣一系列鏈接的原型對象就是“原型鏈”。
把之前的代碼稍作修改,通過打印的對象觀察原型鏈。
let p = {
a: 'a'
};
let q = Object.create(p);
q.b = 'b';
let o = Object.create(q);
o.c = 'c';
藍(lán)色區(qū)域是p對象,粉色區(qū)域是q對象,o對象從q繼承原型,q對象從p對象繼承原型,這樣形成了一系列的原型對象,這就是原型鏈。
所以在JavaScript這種基于原型的語言,通過原型對象來繼承屬性和方法。
類
在JavaScript中類的實現(xiàn)基于原型繼承機(jī)制。類的所有實例對象都是從同一原型對象上繼承屬性,因此原型對象是類的核心。
構(gòu)造函數(shù)
使用new調(diào)用構(gòu)造函數(shù)創(chuàng)建一個新對象,構(gòu)造函數(shù)的prototype屬性被用作新對象的原型。這意味著同一個構(gòu)造函數(shù)創(chuàng)建的所有對象都繼承自一個相同的對象,因此他們都是同一個類的成員。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructor: Person,
introduction: function() {
console.log('My name is ' + this.name + ', I\'m ' + this.age + '.');
}
}
let dav = new Person('David', 16);
dav.introduction();
- 按照約定構(gòu)造函數(shù)首字母大寫。定義構(gòu)造函數(shù)就是定義類。
- 構(gòu)造函數(shù)必須通過new關(guān)鍵字調(diào)用。在定義構(gòu)造函數(shù)前就已經(jīng)創(chuàng)建了新對象,通過this關(guān)鍵字可以獲取這個新對象。
- 構(gòu)造函數(shù)只是初始化this。構(gòu)造函數(shù)甚至不必返回這個新創(chuàng)建的對象,構(gòu)造函數(shù)會自動創(chuàng)建對象,然后將構(gòu)造函數(shù)作為這個對象的方法來調(diào)用一次,最后返回這個新對象。
- 構(gòu)造函數(shù)是類的外在表現(xiàn),構(gòu)造函數(shù)的名字通常用類名,可以使用instanceof來檢測對象是否屬于某個類。
- constructor指代它的構(gòu)造函數(shù)。構(gòu)造函數(shù)是類的公共標(biāo)識,constructor屬性為對象提供了類。