星際巡航術(shù)—玩轉(zhuǎn)javascript中this!

在javascript異步編程枕稀、函數(shù)式編程中询刹,有兩個至關(guān)重要的技術(shù)callbackthis變量,又稱之為回調(diào)當(dāng)前對象上下文萎坷。

一凹联、星際迷航

javascript中的回調(diào)函數(shù),我借用科幻小說的比喻哆档,有點類似不同的宇宙空間蔽挠。而且宇宙空間有兩類:

  1. 一類就像從地球到火星,在代碼上的表現(xiàn)是瓜浸,在同一個時刻(幀)代碼執(zhí)行有嚴(yán)格的先后順序澳淑。
  2. 另一類回調(diào)函數(shù),像從當(dāng)下去了天堂或冥界插佛,跟現(xiàn)在下不屬于同一個宇宙空間杠巡,代碼在未來某一時刻才會進入。

而且這些宇宙空間還相可以互嵌套雇寇,簡單理解可以用同步忽孽、異步函數(shù)來區(qū)別。

image.png

舉個列子谢床,先看看從地球到火星的旅行:

onLoad() {
    let array = ['1','2','3','4','5'];
    //過慮出數(shù)組中的奇數(shù)元素
    this._num = 2;
    array = array.map(function(i) {
        return parseInt(i);
    }).filter(function(i) {
        return i % this._num;
    }, this); //注意這里的this參數(shù)
}

上面代碼中array對象上的map與filter中的匿名函數(shù)兄一,就像兩個小行星。onLoad外層就是地球识腿,他們是在同一個時空之中出革,array中的元素像是做了一次星際旅行,斷點會從上到下一句一句地執(zhí)行渡讼。

Shawn對es6太過依賴骂束,忍不住寫了一行es6的等價代碼:

//再看看es6的寫法
array = array.map(i => parseInt(i)).filter(i => i % this._num);

這里解釋一下,注意兩點:

  1. 箭頭函數(shù)中參數(shù)只有一個時成箫,可以省略參數(shù)上的圓括號(arg)直接寫成arg展箱。
  2. 箭頭函數(shù)中函數(shù)體只有一行代碼,可以省略大擴號{}直接寫表達示蹬昌,同時將表達式的值默認(rèn)為函數(shù)返回值混驰,所以不需要寫return。

再來看看Creator中常見的回調(diào)用法,在不同的宇宙空間的穿梭:

onLoad() {
    this._button.active = false;
    this.scheduleOnce(() => {
        this._button.active = true;
    }, 5);
}

使用scheduleOnce延時5秒顯示_button節(jié)點栖榨,他與上面的map昆汹、filter函數(shù)不同的是異步執(zhí)行。在調(diào)試中會發(fā)現(xiàn)斷點在代碼前后跳躍婴栽,斷點前后跳躍不是關(guān)鍵满粗,關(guān)鍵的是scheduleOnce函數(shù)他不會阻塞,不論scheduleOnce函數(shù)中的回調(diào)函數(shù)如何復(fù)雜都不會影響當(dāng)前這一幀的運行效率愚争。

在Creator中cc.loader.loadRes映皆、cc.loader.load就是異步回調(diào)的,如果資源已經(jīng)被加載過了轰枝,可以使用cc.loader.getRes通過函數(shù)返回值同步獲取劫扒。理解同步與異步是編寫javascript函數(shù)的重要心法,善于駕馭異步流程你就能在javascript中自由遨游狸膏,使用async.js來控制異步流程是一個高效的作法沟饥。

二、搞清楚this是誰

在紛繁復(fù)雜的星際旅行中湾戳,不論是同步還是異步贤旷,最為重要的是不要忘記“我是誰”。No不好意思砾脑,搞清楚我不重要幼驶,在你人生旅途中,要時間清醒韧衣,此刻的你到底是誰更重要盅藻。


我是誰

