精讀JavaScript模式(七),命名空間模式,私有成員與靜態成員

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);

 那麼到這裏,第五章的內容大概就看完了,其實博客中我省略了比較多的東西,好比沙箱模式,模塊模式等,在看以前我仍是有所期待的,但在實際閱讀中,這幾個知識點的收貨是極少的,一方面是例子難懂以及存在錯誤,其實可能我我的境界還不是過高,看了也沒法馬上在實際開發中實踐出來,因此更可能是記錄了一些我我的以爲有意義的東西,哪怕是多知道了一句概念。

睡覺吧,要收收心了。

相關文章
相關標籤/搜索