創(chuàng)建對象的設(shè)計模式
- 工廠模式
- 抽象了創(chuàng)建具體對象的過程,用函數(shù)封裝以特定接口創(chuàng)建對象的細(xì)節(jié)
- 解決了創(chuàng)建多個相似對象的問題,沒有解決對象識別的問題
function createPerson(name,age){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sayName = function(){
alert(this.name)
}
return obj;
}
var person = createPerson('aa',12);
- 構(gòu)造函數(shù)模式
- 構(gòu)造函數(shù)可以用來創(chuàng)建特定類型的對象,可以用instanceof檢測類型
- 使用new操作符會經(jīng)歷以下四個步驟
- 創(chuàng)建一個新對象
- 將構(gòu)造函數(shù)的作用域賦給新對象(因此this指向了這個新對象)
- 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性)
- 返回新對象
- 缺點:
- 每個方法都要在每個實例上重新創(chuàng)建一遍
- 每個方法(函數(shù))就是一個對象,不同實例上的同名函數(shù)是不相等的
- 將函數(shù)抽離到外部使多個對象共共享全局作用域的函數(shù)雖然可以解決上面問題,但讓this.sayName = sayName全局方法會破壞了對象的封裝性
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name)
}
}
var person = new Person('aa',12);
person instanceof Person => true
原型模式
- prototype
- 只要創(chuàng)建了一個新函數(shù),就會為函數(shù)創(chuàng)建一個prototype屬性,這個屬性指向函數(shù)的原型對象
- 包含了特定類型的所有對象實例共享的方法和屬性
- prototype是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個對象實例的原型對象
- 默認(rèn)情況下所有原型對象會獲得一個constructor(構(gòu)造函數(shù)屬性),這個屬性指向prototype屬性所在函數(shù)的指針
- 當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個新實例時,內(nèi)部將包含一個指針,指向原型對象,連接實例與原型對象
function Person(name,age){
}
Person.prototype.name = name;
Person.prototype.age = age;
Person.prototype.sayName = function(){
alert(this.name)
}
var person = new Person('aa',12);
person instanceof Person => true
-
判斷原型對象
- Person.prototype.isPropertyOf(person) => true
- Object.getPropertyOf(person) == Person.prototype => true (es5)
-
多個對象實例共享原型所保存的屬性和方法的基本原理
- 每當(dāng)代碼讀取某個對象的某個屬性時,搜索從對象實例本身開始,如果在實例中找到具有給定名字的屬性則返回該屬性的值
- 如果沒有找到則繼續(xù)搜索指針指向的原型對象,如果在原型對象中找到給定名字的屬性,則返回這個值
雖然可以通過對象實例訪問保存在原型實例中的值,但是不能通過對象實例重寫原型中的值,如果在實例中添加一個與原型實例中的一個屬性同名,訪問時實例屬性會屏蔽原型中這個屬性的值,只能訪問到這個實例上屬性的值??梢酝ㄟ^delete操作符刪除實例屬性,重新訪問原型中的屬性
使用hasOwnProperty()可以檢測一個屬性是存在實例中還是原型中,只有存在實例中才會返回true;person.hasOwnProperty('name')
'name' in person 無論name是存在實例中還是原型對象中都會返回true
//判斷對象原型上是否有這個屬性
function hasPrototypeProperty(obj, attr){
return (attr in obj) && !obj.hasOwnProperty(attr);
}
在使用for-in 循環(huán)時,返回的是能夠通過對象訪問的,可枚舉的屬性,包括了實例上的屬性和原型上的屬性。原型上不可枚舉的屬性,但是在實例中定義了也可以獲取得到,如在實例中定義toString
-
Object.keys() (es5)
- 取得對象上所有可枚舉的實例屬性,返回一個包含所有可枚舉的字符串?dāng)?shù)組
- 也可以傳入一個原型對象,Object.keys(Person.prototype),但不會沿著原型鏈往上尋找,只返回當(dāng)前prototype下的屬性
-
Object.getOwnPropertyNames()(ie9+)
- 枚舉所有實例屬性,不管是否可枚舉
原型的動態(tài)性
- 在原型中查找值的過程是一次搜索,對原型 對象所做的任何修改都能立即從實例上反映出來--即使是先創(chuàng)建了實例后修改原型也照樣如此
var friend = new Person();
Person.prototype.sayHi = function(){
alert('hi')
}
friend.sayHi() //hi
- 實例和原型之間松散的連接關(guān)系,可以隨時為原型添加屬性和方法,并且修改能夠立即在所有對象實例中反映出來
- 但是重寫整個原型對象就不一樣了,調(diào)用構(gòu)造函數(shù)時會為實例添加一個指向最初原型的[[prototype]]指針,而把原型修改為另外一個對象就等于切斷了構(gòu)造函數(shù)與最初原型的聯(lián)系
- 實例中的指針僅指向原型對象,而不指向構(gòu)造函數(shù)
var friend = new Person();
Person.prototype = {
sayHi:function(){
alert('hi')
}
}
friend.sayHi() //error