對于javascript中的回調(diào)函數(shù)來說,函數(shù)中的this變量到底是誰畅铭,搞不清這個你很可能就會在旅行中回不來了氏淑,回到之前代碼中的filter中的回調(diào)函數(shù):

onLoad() {
    let array = ['1','2','3','4','5'];
    let array = [];
    let this._num = 2;
    array.filter(function(i) {
        return i % this._num;
    }, this); //<-----注意這里的this參數(shù)
}

filter的第二個參數(shù)this是用來改變回調(diào)函數(shù)中的this變量,如果不傳這個this參數(shù)硕噩,里面的this._num訪問就會有問題假残。

例如在Creator中有不少需要注冊回調(diào)的API,后面都會緊跟一個target參數(shù)炉擅,target將來回調(diào)后的this變量辉懒。

this.node.on(cc.Node.EventType.TOUCH_START, this.memberFunction, this);

如果你不傳入第三個參數(shù)this你的代碼很可能會掛掉,函數(shù)的this上下文默認(rèn)受調(diào)用者所控制谍失。

//模擬一個組件中的點擊事件
_onButtonTouchEnd() {
    //定義一個回調(diào)函數(shù)
    let callback = function() { 
        cc.log(this);  //<----這個this是全局window
    }
    //執(zhí)行回調(diào)函數(shù)眶俩,函數(shù)中的this是全局window
    callback(); 
}

上面代碼callback中的this是全局window,這里我使用慣用方式總結(jié)了幾個大招可以用來改變callback中的this變量快鱼。

三颠印、星際巡航

javascript與c/c++纲岭、java等語言有個最大區(qū)別就是,函數(shù)中的this變量是可變的嗽仪。幾乎每個人都會在這一點栽跟頭,這個特性既成就了javascript的高度靈活性柒莉,但也讓不少初學(xué)者產(chǎn)生迷惑闻坚。改變js函數(shù)中this變量的技法我將其稱之為:星際巡航術(shù),為的是在迷航中認(rèn)清自己兢孝。

第一式:凝神訣

Function.bind

javascript中所有的函數(shù)對象上都有bind方法窿凤,執(zhí)行它將返回一個新的函數(shù)變量,這個返回的函數(shù)執(zhí)行時的this上下文由bind的第一個參數(shù)所決定跨蟹■ㄊ猓看看在節(jié)點事件中的運用:

//去掉了第三個target參數(shù)
this.node.on(cc.Node.EventType.TOUCH_START, this.memberFunction.bind(this));

使用bind搞定,是不是很簡單窗轩,我看好多人是這樣做的夯秃。但請你思考一下那為什么Array.map、Array.filter痢艺、CreatorAPI要設(shè)計target參數(shù)呢仓洼?使用bind注冊回調(diào),容易踩到一個坑堤舒,稍后說明一下我的理解色建。我們再稍微深入一點,看看bind更多的用法:

//模擬一個組件中的點擊事件
_onButtonTouchEnd() {
    //定義一個回調(diào)函數(shù)
    let callback = function(name, event) { 
        cc.log(this);         //打印當(dāng)前this
        cc.log(name, event);  //打印參數(shù) 
    }
    //施展綁定訣舌缤,將callback中的this綁定為當(dāng)前函數(shù)上下文中的this
    let callback1 = callback.bind(this); 
    //執(zhí)行回調(diào)函數(shù)箕戳,函數(shù)中的this是曾經(jīng)bind傳入?yún)?shù),這里就是當(dāng)前組件對象
    callback1('button', 'touchEnd');

    //將callback中的this綁定為當(dāng)前this上的_button節(jié)點對象
    let callback2 = callback.bind(this._button); 
    //執(zhí)行回調(diào)函數(shù)国撵,函數(shù)中的this是bind傳入的_button節(jié)點
    callback2('button', 'touchEnd');

凝神訣要義在于bind時的參數(shù)設(shè)定陵吸,就像是搓出一股波動拳,蓄而未發(fā)介牙,“啊啰啰啰啰......”就是不“哽”出去走越。

啊啰啰啰啰......

而且bind函數(shù)還可以給函數(shù)傳遞參數(shù),請仔細閱讀下面代碼:

 //定義一個回調(diào)函數(shù)
 let callback = function(arg1, arg2) { 
     cc.log(this);         //打印當(dāng)前this
     cc.log(arguments)     //打印隱藏參數(shù)對象 
     cc.log(arg1, arg2);  //打印參數(shù) 
 }
 
 //綁定決還可以傳入?yún)?shù)耻瑟,傳入的參與會排在原函數(shù)定義的參數(shù)之前
 let callback1 = callback.bind(this._button, 'button', 'touchEnd');
 //參數(shù)已經(jīng)在bind時傳入了旨指,此時可以不用傳入?yún)?shù)了
 callback1();
 //如果傳入?yún)?shù),調(diào)用時的參數(shù)會排在綁定時的參數(shù)后面
 callback1(1, 2); //參數(shù)順序:['button', 'touchEnd', 1, 2]

將這股凝聚的能量任意流動(一系列的參數(shù)傳遞喳整、變量賦值)谆构,在適合的地方釋放出來,其中this變量與參數(shù)是由你之前精心設(shè)計的框都,這時會產(chǎn)生情人的效果搬素,是一般靜態(tài)語言難以做到的。

發(fā)動

還需要特別的注意,每一股搓出的一股波動拳都是不同的函數(shù)對象熬尺。

let func1 = callback.bind(xxx);
let func2 = callback.bind(xxx);
//f1與f2是兩個不同的函數(shù)對象
f1 === f2;  //返回false

這就是為什么在節(jié)點事件注冊時使用bind容易掉入進的坑摸屠,當(dāng)你想使用node.off你不能將之前事件回調(diào)給刪除掉,這就是為什么要給你一個target參數(shù)的原因了粱哼。

不過Shawn還有更簡單的辦法注冊事件季二,而且也不需要傳入target,因為bind是es5時代的產(chǎn)物揭措,es6有更好用的招數(shù)胯舷。

第二式:召喚訣

Function.call

你可能在想,Creator的API是如何利用target參數(shù)修改的回調(diào)中的this的呢绊含?其實與Function.bind一樣桑嘶,javascript中所有的函數(shù)對象上都有一個call方法

//模擬一個組件中的點擊事件
_onButtonTouchEnd() {
    //定義一個回調(diào)函數(shù)
    let callback = function(name, event) { 
        cc.log(name, event); 
    }
    //call的第一個參數(shù)是想變換的this上下文,后面為該函數(shù)的實際參數(shù)
    callback.call(this, 'button', 'touchEnd'); 
}

召喚訣的特點是:隨喊隨到躬充,立即執(zhí)行逃顶,其中最為重要的是call傳入的第一個參數(shù),就是你想變換的this變量充甚,后面緊跟此函數(shù)的參數(shù)口蝠。

召喚

一個更有趣的實踐hack一下Creator的cc.Button組件,做個神奇的勾子:

//先保存button狀態(tài)切換函數(shù)
let updateState = cc.Button.prototype._updateState;
//自己寫個函數(shù)來將他覆蓋了
cc.Button.prototype._updateState = function () {
    //執(zhí)行時的第一句津坑,執(zhí)行原來保存的_updateState妙蔗,相當(dāng)于執(zhí)行基類函數(shù)
    //這里不能直接調(diào)用updateState,需要用call將內(nèi)部this修正為當(dāng)前button
    updateState.call(this); 
    if (this.node.interactable === this.interactable) {
        return;
    }
    //下面是根據(jù)是否禁用疆瑰,設(shè)置button節(jié)點下的子節(jié)點變灰
    //做了條件判斷只在不設(shè)置disabledSprite時生效
    this.node.interactable = this.interactable;
    if (this.enableAutoGrayEffect && this.transition !== cc.Button.Transition.COLOR) {
        if (!(this.transition === cc.Button.Transition.SPRITE && this.disabledSprite)) {
            this.node.children.forEach((node) => {
                let sprite = node.getComponent(cc.Sprite);
                if (sprite && sprite._sgNode) {
                    sprite._sgNode.setState(this.interactable ? 0 : 1);
                }

                //原生平臺退出 
                if(cc.sys.isNative) {
                    return;
                }
                
                //Label的置灰實現(xiàn)目前只能在web端使用
                let label = node.getComponent(cc.Label);
                if (label && label._sgNode) {
                    let shaderProgram = this.interactable ?
                        cc.shaderCache.programForKey(cc.macro.SHADER_SPRITE_POSITION_TEXTURECOLOR) :
                        cc.Scale9Sprite.WebGLRenderCmd._getGrayShaderProgram();

                    label._sgNode._renderCmd.setShaderProgram(shaderProgram);
                }
            });
        }
    }
};

來看看演示效果:


Shawn還嘗試了眉反,將bind過的函數(shù)對象,再調(diào)用call穆役,this任然是之前bind時的this不受call的第一個參數(shù)控制寸五。

let func = callback.bind(xxx);
//執(zhí)行時func函數(shù)的this任然是xxx,函數(shù)參數(shù)有效
func.call(yyy, arg1, arg2); 

es5的時候call出現(xiàn)的頻率是非常高的耿币,但現(xiàn)在使用了es6除了做一些hack行為與面向?qū)ο蟮哪M外梳杏,大多數(shù)回調(diào)都可以用更加簡單的一陽指可以搞定。

