1.對象適合於收集和管理數據,容易形成樹型結構。 Javascript包括一個原型鏈特性,允許對象繼承另一對象的屬性。正確的使用它能減少對象的初始化時間和內存消耗。 2.函數它們是javascript的基礎模塊單元,用於代碼復用、信息隱藏和組合調用。函數用於指定對象的行為。一般來說,編程就是將一組需求分解成一組函數和數據結構的技能。 3.模塊我們可以使用函數和閉包來構造模塊。模塊是一個提供接口卻隱藏實現狀態和實現的函數或對象。
1.自定義類型--構造函數模式(偽類模式)
在基於類的系統中,對象是這樣定義的:使用類來描述它是什麼樣的。假如建築是基於類的系統,則建築師會先畫出房子的藍圖,然後房子都按照該藍圖來建造。
在使用自定義類型模式實現繼承的時候,我們只需要將參數傳遞給構造函數,然後將參數掛載在實例對象上。其他關於實例對象的方法都不用傳遞參數,因為通過 實例對象調用的方法內部的this都可以訪問到該參數。掛載在實例this對象上的變量稱為實例變量。
組合--繼承
function Person (name, age, job) { // 實例變量 this.name = name; this.age = age; this.job = job; } Person.prototype.sayName = function () { alert(this.name); } var person1 = new Person('Nicholas', 29, 'Software Engineer'); var person2 = new Person('Greg', 27, 'Doctor'); function SuperType (name) { this.name = name; this.colors = ['red','blue', 'green']; } SuperType.prototype.sayName = function () { console.log(this.name); } function SubType (name, age) { // 繼承屬性 SuperType.call(this,name); this.age = age; } // 繼承方法 SubType.prototype = new SuperType(); SubType.prototype.sayAge = function () { console.log(this.age) } var instance1 = new SubType('Nicholas', 29); instance1.colors.push('black') console.log(instance1.colors); instance1.sayName(); instance1.sayAge(); var instance2 = new SubType('Greg', 27) console.log(instance2.colors); instance2.sayName(); instance2.sayAge();
在繼承屬性和繼承方法上,我們一共調用了兩次超類構造函數,當通過new調用超類構造函數創建子類構造函數的原型時,有一個問題,子類構造函數的原型對象現在便是超類構造函數的實例,因此也會有在超類構造函數為實例對象this添加的屬性,只是值為undefined而已,也就是說通過new調用超類構造器函數來更改子類改造器的原型時,那麼在子類構造器的原型上便會有多余的屬性。這便造成了浪費。而我們需要的其實只是,子類構造器的原型能夠繼承超類構造器原型的方法而已。因此我們需要的,
1.創建一個子類構造器原型對象。
2.此子類構造器原型繼承自超類構造器的原型。
3.因為我們在1中改寫了子類構造器的原型對象,也就是重新創建了原型對象,因此我們需要在新創建的原型對象上添加constructor屬性並將其賦值為子類構造器函數。
將上面的代碼改寫一些,如下所示。
關於constructor屬性:只在構造器函數的原型上才有的屬性並指向該構造器,改寫了的原型對象默認是沒有constructor屬性的。
寄生組合式--繼承
function inheritPrototype (subType,superType) { var prototype = Object.creat(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; }; function SuperType (name) { this.name = name; this.colors = ['red', 'blue', 'green']; } SuperType.prototype.sayName = function () { console.log(this.name); } function SubType(name, age) { //繼承屬性 SuperType.call(this,name); this.age = age; } //繼承方法 inheritPrototype(SubType,SuperType); SubType.prototype.sayAge = function () { console.log(this.age); } var instance = new SubType();
通過隱藏那些所謂的prototype操作細節,現在看起來沒那麼怪異了。但是否真的有所發現:
沒有私有環境,所有屬性都是公開的。無法訪問父類的方法。難以調試
2.原型
在一個純粹的原型模式中,我們會擯棄類,轉而專注對象。基於原型的繼承相比基於類的繼承在概念上更簡單:一個新對象可以繼承一個舊對象的屬性。你通過構造有用的對象開始,接著可以構造更多和那個對象類似的對象。這就可以完全避免把一個應用拆解成一系列嵌套抽象類的分類過程
在基於原型的系統中,我們創建的對象,看起來要像我們想要的所有這種類型的對象那樣,然後告訴javascript引擎,我們想要更多像這樣的對象。如果建築是基於原型的,建築師會先建一所房子,然後將房子都建成像這種模樣的。
方法Object.creat()作為new操作符的替代方案,使用它來創建javascript對象時,能增添一種更像是基於原型的感覺。
function myMammal = { name : 'Herb the Mammal', get_name : function () { return this.name; }, says : function () { return this.saying || ''; } } var myCat = Object.create(myMammal); myCat.name = 'Henrietta'; myCat.saying = 'meow'; myCat.purr = function (n) { var i, s = ''; for (i = 0;i < n; i += 1) { if(s) { s += '-' } s += 'r'; } return s; } myCat.get_name = function () { return this.says + ' ' + this.name + this.says; }
這是一種"差異化繼承"。通過定制一個新的對象,我們指明它與所基於的基本對象的區別。
有時候,它對某些數據結構繼承於其他數據結構的情形非常有用。
3.函數化--工廠模式
在偽類模式裡,構造器函數Cat不得不重復構造器Mammal已經完成的工作。在函數化模式中那不再需要了,因為構造器Cat將會調用構造器Mammal,讓Mammal去做對象創建中的大部分工作,所有Cat只關注自身的差異即可。
函數化模式有很大的靈活性。它相比偽類模式不僅帶來的工作更少,還讓我們得到更好的封裝和信息隱藏,以及訪問父類方法的能力。
如果我們用函數化得樣式去創建對象,並且該對象的所有方法都不用this或that,那麼該對象就是持久性的。一個持久性的對象就是一個簡單功能函數的集合。
私有變量:任何在函數中定義的變量,都可以認為是私有變量,因為不能在函數外部訪問這些變量。
閉包
閉包是阻止垃圾回收器將變量從內存中移除的方法,使的在創建變量的執行環境的外面能夠訪問到該變量。
請記住:閉包由函數創建。每次調用函數會創建一個唯一的執行環境對象。函數執行完後,執行對象就會被丟棄,除非調用者引用了它。當然,如果函數返回的是數字,就不能引用函數的執行環境對象。但是如果函數返回的是一個更復雜的結構,像是函數、對象或者數組,將返回值保存到一個變量上,就創建了一個對執行環境的引用。
Function.prototype.method = function (name,func) { this.prototype[name] = func; return this; } // 工廠mammal函數 var mammal = function (spec) { var that = {}; that.get_name = function () { return spec.name; } that.says = function (spec) { return spec.saying || ''; } return that; } // 工廠cat函數(基於mammal的函數) var cat = function (spec) { spec.saying = spec.saying || 'meow'; var that = mammal(spec); that.purr = function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if(s) { s += '-'; } s += 'r'; } } that.get_name = function () { return that.says() + ' ' + spec.name + ' ' + that.says(); } return that; } // 創建myCat對象 var myCat = cat({name: 'Henrietta'}); Object.method('superior',function (name) { var that = this, method = that[name]; return function () { return method.apply(that, arguments) } }) // 工廠coolcat函數(基於cat函數) var coolcat = function (spec) { var that = cat(spec), super_get_name = that.superior('get_name'); that.get_name = function (n) { return 'like ' + super_get_name() + ' baby'; } return that; } var myCoolCat = coolcat({name : 'Bix'}); var name = myCoolCat.get_name();
函數化模塊模式有很大的靈活性。它相比構造函數模式不僅帶來的工作更少,還讓我們得到更好的封裝休息和隱藏,以及訪問父類方法的能力。如果對象的所有狀態都是私有的,那麼該對象就成為一個"防偽(tamper-proof)"對象。該對象的屬性是可以被替換或者刪除,當該對象的完整性不會受到損壞。我們用函數式的樣式創建一個對象,並且該對象的所有方法都不使用this或者that,那麼該對象就是持久性對象。一個持久性對象,就是一個簡單的函數功能的集合。
一個持久性的對象不會被入侵。訪問一個持久性的對象時,除非有方法授權,否則攻擊者不會訪問對象的內部狀態。
模塊模式
前面的模式是用於 自定義類型創建私有變量和特權方法的。而道格拉斯所說的模塊模式則是為 單例創建私有變量和特權方法。所謂單例指的就是只有一個實例的對象。(就是用對象字面量表示法創建的對象)
var singleton = function () { // 私有變量和函數 var privateVariable = 10; function privateFunction () { return false; } //特權/公有方法和屬性 return { publicProvperty: true; publicMethod: function () { privateVariable++; return privateFunction(); } } }
從本質上講,這個對象字面量定義的是單例的公共接口。這種模式在需要對單例進行某些初始化,同時又需要維護其私有變量時非常有用。簡言之,如果必須創建一個對象並以某些數據對其進行初始化,同時還要公開一些能夠訪問這些私有數據的方法。
增強的模塊模式
這種增強的模塊模式適合那些單例必須是某種類型的實例,同時還必須添加某些屬性和方法對其加以增強的例子。
var singleton = function () { // 私有變量和函數 var privateVariable = 10; function privateFunction () { return false } // 創建對象 var object = new CustomType(); // 添加特權/公有屬性和方法 object.publicProperty = true; object.publicMethod = function () { privateVariable++; return privateFunction(); } return object; }()