- 原型模式的缺點
- 省略了為構(gòu)造函數(shù)傳遞初始化參數(shù),所有實例在默認(rèn)情況下都將取得相同的值
- 當(dāng)使用原型屬性時會只要在一個實例上修改都會影響到所有的實例,例如在一個實例上修改數(shù)組
組合使用構(gòu)造函數(shù)和原型模式
- 構(gòu)造函數(shù)定義實例屬性,原型模式定義方法和屬性
動態(tài)原型模式
- 通過檢查某個應(yīng)該存在的方法是否有效,來決定是否需要初始化原型
function Person(name,age){
this.name = name;
this.age = age;
**if( typeof this.sayName != 'funcction'){
Person.prototype.sayName = function(){
alert('hi')
}
}**
}
- 只有初次調(diào)用構(gòu)造函數(shù)時才會執(zhí)行將函數(shù)添加到原型中
寄生構(gòu)造函數(shù)模式
- 類似于工廠模式,封裝創(chuàng)建對象的代碼,然后返回新創(chuàng)建對象,return語句可以重寫調(diào)用構(gòu)造函數(shù)時返回的值
function Person(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert('hi');
}
return o;
}
使用場景:特殊情況下用來為對象創(chuàng)造構(gòu)造函數(shù)。
-
假設(shè)創(chuàng)建一個具有額外方法的特殊數(shù)組,由于不能直接修改Array構(gòu)造函數(shù)
function SpecialArray(){ var arr = new Array(); arr.push.apply(values,arguments); arr.toPipedString = function(){ return this.join('|'); } return arr; }
var colors = new SpecialArray('green','red');
colors.toPipedString()
- 缺點:返回的對象和構(gòu)造函數(shù)或者與構(gòu)造函數(shù)的原型屬性之間沒有什么關(guān)系,不能使用instanceof來確定對象類型
### 穩(wěn)妥構(gòu)造函數(shù)模式
- 特點
- 新創(chuàng)建對象的實例方法不引用this
- 不使用new操作符調(diào)用構(gòu)造函數(shù)
- 除了返回對象上的屬性和方法,沒有其他辦法訪問到構(gòu)造函數(shù)內(nèi)的數(shù)據(jù)
```js
function Person(name,age){
var o = new Object();
//這里可以定義私有數(shù)據(jù)
o.sayName = function(){
alert(name)
}
return o;
}
var person = Person('green',12);
person.sayName() //只能通過sayName()方法去訪問name的值
繼承
原型鏈繼承
- js以原型鏈作為實現(xiàn)繼承的主要方法
- 基本思想是利用原型鏈讓一個引用類型繼承另一個引用類型的屬性和方法
- 構(gòu)造函數(shù)、原型、實例的關(guān)系
每個構(gòu)造函數(shù)都有一個原型對象
原型對象都有一個指向構(gòu)造函數(shù)的方法
實例都包含一個指向原型對象的一個指針
function SuoperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubType = function(){
return this.subprototype;
}
var instance = new SubType()
instance.getSuperValue();//true

SubType不僅具有作為一個SuperType的實例所擁有的全部屬性和方法,而且擁有一個指向SuperType原型的指針
instance指向SubType原型,SubType原型又指向SuperType的原型
確定實例與原型的關(guān)系
instanceof操作符 只要用這個操作符來測試實例與原型中出現(xiàn)過的構(gòu)造函數(shù),結(jié)果就會返回true
isPropertyOf() 只要是原型鏈中出現(xiàn)的原型,都可以說是該原型鏈所派生的實例的原型,都會返回true
-
缺點:
- 原型屬性會被所有實例共享,如果在父構(gòu)造函數(shù)中有一個this.colors=[]的數(shù)組,子構(gòu)造函數(shù)繼承后的實例可以修改這個存在于子構(gòu)造函數(shù)原型對象上的原型屬性
- 在創(chuàng)建子類型的實例時,不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)。實際上是沒有辦法在不影響所有實例的情況下給超類型的構(gòu)造函數(shù)傳遞參數(shù)
借用構(gòu)造函數(shù)
- 在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù),通過call,apply 改變對象的this指向
function SuoperType(name){
this.colors = ['red'];
this.name = name;
}
function SubType(name){
SuperType.call(this,name);
}
SubType.prototype = new SuperType();
var instance = new SubType('1')
instance.colors.push('green') => ['red','green']
var instance2 = new SubType('2')
instance2.colors.push('black') => ['red','black']
組合繼承
- 將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一起
- 使用原型鏈實現(xiàn)對原型屬性和方法的繼承
- 使用借用構(gòu)造函數(shù)實現(xiàn)對實例屬性的繼承
function SuperType(name){
this.colors = ['red'];
this.name = name;
}
SuperType.prototype.sayName = function(){
alert(this.name)
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayName = function(){
alert(this.name)
}
var instance = new SubType('1',12)
instance.sayName() => 1
instance.colors => ['red']
var instance2 = new SubType('2',21)
instance2.sayName() => 2
instance2.colors => ['red']
原型式繼承
- 基于一個對象上,這個對象相當(dāng)于作為原型,再根據(jù)需求對得到的對象加以修改
- 在沒有必要興師動眾創(chuàng)建構(gòu)造函數(shù),而只想讓一個對象與另一個對象保持類似的情況下使用
function object(o) {
function F(){}
F.prototype = o;
return new F();
}
var person = {
name:'n',
friends:['a','b']
}
var anotherPerson = object(person);
anotherPerson.name = 'y'; =>y
anotherPerson.colors.push('c'); => ['a','b','c']
var person = object(person);
person.name = 'yy'; =>yy
person.color; =>['a','b','c']
- Object.create() es5新增規(guī)范原型式繼承
var person = {
name:'n',
friends:['a','b']
}
var anotherPerson = Object.create(person);
anotherPerson.name = 'y'; =>y
anotherPerson.colors.push('c'); => ['a','b','c']
var person = Object.create(person);
person.name = 'yy'; =>yy
person.color; =>['a','b','c']
寄生式繼承
- 創(chuàng)建一個僅用于封裝繼承過程的函數(shù),在函數(shù)內(nèi)部以某種方式增強(qiáng)對象
function object(o) {
function F(){}
F.prototype = o;
return new F();
}
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){
alert('hi')
}
return clone;
}
var person = {
name:'n',
friends:['a','b']
}
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); =>hi
寄生組合式繼承
- 組合繼承最大的問題是無論什么情況下都會調(diào)用兩次超類型構(gòu)造函數(shù)
- 一次是創(chuàng)建子類型原型時,一次是子函數(shù)的內(nèi)部構(gòu)造函數(shù)
- 寄生組合繼承通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈混成形式來繼承方法。
- 思路是不必為了指定的子類型原型而調(diào)用超類型的構(gòu)造函數(shù)。
- 使用寄生式繼承來繼承超類型的原型,然后再將結(jié)果指定給子類型的原型
function inheritPrototype(subType,superType){
var prototype = object(superType);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name){
this.colors = ['red'];
this.name = name;
}
SuperType.prototype.sayName = function(){
alert(this.name)
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.prototype =inheritPrototype(subType,superType) ;
SubType.prototype.sayName = function(){
alert(this.name)
}