第三式:降龍訣

Function.apply

javascript中函數(shù)的參數(shù)變化無窮淹接,參數(shù)個數(shù)可長可短(參數(shù)個數(shù)0~n)十性,神鬼莫測,猶如一條游龍塑悼!降龍訣就是用來馴服這條善變的怪獸的劲适!

_onButtonTouchEnd() {
    //定義一個回調(diào)函數(shù),根據(jù)不同的參數(shù)個數(shù)有不同的處理
    let callback = function() { 
          switch(arguments.lenght) {
              case 1:
                   ...
                   break;
              case 2:
                   ...
                   break;
        }
    }
    //call的第一個參數(shù)是想變換的this上下文,后面接一個數(shù)組參數(shù)
    callback.apply(this, ['button', 'touchEnd']); 

同樣的厢蒜,所有函數(shù)上都有一個apply方法霞势,降龍訣的精髓有兩點:

  1. 控制this上下文的變化烹植,
  2. 可以將參數(shù)用一個數(shù)組打包進行傳遞,

函數(shù)執(zhí)行任然是像普通調(diào)用一樣愕贡,在平時用的地方不多草雕,但在類的繼承、執(zhí)行基類函數(shù)固以、模擬面向?qū)ο蟮燃夹g(shù)上是離不開它的墩虹。

第四式:一陽指

箭頭函數(shù) () => { ... }

一陽指又稱箭頭函數(shù),所指之處的函數(shù)this上下文嘴纺,皆為當(dāng)時調(diào)用時的this败晴,看似平淡無其浓冒,實則威力巨大栽渴。

//模擬一個組件中的點擊事件
_onButtonTouchEnd() {
    //定義一個箭頭函數(shù),當(dāng)前this為組件對象
    let callback = (arg1, arg2) => { 
        //此刻的this為定義函數(shù)時的this上下文對象
        cc.log(this);
    }
   callback(xxx, yyy); 
}
一陽指.jpg

凝神訣和召喚訣的運用大多數(shù)是為了修正匿名函數(shù)中的this為當(dāng)前調(diào)用時的this稳懒,可顯的有點啰哩叭嗦闲擦,一記一陽指輕松搞定!

在一陽指還沒有被創(chuàng)造之前场梆,使用的是閉包變量來做的:

