構造函數覆蓋模式,使用new操作符調用該函數的行為就如以函數調用它的行為一樣。這能工作完全利益於js允許new表達式的結果可以被構造函數中的顯式return語句所覆蓋。當User函數返回self對象時,new表達式的結果就變為self對象。該self對象可能是另一個綁定到this的對象。 防範誤用構... ...
當使用函數作為一個構造函數時,程式依賴於調用者是否記得使用new操作符來調用該構造函數。註意:該函數假設接收者是一個全新的對象。
一個例子
function User(name,pwd){
this.name=name;
this.pwd=pwd;
}
當調用者,忘記使用new關鍵字時,那麼這個函數的接收者是全局對象。
var u=User('wengxuesong','asdfasdfadf');
u;//undefinedthis.name;//'wengxuesong'this.pwd;//'asdfasdfadf'
該函數返回了無意義的undefined,還會修改全局對象。
如果將User函數應用ES5的嚴格模式,那麼它的接收者為undefined
function User(name,pwd){
'use strict';
this.name=name;
this.pwd=pwd;
}
var u=User('wengxuesong','asdfasdfadf');//error:this is undefined
上面代碼會報錯,因為在嚴格模式下直接運行的函數中的this是undefined,無法給undefined定義屬性。
如何才能使User函數在不使用new操作符的情況下,還是按預期工作呢?提供一個不管怎樣調用都工作如構造函數的函數。實現該函數的一個簡單方法是檢查函數的接收者是否是正確的User實例。
function User(name,pwd){
if(!(this instanceof User)){
return new User(name,pwd);//額外的函數調用
}
this.name=name;
this.pwd=pwd;
}
使用這種方式無論如何調用User函數,都會返回一個繼承自User.prototype的對象
var x=User('wengxuesong','12123123');
var y=new User('songqiang','sfasdfasdf');
x instanceof User;//true
y instanceof User;//true
這個模式的一個缺點是它需要額外的函數調用,因此代價有點高。而且,很難適用於可變參數函數,因為沒有一種直接模擬apply方法將可變參數函數作為構造函數調用的方式。
function User(name,pwd){
var self=this instanceof User?this:Object.create(User.prototype);
self.name=name;
self.pwd=pwd;
return self;
}
Object.create需要一個原型對象作為參數,並返回一個繼承自該原型對象的新對象。因此,當以函數的方式調用該版本的User函數時,結果將返回一個繼承自User.prototype對象的新對象,並且該對象具有已經初始化的name和pwd屬性。
Object.create只有在ES5環境中才是有效的,可以通過代碼進行相容。上節已經寫過一次了,再寫一遍加深印象吧。
if(typeof Object.create === 'undefined'){
Object.create=function(prototype){
function F(){};
F.prototype=prototype;
return new F();
}
}
註意:這裡只實現了單參數的Object.create函數。真實版本的Object.create函數還接受一個可選的參數,該參數描述了一組定義在新對象上的屬性描述符。
如果使用new操作符調用新版本的User參數會發生什麼?
構造函數覆蓋模式,使用new操作符調用該函數的行為就如以函數調用它的行為一樣。這能工作完全利益於js允許new表達式的結果可以被構造函數中的顯式return語句所覆蓋。當User函數返回self對象時,new表達式的結果就變為self對象。該self對象可能是另一個綁定到this的對象。
防範誤用構造函數可能並不是太值得去做,尤其是當僅僅是局部使用構造函數時。但是理解如果以錯誤的方式調用構造函數會造成嚴重後果很重要。至少文檔化構造函數期望使用new操作符調用是很重要的,尤其是在跨大型代碼庫中其享構造函數或該構造函數來自一個共用庫時。
提示
-
通過使用new操作符或Object.create方法在構造函數定義中調用自身使得該構造函數與調用語法無關
-
當一個函數期望使用new操作符調用時,清晰地文檔化該函數
附錄一:Object.create方法
以下內容來自Mozilla 開發者社區
Object.create()方法創建一個擁有指定原型和若幹指定屬性的對象。
語法
Object.create(原型對象,[屬性對象集])
異常
如果原型對象不是null或一個對象值,則拋出一個TypeError異常
實現類式繼承
單繼承
function Shape(){
this.x=0;
this.y=0;
}
Shape.prototype.move=function(x,y){
this.x+=x;
this.y+=y;
console.info('Shape moved.');
};
function Rectangle(){
Shape.call(this);//構造函數借用
}
Rectangle.prototype=Object.create(Shape.prototype);
var rect=new Rectangle();
rect instanceof Rectangle;//true
rect instanceof Shape;//true
rect.move(1,1);//"Shape moved"
多繼承
function MyClass(){
SuperClass.call(this);
OtherSuperClass.call(this);
}
MyClass.prototype=Object.create(SuperClass.prototype);
mixin(MyClass.prototype,OtherSuperClass.prototype);//mixin
MyClass.prototype.myMethod=function(){
};
propertyObject參數
var o;
//創建原型為null的空對象
o=Object.create(null);
o={};
//以字面量方式創建空對象相當於下麵這句代碼
o=Object.create(Object.prototype)
o=Object.create(Object.prototype,{
//創建對象的數據屬性
foo:{writable:true,configurable:true,value:'hello'},
//創建對象的訪問器屬性
bar:{
configurable:false,
get:function(){return 10;},
set:function(val){console.log('setting "o.bar" to',val);}
}
});
function Constructor(){}
o=new Constructor();
//相當於
o=Object.create(Constructor.prototype);
//如果Constructor里有一些初始化代碼,Object.create不能執行那些代碼
//應該相當於
o={};
o=Object.create(Constructor.prototype);
Constructor.call(o,args);
//創建一個以另一個空對象為原型,且擁有一個屬性p的對象
o=Object.create({},{p:{value:42}});
//省略了的屬性預設為false,所以屬性p是不可寫,不可枚舉,不可配置的
o.p=24;
o.p;//42
o.q=12;
for(var prop in o){
console.log(prop);
}
//'q'delete o.p;//false
//創建一個可寫的,可枚舉的,可配置的屬性p
o2=Object.create({},{p:{value:42,writable:true,enumerable:true,configurable:true}});
相容版
if(typeof Object.create !== 'function'){
Object.create=(function(){
function NOP(){}
var hasOwn=Object.prototype.hasOwnProperty;
return function(o){
//1、如果o不是Object或null,拋出一個類型錯誤異常
if(typeof o!=='object'){
throw TypeError('Object prototype may only be an Object or null.');
}
//2、使創建的一個新的對象為obj
//3、設置obj的內部屬性[[Prototype]]為o
NOP.prototype=o;
var obj=new NOP();
NOP.prototype=null;//解除NOP函數的prototype的引用
//4、如果參數有Properties,那麼檢測並添加屬性到obj上。
if(arguments.length>1){
var Properties=Object(arguments[1]);
for(var prop in Properties){
if(hasOwn.call(Properties,prop)){
obj[prop]=Properties[prop];
}
}
}
return obj;
}
})();
}