本文為 碼農翻身 微信公眾號投稿施逾,未經碼農翻身同意禁止轉載
又見 JSON 酒館
Java 小王子在 JavaScript 王國待了也有一段時間,這里雖然不像 Java 帝國那樣規(guī)范嚴苛例获,但也因此千奇百怪汉额,五光十色。
要說小王子最喜歡待的地方榨汤,那還是人來人往的 JSON 酒館蠕搜,不僅有上好的酒菜,還有機會認識到各式各樣的人收壕。這不妓灌,一來二去他已經和上回認識的眼鏡官員成了朋友轨蛤,甚至私底下還稱兄道弟的。
(參見上回)
今天小王子又和眼鏡大哥一起約來吃酒虫埂。才寒暄了幾句祥山,筷子還沒動,酒館門口就發(fā)生了一點騷動告丢。只見來了一位看起來像是教書先生的精瘦男子枪蘑,旁邊還有些隨從。
“...... 大哥岖免,這人什么來頭岳颇,氣度不凡啊÷妫”小王子悄聲向旁邊的眼鏡官員問到话侧。
“哈哈,他是函數式大主教闯参,最近他們教派的信眾激增啊瞻鹏,真是風水輪流轉÷拐”
“函數式新博?以前隨父親經商途中是聽說過他們的事情,好像非常古老而且高深莫測啊脚草,據說只有學者和虔誠的教徒才會加入他們赫悄,怎么最近也接收新人了?”
“小弟果然是見多識廣馏慨,不錯埂淮,在很久以前的確是這樣,不過為何有大批新教徒這種事情也不在我的管轄范圍內啊写隶,所以也不太清楚倔撞。但是畢竟我是本國語言規(guī)范審查官,還是與他打過一些交道慕趴。不妨我們邀請他來一起喝酒痪蝇,你親自問他≈确。”
小王子本來聽到眼鏡大哥也不了解情況正有些失望霹俺,突然得知可以直接面對面打探對方的底細,頓時興奮了起來毒费。
“那太好了丙唧!”
“這不是眼鏡老弟么,別來無恙啊觅玻∠爰剩”
“主教兄培漏,甚好甚好,要不這頓我請胡本?小二牌柄!”
飯菜上桌,互相客套了幾句之后侧甫,話題就開始了珊佣。
“主教兄,這是我最近認識的朋友披粟,年紀輕輕就周游四方咒锻,他有些事情要問你∈靥耄”
“哦惑艇?”函數式主教把目光放了過來,“你有何打探拇泛?”
“久問貴教派向來神秘滨巴,為何最近有如此多的新教徒加入呢?”
“哈俺叭,這個嘛 ... 現在的年輕人都不喜歡條條框框恭取,本教向來以簡潔強大著稱,自然就受歡迎了熄守』嗷纾”
小王子心想,這個主教倒也是有話直說自賣自夸柠横,不過難道沒了面向對象這種強大的武器,他還能變出怎么樣的花兒來课兄?
“那還敢請教大主教牍氛,依你看要怎么實現 Animal, Cat, Dog 這些對象呢?”想了一會兒烟阐,小王子認為不如直接發(fā)問搬俊。
“這個嘛,本教派并無對象這種說法蜒茄,不過如果你愿意唉擂,也可以構造一個對象出來,只需要......”
“我們不用對象~”檀葛,還沒等著主教說完玩祟,旁邊的妹子突然發(fā)話了。
“哦屿聋,小萊空扎,你來說吧藏鹊。”這位被叫做小萊的少女像是主教的助手转锈,看起來深得主教信任盘寡。
“嘻嘻,主教大人說的太復雜了撮慨,其實實現你說的那些根本不用什么對象竿痰。”
“哦砌溺?那該怎么做影涉?”小王子頓時來了興趣。
函數的翻身
“你想啊抚吠,搞出來這些貓啊狗啊的常潮,不就是想讓他們都可以吃東西么?干嘛要封裝到一個對象中楷力,太壓抑了喊式。”
let animal = { name: "animal" };
function eat(animal) {
console.log(animal.name + " is eating");
}
eat(animal); // animal is eating
哦萧朝!Java 小王子恍然大悟岔留,原來 JavaScript 里的方法根本不用強制放在對象中去聲明,在這里函數已經翻身做主人检柬,成為了一等公民献联,再也不用困在對象的牢籠中,聲明后就可以直接使用何址。
“也就是說里逆,貓狗都可以直接拿來調用咯?”
let dog = { name: "dog" };
let cat = { name: "cat" };
eat(dog); // dog is eating
eat(cat); // cat is eating
函數式“封裝”
“是的呢~”小萊的臉上還是一樣的笑容用爪。
這樣的話原押,繼承和多態(tài)就變得毫無用武之地。函數直接操作的是數據偎血,數據不同诸衔,結果自然就不同,好似“多態(tài)”的體現颇玷,而且由于函數成了通用的東西笨农,無需繼承大家都可以使用,如有需要“重寫”帖渠,就干脆再寫一個函數就行了谒亦。而且 JavaSript 是動態(tài)類型,根本不需要再抽象出來一個“接口”來統一描述某種抽象。
“嗯... ”小王子總覺得哪里不對诊霹,好像少了點什么羞延,但又說不上來∑⒒梗“這是不是太自由了點... eat 本來只能是 animal 才能使用伴箩,現在任何東西都能用。這根本沒有封裝氨陕嗤谚!”
“是的呢~ 只要有 name 都可以用哦~這樣不是更好么?不過說到封裝怔蚌,我們的封裝可更厲害哦~”
function createAnimal(name) {
let animal = {};
animal.name = name;
return animal;
}
// 或者直接
function createAnimal(name) {
return { name: name };
}
eat(createAnimal("human")); // human is eating
更奇怪的封裝
沒錯巩步,封裝說到底是為了屏蔽細節(jié)實現,防止外界干擾桦踊。這么一來椅野,外界就不需要了解 eat 和 creatAnimal 的實現原理。小王子很是吃驚籍胯,但不得不承認這是一種有效的辦法竟闪。
“嗯~而且不僅可以封裝一些數據,還可以對函數進行封裝呢~”還沒等小王子緩過神兒杖狼,輕快的聲音又把他拉回現實炼蛤。“我們函數式教派里面叫它閉包蝶涩±砼螅”
function generateEatFunction(description) {
function eat(animal) {
console.log(animal.name + description);
}
return eat;
}
// 或者直接
function generateEatFunction(description) {
return function(animal) {
console.log(animal.name + description);
}
}
let eatZH_CN = generateEatFunction(" 在吃東西~");
let xiaolai = createAnimal("小萊");
eatZH_CN(xiaolai); //小萊 在吃東西~
“啊绿聘?嗽上!”這次小王子徹底呆住了,信息量太大熄攘,他沒有想到函數還可以這么使用炸裆,竟然直接作為返回值返回,還可以“封裝”一些值進去鲜屏。
看到呆住的小王子,小萊解釋了起來国拇,“嘿嘿洛史,第一次見到的人都是這樣子,沒事酱吝,習慣了就好也殖。在我們函數式里面呢,函數中可以嵌套函數,內層的函數經常需要引用外層函數中的一些值忆嗜,而且可以把這個內層函數作為返回值呢~我肯定希望這個函數引用的外層值可以繼續(xù)使用啊己儒,這樣多方便啊。所以 JavaScript 就對其進行了處理捆毫,如果返回的函數被引用闪湾,JavaScript 就會同時保留這個函數所引用的東西,并不會回收它們(比如這里的 description)绩卤。你們面向對象的人其實也經常在方法中聲明局部變量是吧途样,但是你們并不會把一個函數返回出去,自然也就不會考慮方法執(zhí)行完畢之后要保持哪些局部變量濒憋,直接回收掉就好了何暇。”
“哦凛驮,這樣啊...”小王子還是一臉似懂非懂的樣子裆站。
“來,我們做一下分解動作黔夭,首先 description 在 generateEatFunction 內部是一個局部量宏胯。”
function generateEatFunction(description) {
// description == " 在吃東西~"
...
}
“然后內層的函數使用了這個局部量纠修。此時一切都沒有問題胳嘲。”
function generateEatFunction(description) {
function eat(animal) {
// 這里引用了 description
console.log(animal.name + description);
// 相當于
// console.log(animal.name + " 在吃東西~");
}
...
}
“現在精彩的地方來了扣草,我把這個函數返回了出去~當然我不希望這個局部變量被回收了牛,我們依然希望它符合我的預期想法〕矫睿”
function generateEatFunction(description) {
function eat(animal) {
// 這里引用了 description
console.log(animal.name + description);
// 相當于
// console.log(animal.name + " 在吃東西~");
}
return eat;
// 希望 eat 的行為可以像這樣:
// function eat(animal) {
// console.log(animal.name + " 在吃東西~");
// }
}
let eatZH_CN = generateEatFunction(" 在吃東西~");
// 謝天謝地 JavaScrpit 的確沒有回收掉那個局部量鹰祸。
// 效果如同下面這樣:
// eatZH_CN = function eat(animal) { console.log(animal.name + " 在吃東西~"); }
// eatZH_CN 成功指向了我們生成的函數。
// 接下來就是正常的調用啦~
eatZH_CN(xiaolai); //小萊 在吃東西~
邁向高階
“嘿嘿密浑,還不止這樣呢~再給你看個東西~”小萊的話匣子一打開就停不下來蛙婴,由不得小王子發(fā)呆,自顧自的繼續(xù)介紹起來尔破〗滞迹“函數還可以作為參數傳呢~ 這些都叫高階函數~”
function each(list, fun) {
for(let i = 0; i < list.length; i++){
console.log(fun(list[i]));
}
}
let animals = [dog, cat, xiaolai];
each(animals, eat);
// dog is eating
// cat is eating
// 小萊 is eating
//
each(animals, eatZH_CN);
// dog 在吃東西~
// cat 在吃東西~
// 小萊 在吃東西~
“哇!傳遞進來的函數竟然真的就可以直接把它當作函數來使用懒构!這太神奇了餐济!”
“當然這里的 each 函數還不夠函數式,其實可以用遞歸實現啦~畢竟函數式里面不提倡用數組下標來做遍歷胆剧,但是到了 ES6 才有尾遞歸優(yōu)化絮姆,所以... ”小萊還在說著......
未來
“咳咳,小萊你夠了「菝酰客人都被你嚇到了蚁阳。”這時鸽照,在旁邊默默看著的大主教說話了螺捐。
小王子還在震驚之中,不過畢竟是皇族移宅,閱歷豐富归粉,還是悟出了一點門道,“我大概明白了一點漏峰,由于 JavaScript 是動態(tài)類型糠悼,其實無論是函數還是對象,在這里都可以做為一個值來傳遞浅乔。函數式里面偏向對值直接進行處理倔喂,通過對這些值的傳遞和組合,就可以組裝實現更高級的功能靖苇。上面的那個 each 方法也展現出來另一種“多態(tài)”的體現席噩。”
“不錯不錯贤壁,我看你這位朋友的來頭可不一般啊悼枢,新加入的信徒大多都經過漫長的適應期才能理解,他卻立即悟出這些道理來脾拆÷鳎”大主教對小王子贊賞有加,或許以他的智慧已經識破了小王子的身份名船。
“不不不绰上,還是因為小萊妹妹講的好啊∏眨”小王子臉紅的說道蜈块。
“這還不是多虧了我們的原型才能自由靈活的實現各種編程范式么∶陨龋”眼鏡大哥也參合了起來百揭。
“哈哈哈,你又在自夸了蜓席⌒啪”
“來,說了這么多 eat瓮床,不說了,吃菜吃菜~”
看來編程世界上還有這么多種形態(tài),小王子下次要去哪里隘庄,又會見識到怎樣的東西呢踢步?或許也能給他的 Java 帝國帶回一些新鮮的血液?
附
登場人物名:
小萊 == lambda