var self = this;
function callback() {
    //使用self變量墅冷,指向調(diào)用時的this上下文
    cc.log(self);
    ...
}
callback(xxx, yyy);

此方法也正是Bable編譯器將es6轉(zhuǎn)es5時生成的套路。

對于this的控制是凌波微步的內(nèi)功基本詳見《
英雄之舞—凌波微步》或油,如果運用的不好寞忿,就會如文中所講的,強行走將起來顶岸,會造成經(jīng)脈堵塞的危境腔彰!

四、結(jié)束

最后總結(jié)一下我們介紹的招數(shù)

凝神訣—Function.bind
召喚訣—Function.call
降龍訣—Function.apply
一陽指—箭頭函數(shù)

這些招數(shù)都是為了在回調(diào)函數(shù)中不要迷失this辖佣,或都說在回調(diào)中可以任意控制this霹抛。在javascript中函數(shù)是第一位的,函數(shù)可以動態(tài)生成卷谈,可以當(dāng)參數(shù)傳遞杯拐,可以說javascript是披著c/c++的狼,骨子里其實是函數(shù)式編程語言世蔗。


宇宙在你手中
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末端逼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子污淋,更是在濱河造成了極大的恐慌裳食,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芙沥,死亡現(xiàn)場離奇詭異诲祸,居然都是意外死亡浊吏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門救氯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來找田,“玉大人,你說我怎么就攤上這事着憨《昭茫” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵甲抖,是天一觀的道長漆改。 經(jīng)常有香客問我,道長准谚,這世上最難降的妖魔是什么挫剑? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮柱衔,結(jié)果婚禮上樊破,老公的妹妹穿的比我還像新娘。我一直安慰自己唆铐,他們只是感情好哲戚,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著艾岂,像睡著了一般顺少。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上王浴,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天脆炎,我揣著相機與錄音,去河邊找鬼叼耙。 笑死腕窥,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的筛婉。 我是一名探鬼主播簇爆,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼爽撒!你這毒婦竟也來了入蛆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤硕勿,失蹤者是張志新(化名)和其女友劉穎哨毁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體源武,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡扼褪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年想幻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片话浇。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡脏毯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出幔崖,到底是詐尸還是另有隱情食店,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布赏寇,位于F島的核電站吉嫩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嗅定。R本人自食惡果不足惜自娩,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望露戒。 院中可真熱鬧椒功,春花似錦捶箱、人聲如沸智什。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荠锭。三九已至,卻和暖如春晨川,著一層夾襖步出監(jiān)牢的瞬間证九,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工共虑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留愧怜,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓妈拌,卻偏偏與公主長得像拥坛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子尘分,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

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

  • 函數(shù)參數(shù)的默認(rèn)值 基本用法 在ES6之前猜惋,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法培愁。 上面代碼檢查函數(shù)l...
    呼呼哥閱讀 3,357評論 0 1
  • 變天了著摔,降溫了,人也隨之感冒了定续,感覺有些渾身無力……不過谍咆,畫石頭畫還是要堅持的禾锤,畢竟,在不知不覺中摹察,我已經(jīng)連續(xù)畫了...
    小崔喵喵噠石頭畫閱讀 930評論 11 30
  • 歸路 BY:豆皮先森 黃葉青苔歸路时肿,屧粉衣香何處。*1 . . 老實說翻來覆去地向別人敘述我的人生已經(jīng)是件膩味十足...
    豆皮先生閱讀 274評論 0 0
  • 那一片落葉悠悠而下港粱, 帶走的不止歲月的繁華螃成; 稀索的雨珠一滴滴滑下, 沖刷著記憶中不清晰的畫查坪; 窗臺上的灰有多久沒...
    莞月閱讀 257評論 0 1
  • 1. 基礎(chǔ)常識 1.1 主流屏幕尺寸 1.2 圖標(biāo)尺寸 1.3 顏色值 Android 定義顏色color時有6...
    darren_he閱讀 57,882評論 12 262