JavaScript被很多人認為並不是一種物件導向語言,原因有很多種,比如JavaScript沒有類,不能提供傳統的類式繼承;再比如JavaScript不能實現資訊的隱藏,不能實現私人成員。本文並不是為了打破以上誤解(實際上筆者自己也有困惑),只是簡單介紹幾種JavaScript實現私人屬性的方式,以及各自的優劣。
1. 基於編碼規範約定實現方式
很多編碼規範把以底線_開頭的變數約定為私人成員,便於同團隊開發人員的協同工作。實現方式如下:
function Person(name){
this._name = name;
}
var person = new Person('Joe');
這種方式只是一種規範約定,很容易被打破。而且也並沒有實現私人屬性,上述代碼中的執行個體person可以直接存取到_name屬性:
alert(person._name); //'Joe'
2. 基於閉包的實現方式
另外一種比較普遍的方式是利用JavaScript的閉包特性。建構函式內定義局部變數和特權函數,其執行個體只能通過特權函數訪問此變數,如下:
function Person(name){ var _name = name; this.getName = function(){ return _name; }}var person = new Person('Joe');
這種方式的優點是實現了私人屬性的隱藏,Person 的執行個體並不能直接存取_name屬性,只能通過特權函數getName擷取:
alert(person._name); // undefined
alert(person.getName()); //'Joe'
使用閉包和特權函數實現私人屬性的定義和訪問是很多開發人員採用的方式,Douglas Crockford也曾在部落格中提到過這種方式。但是這種方式存在一些缺陷:
私人變數和特權函數只能在建構函式中建立。通常來講,建構函式的功能只負責建立新對象,方法應該共用於prototype上。特權函數本質上是存在於每個執行個體中的,而不是prototype上,增加了資源佔用。
3. 基於強引用散列表的實現方式
JavaScript不支援Map資料結構,所謂強引用散列表方式其實是Map模式的一種變體。簡單來講,就是給每個執行個體新增一個唯一的標識符,以此標識符為key,對應的value便是這個執行個體的私人屬性,這對key-value儲存在一個Object內。實現方式如下:
var Person = (function() { var privateData = {}, privateId = 0; function Person(name) { Object.defineProperty(this, "_id", { value: privateId++ }); privateData[this._id] = { name: name }; } Person.prototype.getName = function() { return privateData[this._id].name; }; return Person;}());
上述代碼的有以下幾個特徵:
使用自執行函數建立Person類,變數privateData和privateId被所有執行個體共用;
privateData用來儲存每個執行個體的私人屬性name的key-value,privateId用來分配每個執行個體的唯一識別碼_id;
方法getName存在於prototype上,被所有執行個體共用。
這種方式在目前ES5環境下,基本是最佳方案了。但是仍然有一個致命的缺陷:散列表privateData對每個執行個體都是強引用,導致執行個體不能被記憶體回收處理。如果存在大量執行個體必然會導致memory leak。
造成以上問題的本質是JavaScript的閉包引用,以及只能使用字串類型最為散列表的key值。針對這兩個問題,ES6新增的WeakMap可以良好的解決。
4. 基於WeakMap的實現方式
WeakMap有以下特點:
支援使用物件類型作為key值;
弱引用。
根據WeakMap的特點,便不必為每個執行個體都建立一個唯一識別碼,因為執行個體本身便可以作為WeakMap的key。改進後的代碼如下:
var Person = (function() { var privateData = new WeakMap(); function Person(name) { privateData.set(this, { name: name }); } Person.prototype.getName = function() { return privateData.get(this).name; }; return Person;}());
改進的代碼不僅僅乾淨了很多,而且WeakMap是一種弱引用散列表, 這意味著,如果沒有其他引用和該鍵引用同一個對象,這個對象將會被當作記憶體回收掉。解決了記憶體泄露的問題。
不幸的是,目前瀏覽器對WeakMap的支援率並不理想,投入生產環境仍然需要等待。
javascript實現私人屬性私人方法代碼執行個體
function People(name) { var _name = name; //私人屬性 function privateMethod() { //私人方法 alert('private'); } return{ age: 0, //公有屬性 setName: function(name) { //公有方法 _name = name; }, getName: function() { //公有方法 return _name; } }}var p = People('zhangsan');//p.privateMethod();console.log(p.getName());console.log(p.age);p.age = 1;p.setName('lisi');console.log(p.getName());console.log(p.age);