1、前言html
惰性十足,這篇2月19號就開始寫了,拖到了如今,就是不肯意花時間把看過的東西整理一下,其它的任何事都比寫博客要有吸引力,我要檢討本身。node
從這篇開始,是關於JS對象建立模式的探討,JS語言簡單直觀,並無模塊,包,私有屬性,靜態成員等語法特性。而這一大章將介紹一些有用的模式,例如命名空間,依賴聲明,模塊模式以及沙箱模式等。這些能幫助咱們更好的組織代碼,減輕全局污染問題。promise
2、命名空間模式(Namespace Pattern)安全
命名空間能夠減小全局變量的數量,還能有效避免命名衝突以及名稱前綴的濫用,命名是個很頭疼的事情,我想你們都有這種狀況,命名到詞窮。閉包
JS默認語法是不支持命名空間,不過很好實現,命名空間很實用,對於類庫,應用,插件編寫,咱們均可覺得其建立一個全局對象,而後將全部的功能都添加到這個對象上,而不是處處申明大量的全局函數,對象等,這樣看着很亂。app
const MYAPP = {}; MYAPP.Parent = function () {}; MYAPP.number = 4; MYAPP.modules = {}; MYAPP.modules.data = {};
上述代碼中MYAPP就是命名空間對象,名稱隨意取,可是一般採用大寫,還須要注意的是,通常大寫的變量都表示常量。函數
很明顯這樣的寫法在命名衝突上可能性就大大下降了,可是也存在一些問題,例如在MYAPP.modules.data這裏代碼量就明顯增長了,總體會增長文件的大小,其次,在獲取某個方法會屬性時,得從MYAPP一層層往下讀,讀取較慢。並且該全局實例可能被誤操做修改,雖然咱們理論上說了這是常量不應被修改。測試
3、通用命名空間函數(思想是好的,但很雞肋)this
有個問題,當程序的複雜性提高咱們很難保證命名空間的建立是否已存在,不當心修改了已存在的變量是很麻煩的事情,所以建立前的檢查行爲是更爲安全的。spa
var MYAPP = MYAPP || {}
這個知識點後續內容我選擇跳過了,原書中的觀點是對於命名空間的建立最好作個檢查,而後提供了一個通用的命名空間檢測檢查函數,我通過了測試,發現提供的函數永遠返回一個空對象,並沒達到預期的檢查效果;其次,我認爲建立一個對象每次都要檢測真的過於繁瑣,即使是封裝一個檢查函數,我還得調用。例如a.b.c.d.e,我不可能對於每層都作一個檢測是否存在,安全建立思想是好的,但我的以爲過於雞肋了。
4、對象的私有屬性和方法
JS並無專門提供保護私有成員,方法的語法,咱們在全局建立一個對象,是能夠輕易訪問到對象的全部屬性的。
let obj = { num:1, getNum:function () { console.log(this.num); } }; //在函數外部能夠輕易訪問 console.log(obj.num);//1 obj.getNum();//1
即使是構造函數,也是如此。
// 構造函數 function GetNum() { this.num = 2; this.getNum = function () { console.log(this.num); } } let Num = new GetNum(); console.log(Num.num);//2 Num.getNum();//2
如何作到對象屬性私有化,咱們可使用閉包作到這一點,只有閉包內部函數才能夠訪問到內部變量,外部沒法直接訪問。
function GetNum() { let num = 3; this.getNum = function () { console.log(num); } } let Num = new GetNum(); console.log(Num.num);//undefined Num.getNum();//3
除了調用getNum方法之外,咱們並不能直接訪問到num變量,因此通常咱們稱getNum方法爲特權方法,由於它擁有訪問num屬性的特殊權限。
來聊聊特權方法權限的問題,假設咱們的閉包返回的是一個對象,而非一個字符串。經過特權方法能夠修改影響到閉包內部的本地變量。
function BoxInfo() { let boxSize = { widht:200, height:300, color:'yellow' }; this.getBox = function () { return boxSize; } } //實例一個對象獲得box1 let box1 = new BoxInfo(), size = box1.getBox(); //咱們修改size的顏色 size.color = 'bule'; //再取一次size信息,能夠看到size顏色已被修改 size1 = box1.getBox(); console.log(size1)//{widht: 200, height: 300, color: "bule"} //若是在修改後你想取到沒修改的初始數據,你只能再次new一個實例 let box2 = new BoxInfo(), size2 = box2.getBox(); console.log(size2);//{widht: 200, height: 300, color: "yellow"}
在取得實例修改顏色,後續再讀取對象發現顏色已改變,這是確定的,畢竟對象的賦值只是賦予了值的引用地址而非值自己,這種隨意修改數據的作法不太安全,針對這個問題,咱們可使用「最低受權原則」,永遠不要給出比需求更多的東西。
好比需求是要訪問boxSize的height與color屬性,那麼特權方法再也不是能夠訪問整個對象,而是隻能訪問到長與顏色屬性,像這樣:
this.getBox = function () { return { height:boxSize.height, color:boxSize.color } }
咱們只提供需求須要的屬性,拼裝爲全新的對象返回,後續不管你怎麼修改,咱們永遠獲得的是最初的原始數據。
或者,當咱們第一次獲得box1實例時,深拷貝一份,做爲原屬性再也不動用它,那另外一份數據就隨便你玩了。「最低受權原則」這個思想我以爲仍是蠻不錯的。
除了經過構造函數建立私有成員外,咱們也能夠經過對象字面量結合自調函數來達到目的。
(function () { var name = "時間跳躍"; myobj = { getName : function () { return name; } } })(); let myName = myobj.getName(); console.log(myName);//時間跳躍
這種實現方式就是經過自調函數建立了一個獨立的做用域,外部沒法訪問,可是能夠經過函數內部的對象訪問到私有屬性name,思想上是差很少的。
5、原型和私有成員(屬性)
使用構造函數建立私有成員有個弊端,或者說使用構造函數建立實例時都會存在的弊端,每當調用一次構造函數,私有成員都會被建立一次。
這是由於每次new一個構造函數,都隱性的建立了一個空對象賦予給this,而後複製構造函數this上的屬性方法,最終返回this,這點在精讀JS模式三這篇文章的第四個知識點有說,有疑惑能夠去看看。
同理,哪怕是在創造私有成員時,若是這個成員不少地方都會用到,那就不必加載構造函數中被反覆建立,直接將此成員添加在prototype上。
function Mine() { let name = "echo"; this.getName = function () { console.log(name); }; }; //假設age屬性每一個實例都須要使用,就不要加在上方構造函數了,每次new都要建立,不必 Mine.prototype = (function () { var age = 26; return { getAge : function () { console.log(age); } }; })(); let me = new Mine(); me.getName();//echo me.getAge();//26
6、靜態成員(屬性和方法)
1.構造函數的靜態方法
當咱們但願某個方法只有構造函數自身可使用,實例沒法繼承使用,此方法就應該使用靜態方法。
而在JS中並無專門建立靜態成員的語法,但咱們能夠經過構造函數添加屬性的方法來添加靜態方法。
let Func = function () {}; //這是func的靜態方法 Func.myName = function () { console.log('My name is echo'); }; //這是func的實例方法 Func.prototype.myAge = function () { console.log('My age is 26'); }; Func.myName(); let me = new Func(); me.myAge();
在上述代碼中,我爲函數Func添加了一個靜態方法myName和一個實例方法myAge。
myName方法之因此是靜態方法是由於Func函數能夠直接調用,它不須要指定一個對象去調用它,也不須要實例調用。但myAge方法則須要實例調用。固然相對的,函數Fcun沒法直接調用實例方法,就像實例沒法直接調用靜態方法。
Func.myAge()//沒法找到 me.myName()//沒法找到
固然咱們也能夠將靜態方法添加在原型鏈上,像這樣(其實看到這裏,我所理解的靜態方法就是直接添加在函數上的方法,照常理說實例是沒法使用的)
Func.prototype.myName = Func.myName let me = new Func(); me.myName()//My name is echo
但區別在於,經過Func調用myName函數時,函數this指向Func函數,但經過後者實例調用時,this指向了實例me,這是有區別的。
2.構造函數的靜態屬性與私有靜態屬性
靜態屬性添加與靜態方法相同,直接添加在構造函數上。
let Parent = function () {}; //靜態方法 Parent.sayAge = function () { console.log(this.age); }; //靜態屬性 Parent.age = 26; Parent.sayAge()//26
什麼是私有靜態屬性呢?有兩大特色,第一,此屬性在全部由同一構造函數建立的對象中可共享;第二,不容許在構造函數外部訪問。
let KissMe = (function() { let counter = 0; return function() { console.log(counter += 1); }; })(); console.log(KissMe); let one = new KissMe();//1 let two = new KissMe();//2 let three = new KissMe();//3
上述代碼中,我定義了一個記錄親吻我(實例)次數的構造函數,其中變量counter外部沒法訪問,且三次調用獲得的實例共享counter,由於第二次調用時counter已經變成了1而非0,那麼咱們能夠說counter就是一個私有的靜態屬性。
仔細看代碼,其實就是一個構造函數被包裹在了一個自調函數中,去掉外層自調函數來看,這個實現的本質就是一個全局變量counter以及一個使用此變量的函數。
let counter = 0; let KissMe = function() { console.log((counter += 1)); }; console.log(KissMe); let one = KissMe(); //1 let two = KissMe(); //2 let three = KissMe(); //3
有沒有發現,假設咱們想知道一個構造函數被new了多少次,或者想知道這個實例是構造函數的第幾個孩子,這個簡單的實現就能計算出次數。(也許真的會用到)
上面自調函數的例子說是構造函數其實有點牽強,畢竟咱們new KissMe的時候函數都已經執行完畢了,都沒有經過實例調用方法的機會了,因此咱們改改代碼。結果仍是同樣,只是更像構造函數模式了。
另外,私有成員和私有靜態成員的區別是,私有成員在每次實例中都是一個新的,並不會共享,很明顯私有靜態成員第二個實例受到了第一個實例調用時的影響。
let KissMe = (function() { let counter = 0, getNum = function() { counter += 1; }; getNum.prototype.getLastId = function() { console.log(counter); }; return getNum; })(); let one = new KissMe(); one.getLastId();//1 let two = new KissMe(); one.getLastId();//2 let three = new KissMe(); one.getLastId();//3
經過上面的例子咱們能夠看到,靜態屬性(公有或私有)能夠包含和實例無關的方法或數據,建立實例時,這些私有屬性不會被反覆建立,但實例卻可使用,我感受與原型鏈繼承比較像,但與原型鏈添加方法的不一樣在於,最終執行時this指向不一樣,前面舉例有說。
7、有趣的對象鏈式調用模式
假設咱們須要連續調用一個對象上的多個方法,且操做的數據有所關聯,咱們就能夠在每次調用時,直接將this做爲函數調用的返回值,從而避免每次調用返回值做爲下次調用函數參數的繁瑣。
let obj = { value: 1, plus: function(a) { this.value += a; return this; }, reduce: function() { this.value -= 1; return this; }, multiply: function() { this.value *= 2; console.log(this.value); } }; obj.plus(3).reduce().multiply();//6
這個挺像promise鏈式結構的寫法,每次promise的執行都返回一個新的promise對象,這裏就是每次返回了this,由於操做的全是this,這個就很少說了。
使用鏈式調用模式很明顯能節約代碼量,其實閱讀起來更像一個句子,更容易將函數之間的調用關聯起來。其實這種模式很是常見,好比咱們經常使用的JQ獲取DOM的寫法:
document.getElementById("#echo").appendChild(new node);
那麼到這裏,第五章的內容大概就看完了,其實博客中我省略了比較多的東西,好比沙箱模式,模塊模式等,在看以前我仍是有所期待的,但在實際閱讀中,這幾個知識點的收貨是極少的,一方面是例子難懂以及存在錯誤,其實可能我我的境界還不是過高,看了也沒法馬上在實際開發中實踐出來,因此更可能是記錄了一些我我的以爲有意義的東西,哪怕是多知道了一句概念。
睡覺吧,要收收心了。