一、理解對象
1.創建一個對象,然后給這個對象新建屬性和方法。
①常見的創建方式
var person = new Object(); //創建一個Object 對象person.name = 'XIE'; //創建一個name 屬性并賦值person.age = 20; //創建一個age 屬性并賦值person.sayName = function () { //創建一個run()方法并返回值return this.name; //this指的是person本身};
②字面量的形式
var person = {name:"xie",age:20,job:"學生",sayName:function(){return this.name;}
}
2.數據屬性
數據屬性有4個描述其行為的特性。
特性 | 默認值 | 含義 |
---|---|---|
configurable | true | 是否可配置,表示能否刪除屬性重新定義,能否修改屬性的特性,能否修改為訪問器屬性。 |
enumerable | true | 是否可枚舉 |
writable | true | 是否可修改屬性值 |
value | undefined | 屬性的數據值 |
注:configurable特性一旦設置為false后,則不能再變為true.
var person={};
Object.defineProperty(person,"name",{configurable:true,enumerable:true,writable:true,value:"xie"
})
3.訪問器屬性
特性 | 默認值 | 含義 |
---|---|---|
configurable | true | 是否可配置,表示能否刪除屬性重新定義,能否修改屬性的特性,能否修改為訪問器屬性。 |
enumerable | true | 是否可枚舉 |
get | undefined | 讀取屬性時調用 |
set | undefined | 寫入屬性時調用 |
二、創建對象
1.工廠模式
function createObject(name, age) { //集中實例化的函數var obj = new Object();obj.name = name;obj.age = age;obj.run = function () {return this.name + this.age + '運行中...';};return obj;
}
var box1 = createObject('Lee', 100); //第一個實例
var box2 = createObject('Jack', 200); //第二個實例
工廠模式解決了重復實例化的問題,但還有一個問題,那就是識別問題,因為根本無法弄清楚一個對象的類型。
2.構造函數模式
function Person(name,age){this.name = name;this.age = age;this.run = function(){alert(this.name);}
}
var person1 = new Person("Lee",100);
var person2 = new Person("Jack",200);
使用了構造函數的方法,和使用工廠模式的方法他們不同之處如下:
- 構造函數方法沒有顯示的創建對象(new Object());
- 直接將屬性和方法賦值給this 對象;
- 沒有renturn 語句。
構造函數的方法有一些規范:
- .函數名和實例化構造名相同且大寫,(PS:非強制,但這么寫有助于區分構造函數和普通函數);
- 通過構造函數創建對象,必須使用new 運算符。
使用new操作符創建實例時,執行過程如下:
- 創建一個新對象;
- 將構造函數的作用域賦給新對象(因此this就指向了這個對象);
- 執行構造函數中的代碼(為這個新對象添加屬性);
返回新對象
使用構造函數的主要問題,就是每個方法都要再每個實例上重新創建一遍。通過下面可知不一樣:
alert(person1.run == person2.run); //false
3.原型模式
我們創建的每個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,它的用途是包含可以由特定類型的所有實例共享的屬性和方法。使用原型的好處可以讓所有對象實例共享它所包含的屬性和方法。也就是說,不必在構造函數中定義對象信息,而是可以直接將這些信息添加到原型中。
3.1常見的語法
// 原型模式
function Box() {} //聲明一個構造函數
Box.prototype.name = 'Lee'; //在原型里添加屬性
Box.prototype.age = 100;
Box.prototype.run = function () { //在原型里添加方法return this.name + this.age + '運行中...';
};
//比較一下原型內的方法地址是否一致:
var box1 = new Box();
var box2 = new Box();
alert(box1.run == box2.run); //true,方法的引用地址保持一致
為
下面可通過圖進一步了解
在原型模式聲明中,多了兩個屬性,這兩個屬性都是創建對象時自動生成的。proto屬性是實例指向原型對象的一個指針,ECMAScript5稱這個指針為[[Prototype]],它的作用就是指向構造函數的原型屬性constructor。
isPrototypeOf()方法來判斷一個對象是否指向了該構造函數的原型
alert(Box.prototype.isPrototypeOf(box1)); //true,只要實例化對象,即都會指向
Object.getPrototype()方法返回[[Prototype]]的值。IE9+,Firefox3.5+,Safari5+,Opera12+ 和Chrome支持
alert(Object.getPrototype(box1).name); //Lee
原型模式的執行流程:
- .先查找構造函數實例里的屬性或方法,如果有,立刻返回;
- 如果構造函數實例里沒有,則去它的原型對象里找,如果有,就返回;
hasOwnProperty()函數判斷構造函數的實例中是否有該屬性。
alert(box.hasOwnProperty('name')); //實例里有返回true,否則返回false
in操作符會在通過對象能夠訪問給定屬性時返回 true,無論該屬性存在于實例中還是原型中。
alert('name' in box); //true,存在實例中或原型中
function isProperty(object, property) { //判斷原型中是否存在屬性return !object.hasOwnProperty(property) && (property in object);
}
3.2更簡單的原型語法
為了讓屬性和方法更好的體現封裝的效果,并且減少不必要的輸入,原型的創建可以使用字面量的方式:
function Box() {};
Box.prototype = { //使用字面量的方式name : 'Lee',age : 100,run : function () {return this.name + this.age + '運行中...';}
};
使用構造函數創建原型對象和使用字面量創建對象在使用上基本相同,但還是有一些區別,字面量創建的方式使用constructor 屬性不會指向實例,而會指向Object,構造函數創建的方式則相反。
var box = new Box();
alert(box instanceof Box); //true
alert(box instanceof Object); //true
alert(box.constructor == Box); //字面量方式,返回false,否則,true
alert(box.constructor == Object); //字面量方式,返回true,否則,false
如果想讓字面量方式的constructor 指向實例對象,可以如下所示
function Box() {};
Box.prototype = { //使用字面量的方式constructor : Box, //直接強制指向即可name : 'Lee',age : 100,run : function () {return this.name + this.age + '運行中...';}
};
重設constructor會導致它的enumerable特性被修改為true,默認情況下,原生的constructor是不可枚舉的,可使用Object.defineProperty()方法重設構造函數。
Object.defineProperty(Box.prototype,"constructor",{enumerable:false,value:Box
});
原型的聲明是有先后順序的,所以,重寫的原型會切斷之前的原型。
function Box() {};
Box.prototype = { //原型被重寫了constructor : Box,name : 'Lee',age : 100,run : function () {return this.name + this.age + '運行中...';}
};
Box.prototype = {age = 200
};
var box = new Box(); //在這里聲明
alert(box.run()); //erroe,因為box 只是最初聲明的原型
3.3原型對象的問題
原型模式創建對象也有自己的缺點,它省略了構造函數傳參初始化這一過程,帶來的缺點就是初始化的值都是一致的。而原型最大的缺點就是它最大的優點,那就是共享。原型中所有屬性是被很多實例共享的,共享對于函數非常合適,對于包含基本值的屬性也還可以。但如果屬性包含引用類型,就存在一定的問題:
function Box() {};
Box.prototype = {constructor : Box,name : 'Lee',age : 100,family : ['父親', '母親', '妹妹'], //添加了一個數組屬性run : function () {return this.name + this.age + this.family;}
};
var box1 = new Box();
box1.family.push('哥哥'); //在實例中添加'哥哥'
alert(box1.run());
var box2 = new Box();
alert(box2.run()); //共享帶來的麻煩,也有'哥哥'了
4.動態原型模式
為了解決構造傳參和共享問題,可以組合構造函數+原型模式,即動態原型模式。
function Box(name ,age) { //將所有信息封裝到函數體內this.name = name; //不共享的使用構造函數形式this.age = age;if (typeof this.run != 'function') { //僅在第一次調用的初始化Box.prototype.run = function () { //共享的使用原型模式return this.name + this.age + '運行中...';};}
}
var box = new Box('Lee', 100);
alert(box.run());
當第一次調用構造函數時,run()方法發現不存在,然后初始化原型。當第二次調用,就不會初始化,并且第二次創建新對象,原型也不會再初始化了。這樣及得到了封裝,又實現了原型方法共享,并且屬性都保持獨立。
PS:使用動態原型模式,要注意一點,不可以再使用字面量的方式重寫原型,因為會切斷實例和新原型之間的聯系。
5.寄生構造函數模式
寄生構造函數,其實就是工廠模式+構造函數模式。這種模式比較通用,但不能確定對象關系。
function myString(string) {var str = new String(string);str.addstring = function () {return this + ',被添加了!';};return str;
}
var box = new myString('Lee'); //比直接在引用原型添加要繁瑣好多
alert(box.addstring());
使用場景:要創建一個具有額外方法的引用類型時。
6.穩妥構造函數模式
在一些安全的環境中,比如禁止使用this 和new,這里的this 是構造函數里不使用this,這里的new 是在外部實例化構造函數時不使用new。
function Box(name , age) {var obj = new Object();obj.run = function () {return name + age + '運行中...'; //直接打印參數即可};return obj;
}
var box = Box('Lee', 100); //直接調用函數
alert(box.run());
PS:穩妥構造函數和寄生類似。