屬性類型
ECMAScript中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性。
數(shù)據(jù)屬性包含一個數(shù)據(jù)值的位置。數(shù)據(jù)屬性有4個行為描述其行為的特性。
想要修改屬性的默認特性,必須使用Object.defineProperty(),接受三個參數(shù),屬性所在的對象,屬性的名字和一個描述符對象。調(diào)用這個方法時,如果不設(shè)定,configurable,enumberable,writable特性的默認值是false。
訪問器屬性不包含數(shù)據(jù)值,包含一對getter和setter函數(shù)。具有四個特性:
定義多個屬性O(shè)bject.defineProperties()
創(chuàng)建對象
工廠模式(創(chuàng)建多個相似對象,但是沒解決對象類型識別問題)
創(chuàng)建對象可以通過Object構(gòu)造函數(shù)或者對象字面量形式創(chuàng)建單個對象。但是這兩種方式有明顯的缺點,通過同一個接口創(chuàng)建很多對象,會產(chǎn)生大量重復(fù)的代碼,于是我們開始使用工廠函數(shù)的一種變體。
function factory (name, age, job) {
var person = new Object()
person.name = name
person.age = age
person.job = job
person.sayName = function () {
alert(this.name)
}
return person
}
var person1 = factory(‘nick’,19,’stduent’)
var person2 = factory(‘lucy’,29,’hr’)
構(gòu)造函數(shù)模式
function Person (name, age, job) {
this. name = name
this.age = age
this.job = job
this.sayName = function (){
alert(this.name)
}
}
var person1 = new Person(‘nick’,19,’stduent’)
var person2 = new Person(‘lucy’,29,’hr’)
構(gòu)造函數(shù)的函數(shù)名首字母應(yīng)該大寫,這借鑒了其他OO語言,構(gòu)造函數(shù)本身也是函數(shù),只不過可以用來創(chuàng)建對象而已。
要創(chuàng)建一個Person的實例,必須使用new構(gòu)造符。以這種方式調(diào)用構(gòu)造函數(shù)會經(jīng)歷一下4步。
1.創(chuàng)建一個新對象。
2.將構(gòu)造函數(shù)的作用域賦給新對象(因此this就指向這個對象)
3.執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性)
4.返回新對象
檢測創(chuàng)建的對象是不是Object或Person的實例,建議使用instanceof
構(gòu)造函數(shù)與普通函數(shù)的區(qū)別是調(diào)用方法不同。使用new操作符來調(diào)用,那它就可以作為構(gòu)造函數(shù),而任何函數(shù),如果不通過new調(diào)用,那就和普通函數(shù)沒有區(qū)別。
//作為構(gòu)造函數(shù)調(diào)用,以上一個Person()函數(shù)為例
var person3 = new Person(‘a(chǎn)nna’,20,’doctor’)
person3.sayName() //‘a(chǎn)nna’
//作為普通函數(shù)調(diào)用
Person(‘john’,25,’teacher’)
window.sayName() //‘john’
//在另一個對象的作用域調(diào)用
var person4 = new Object()
Person.call(person4,’allen’,26,’worker’)
person4.sayName() //‘a(chǎn)llen’
構(gòu)造函數(shù)缺點:每個方法都要在每個實例上創(chuàng)建一遍。ECMAScript中函數(shù)也是對象,每創(chuàng)建一個函數(shù),就實例化一個對象。
原型模式
原型模式,使用原型對象的好處是可以將所有對象實例共享它所包含的屬性和方法。不必在構(gòu)造函數(shù)中定義對象的實例信息,而是直接將這些信息添加在原型對象中。
function Person(){
}
Person.prototype.name = 'nick'
Person.prototype.age = '20'
Person.prototype.job = 'teacher'
Person.prototype.sayName = function () {
alert(this.name)
}
var person1 = new Person()
Person.sayName() //'nick'
原型對象:無論何時,只要創(chuàng)建了一個函數(shù),都會生成一個prototype屬性,指向函數(shù)的原型對象。而所有的原型對象都會有一個constructor屬性指向prototype屬性所在函數(shù)。當調(diào)用構(gòu)造函數(shù)創(chuàng)建一個實例后,實例內(nèi)部會有一個內(nèi)部屬性proto指向構(gòu)造函數(shù)的原型對象。
Person.prototype.constructor = Person
使用isPrototypeOf()檢測原型對象和實例之間關(guān)系
alert(Person.prototype.isPrototypeOf(person1)) //true
使用Object.getPrototypeOf() 返回雙下劃線proto的值
alert(Object.getPrototypeOf(person1) == Person.prototype) //true
alert(Object.getPrototypeOf(person1).name) // nick
使用hasOwnProperty() 檢測一個屬性存在于實例中還是原型中。存在于實例屬性返回true,原型中返回false。
雖然可以通過對象實例訪問保存在原型中的值,但是不能通過對象實例重寫原型中的值。如果我們給實例添加一個屬性,而這個屬性和原型中的一個屬性同名,那我們就在實例中創(chuàng)建該屬性,該屬性會屏蔽掉原型中的屬性。同時通過delete操作符可以刪除實例屬性。
function Person(){
}
Person.prototype.name = 'nick'
Person.prototype.age = '20'
Person.prototype.job = 'teacher'
Person.prototype.sayName=function (){
alert(this.name)
}
var person1 = new Person()
var person2 =new Person()
person1.name = 'alice'
alert(person1.name) //'alice'
alert(person2.name) //'nick'
alert(person1.hasOwnProperty(name)) //true
alert('name' in person1) //true
delete person1.name
alert(person1.name) //'nick'
alert(person1.hasOwnProperty(name)) //false
alert('name' in person1) //true in操作符表示只要對象能夠訪問到屬性就返回true,不管是在原型還是實例中。
要取得對象上所有可枚舉的實例屬性,可以使用Object.keys(),接收一個參數(shù),返回可枚舉屬性的字符串數(shù)組。
如果想得到所有實例屬性,無論是否可枚舉(constructor屬性),可以使用Object.getOwnPropertNames()。
使用對象字面量重寫整個原型,這時候constructor指向不會再指向Person函數(shù),指向Object構(gòu)造函數(shù)??梢杂胕nstantOf()操作符返回正確的結(jié)果,但是通過constructor已經(jīng)無法確定對象的類型。如果constructor的值真的很重要,可以手動設(shè)置適當?shù)闹怠?/p>
function Person () {
}
Person.prototype = {
constructor : Person,
name : 'nick',
age : '20',
job : 'teacher',
sayName : function () {
alert (this.name)
}
}
var person1 = new Person()
alert(person1 instanceOf Person) //true
alert(person1 instanceOf Object) //true
alert(person1.constructor == person) //false
alert(person1.constructor == Object) //true
構(gòu)造函數(shù)并不是沒有缺點,它省略了為構(gòu)造函數(shù)傳遞出實話參數(shù)這一環(huán)節(jié),結(jié)果所有實例在默認情況下都將取得相同的屬性值。原型模式的最大問題是其共享的本質(zhì)所導(dǎo)致的。原型中共享對于函數(shù)非常合適,對于包含基本值的屬性也還說的過去,通過在實例上添加同一屬性名,可以隱藏原型中對應(yīng)的屬性。但是對于包含引用類型的值的屬性來說,問題很突出。
function Person () {
}
Person.prototype = {
constructor : Person,
name : 'nick',
age : '20',
job : 'teacher',
friends: [‘lily’,’john’],
sayName : function () {
alert (this.name)
}
}
var person1 = new Person()
var person2 = new Person()
person1.friends.push(‘mike’)
alert(person1.friends) // ['lily','john','mike']
alert(person2.friends) // ['lily','john','mike']
alert(person1.friends == person2.friends) //true
組合使用構(gòu)造函數(shù)和原型模式
將實例屬性都定義在構(gòu)造函數(shù)中,共享的屬性constructor和方法定義在原型中。
function Person (name, age,job) {
this.name = name
this.age = age
this.job = job
this.friends = ['lily','john']
}
Person.prototype = {
constructor : Person,
sayName : function () {
alert (this.name)
}
}
var person1 = new Person('nick','20','teacher')
var person2 = new Person('lucy','18','student')
person1.friends.push('mike')
alert(person1.friends) // [‘lily’,’john’,’mike’]
alert(person2.friends) // [‘lily’,’john’]
alert(person1.friends == person2.friends) //false
alert(person1.sayName == person2.sayName) //true
動態(tài)原型模式
將所有的信息封裝在構(gòu)造函數(shù)里,而在構(gòu)造函數(shù)中初始化原型(僅在必要條件下),保持了構(gòu)造函數(shù)和原型的優(yōu)點。
function Person (name,age,job) {
this.name = name
this.age = age
this.job = job
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function () {
alert (this.name)
}
}
}
var friend = new Person('nick','20','techer')
friend.sayName() //nick
這里只有在sayName方法不存在的時候,才會把它添加到原型中。這段代碼只有在構(gòu)造函數(shù)初次調(diào)用時才會執(zhí)行。使用動態(tài)原型模式不可以使用對象字面量重寫原型。
寄生構(gòu)造函數(shù)模式
function Person (name,age,job) {
var o = new Object()
o.name = name
o.age = age
o.job = job
o.sayName= function (){
alert(this.name)
}
return o
}
var person1 = new Person('nick','20','teacher')
person1.sayName() //'nick'
穩(wěn)妥構(gòu)造函數(shù)(不使用this,new)
function Person (name,age,job) {
var o = new Object()
o.sayName = function (){
alert(name)
}
return o
}
var person1 = Person('nick','20','teacher')
person1.sayName() //'nick'
繼承
ECMAScript中繼承主要通過原型鏈繼承。
借用構(gòu)造函數(shù),在子構(gòu)造函數(shù)內(nèi)部調(diào)用超類型構(gòu)造函數(shù),使用這種方法無法實現(xiàn)函數(shù)復(fù)用
function a () {
this.color = ['green','red','blue']
}
function b () {
a.call(this) //b繼承了a
}
var Color1 = new b()
Color1.color.push('black')
alert(Color1.color) //['green','red','blue’,’black’]
var Color2 = new b()
alert(Color2.color) //['green','red','blue']
組合繼承
function a (name) {
this.name = name
this.color = ['green','red','blue']
}
a.prototype.say = function () {
alert(this.name)
}
function b(name,age) {
a.call(this,name)
this.age = age
}
b.prototype = new a()
b.prototype.constructor = b
b.prototype.sayAge = function (){
alert(this.age)
}
var b1 = new b('nick','10','student')
b1.color.push('black')
alert(b1.color) //['green','red','blue','black']
b1.say() // 'nick'
b1.sayAge() //'10'
var b2 = new b('Anna','20','teacher')
alert(b2.color) //['green','red','blue']
b2.say()//'Anna'
b2.sayAge() //'20'
原型式繼承/寄生式繼承/寄生組合式繼承