從java
到javascript
的快速入門
本文專門為學(xué)過C語言的Java程序員起便,快速理解javascript語言的重要概念户秤,量身定做。筆者對javascript其他實(shí)現(xiàn)面向?qū)ο蟮姆绞剑M可能保持克制熊户,盡可能讓javascript的面向?qū)ο箫L(fēng)格與java對標(biāo)起來途事,幫助java程序員快速掌握javascript面向?qū)ο蟆?/p>
之所以有必要講解下javascript
验懊,是因?yàn)槿缃竦?code>javascript是全能(全棧)的,除了運(yùn)行于瀏覽器尸变;還可以借助node
運(yùn)行到服務(wù)器义图;還可以借助electron
這類框架,開發(fā)桌面程序召烂;還可以React-Native
碱工,開發(fā)手機(jī)程序;還有在類似cassandra
服務(wù)器里面用javascript
做嵌入式腳本語言奏夫。類似這樣的變化怕篷,還在不斷發(fā)生。
文章用到的代碼均托管在 github 的 downgoon/java2javascript 上酗昼,可通過issue給我反饋 廊谓。
1. 從函數(shù)開始
要實(shí)現(xiàn)一個(gè)sumlen
函數(shù),輸入兩個(gè)字符串麻削,返回它們的長度之和蒸痹。java
代碼(完整代碼見C1Sumlen.java)是這樣的:
// 函數(shù)定義
int sumlen(String x, String y) {
return x.length() + y.length();
}
// 函數(shù)調(diào)用
int sl = sumlen("hello", "world");
相比較javascript
代碼(完整代碼見C1Sumlen.js)是這樣的:
// 函數(shù)定義
function sumlen(x, y) {
return x.length + y.length;
}
// 函數(shù)調(diào)用
var sl = sumlen("hello", 'world');
兩者的相似與差異正如它們的名字(據(jù)說javascript
當(dāng)初叫這個(gè)名字也正是想借一下java
的勢頭):
- 相似處:語法上很像。
- 差異處:(1)JS是弱類型呛哟,Java是強(qiáng)類型叠荠;(2)JS函數(shù)需要
function
關(guān)鍵字,Java無需竖共。
弱類型
JS不像Java那樣蝙叛,定義變量x的時(shí)候,要指定數(shù)據(jù)類型是String
公给,Boolean
或Integer
借帘。JS形式上都是var
(類似Java的Object
是一切類的超類
)蜘渣,只不過當(dāng)它賦值后,就隱含了數(shù)據(jù)類型(可用系統(tǒng)typeof
函數(shù)查看)肺然。如下詳細(xì)代碼見C1JsType.js:
var x; // 類型 undefined
x = 'helloworld'; // 類型 string
x = 3.14; // 類型 number
x = true; // 類型 boolean
x = ['helloworld', 3.14, true]; // 屬性 數(shù)組Object
function
關(guān)鍵字
正因?yàn)镴S是弱類型蔫缸,于是定義函數(shù)的時(shí)候,函數(shù)簽名時(shí)的輸入?yún)?shù)和輸出參數(shù)就沒必要都帶個(gè)var
了际起,這樣代碼(詳細(xì)見C1JsFunc.js)更簡潔:
// 無輸入拾碌,無輸出
function f_in0_ou0() {
console.log('無輸入,無輸出');
}
// 無輸入街望,有輸出
function f_in0_ou1() {
return 'helloworld';
}
// 有輸入校翔,有輸出
function f_in1_ou1(x) {
return x.length;
}
// 有輸入,無輸出
function f_in0_ou1() {
return Math.random();
}
函數(shù)指針與調(diào)用運(yùn)算符
弱類型
談到無論是布爾類型灾前,數(shù)字類型還是字符串類型都可以賦值給var
防症。在JS中,function
也是一種類型哎甲,它也可以賦值給var
變量蔫敲,這點(diǎn)非常類似C語言的函數(shù)指針或說Java語言的interface
。詳細(xì)代碼見C1JsFuncPointer.js:
function sumlen(x, y) {
return x.length + y.length;
};
var f = sumlen; // 函數(shù)賦值給變量(類似函數(shù)指針)
sumlen('hello', 'world'); // 按函數(shù)名稱調(diào)用
f('hello', 'world'); // 按函數(shù)指針調(diào)用
f('hello', 'world');
語法就是在函數(shù)指針
后面加一個(gè)()
調(diào)用運(yùn)算符炭玫,就表示調(diào)用奈嘿。
優(yōu)先運(yùn)算符
函數(shù)指針后面的()
是調(diào)用運(yùn)算符
。實(shí)際上我們還可以在函數(shù)指針前面加()
吞加,表示優(yōu)先運(yùn)算符
裙犹。例如:
f('hello', 'world'); // 按函數(shù)指針調(diào)用
(f)('hello', 'world'); // 函數(shù)指針加了一個(gè) 優(yōu)先運(yùn)算符
((f))('hello', 'world'); // 加2個(gè)優(yōu)先運(yùn)算符
它就類似算數(shù)運(yùn)算表達(dá)式: 1*2+3*4
也可更加清楚的寫為 (1*2)+(3*4)
。
匿名函數(shù)
var g = function (x, y) {
return x.length + y.length;
}
console.log('匿名函數(shù): ' + g('hello', 'world'));
JS的函數(shù)衔憨,還可以不用起名字伯诬,用function
聲明后,立即賦值給一個(gè)變量(如果不賦值給一個(gè)變量巫财,以后就沒法調(diào)用了)。這種不起名字的函數(shù)就叫匿名函數(shù)
哩陕。
匿名函數(shù)的立即調(diào)用
前面我們看到平项,函數(shù)可以賦值給一個(gè)變量(函數(shù)指針),然后通過函數(shù)指針可以調(diào)用函數(shù):
var f = sumlen; // 函數(shù)賦值給變量
f('hello', 'world'); // 按函數(shù)指針調(diào)用
也提到如果是匿名函數(shù)定義悍及,必須賦值給一個(gè)變量闽瓢,不然以后沒有調(diào)用句柄
。但是我們有沒有想過心赶,如果定義的時(shí)候扣讼,就立即調(diào)用呢?這樣不就不需要調(diào)用句柄么缨叫?答案:的確可以椭符。
(function (x, y) {
console.log('匿名函數(shù)的立即調(diào)用:' + x + ',' + y);
return x.length + y.length;
})('hello', 'world');
這個(gè)匿名函數(shù)是帶兩個(gè)輸入?yún)?shù)的荔燎,也可以不帶輸入?yún)?shù)的:
(function() {
console.log('不帶參數(shù)的匿名函數(shù)的立即調(diào)用');
})();
上述結(jié)構(gòu)是這樣的:
- 匿名函數(shù):
function() {}
- 匿名函數(shù)調(diào)用:先用了一個(gè)
優(yōu)先運(yùn)算符
,把函數(shù)優(yōu)先起來销钝,(function(){})
有咨;接著使用調(diào)用運(yùn)算符
,(function(){})()
蒸健。
2. 插播JS對象
對象定義
前面談了JS的函數(shù)座享,還沒完,但我們先放一放似忧。插播下JS對象渣叛。對Java程序員來說,JS對象不會(huì)陌生盯捌,它非常類似JSON數(shù)據(jù)(因?yàn)镴SON就是從JS來的)淳衙,具體代碼詳見C2JsObj.js:
var x = {'name': 'zhangsan', 'age': 25, 'male': true};
當(dāng)然,為了方便挽唉,JS還允許它寫成如下格式(屬性名可以不用引號(hào)):
var x = {name: 'zhangsan', age: 25, male: true};
屬性讀取
如果要引用它的屬性呢滤祖?下面兩種方式都可以:
x.name // 很像Java的public成員,也像C的結(jié)構(gòu)體成員
x['name'] // 居然還可以類似Java的Map
恐怕讓Java程序員吃驚的是對象的屬性還可以x['name']
引用瓶籽。其實(shí)Java也可以的匠童,只不過是要用反射
機(jī)制。
自此JS的變量塑顺,可以被賦值以下各種類型:
var x;
x = 3.14;
x = `helloworld`;
x = true;
x = function() {};
x = {'name': 'zhangsan', 'age': 25, 'male': true};
屬性追加
JS對象還有個(gè)特點(diǎn)汤求,它可以隨時(shí)追加屬性。比如上面的人除了姓名严拒、年齡和性別外扬绪,我們還可以加一個(gè)學(xué)歷信息:
x.edu = 'master';
console.log(x); // 它輸出: { name: 'zhangsan', age: 25, male: true, edu: 'master' }
對Java程序員看來,JS對象裤唠,有點(diǎn)像Class類new出來的對象挤牛,又有點(diǎn)像Map這種字典類或者是基于反射的類,可以隨時(shí)加屬性种蘸。
屬性遍歷
如果要遍歷對象x
下的所有屬性墓赴,可以類似Java的Map遍歷一樣:
for (var f in x) {
console.log(f);
}
3. 對象與函數(shù)糾纏
有了函數(shù)
和對象
的基本知識(shí),我們接著看看它們之間的糾纏組合航瞭。詳細(xì)代碼見C3FuncObj.js
對象的屬性是一個(gè)函數(shù)
剛才的var x = {name: 'zhangsan', age: 25, male: true};
诫硕,對象的屬性都是普通類型的數(shù)據(jù),而對象屬性本質(zhì)上就是一個(gè)變量刊侯,這個(gè)變量既可指向普通數(shù)據(jù)章办,也可指向另一個(gè)對象,還可以指向一個(gè)函數(shù)。
var x = {
name: 'zhangsan',
age: 25,
male: true,
selfIntro: function() {
console.log('我叫'+this.name+',今年'+this.age+'歲藕届,性別'+(this.male?'男':'女'));
},
sayHi: function() {
console.log("Hello, World");
},
parents: [
{'name': 'wangyi', 'age': 42},
{name: 'maliu', 'age': 40}
]
};
console.log(x.selfIntro); // 打印出函數(shù)
x.selfIntro();
x.sayHi();
這里的sayHi
屬性和selfIntro
屬性都指向一個(gè)匿名函數(shù)挪蹭;而parents
屬性指向一個(gè)對象數(shù)組。
注意
在
selfIntro
方法中翰舌,為了訪問name等屬性嚣潜,還使用到了this
關(guān)鍵字,表示本對象的引用椅贱。
返回對象的函數(shù)
截止目前懂算,我們之前講的函數(shù),要么沒有返回值庇麦,要么返回的只是普通數(shù)據(jù)计技。這里我們要在一個(gè)函數(shù)調(diào)用后,返回一個(gè)對象:
function Person() { // 函數(shù)名稱的首字母故意大寫
return {'name': 'zhangsan', 'age': 25, 'male': true};
};
var p1 = Person(); // 函數(shù)調(diào)用第一次山橄,返回一個(gè)對象
var p2 = Person(); // 函數(shù)調(diào)用第二次垮媒,返回第二個(gè)對象
console.log(p1==p2); // 打印false,表示的確是兩個(gè)不同對象
代碼var p1 = Person();
航棱,完全是函數(shù)的一次調(diào)用睡雇,只不過這個(gè)函數(shù)的首字母大寫了,就會(huì)馬上讓Java程序員聯(lián)想到Person p1 = new Person();
饮醇。我們還可以引入3個(gè)輸入變量它抱,把Person
函數(shù)改寫下:
function Person(name, age, male) {
return {'name': name, 'age': age, 'male': male};
};
返回函數(shù)的函數(shù)
前面的章節(jié),大家感受到在JS里函數(shù)
也是個(gè)對象朴艰,可以賦值給var x
观蓄。那么我們有沒有想過,如果一個(gè)函數(shù)的返回值是另外一個(gè)函數(shù)呢祠墅?
function Person(n, a, m) { // 函數(shù)的輸入?yún)?shù)
var name = n;
var age = a; // 定義局部變量(函數(shù)內(nèi)的)
var male = m;
return function() { // 函數(shù)的返回值是一個(gè)匿名函數(shù)
return '我叫'+name+',今年'+age+'歲侮穿,性別'+(male?'男':'女');
};
};
var pSelfIntro = Person('chenliu', 36, true);
console.log(pSelfIntro());
這段代碼是一個(gè)名叫Person
的函數(shù),它有點(diǎn)特別的是函數(shù)調(diào)用后的返回值不是普通數(shù)據(jù)毁嗦,而是一個(gè)匿名函數(shù)(當(dāng)然這個(gè)匿名函數(shù)的返回值是一個(gè)組合字符串)亲茅。語句var pSelfIntro = Person('chenliu', 36, true);
是把函數(shù)調(diào)用的結(jié)果(一個(gè)匿名函數(shù))賦值給了一個(gè)pSelfIntro
變量,這個(gè)變量后面接一個(gè)()
調(diào)用運(yùn)算符狗准,實(shí)現(xiàn)調(diào)用芯急,并把調(diào)用結(jié)果用console輸出。
函數(shù)嵌套另一個(gè)函數(shù)
剛才的例子驶俊,返回值是一個(gè)函數(shù)的時(shí)候,其實(shí)也意味著在JS中免姿,一個(gè)函數(shù)的內(nèi)部還可以定義另外一個(gè)函數(shù)饼酿。也就是函數(shù)嵌套
。
上述返回函數(shù)的函數(shù)改寫下,以便更清晰地展現(xiàn)函數(shù)嵌套的概念:
function PersonOuter(n, a, m) {
var name = n;
var age = a; // 定義局部變量(函數(shù)內(nèi)的)
var male = m;
function PersonInner() { // 函數(shù)嵌套
return '我叫'+name+',今年'+age+'歲故俐,性別'+(male?'男':'女');
};
return PersonInner; // 函數(shù)的返回值是一個(gè)帶名函數(shù)
};
var innerFunc = PersonOuter('chenliu', 36, true);
innerFunc(); // 函數(shù)調(diào)用
我們可以看到想鹰,在內(nèi)部函數(shù)可以訪問外部函數(shù)的變量。再來C3FuncObj-nestfun.js:
var outer_var = 1;
function outer_fun() { // 外層函數(shù)定義
var midd_var = 2;
function inner_fun() { // 內(nèi)層函數(shù)定義
var inner_var = 3;
console.log(outer_var + midd_var + inner_var);
function core_fun() { // 核層函數(shù)定義
var core_var = 4;
console.log(outer_var + midd_var + inner_var + core_var);
};
core_fun(); // 核層函數(shù)調(diào)用
};
inner_fun(); // 內(nèi)層函數(shù)調(diào)用
};
outer_fun(); // 外層函數(shù)調(diào)用
這個(gè)例子簡單來說药版,就是說明了JS函數(shù)可以嵌套定義辑舷,并且內(nèi)層可以訪問外層的變量(包括父節(jié)點(diǎn)和祖先節(jié)點(diǎn))。
4. 閉包
封裝
有了前面這么多鋪墊后槽片,我們可以講解JS的一個(gè)比較費(fèi)解的概念閉包
何缓。
假設(shè)我們要實(shí)現(xiàn)一個(gè)SDK,它用來登陸一個(gè)網(wǎng)站还栓,它需要給各個(gè)子系統(tǒng)共享碌廓,以便減少大家的重復(fù)開發(fā)。但是需要考慮避免全局變量等的沖突剩盒。我們期望的使用代碼是這樣的:
var passed = PassportSDK.login('guest', 'guest');
console.log('登陸結(jié)果:' + passed); // 輸出 false
passed = PassportSDK.login('admin', '123456');
console.log('登陸結(jié)果:' + passed); // 輸出 true
console.log('失敗次數(shù):' + PassportSDK.getFailCount()); // 輸出 1 (因?yàn)樵袷∵^一次)
console.log('已經(jīng)登錄谷婆?:' + PassportSDK.isLogined()); // 輸出 true (當(dāng)前是登陸狀態(tài))
console.log(PassportSDK.dbPwd); // 報(bào)錯(cuò),試圖訪問私有成員
那么實(shí)現(xiàn)代碼是:
var PassportSDK = (function() { // 匿名函數(shù)
// 扮演私有成員:函數(shù)的內(nèi)部變量
var dbUser = 'admin'; // 數(shù)據(jù)庫賬號(hào)
var dbPwd = '123456'; // 數(shù)據(jù)庫密碼
var failCnt = 0; // 存儲(chǔ)失敗次數(shù)
var status = false; // 記錄登陸狀態(tài)
// 扮演公開方法:函數(shù)的返回值
// 返回值是一個(gè)對象辽聊,對象的有些屬性是一個(gè)函數(shù)
return {
login: function(user, password) {
var isPassed = (dbUser == user && dbPwd === password);
if (! isPassed) {
failCnt ++;
} else {
status = true;
}
return isPassed;
},
getFailCount: function() {
return failCnt;
},
isLogined: function() {
return status;
}
};
})(); // 匿名函數(shù)的立即調(diào)用
怎么解釋下這段代碼呢纪挎?有了前面的鋪墊,我們可以這么解釋:
閉包
是匿名函數(shù)的立即調(diào)用跟匆;- 調(diào)用的返回值是一個(gè)對象异袄;
- 對象的某些屬性是一個(gè)函數(shù)。
如果您是javascript新手贾铝,請反復(fù)琢磨這句話隙轻,并用這句話解讀PassportSDK
代碼,您一定會(huì)有所悟垢揩。
閉包
有什么好處呢玖绿?
- 名字空間:它好比Java的package機(jī)制。比如
PassportSDK.login
方法不會(huì)跟其他的login
引發(fā)名字沖突叁巨。- 私有變量:函數(shù)嵌套中的內(nèi)部變量微渠,按道理內(nèi)部函數(shù)執(zhí)行完后,就會(huì)釋放掉璃谨;但是因?yàn)楹瘮?shù)把內(nèi)部變量
植入
到另一個(gè)對象芽狗,當(dāng)做返回值返出去了,導(dǎo)致一方面不能直接訪問(發(fā)揮私有成員
的效果了)庶橱,另一方面變量還沒有釋放(因?yàn)楸话鳛榉祷刂盗耍?/li>
繼承
剛才的閉包
實(shí)現(xiàn)了名字空間贮勃,還實(shí)現(xiàn)了私有成員。但是Java的面向?qū)ο筮€有繼承苏章,假如我們要在開閉原則
下寂嘉,拓展PassportSDK
奏瞬,加入一個(gè)logout
方法呢?期望使用:
PassportSDKExt.login('admin', '123456'); // 復(fù)用 PassportSDK 的實(shí)現(xiàn)
PassportSDKExt.logout(); // 增加新的功能
實(shí)現(xiàn)代碼如下:
var PassportSDKExt = (function(parent) {
parent.logout = function() { // 對象可以動(dòng)態(tài)追加屬性
parent.status = false; // 訪問不了私有成員泉孩,沒有Java的Protected概念
};
return parent;
})(PassportSDK); // PassportSDK 是之前的變量
上述代碼就是直接利用了對象可以動(dòng)態(tài)可以追加屬性
的機(jī)制硼端。當(dāng)然同樣是用了匿名函數(shù)的立即調(diào)用,只不過這個(gè)匿名函數(shù)有一個(gè)輸入?yún)?shù)寓搬,所以立即調(diào)用時(shí)珍昨,需要把PassportSDK
當(dāng)做輸入?yún)?shù)傳遞進(jìn)去。
但是這個(gè)代碼實(shí)際上無法運(yùn)行句喷,因?yàn)?code>parent.status是訪問不了的镣典,它沒有Java的protected
成員的概念(子類可以訪問)。
5. 面向?qū)ο?/h2>
模板與實(shí)例
上面的方式我們都采用了 匿名函數(shù)的立即調(diào)用 這種形式脏嚷。但這種形式有個(gè)問題骆撇,只能在定義的時(shí)候調(diào)用一次,后面再想調(diào)用就調(diào)用不了父叙。有點(diǎn)類似Java設(shè)計(jì)模式里面的單例模式
神郊。但面向?qū)ο螅瑧?yīng)該有類
這種模板
機(jī)制趾唱,類
每次調(diào)用構(gòu)造方法都能構(gòu)建一個(gè)新的對象
涌乳。為了能多次調(diào)用,我們可以把匿名函數(shù)改成命名函數(shù)甜癞。本實(shí)例完整代碼見 C5Session.js
在改成命名函數(shù)前夕晓,為了更加符合RESTful
風(fēng)格,我們把上面PassportSDK
的名字重構(gòu)一下悠咱,login
和logout
分別重構(gòu)為session.create
與session.remove
蒸辆,因?yàn)閺馁Y源與資源操作的角度看,登陸的本質(zhì)就是在服務(wù)端分配了一個(gè)session
資源析既,登出就是回收session
資源躬贡。
- 名稱重構(gòu)
重構(gòu)后的 匿名函數(shù)的立即調(diào)用
形式:
var session = (function () { // 匿名函數(shù)的立即調(diào)用
var status = false; // 私有成員
return {
create: function (name, pwd) { // 公有方法
status = ('admin' == name && '123456' == pwd);
return status;
},
remove: function () { // 公有方法
status = false;
},
isActive: function () { // 公有方法
return status;
}
};
})();
session.create('guest', 'guest'); // 使用
- 改成命名函數(shù)
var Session = function () { // 命名函數(shù)
var status = false; // 私有成員
return {
create: function (name, pwd) { // 公有方法
status = ('admin' == name && '123456' == pwd);
return status;
},
remove: function () { // 公有方法
status = false;
},
isActive: function () { // 公有方法
return status;
}
};
};
var session = Session(); // 命名函數(shù)的調(diào)用
session.create('guest', 'guest'); // 使用
改成命名函數(shù)后,如果想“構(gòu)造一個(gè)對象”眼坏,可以執(zhí)行var session = Session();
拂玻,但這種風(fēng)格總跟Java有點(diǎn)不習(xí)慣,Java需要一個(gè)new
關(guān)鍵字宰译,比如:Session session = new Session();
檐蚜。但是Java領(lǐng)域,F(xiàn)actory模式都不太主張?jiān)谑褂梅街苯佑?code>new了沿侈,因?yàn)槟菢邮菑?qiáng)耦合闯第,設(shè)計(jì)模式里倡導(dǎo)的風(fēng)格是Session session = sessionFactory.newInstance();
,而且最近幾年開始流行Vertx vertx = Vertx.vertx()
這種風(fēng)格(Java的一個(gè)叫vertx.io
的框架中有很多這種風(fēng)格)缀拭。Vertx.vertx()
這種風(fēng)格咳短,如果換到j(luò)avascript里面肃廓,也是非常容易實(shí)現(xiàn)的,它就是Vertx
對象诲泌,有個(gè)叫vertx
的屬性,這個(gè)屬性是一個(gè)函數(shù)铣鹏。于是我們再改寫一下:
-
Vertx.vertx()
風(fēng)格
var Session = {
session: ${上面的命名函數(shù)}
};
var session = Session.session();
session.create('guest', 'guest');
完整的代碼:
var Session = { // Vertx.vertx() 風(fēng)格
session: function () { // ${上面的命名函數(shù)}
var status = false; // 私有成員
return {
create: function (name, pwd) { // 公有方法
status = ('admin' == name && '123456' == pwd);
return status;
},
remove: function () { // 公有方法
status = false;
},
isActive: function () { // 公有方法
return status;
}
};
};
};
var session = Session.session();
session.create('guest', 'guest');
到這敷扫,模板就是Session
,構(gòu)造實(shí)例就是Session.session()
诚卸,跟Java就非常吻合了葵第。利用閉包,也有了類似status
屬性的私有成員合溺;通過返回對象卒密,對象的屬性是一個(gè)函數(shù)的形式,有了公有方法棠赛。
靜態(tài)成員與靜態(tài)方法
如果我們把上面的Session
比作是Java的類哮奇,那么Session.session()
就好比是靜態(tài)方法。如果我們需要一個(gè)靜態(tài)成員睛约,來記錄實(shí)例的個(gè)數(shù)呢鼎俘?
var Session = {
instanceCount: 0, // 類的靜態(tài)成員 (無法私有化)
session: function () { // 類的靜態(tài)方法
Session.instanceCount ++; // 調(diào)用Session.session() 后,實(shí)例數(shù)+1
var status = false; // 實(shí)例的私有成員
return {
create: function (name, pwd) { // 實(shí)例的公有方法
status = ('admin' == name && '123456' == pwd);
return status;
}
};
},
getInstanceCount: function() { // 類的靜態(tài)方法
return Session.instanceCount; // 只是對標(biāo)Java風(fēng)格辩涝,實(shí)際instanceCount并非私有
}
};
var session = Session.session();
Session.getInstanceCount();
類的繼承
類的繼承
依然可以用前面講過的方式:
var SessionSub = {
sessionSub: (function(parent) { // 這段代碼前面講過
parent.currentUser = function() {
return '{}';
}
return parent;
})(Session); // 把超類作為參數(shù)
};
SessionSub sessionSub = SessionSub.sessionSub();
sessionSub.currentUser();
6. 包命名空間
前面我們講閉包的時(shí)候贸伐,提到了它類似Java的package機(jī)制。現(xiàn)在我們把前面的閉包
和這里的面向?qū)ο螅▽?shí)際上這里面也是有閉包
怔揩,只是我們前面自己把閉包
的定義弄成了以匿名函數(shù)的立即調(diào)用
為前提捉邢,主要是為了初級(jí)接觸時(shí),能記住和理解商膊,對概念咬文嚼字伏伐,筆者認(rèn)為沒有太必要,畢竟使用javascript是一種技能翘狱,并非一種理論)組合起來運(yùn)用秘案,詳細(xì)代碼見C6PackageSession.js。
以下我們定義一個(gè)package
潦匈,名字叫io_example_passport
阱高,它下面有兩個(gè)類:Session
與User
:
var io_example_passport = (function () { // 依然是匿名函數(shù)的立即調(diào)用
return {
Session: { // 類似Java的類
instanceCount: 0, // 類的靜態(tài)成員 (無法私有化)
session: function () { // 類的靜態(tài)方法
Session.instanceCount++; // 調(diào)用Session.session() 后,實(shí)例數(shù)+1
var status = false; // 實(shí)例的私有成員
return {
create: function (name, pwd) { // 實(shí)例的公有方法
status = ('admin' == name && '123456' == pwd);
return status;
},
isActive: function() {
return status;
}
};
},
getInstanceCount: function () { // 類的靜態(tài)方法
return Session.instanceCount; // 只是對標(biāo)Java風(fēng)格茬缩,實(shí)際instanceCount并非私有
}
},
User: { // 類似Java的類
user: function(session) { // 帶參數(shù)的構(gòu)造函數(shù)
var priSession = session; // 私有成員
var user = {};
user.getUser = function () {
if (priSession.isActive) {
return "{'name': 'zhangsan', 'uid': 514233}";
} else {
throw 'Please login first';
}
};
user.getSession = function () {
return priSession;
};
return user;
}
}
}
})();
如何使用上面的代碼呢赤惊?跟Java還是比較相似的:
// Session類
var Session = io_example_passport.Session;
var session1 = Session.session();
var session2 = Session.session();
console.log('實(shí)例個(gè)數(shù):' + Session.getInstanceCount());
var s1Passed = session1.create('guest', 'guest');
console.log('s1 登陸結(jié)果:' + s1Passed);
var s2Passed = session2.create('admin', '123456');
console.log('s2 登陸結(jié)果:' + s2Passed);
console.log('s2 登陸狀態(tài):' + session2.isActive());
// User 類
var User = io_example_passport.User;
var user = User.user(session2);
var currentUser = user.getUser();
console.log('current user on session2: ' + currentUser);
console.log('當(dāng)前用戶依然保持登陸嗎?' + user.getSession().isActive());
運(yùn)行結(jié)果:
$ node C6PackageSession.js
實(shí)例個(gè)數(shù):2
s1 登陸結(jié)果:false
s2 登陸結(jié)果:true
s2 登陸狀態(tài):true
current user on session2: {'name': 'zhangsan', 'uid': 514233}
當(dāng)前用戶依然保持登陸嗎凰锡?true
雖然這里是通過node C6PackageSession.js
來運(yùn)行的未舟,其實(shí)您也可以通過瀏覽器運(yùn)行圈暗,用瀏覽器直接打開C6PackageSession.html即可。
7. 回歸總結(jié)
本文重點(diǎn)講解了javascript的函數(shù)與對象的特點(diǎn)裕膀,并由它們的相互糾纏(即:返回對象的函數(shù)员串,返回函數(shù)的函數(shù),對象屬性是一個(gè)函數(shù))引出了閉包
的概念昼扛。我們把閉包
不嚴(yán)謹(jǐn)?shù)亩x為:匿名函數(shù)的立即調(diào)用寸齐;調(diào)用返回的是一個(gè)對象;對象的某些屬性是一個(gè)函數(shù)抄谐。
渺鹦,然后再由匿名函數(shù)的一次調(diào)用的缺陷,重構(gòu)成命名函數(shù)蛹含,Vertx.vertx()
風(fēng)格毅厚,從而實(shí)現(xiàn)了類似Java的面向?qū)ο蟆W詈笃窒洌验]包和面向?qū)ο笤俳M合了一次吸耿,引入了package
命名空間機(jī)制。
當(dāng)然javascript是發(fā)展的憎茂,關(guān)于模塊化與面向?qū)ο蟮臋C(jī)制珍语,還有prototype
機(jī)制,還有ES6
直接從語言層面引入了類機(jī)制竖幔。另外一個(gè)是板乙,借助webpack
這種工具,可以更好的模塊化拳氢,然后“編譯”生成一個(gè)單一的js文件募逞。您完全沒有聽錯(cuò),當(dāng)項(xiàng)目規(guī)模大的時(shí)候馋评,為了更好的模塊化放接,即便是javascript動(dòng)態(tài)腳本語言,都需要類似Java語言一樣去“編譯”(可能的工作包括把多個(gè)JS文件合并成一個(gè)JS文件留特;也可以用類JS語言寫纠脾,比如CoffeJS,然后翻譯成純JS蜕青;還可以壓縮最小化等)苟蹈。