前言
今天從家里回到了學校,在家呆了十天,胖了幾斤的重量,又折騰回學校了,春節回家真是艱辛的路途。隨便扯扯我的往返行程:為了省錢我沒有選擇直飛到長春往返都是到北京轉的,這樣我和女朋友可以節省4000塊左右。1月24號深圳-飛機-北京(飛機晚點1個小時),到北京已經凌晨兩點多,機場大巴就剩兩個了,做一個大巴到了市里在打的到同學那100塊沒了,到同學那(天通苑附近)都三點了,吃飯喝酒(一瓶牛欄山+2瓶啤酒)到了五點,睡覺到十點,起床去打了兩個小時的籃球,回來沒吃飯地鐵去車站,差一點沒有趕上動車去長春,晚上九點多到了長春,打車去長春理工同學那住,晚上要了肯德基外賣,100塊又沒了,十二點睡覺三點起床趕車回松原下鄉參加同學婚禮,下午1點回來做汽車回家。哎,折騰死人了,放假在家天天叔叔大爺家輪流吃飯,天天打麻將......2月6號四點半起床趕車去市里做火車去長春為了趕上10點25分的車去北京,九點左右到了北京,坐機場塊錢去首都機場,坐飛機直飛香港,到香港凌晨3點多,出戰坐大巴去深圳灣回深圳,到宿舍8點多。感覺一直都是在趕車,好累,有錢就直飛,哼。
就扯到這,今天和大伙一起學習javascript中的對象和創建對象的一些方式。
對象
javascript中什么是對象呢,javascript中一切都是對象,任何對象的都是基於Object的,確切的說每一個對象都是基於一個引用類型創建的,這個引用類型可以是原生的引用類型也可以是我們自己創建的,這些引用類型是基於Object的。最簡單的創建對象的方法是
var person = new Object(); person.name = "hainan"; person.age = "25";
直接new出來一個Object對象,之后給這個對象添加屬性和方法。
屬性類型
在第五版的javascript中,定義了在內部使用的特性,描述了屬性的各種特征,這些特性是javascript引擎使用的,在javascript中我們不能使用它們,為了區別將他們放在了兩對方括號中,屬性類型分為兩種:數據屬性和訪問器屬性。
數據屬性
- [[Configurable]] 表示能否通過delete刪除屬性,默認為true
- [[Enumerable]] 表示能否通過for-in枚舉屬性,默認為true
- [[Writable]] 表示能否修改屬性的值,默認為true
- [[Value]] 表示這個屬性的值,默認為undefined
訪問器屬性
- [[Configurable]] 表示能否通過delete刪除屬性,默認為true
- [[Enumerable]] 表示能否通過for-in枚舉屬性,默認為true
- [[Get]] 讀取屬性是調用的函數,默認值為undefined
- [[Set]] 寫入屬性時調用的函數,默認值為undefined
ECMAScript5中的屬性修改時通過Object.defineProperty()這個方法,三個參數分別為對象,屬性名,描述符對象。直接看例子,value特性
var person = {}; Object.defineProperty(person,"name",{configurable:true,value:"hainan"}) console.log(person.name);//hainan //等價於var person = {name : "hainan"}
再看看writable
var person = {}; Object.defineProperty(person,"name",{configurable:true,value:"hainan",writable:false}); person.name = "allenxing";//修改屬性 console.log(person.name);//hainan 嚴格模式下出現錯誤
下面看看configuration特性
var person = {}; Object.defineProperty(person,"name",{configurable:false,value:"hainan"}); delete person.name;//刪除屬性 console.log(person.name);//hainan 嚴格模式下出現錯誤
configuration屬性修改成不可以配置的,就是指為false時,就不能再把它修改成可配置的了,看看例子
var person = {}; Object.defineProperty(person,"name",{configurable:false,value:"hainan"}); Object.defineProperty(person,"name",{configurable:true,value:"hainan"}); //TypeError: Cannot redefine property: name
enumerable特性
var person = {}; Object.defineProperty(person,"name",{configurable:true,value:"hainan"}); for(var pro in person){ console.log(pro); } //不輸出 //在chrome下默認的enumeration默認為false
var person = {}; Object.defineProperty(person,"name",{configurable:true,value:"hainan",enumerable:true}); for(var key in person){console.log(key)} //"name"
再看一下訪問器屬性
var person = { name : "hainan", age : 25 } Object.defineProperty(person,"year",{ get : function(){ return this.age + 1; }, set :function(value){ this.age = value + 10; } }); console.log(person.year);//26 person.year= 10; console.log(person.age);//20
ECMAScript5還有一個方法Object.defineProperties()可以定義多個屬性,用法如下
var person = {} Object.defineProperties(person,{ name : {value : "hainan"}, age : {value : 25}, year : { get :function(){ return this.age + 1; }, set : function(value){ this.age = value +10; } } }); console.log(person.year);//26 person.year = 10; console.log(person.age);//25 注意這里
這里我也不知道為啥age的值沒有改變,但是下面這樣就可以改變了
var person = {name :"hainan",age:25} Object.defineProperties(person,{ year : { get :function(){ return this.age + 1; }, set : function(value){ this.age = value + 10; } } }); console.log(person.year);//26 person.year = 10; console.log(person.age);//20 注意這里
求大神指導下,謝謝
多寫@赤腳非大仙的幫助,如果沒有指定描述符各屬性值,默認是false或者undefined
var person = {} Object.defineProperties(person,{ name : {value : "hainan"}, age : {value : 25,writable : true}, year : { get :function(){ return this.age + 1; }, set : function(value){ this.age = value +10; } } }); console.log(person.year);//26 person.year = 10; console.log(person.age);//25 注意這里
其實上面這些修改屬性的特性一般的時候我們是用不到的,理解這些可以幫助我們更好的了解javascript的對象。
創建對象
直接new
創建對象有許多種方法,最簡單的就是直接new 一個Object對象,之后再添加屬性了
var person = new Object(); person.name = "hainan"; person.age = 25;
對象字面量
對象字面量是比較常用的創建對象的方法,其實和new差不多,但是寫起來比較簡潔
var person = { name : "hainan", age : 25 }
缺點:看起來比較簡單,但是假如我們要創建多個對象呢,例如person1,person2.....,直接new和對象字面量都得必須重寫代碼,也就是會有大量的重復代碼。
工廠模式
工廠模式就是解決了代碼的不可復用的問題,工廠模式用一個函數封裝了創建對象的過程,看代碼
function personFactory(name,age){ var obj = new Object(); obj.name = name; obj.age = age; return obj; } var person1 = personFactory("hainan",25); var person2 = personFactory("allenxing",26);
這樣我們需要創建一個person對象時只需要使用personFactory()就可以了,解決了創建多個對象的復雜代碼問題。其實可以看出本質還是直接new出來一個Object對象,只不過是用函數封裝了起來,用一個return返回這個Object對象。
缺點:我們不知道這個對象是具體屬於哪個類型的實例,也就是它沒有解決對象的識別問題,當然知道對象是Object的實例沒有任何實質性意義。
構造函數模式
構造函數可以創建特定類型的對象, Object和Array等是原生的構造函數,是javascript運行時就存在執行環境當中的,不需要我們自己定義構造函數,當我們需要自定義對象時我們可以自定義構造函數,
function Person(name,age){ this.name = name; this.age = age; } var person1 = new Person("hainan",25); var person2 = new Person("allenxing",26);
這里我們看到了函數的首字母是大寫的,這是一種慣例,為了和其它的函數有區別。要想創建Person的實例,必須使用new操作符,看看new都干些什么了
- 創建一個新的對象
- 將構造函數的作用域賦值給這個對象,也就是this指向了這個新對象
- 執行構造函數中的代碼
- 返回這個新對象
person1和person2是Person對象的不同實例,它們有一個相同的constructor屬性,並且它們都是Person的實例
console.log(person1.constructor == Person);//true console.log(person2.constructor == Person);//true console.log(person1 instanceof Person);//true console.log(person2 instanceof Person);//true
構造函數模式解決了對象識別的問題。
我們知道構造函數也是函數,如果不使用new關鍵字,那么會是怎樣的呢,我們知道在全局作用域中this指向的是window,那么直接使用構造函數應該就是將屬性或方法添加到了window上了,我們看看是不是這樣的情況
function Person(name,age){ this.name = name; this.age = age; } var person = Person("hainan",25);//添加到了window上 console.log(window.name);//hainan
沒有new實例時,構造函數就普通函數,添加到了this指向的對象中了,在這里this指向的是window。
缺點:每當new一個實例出來,每個實例都要重新創建對象的函數,每個實例中的函數是不同的,因為函數也是對象,定義一個函數也就是實例化一個對象。
function Person(name,age){ this.name = name; this.age = age; this.getName = function(){return this.name};//等於this.getName = new Function("return this.name"); } var person1 = new Person("hainan",25); var person2 = new Person("allenxing",26); console.log(person1.getName == person2.getName);//false
雖然這個函數是所以實例共享的,但是每個實例中都是一個副本,顯然這對性能是不利的。當然我們可以這樣干
function Person(name,age){ this.name = name; this.age = age; this.getName = getName; } function getName(){return this.name;} var person1 = new Person("hainan",25); var person2 = new Person("allenxing",26); console.log(person1.getName == person2.getName);//true
這樣就是每個實例都引用了同一個函數,所以是相等的,但是如果方法很多,我們不能把所有的函數就定義在全局作用域當中,這就需要其他的模式來解決了。
原型模式
原型和原型鏈我們之前已經討論過了,這里我們直接學習這個原型模式就好了。
function Person(){} Person.prototype = { constructor : Person, name : "hainan", getName : function(){return this.name} } var person1 = new Person(); var person2 = new Person(); console.log(person1.getName == person2.getName);//true
這樣,原型模式解決了構造函數中的每個實例都有一個方法的副本的性能和內存問題,但是原型模式也有自己的不足
缺點:原型中的屬性是共享的,這是他的優點也是他的缺點,當屬性是引用類型的時候,這個問題就體現出來了。
function Person(){} Person.prototype = { constructor : Person, name : "hainan", friends : ["James"], getName : function(){return this.name} } var person1 = new Person(); person1.friends.push("Melo"); var person2 = new Person(); console.log(person2.friends);//"James", "Melo"
可以看到我們給person1添加一個朋友,結果person2的朋友也添加上了,就是由於所有實例共享原型中的屬性。
組合模式(構造函數和原型模式)
這種是最常用的方式也是大家推薦的方式,構造函數用於定義實例屬性,原型模式用於定於方法和共享的屬性,這樣就會結合兩種模式的優點,每個實例都會有一個實力屬性的副本,同時共享着對方法的引用,節省了內存。
function Person(name,age){ this.name = name; this.age = age; this.friends = ["James"]; } Person.prototype = { constructor : Person, getName : function(){return this.name} } var person1 = new Person("hainan",25); person1.friends.push("Melo"); console.log(person2.friends);//"James", "Melo" var person2 = new Person("allenxing",26); console.log(person2.friends);//"James"
動態原型模式
所謂的動態原型模式就是將原型模式和構造模式放在了構造函數中,在定義方法時先檢查是否存在這個方法,如果存在就什么就不做,若果不存在就在原型上添加這個方法。
function Person(name,age){ this.name = name; this.age = age; if(typeof this.getName != "function"){ Person.prototype.getName = function(){ return this.name; } } } var person1 = new Person("hainan",25); var person2 = new Person("allenxing",26);
寄生構造函數模式
書上說前面的幾種不合適的話就可以使用這種模式,這個模式和工廠模式其實很類似,都是將創建對象的過程封裝到一個函數內部,只不過這種模式最后使用了new操作符,new操作符我們以后在詳細說說,這里就不說了。
function Person(name,age){ var obj = new Object(); obj.name = name; obj.age = age; return obj; } var person1 = new Person("hainan",25); var person2 = new Person("allenxing",26);
這里是使用了帶返回值的構造函數,帶的返回值會覆蓋構造函數默認的返回值,這個我會寫一個關於這個的文章。
穩妥構造函數模式
書上說的穩妥對象就是沒有公共屬性,其方法也不引用this對象,看看例子,這個大家還是看看《高級程序設計》上的吧,和前面的幾種對比一下就知道為啥叫穩妥了,
function Person(name,age){ var obj = new Object(); obj.getName = function(){ return name;//沒有使用this }; return obj; } var person1 = Person("hainan",25);//沒有使用new console.log(person1.getName());
小結
這篇就這樣吧,開學了,還是得先緊起來,好好寫博客,抓緊搞論文。大家對這篇有什么疑問盡量說,對我這篇有什么意見盡量提,小弟謝謝大家了。下篇說說繼承。