1.對象的概念:無需屬性的集合,屬性可以為數值,對象或函數,ECMAscript中沒有類的概念,這點是javascript與其他面向對象(OO)語言不同的地方。
//創建一個自定義對象 var person=new Object(); person.name="Tom"; person.age=23; person.job="web前端工程師"; person.sayName=function(){ alert(person.name); } person.sayName(); //用對象字面量語法來創建自定義對象 var person={ name:"Tom", age:23, job:"web前端工程師", sayName:function(){ alert(this.name); } } person.sayName();
2.屬性類型
(1)為了描述對象屬性(property)的各種特征,ECMAscript引入特性(attribute)的概念,同時為了表示特性是內部值,所以將特性放在[[]]中。
(2)ECMAscript有倆中屬性:數據屬性和訪問器屬性
數據屬性的特性:[[Configurable]]:表示能否通過delete刪除屬性從而重新定義屬性,默認為true。
[[Enumerable]]:表示能否通過for-in循環來返回屬性。默認為true。
[[Writable]]:表示能否修改屬性的值。默認為true。
[[Value]]:包含這個屬性的值。默認為undefined。
如果想修改數據屬性的特性可以調用defineProperty(屬性所在對象,"屬性名",{描述符對象})。
var person={}; Object.defineProperty(person,"name",{ writable:false, value:"Tom" }); alert(person.name); delete person.name; alert(person.name);
注意:調用defineProperty()時,沒有指定Configurable,writable,enumerable特性時,默認為false。
訪問器屬性:[[Configurable]]同上。
[[Enumerable]]同上。
[[Get]]:在讀取屬性是調用,默認為undefined
[[Set]]:在寫入屬性時調用,默認為undefined
Object.defineProperties()可以通過描述符一次定義多個屬性
Object.getOwnPropertyDescriptor(book,"_year");讀取屬性的特性,返回的是對象。
-----------------------------------------------------------------分割線---------------------------------------------------------------------------------------------------
創建對象的方式:
1.工廠模式
function createPerson(name,age,job) { var o=new Object(); o.name=name; o.age=age; o.job=job; o.sayName=function(){ alert(this.name);//this指的是o } return o; } var person1=createPerson("Tom",23,"web前端工程師"); person1.sayName();
工廠模式雖然解決多次創建相似對象的重復性問題,但是並沒有解決對象識別問題,也就是typeof之后他都顯示object,具體的對象是什么並沒有顯示(ps傳送門對象都有哪些http://blog.sina.com.cn/s/blog_70a3539f0101eww3.html),所以構造函數模式出現了。
2.構造函數模式
function Person(name,age,job) { this.name=name; this.age=age; this.job=job; this.sayName=function(){ alert(this.name);//this是Person } } var person1=new Person("Tom",23,"web前端工程師"); person1.sayName();
構造函數模式和工廠模式的區別
1.沒有顯式的創建對象。
2.將屬性和方法賦給了this對象。
3.沒有return語句。
4.函數名第一個字母大寫。
構造函數模式優於工廠模式的原因就是,構造函數模式中的對象實例(person1)通過constructor屬性或instanceof操作符可以驗證person1既是Object的實例,也是Person的實例,同時也證明所有對象均來自於Object。
但是構造函數也有缺點,對象是引用類型,對象實例化不是指針的改變,而是簡單的復制,復制對象的方法和屬性,假設一個對象有上千個實例,它就會復制上千個功能相同的方法,這顯然是不可取的,我們看下面這個例子:
function Person(name,age,job) { this.name=name; this.age=age; this.job=job; this.sayName=sayName; } function sayName(){ alert(this.name) } var person1=new Person("Tom",23,"web前端工程師"); person1.sayName();
我們也可以把sayName()函數的定義戰役到構造函數的外部,這樣我們就將sayName屬性設置成等於全局的sayName函數,這樣實例化對象就共享全局作用域中的同一個sayName(),解決了構造函數對象方法的多次創建問題。但是全局作用域定義的sayName()函數只能被某個對象調用談什么全局作用域,而且如果構造函數對象的方法有很多,就需要定義很多全局函數,封裝性又從何談起,於是原型模式應運而生。
3.原型模式
我們創建的每一個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,這個對象中包含着所有對象實例的屬性和方法,這個對象就是原型對象。通俗的說,原型對象中的方法和屬性可以被所有對象的實例所共享,對象實例就不用多次創建相同的方法和屬性,我們看下面這個例子:
function Person(){ }; Person.prototype={ name:"Tom", age:23, job:"web前端工程師", sayName:function(){ alert(this.name); } } var person1=new Person(); person1.sayName();
雖然可以通過對象實例訪問保存在原型對象中的值,但卻不能通過對象實例重寫原型的值。其實對象實例獲取某一屬性的值是從本身開始尋找,然后是原型對象,最后是構造函數對象,所以重寫對象實例的屬性值(這個值可以通過delete操作符刪除)僅僅是阻斷了獲取原型屬性值的途徑,但是沒有改變其中的值,我們看下面這個例子:
function Person(){
};
Person.prototype.name="Tom";
Person.prototype.age=23;
Person.prototype.job="web前端工程師";
Person.prototype.sayName=function(){
alert(this.name);
}
var person1=new Person();
var person2=new Person();
person1.name="Mike";
alert(person1.name);
alert(person2.name);
alert(person1.name);
alert(person2.name);
用對象字面量語法表示的時候,原型對象的constructor屬性不在指向Person,因為每創建一個函數,同時會創建它的prototype對象,用對象字面量語法本質上相當於重寫了prototype對象,constructor屬性也會變成新對象的constructor屬性(這里指向Object),我們看下面這個例子:
function Person(){ }; Person.prototype={ constructor:Person, name:"Tom", age:23, job:"web前端工程師", sayName:function(){ alert(this.name); } } var person1=new Person(); var person2=new Person(); person1.name="Mike"; alert(person1.name); alert(person2.name);
原型模式的缺點:因為所以對象實例共享原型對象的方法和屬性,但是往往實例都有他自己私有的屬性,這時候原型模式就不適用了,所以我們可以組合使用構造函數模式和原型模式。
4.組合使用構造函數模式和原型模式
組合使用構造函數模式和原型模式結合了構造函數和原型模式的優點,構造函數定義實例的私有屬性,原型模式定義共享屬性和方法,下面我們看一個例子:
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; }; Person.prototype={ constructor:Person, sayName:function(){ alert(this.name); } } var person1=new Person("Tom"); var person2=new Person("Mike"); alert(person1.name); alert(person2.name);
---------------------------------------------------------------分割線------------------------------------------------------------------------------------------------------
繼承
許多OO語言支持倆中繼承方式:接口繼承(繼承方法的簽名)和實現繼承(繼承實際的方法),因為函數沒有簽名,所以ECMAscript只有實現繼承,而實現繼承主要由原型鏈來實現。
1.原型鏈
簡單地說,原型鏈就是讓子對象的原型等於父對象的實例,層層遞進,實現實例和原型的鏈條。下面看一個例子:
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; } function SubType(){ this.subproperty=false; } SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function(){ return this.subproperty; } var instance=new SubType(); alert(instance.getSuperValue());//true
這里需要注意SubType.prototype對象的constructor屬性已經不指向SubType,而是指向SuperType.
原型鏈的問題:
1.原型鏈也會遇到原型模式的問題,這里就不多說了。
2.創建子對象的實例時,沒有辦法在不影響所有對象實例的情況下,給父對象的構造函數傳遞參數。
2.借用構造函數
function SuperType(){ this.colors=["red","blue","green"]; } function SubType(){ SuperType.call(this)//或者apply() } var instance1=new SubType(); instance1.colors.push("black"); alert(instance1.colors); var instance2=new SubType(); alert(instance2.colors);
借用構造函數也可以通過子對象向父對象傳遞參數
function SuperType(name){ this.name=name } function SubType(){ SuperType.call(this,"Tom")//或者apply() } var instance1=new SubType(); alert(instance1.name);
雖然借用構造函數解決了實例共享問題和無法傳遞參數的問題,但是他做的並不完美,因為所有方法都在構造函數中定義,函數的復用性從何談起,而且父原型對象中定義的方法,對子對象是不可見的,結果所有類型都只能使用構造函數模式,於是組合繼承出現了。
3.組合繼承
function SuperType(name,colors) { this.name=name; this.colors=colors; }; SuperType.prototype.sayName=function() { alert(this.name); }; function SubType(age,name,colors) { SuperType.call(this,name,colors); this.age=age; }; SubType.prototype=new SuperType(); SubType.prototype.sayAge=function() { alert(this.age); }; var person1=new SubType(23,"Tom",["blue","red","green"]); document.writeln(person1.colors);//來自父對象構造函數 document.writeln(person1.name);//來自父對象構造函數 person1.sayName();//來自父原型對象 document.writeln(person1.age);//來自子對象構造函數 person1.sayAge();//來自子原型對象
這里再強調一下,構造函數創建的屬性是實例私有的,原型創建的屬性和方法是實例共享的。
4.原型式繼承
這種方法並沒有使用嚴格意義的構造函數,而是借助原型可以基於已有的對象創建新對象,同時還不用創建自定義類型來達到這個目的。
以下代碼是封裝在object函數當中的,在使用的時候直接使用object()
function object (o)//這個o相當於父對象實例 { function F(){}//這個F相當子對象 F.prototype=o;//繼承 return new F();//實例化傳出 }
但是ECMAscript5定義了Object.create()方法,可以直接使用,他有倆個參數,下面來一個例子:
var person={ name:"Tom", age:23, job:"web前端工程師" }; var anotherPerson=Object.create(person,{ name:{ value:"Mike" } }); alert(anotherPerson.name);
在上面這個例子中,create的第二參數是一個對象,其中的屬性如果和person重名,則會覆蓋person的屬性。
5.寄生式繼承
function object (o)//這個o相當於父對象實例 { function F(){}//這個F相當子對象 F.prototype=o;//繼承 return new F();//實例化傳出 } function createAnother(o) { var clone=object(o); clone.sayName=function() { alert(this.name) }; return clone; } var person={ name:"Tom", age:23 } var anotherPerson=createAnother(person); alert(anotherPerson.name);
寄生式繼承的缺點:不能做到函數復用而降低效率。
6.寄生組合式繼承
組合繼承雖然是javascript最常用的繼承模式,但是他也不是完美的,這種繼承的問題是不管怎么樣我么都會調用倆次父對象構造函數,大家可以看之前的例子,下面這個是寄生組合式繼承的例子,是最完美的繼承:
function object (o)//這個o相當於父對象實例 { function F(){}//這個F相當子對象 F.prototype=o;//繼承 return new F();//實例化傳出 } function inheritPrototype(subType,superType) { var prototype=object(superType.prototype);//創建對象 prototype.construct=subType;//增強對象 subType.prototype=prototype;//指定對象 } function SuperType(name) { this.name=name; } SuperType.prototype.sayName=function() { alert(this.name); } function SubType(name,age) { SuperType.call(this,name); this.age=age; } inheritPrototype(SubType,SuperType); SubType.prototype.sayAge=function(){ alert(this.age); } var person1=new SubType("Tom",23);
person1.sayName();
----------------------------------------------------------分割線---------------------------------------------------------------------------------------------------
以上就是javascript對象,原型和繼承總結的所有知識,如果有任何錯誤請大家指正。