我寫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
這種方法存在很多問題,比如多個對象的方法是獨立的,沒用共享。不能判斷對象的類型
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對象的最普適的方法應該是組合模式了吧。