JavaScript學習總結(jié)之Is this Bothering You?

寫在前面:本文為JavaScript新手之作沮榜,大牛輕噴

圖片盜自 https://www.youtube.com/watch?v=yVdU2coJ1VQ

要說當下編程社區(qū)里最火的語言是什么慌洪,如果JavaScript稱第二拣宰,怕是沒其他語言敢稱第一柄冲。不過這幾年JS實在發(fā)展得太快,而在ES5之后辽幌,好像除了統(tǒng)一的模塊機制增淹,我其實并沒有覺得JS的發(fā)展給前端加上了什么非用不可的語言特性——嗯,是的我覺得前端不太用得上 async 和 await(什么乌企?class虑润?我沒聽過誒)。

不過JS仍然是一個很好玩的語言加酵,和Lua一樣拳喻,有著Scheme一樣的風骨。所以竊以為猪腕,要理解一些JS的設計思路和編碼習慣冗澈,還是要搞點函數(shù)式才行。網(wǎng)上有一套開源的書叫 You Don't Know JS陋葡,我之前小翻了前幾本亚亲,覺得寫得很不錯。但是仔細想想它解釋一些概念的說法腐缤,還是可能對命令式編程背景的讀者不是那么友好捌归。前兩天組里有一個同事問我能不能對JS里的 this 給一個相對淺顯的理解,我第一反應就是把 YDKJS 里讓我印象深刻的說法丟出來:

this 相當于JS靜態(tài)作用域里的一個動態(tài)變量

——呃岭粤,這個定義是挺準確的惜索,但是真的好理解嗎?

好吧绍在,展開來寫一篇我自己是怎么一步一步地理解JS里的 this 這個磨人的小妖精

靜態(tài)作用域和動態(tài)作用域

變量作用域其實和所有編程語言都相關(guān)门扇,但是函數(shù)式編程語言為了閉包的支持,對這個概念尤其看重偿渡。來一個簡單的栗子——我們有如下兩個函數(shù)一個變量和一次調(diào)用:

var v = 1;

function f1() {
    console.log(v);
}

function f2() {
    var v = 2;
    f1();
}

f2();

我們知道JS是一個靜態(tài)作用域的語言臼寄,如果把上面代碼貼到瀏覽器的 console 里執(zhí)行一下,會輸出 1溜宽。這說明 v 這個變量的綁定是在我們寫 f1 的定義時就確定的吉拳,也可以說 v 的作用域是靜態(tài)的,所以這樣的設計叫靜態(tài)作用域(也有人叫詞法作用域)适揉。但是如果JS是一個動態(tài)作用域的語言留攒,v 的綁定就是在 f1 運行的時候確定的,在 f2 里這個值指向了上面那句 var v = 2嫉嘀,最終輸出的結(jié)果就會是 2炼邀。這個問題實在太重要了,因為正是作用域在函數(shù)間的隔離剪侮,給了函數(shù)足夠的抽象能力來實現(xiàn)各種對邏輯和數(shù)據(jù)的封裝拭宁。

Now this is the main dish

好了,現(xiàn)在我們理解了靜態(tài)和動態(tài)作用域以后瓣俯,就可以貼點復雜一些但是更能說明問題的例子了——首先杰标,假設我們的App有這么一個工具集:

var utils = {
    base: 0,
    accumulate: function(list, acc) {
        var result = this.base;
        for (var i of list) result = acc(result, i);
        return result;
    },
};

系不系很簡單?accumulate 函數(shù)就是拿一列數(shù)字和一個 acc 函數(shù)彩匕,然后把這個函數(shù)對這列數(shù)字層層套上(如果腦海里飛過left fold/reduce這樣的概念腔剂,請給自己一朵小紅花)。現(xiàn)在假設我們的App就是一個用來把1驼仪、2掸犬、3、4加起來的簡單計算器——

var app = {
    list: [1, 2, 3, 4],
    simpleSum: function() {
        var result = utils.accumulate(this.list, function(x, y) { return x + y });
        console.log(result);
    },
};

app.simpleSum();

是不是很明顯會log一個10绪爸?現(xiàn)在我們來讓邏輯復雜一些登渣,求這幾個數(shù)的平方和:

app.square = function(x) { return x * x };

app.sqrSum = function() {
    console.log(utils.accumulate(
        this.list,
        function(x, y) { return x + this.square(y) }  // <-- watch this
    ));
};

app.sqrSum();  // Hmm...

發(fā)現(xiàn)了嗎,傳遞給 app.sqrSum 的函數(shù)里毡泻,this 指向的并不是 app胜茧。問題出在哪里呢?問題就在于仇味,this函數(shù)里的作用域不是跟著定義走呻顽,而是在運行的時候被動態(tài)分發(fā)的(敲黑板!劃重點5つ)廊遍。是不是看起來和動態(tài)作用域簡直一毛一樣?

所以我們怎么繞開這個坑呢贩挣?這就是在遙遠的ES5時代及以前喉前,大家經(jīng)常用的一個辦法:把 this 存成另一個變量没酣,再把這個變量傳進子函數(shù)里:

app.sqrSum = function() {
    var self = this;  // some may prefer using `that`
    console.log(utils.accumulate(
        this.list,
        function(x, y) { return x + self.square(y) }  // now self is referenced correctly
    ));
};

this 賦值給另一個變量再往里傳是不是其實有點蛋疼?確實有點卵迂。JS社區(qū)里大家也這么覺得裕便,所以才為此在ES6里搞了個箭頭函數(shù),在箭頭函數(shù)定義里的 this 就是跟著詞法作用域走的见咒。這個特性在網(wǎng)上已經(jīng)有太多討論偿衰,一搜一大把,這里就不展開了改览。

