JavaScript OOP 創建對象的7種方式

2022-01-31 腳本之家

我寫JS代碼,可以說一直都是面向過程的寫法,除了一些用來封裝數據的對象或者jQuery插件,可以說對原生對象了解的是少之又少。所以我拿著《JavaScript高級程序設計 第3版》惡補了一下,這裡坐下總結筆記,屬於菜鳥級別,大神請直接無視。

1、工廠模式

1 /** 2  * 工廠模式 3  */ 4 function createPerson(name,age,job){ 5     var o = new Object(); 6     o.name = name; 7     o.age  = age; 8     o.job = job; 9     o.sayName = function(){10         console.log(this.name);11     };12     // 等價於  o.sayName = new Function("console.log(this.name);");13     return o;14 }15 var p1 = createPerson('lvyahui1',12,'devloper');16 var p2 = createPerson('lvyahui2',23,'desigen');17 p1.sayName();18 p2.sayName();19 console.log(p1.sayName === p2.sayName); // false  


這種方法存在很多問題,比如多個對象的方法是獨立的,沒用共享。不能判斷對象的類型

2、構造函數模式

1 /** 2  * 構造函數模式 3  */ 4 function Person (name,age,job){ 5     this.name = name; 6     this.age = age; 7     this.job = job; 8     this.sayName = function(){ 9         console.log(this.name);10     };11 }12 var pp1 = new Person('lvyahui1',12,'dev');13 var pp2 = new Person('lvyahui2',12,'desien');14 pp1.sayName();15 pp2.sayName();16 console.log(pp1.constructor === Person);    //  true17 console.log(pp2.constructor === Person);    //  true18 console.log(pp1 instanceof Object);         //  true19 console.log(pp2 instanceof Person);         //  true                   


這中方式解決了對象類型判斷的問題,但卻沒有解決方法共享的問題,方法依然在每個對象裡創建了一遍 。要解決這樣的問題,可以像下面這樣

1 function Home (name,age,job){2     this.name = name;3     this.age = age;4     this.job = job;5     this.sayAge = sayAge;6 }7 function sayAge(){8     console.log(this.age);9 }

但這樣有帶來了新的問題,在全局作用域定義的function只能在對象環境運行,違背了全局的概念;而且,當要定義的方法非常多的石猴。就要定義n多的全局函數,毫無封裝性可言。

另外,需要注意的是, 構造函數也是函數,它與普通函數的唯一區別,就在於調用方式的不同 。如下面這樣,我門可以拿它當普通函數用,這樣屬性和方法將被綁定到瀏覽器全局的執行環境window上,或者綁定到別的對象上運行,將屬性方法綁定到別的對象

1 // 構造函數也是函數 2 //  1 3 var person = new Person('hahha',23,'ddd'); 4 //  2 做普通函數 5 Person('window',11,'2323dd'); 6 //window.sayName(); 7  8 //  3 在另一個對象的作用域中使用 9 var o = new Object();10 Person.call(o,'lvyahui',23,'2323'),11 o.sayName();           

3、原型模式

js中每一個函數都有一個特殊的屬性-prototype(原型),這個屬性是一個指針,指向一個對象。這個對象包含所有以這個函數為構造函數創建的對象共享的屬性和方法 ,首先看下面的代碼

1 /** 2  * 原型模式 3  */ 4 function Dog(){} 5 Dog.prototype.name = 'a mi'; 6 Dog.prototype.age = 1; 7 Dog.prototype.sayName = function(){ 8     console.log(this.name); 9 }10 11 var d1 = new Dog(),12     d2 = new Dog();13 d1.sayName();14 d2.sayName();15 console.log(d1.sayName === d2.sayName);         //  true16 console.log(Dog.prototype.isPrototypeOf(d1));   //  true17 console.log(Object.getPrototypeOf(d1)); //返回 [[prototype]]的值18 console.log(Object.getPrototypeOf(d1) === Dog.prototype);                


  這裡將屬性和方法都添加到了函數的原型對象中。在第4行的代碼中我定義了Dog構造方法,這讓Dog的原型對象的 constructor 屬性指向了Dog。5-7行代碼又為Dog的原型對象添加了2個屬性和一個方法。

第11行、12行代碼創建了兩個對象實例, 這兩個實例中都只包含了一個特殊的屬性[[Prototype]],這個屬性指向了構造函數的prototype,構造函數的prototype屬性指向了原型對象,原型對象的constructor屬性有指會了Dog構造方法 ,這裡比較繞,我直接取書上面的圖給大家看,稍作了修改,圖裡面的是Person構造函數,這裡實在抱歉,暫時沒在ubuntu系統上找到好的作圖工具,只能將就了,有知道的可以介紹給我用用。

另外這個圖其實還省略了以個繼承關係,就是Person對象其實是繼承Obejct的,這是默認規則,別的語言也有。這也是js對象一創建就有object的方法和屬性的原因。

再一個,上面的代碼中的

原型很重要的一個概念是屬性搜索(方法我認為也是屬性),從實例對象開始一直往原型鏈上遊搜索,如果找到了就停止搜索,所以如過我們在實例對象中添加原型中已有的屬性或方法,會屏蔽掉原型鏈中的屬性或方法。 看下面的代碼

1 d1.name = 'sai mao';    // 屏蔽原型中的屬性2 console.log(d1.name);3 // 要恢復原型中的屬性,必須顯示刪除實例中的屬性4 delete d1.name;5 console.log(d1.name);   


可以看到,可以使用delete恢復原型中的屬性,而下面的方法可以用來檢測一個屬性是在實例對象中,還是在原型鏈的原型對象中。

1 // 檢測一個屬性是在實例中,還是在原型中 2 d1.name = 'hhhh'; 3 console.log(d1.hasOwnProperty('name')); // 只有在給定的屬性存在於對象實例中才返回true 4 delete d1.name; 5 console.log(d1.hasOwnProperty('name')); 6  7 //單獨使用in操作符,只要能在對象中找到屬性則返回true 8 d1.name = 'dsfdsfsd'; 9 console.log('name' in d1);  // true10 console.log('name' in d2);  // ture11 12 //同時使用hasOwnProperty 和 in操作符,就能確定這個屬性到底是在原型中還是在實例中13 function hasPrototypeProperty(object,name){14     return !object.hasOwnProperty(name) && name in object;15 }16 console.log('d1 hasPrototypeProperty :'+hasPrototypeProperty(d1, 'name'));17 console.log('d2 hasPrototypeProperty :'+hasPrototypeProperty(d2, 'name'));        

例外有一種更簡單的原型寫法

1 // 更簡單的原型語法 2 function Cat(){} 3 Cat.prototype = { 4     name    :   'mimi', 5     age     :   12, 6     job     :   'doubi', 7     sayName :   function(){ 8         console.log(this.name); 9     }10 };11 12 var cat = new Cat();13 console.log(cat instanceof Object);14 console.log(cat instanceof Cat);15 console.log(cat.constructor === Cat);//false16 console.log(cat.constructor === Object);//true            


這種方式其實是以字面量的方法 重新創建了一個對象,然後賦值給了原型指針 。它丟棄了原來的原型對象,所以 很顯然的原型對象的constructor屬性不再指向Cat,而是指向了Obejct ,有多種方法可以修復構造函數,比如在定義字面量對象的時候,就顯示制定constructor屬性為Cat,也可以使用下面的方法。

1 // 重設Cat的constructor屬性 2 Cat.prototype.constructor = Cat; 3 //  但這樣constructor變成可枚舉的了 4 var cat_keys = Object.keys(Cat.prototype); 5 console.log(cat_keys);//[ 'name', 'age', 'job', 'sayName', 'constructor' ] 6 console.log(Object.keys(cat));//[] 7  8 // 重設constructor的屬性 9 Object.defineProperty(Cat.prototype,'constructor',{10     enumerable:false,11     value:Cat12 });13 cat_keys = Object.keys(Cat.prototype);14 console.log(cat_keys);//[ 'name', 'age', 'job', 'sayName' ]          

原型模式也不是沒有問題,比如不好向構造函數傳遞參數。它 最大的問題是對引用類型的原型屬性的共享問題 ,看下面的代碼

1 // 原型模式最大的問題在於對引用類型的屬性共享問題 2  3 function House(){} 4  5 House.prototype = { 6     constructor:House, 7     friends:['lvyahui','d'] 8 }; 9 10 var h1 = new House(),11     h2 = new House();12 h1.friends.push('li');13 console.log(h1.friends);//[ 'lvyahui', 'd', 'li' ]14 console.log(h2.friends);//[ 'lvyahui', 'd', 'li' ]              

4、構造函數與原型對象方式組合的模式

組合模式可以說吸取了構造函數模式與原型模式的優點,既保證了每個對象實例都有自己獨立的屬性和方法,同時有可以實現共享的屬性和方法。

1 /** 2  * 常用方式,組合使用構造函數模式和原型模式 3  */ 4  5 function Movie(name,length){ 6     this.name= name; 7     this.length = length; 8     this.links = ['h1','h2']; 9 }10 Movie.prototype = {11     constructor:Movie,12     sayName : function (){13         console.log(this.name);14     }15 };16 var m1 = new Movie('diany1',14),17     m2 = new Movie('diany2',23);18 m1.links.push('h3');19 20 console.log(m1.links);21 console.log(m2.links);22 console.log(m1.links === m2.links);23 console.log(m1.sayName === m2.sayName);             

這種方式集各家之長,我想這種方式應該是用的比較多的了吧(本人還未畢業,對企業裡實際情況不太了解,有知道的可以悄悄告訴我)

當然,還有一種更好的寫法,就是所謂的動態原型模式

5、動態原型模式

1 function Miss(name,age){ 2     this.name = name; 3     this.age = age; 4  5     if(typeof this.sayName != 'function'){ 6         Miss.prototype.sayName = function(){ 7             console.log(this.name); 8         } 9     }10 }11 12 var miss = new Miss('lvyahui',12);13 miss.sayName();            

這種方式的在保持了組合模式的優點的前提下,讓代碼看起了封裝性更好,也更安全。

6、寄生構造模式

這中方式與工廠模式,就只有一點區別,通過new 構造函數的形式創建對象,像下面這樣,注意它只帶創建對象的時候與工廠模式有區別(16行 new)

1 /** 2  * 寄生構造模式 3  */ 4  5 function createPerson2(name,age,job){ 6     var o = new Object(); 7     o.name = name; 8     o.age  = age; 9     o.job = job;10     o.sayName = function(){11         console.log(this.name);12     };13     // 等價於  o.sayName = new Function("console.log(this.name);");14     return o;15 }16 var p1 = new createPerson2('lvyahui1',12,'devloper');


7、穩妥構造函數模式

這種模式基於穩妥對象的概念,這種對象是沒有公共屬性,它的方法中也不使用this的對象。大家都知道js中的this一直都是讓人頭疼的問題。

穩妥模式與寄生模式類似,區別在於

不通過new操作符調用構造函數

不在行創建對象的實例方法中使用this

1 /** 2  * 穩妥構造模式 3  */ 4 function Girl(name,age){ 5     var o = new Object(); 6     o.sayName = function(){ 7         console.log(name); 8     }; 9     o.sayAge = function(){10         console.log(age);11     };12     return o;13 }14 var gril = Girl('d',21);15 console.log(gril.sayName());16 console.log(gril.sayAge());17 //    輸出18 //    d19 //    undefined20 //    2121 //    undefined22 //    為什麼呢?  

大家知道這個輸出為什麼是這麼嗎?知道的留言告訴我吧。

好了,就寫這麼多了,總結一下,原生的創建js對象的最普適的方法應該是組合模式了吧。

相關焦點

  • 如何在javascript中創建一個對象?
    javascript是一門基於對象而不是面向對象的語言,由於它的這個缺陷,在javascript中實現面向對象時十分彆扭,就比如創建對象,由於在ES6之前沒有class關鍵字,想要創建對象必須依賴以下幾種間接方式。
  • 四大對象創建方法,構建javascript語言面向對象的編程基礎
    四大對象創建方法,構建javascript語言面向對象的編程基礎。傳統意義上的面向對象編程,是基於類的概念的,所有的對象都是從類發展過來,對象就是類的實例,並且具有相應類的所有方法與屬性。但是,javascript語言並沒有類的概念。
  • 什麼是JavaScript對象?如何創建並引用?這就告訴你!
    二、對象的創建(重點)2.1怎樣創建對象語法:var obj = { key : value };上面代碼定義了一個對象,它被賦值給變量obj。2.3 創建對象a.直接使用大括號創建對象b.使用new命令生成一個Object對象的實例c.使用Object.create方法創建對象var obj1 = {};
  • js創建對象的5種方式
    首先說說面向對象編程的特點:抽象:抓住核心問題封裝:只能通過對象來訪問方法繼承:從已有的對象下繼承出新的對象多態:多對象的不同形態創建對象的5種方式javascript 創建對象簡單的來說,無非就是使用內置對象或各種自定義對象,當然還可以使用JSON,但寫法有很多,也能混合使用。
  • JavaScript創建對象的7種模式
    以這種方式調用構造函數實際上會經歷以下 4個步驟:(1) 創建一個新對象;(2) 將構造函數的作用域賦給新對象(因此 this 就指向了這個新對象) ;(3) 執行構造函數中的代碼(為這個新對象添加屬性) ;(4) 返回新對象。在前面例子的最後, person1 和 person2 分別保存著 Person 的一個不同的實例。
  • JavaScript七種非常經典的創建對象方式
    JavaScript創建對象的方式有很多,通過Object構造函數或對象字面量的方式也可以創建單個對象,顯然這兩種方式會產生大量的重複代碼,並不適合量產。接下來介紹七種非常經典的創建對象的方式,他們也各有優缺點。一、工廠模式可以無數次調用這個工廠函數,每次都會返回一個包含兩個屬性和一個方法的對象。
  • Java創建對象的幾種方式
    java是一種面向對象語言,所以我們在寫代碼過程中會創建很多對象,那java創建的對象到底有多少種呢?其中每種的差別又有哪些呢?請允許我慢慢道來1.使用new關鍵字這是最常見也是使用最多的一種。但有時候我們會遇到這種情況情況編譯提示錯誤,提示信息告訴我們,這個類的構造函數是private(私有的),外部無法使用,那麼此時也不要擔心,在我們設計這個類的時候,我們需要清楚的知道這個類的作用目的,以便我們更好的設計它的使用範圍,隱藏一個類new方式創建對象的時候,一定會提供其他方法給你創建對象。
  • 七天學會javascript第一天javascript介紹
    javascript介紹javascript數據類型javascript運算符javascript對象javascriptDate對象javascript String對象JavaScript常用於實現一些前端效果。
  • Java創建對象的6種方式
    Java中關於創建對象是大家經常要用的動作,那麼大家對Java都可以通過什麼形式創建對象呢?我總結了一下,分享給大家。第一種:通過new關鍵字進行創建//通過顯式的方式,直接在JVM堆內存聲明了一個對象Object o = new Object();第二種:通過Java工具類種反射機制進行創建try { ObjectDemo o = (ObjectDemo) Class.forName(
  • 淺談JavaScript
    5種基本數據類型:number數字類型string字符串類型boolean布爾類型 true或falseundefined undefined類型,變量聲明未初始化,它的值就是undefinednull null類型,表示空對象,如果定義的變量將來準備保存對象,可以將變量初始化為null,在頁面上獲取不到對象,返回的值就是
  • JavaScript對象創建,最常用的3種方式(建議收藏)
    1.直接創建對象var 對象變量名稱 = new Object();對象變量名稱.屬性名稱 = 值;對象變量名稱.方法名稱 = function(參數列表){函數體}對象變量名稱.屬性名稱;對象變量名稱
  • javascript 創建對象常用幾種方式
    }; return o; } var person = Person("Greg", 27, "Doctor"); person.sayName();// Greg 工廠模式雖然解決了創建多個相似對象的問題
  • javascript正則對象
    1、定義正則對象的兩種方式記住:使用正則表達式必須有一個前提,首先要創建一個正則對象。創建方式有二:1)第一種創建方式:(隱式創建)var 正則對象 = /正則表達式/匹配模式;彈出結果:object所以由此可知:正則對象也是一個對象,那麼其是哪個類實例化的結果呢?
  • JavaScript高級創建對象的幾種方式以及構造函數的原型
    了解原型對象 ,先來看看創建對象的方式有哪些以及為什麼要有原型創建對象的方式:1.簡單方式(1)直接通過new Object()創建:工廠函數通過工廠函數 ,來創建他的實例對象 ,hero 1 2 3……這樣封裝確實簡化多了,通過工廠模式我們解決了創建多個相似對象代碼冗餘的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
  • Javascript 生成器
    在 javascript 中,如果想要使用生成器,則需要: 定義特殊的生成器函數 調用該函數創建一個生成器對象 在循環中使用該生成器對象,或直接調用其 方法 我們以下面這個簡單的程序做為起點,並執行以下每個步驟:
  • JavaScript原型:關於對象的深入研究
    使用任何可用方法創建的所有 JavaScript構造函數(constructor function) 都包含一個屬性。這就是 原型 屬性。這裡很重要的是,原型屬性本身就是一個對象。構造函數的原型屬性可用於訪問 / 修改方法,以及訪問 / 修改在創建對象時分配的原型對象中存在的其他屬性。
  • 用Java創建對象的5種不同方式
    作為Java開發人員,我們通常每天創建許多對象,但是我們始終使用依賴管理系統(例如Spring)來創建這些對象。但是,還有更多創建對象的方法,我們將在本文中進行研究。用Java創建對象的共有五種方法,下面將通過示例說明這些方法,然後介紹創建對象的行的字節碼。
  • 遊戲開發之旅-JavaScript面向對象編程OOP
    以上的方式被稱為抽象-為了我們編程的目標而利用事物的一些重要特性去把複雜的事物簡單化。創造一個真正的對象從上面我們創建的class中, 我們能夠基於它創建出一些對象 - 一些擁有class中屬性及方法的對象。
  • 初見成效,使用Js操作DOM對象
    ,我們成為DOM對象,對於這些對象,我們可以進行各式各樣的操作,查找到某一個或者一類節點對象,可以創建某種節點對象,可以在某個位置添加節點對象,甚至可以動態地刪除節點對象,這些操作可以使我們的頁面看起來有動態的效果,後期結合事件使用,就能讓我們的頁面在特定時機、特定的事件下執行特定的變換。
  • JavaScript - Math對象
    Math對象在我們js中其實是有很多數學計算的需求的,不過不必擔心系統給我們提供了大量的數學運算的方法供我們使用而這些方法全都存在於我們的Math對象中Math常用的屬性:Math.PI 相當於π 3.14159Math對象常用的函數