作用域安全的建構函式

謙行發表於2013-08-12

屬性構造到了window物件

在JavaScript中建構函式其實是一個使用new操作符呼叫的函式,在使用呢我呼叫時, 建構函式內部用到的this物件會指向新建立的物件

function Person(name,age,job){
                this.name=name;
                this.age=age;
                this.job=job;
            }
            
            var person=new Person('Byron',24,'Software Engineer');

 

在這個例子中建構函式使用this物件給三個屬性賦值:name、age、job,當和new操作符連用的時候會建立一個新的Person物件,並給它分配這三個屬性。這樣沒什麼問題,問題出在一疏忽忘記使用new操作符來呼叫建構函式的情況下,由於this物件是在執行期繫結的所以直接呼叫Person(),this會對映到全域性物件window上,導致錯誤物件屬性的意外增加。

var person= Person('Byron',24,'Software Engineer');
            alert(window.name); //Byron
            alert(window.age); //24
            alert(window.job); //Software Engineer

這樣原本針對Person物件的三個屬性被新增window物件,因為建構函式沒有通過new操作符呼叫,而是作為普通函式被呼叫的,由於this的晚繫結而被解析成window物件。window的那麼屬性是用來標識連結目標和框架的,這裡對該屬性的偶然覆蓋可能會導致頁面上的其它錯誤,這個問題的解決方法就是建立一個作用域安全的建構函式。

作用域安全建構函式

作用域安全的建構函式在進行屬性賦值之前會this物件是否是正確型別的例項,如果不是那麼建立新的例項並返回,改造一下上面的建構函式。

function Person(name,age,job){
                if(this instanceof Person){
                    this.name=name;
                    this.age=age;
                    this.job=job;
                }else{
                    return new Person(name,age,job);
                }
            }
            
            var person= Person('Byron',24,'Software Engineer');
            alert(window.name); // ""
            alert(person.name); //Byron
            alert(person.age); //24
            alert(person.job); //Software Engineer

這段程式碼中Person函式新增了一個檢查,確保this物件是Person的例項,如果不是則使用new操作符呼叫,如果是怎在現在例項內新增屬性,這樣保證了無論是否顯示使用new操作符,都可以正確構造物件。貌似完美了,但是仍舊有問題。

建構函式竊取模式的繼承

建構函式竊取模式是常見的一種實現JavaScript繼承的方法,做法是在”子類“的建構函式中呼叫父類的建構函式以實現繼承父類屬性,這個模式有很多缺陷,這裡不做具體說明,和本文內容相關的是這種模式下上面方法所寫的建構函式仍舊不安全。

function Polygon(sides){
                if(this instanceof Polygon){
                    this.sides=sides;
                    this.getArea=function(){
                        return 0;
                    }
                }else{
                    return new Polygon(sides);
                }
            }
            
            function  Rectangle(wifth,height){
                Polygon.call(this,2);
                this.width=this.width;
                this.height=height;
                this.getArea=function(){
                    return this.width * this.height;
                };
            }
            
            var rect=new Rectangle(5,10);
            alert(rect.sides); //undefined

在這段程式碼中,Ploygon的建構函式是安全的,Rectangle的不是,新建立一個Rectangle例項後,這個例項應該通過Polygon.call()來繼承sides屬性。但是由於Polygon建構函式是安全的,this物件並非Polygon物件例項,建構函式會建立並返回一個新的Polygon例項,而不會把sides屬性賦值到this物件上,所以Rectangle物件例項中沒有sides屬性。我們可以略施小計來解決這個問題

function Polygon(sides){
                if(this instanceof Polygon){
                    this.sides=sides;
                    this.getArea=function(){
                        return 0;
                    }
                }else{
                    return new Polygon(sides);
                }
            }
            
            function  Rectangle(wifth,height){
                Polygon.call(this,2);
                this.width=this.width;
                this.height=height;
                this.getArea=function(){
                    return this.width * this.height;
                };
            }
            
            Rectangle.prototype
=new
 Polygon();
            
            var rect=new Rectangle(5,10);
            alert(rect.sides); //2

通過重寫Rectangle的prototype屬性,使它的例項也變成Polygon的例項,這樣既更像是繼承一些,也解決了上面問題。

作用

看起來作用域安全的建構函式很沒有市場的樣子,很多人會說只要寫程式碼的時候小心些就可以了,但是JavaScript的特點決定了其不能夠在編譯期發現錯誤,而在多個程式設計師開發一個頁面的時候,作用域安全的建構函式就很有用了,我們不能保證每個人寫的程式碼都那麼小心,如果一個人程式碼出錯,影響到全域性屬性,那麼這種錯誤難以追蹤除錯,這時候使用作用域安全的建構函式就可以避免此類問題。

相關文章