更好的JS

The Better Parts

不管是箭頭函數(shù)還是重新賦值下翎,其實都不是我心中最好的解決方式——我其實更偏向于直接用函數(shù),而不是用對象來封裝數(shù)據(jù)和邏輯宝当。學JS的朋友大多都讀過或者聽過 JavaScript the Good Parts 這本書的大名吧视事,作者 Douglas Crockford 在14年曾經(jīng)談過他覺得JavaScript里怎樣做到更好的設計模式,這個Talk名字就叫 JavaScript the Better Parts庆揩。其中有一點我特別特別贊同:完全不要用this郑口,因為……根本不需要!JS里的函數(shù)是比對象更全能的抽象方法盾鳞。

再復用剛才的那個例子犬性,我就不需要再特別解釋,直接把用函數(shù)做抽象的代碼貼上來一看就懂了:

var utils = (function utils() {
    var base = 0;
    function accumulate(list, func) {
        var result = base;
        for (var i of list) result = func(result, i);
        return result;
    }
    return { base: base, accumulate: accumulate };
})();

var app = (function app() {
    var list = [1, 2, 3, 4], square = function(x) { return x * x };
    return {
        simpleSum: function() {
            console.log(utils.accumulate(
                list,
                function(x, y) { return x + y }
            ));
        },
        sqrSum: function() {
            console.log(utils.accumulate(
                list,
                function(x, y) { return x + square(y) }
            ));
        },
    };
})();

是不是沒有了 this 以后整個邏輯清晰不少腾仅?再也不用糾結(jié)指的是哪一層對象的字段了乒裆。而且用函數(shù)來做封裝還有另外一個好處,不放在返回對象字段里的變量對外不可見推励,相當于一個私有變量鹤耍。如果不習慣IIFE的寫法,網(wǎng)上的資料也挺多的验辞,搜一下不難稿黄,這里就略過不表咯。至于那些對象實例的 callbind 這些用法細節(jié)跌造,在把 this 拿掉之后杆怕,也一樣沒有必要去深究了——是不是好棒棒?

但是殘酷的現(xiàn)實是寫一個網(wǎng)頁App一般都會用個框架壳贪,框架里一般都會用上 this陵珍,實在是……(啊不,像我這樣沒有能力寫一個框架的小菜雞违施,是不會抱怨的)

P.S. 如果有大牛想吐槽我的API里犯了跟Lodash和Underscore一樣的錯——那就吐吧反正這種科普貼只是我分享自己的入門心得互纯,又不是給大牛看的吼吼……

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末磕蒲,一起剝皮案震驚了整個濱河市留潦,隨后出現(xiàn)的幾起案子只盹,更是在濱河造成了極大的恐慌,老刑警劉巖兔院,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殖卑,死亡現(xiàn)場離奇詭異,居然都是意外死亡秆乳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門钻哩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屹堰,“玉大人,你說我怎么就攤上這事街氢〕都” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵珊肃,是天一觀的道長荣刑。 經(jīng)常有香客問我,道長伦乔,這世上最難降的妖魔是什么厉亏? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮烈和,結(jié)果婚禮上爱只,老公的妹妹穿的比我還像新娘。我一直安慰自己招刹,他們只是感情好恬试,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疯暑,像睡著了一般训柴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妇拯,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天幻馁,我揣著相機與錄音,去河邊找鬼越锈。 笑死宣赔,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的瞪浸。 我是一名探鬼主播儒将,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼对蒲!你這毒婦竟也來了钩蚊?” 一聲冷哼從身側(cè)響起贡翘,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎砰逻,沒想到半個月后鸣驱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蝠咆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年踊东,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刚操。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡闸翅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出菊霜,到底是詐尸還是另有隱情坚冀,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布鉴逞,位于F島的核電站记某,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏构捡。R本人自食惡果不足惜液南,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望勾徽。 院中可真熱鬧贺拣,春花似錦、人聲如沸捂蕴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啥辨。三九已至涡匀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間溉知,已是汗流浹背陨瘩。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留级乍,地道東北人舌劳。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像玫荣,于是被迫代替她去往敵國和親甚淡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 目錄 1.靜態(tài)作用域與動態(tài)作用域 2.變量的作用域 3.JavaScript 中變量的作用域 4.JavaScri...
    一縷殤流化隱半邊冰霜閱讀 7,091評論 37 113
  • 繼承 一捅厂、混入式繼承 二贯卦、原型繼承 利用原型中的成員可以被和其相關(guān)的對象共享這一特性资柔,可以實現(xiàn)繼承,這種實現(xiàn)繼承的...
    magic_pill閱讀 1,062評論 0 3
  • 1. Java基礎部分 基礎部分的順序:基本語法撵割,類相關(guān)的語法贿堰,內(nèi)部類的語法,繼承相關(guān)的語法啡彬,異常的語法羹与,線程的語...
    子非魚_t_閱讀 31,625評論 18 399
  • 如今跳仿,在企業(yè)界诡渴,小組制捐晶、合伙人制很紅火菲语! 韓都衣舍、海爾在搞小組制惑灵,萬科山上、永輝超市在搞合伙人制。上周英支,我們專門寫了...
    鞋服江湖郝鳳茹閱讀 463評論 0 1
  • 花開不見葉來賞栖袋,葉啟未能襯花艷医窿。 花葉固欲相連理,奈何緣盡不得遇。 有情使得同根生捞稿,無緣落得面難圓。 識也思也非戀...
    冬丿寒宿命閱讀 636評論 0 2