真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

javascript基礎(chǔ)修煉(2)——What'sthis(上)

開發(fā)者的javascript造詣取決于對【動態(tài)】和【異步】這兩個(gè)詞的理解水平。

目前成都創(chuàng)新互聯(lián)公司已為上1000家的企業(yè)提供了網(wǎng)站建設(shè)、域名、雅安服務(wù)器托管、網(wǎng)站改版維護(hù)、企業(yè)網(wǎng)站設(shè)計(jì)、鐘樓網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

javascript基礎(chǔ)修煉(2)——What's this(上)

一.this是什么

this是javascript關(guān)鍵字之一,是javascript能夠?qū)崿F(xiàn)面向?qū)ο缶幊?/strong>的核心概念。用得好能讓代碼優(yōu)雅高端,風(fēng)騷飄逸,用不好也絕對是坑人坑己利器。我們常常會在一些資料中看到對this的描述是:

this是一個(gè)特殊的與Execution Contexts相關(guān)的對象,用于指明當(dāng)前代碼執(zhí)行時(shí)的Execution Contextsthis在語句執(zhí)行進(jìn)入一個(gè)Execution Contexts時(shí)被賦值,且在代碼執(zhí)行過程中不可再改變。
注:Execution Contexts也就是我們常聽到的"上下文""執(zhí)行環(huán)境"。

看不懂?看不懂就對了,我也看不懂。
對于this的指向,我們常會聽到這樣一個(gè)原則——this是一個(gè)指針,指向當(dāng)前調(diào)用它的對象。但實(shí)際使用中,我們卻發(fā)現(xiàn)有時(shí)候很難知道當(dāng)前調(diào)用它的是哪個(gè)對象,從而引發(fā)了一系列的誤用和奇怪現(xiàn)象。

今天,我們就換一種思路,試試如何從語言的角度一步一步地去理解this,你會發(fā)現(xiàn):
只要你能聽懂中國話,就意味著你能理解this

二.近距離看this

2.1 this的語法意義

javascript是一門程序設(shè)計(jì)語言,也就是說,它是一種語言,是語言,就有語法特性。如果拋開this的原理和編程中的用法,僅從語文的層面去理解,它的本質(zhì)就是代詞。什么是代詞?漢語中的,,,你們,我們,他們這一類的詞語就是代詞。代詞并不具體指某一個(gè)具體的事物,但結(jié)合上下文,就可以知道這類詞語代替的是誰。
比如下面這幾句描述的語境:

  • 大爺是趙本山
    • 請問:誰大爺是趙本山?
    • 沒法回答,因?yàn)闆]有上下文約束,此處的可能指任何人。
  • 李雷來頭可不小,大爺是趙本山
    • 請問:誰大爺是趙本山?
    • 很容易回答,因?yàn)榍耙痪湓捠沟梦覀兡軌虻弥?dāng)前上下文中,"他"指的就是"李雷"
  • ___來頭可不小,大爺是趙本山
    • 請問:誰大爺是趙本山?
    • 此處空格填誰,誰大爺就是趙本山。

小結(jié)一下:

代詞,用于指代某個(gè)具體事物,當(dāng)結(jié)合上下文時(shí),就可以知道其具體的指向。換句話說,有了上下文時(shí),代詞就有了具體的意義。this在javascript語言中的意義,就如同代詞在漢語中的意義是一樣的。

2.2 不同作用域中的this

在ES6出現(xiàn)前,javascript中的作用域只分為全局作用域和函數(shù)作用域兩種。(以下部分暫不討論嚴(yán)格模式)。

  • 全局作用域中使用this

全局作用域中的this是指向window對象的,但window對象上卻并沒有this這個(gè)屬性:
javascript基礎(chǔ)修煉(2)——What's this(上)

  • 函數(shù)作用域使用this

函數(shù)作用域中的this也是有指向的(本例中指向window對象),我們知道函數(shù)的原型鏈?zhǔn)菚赶?code>Object的,所以函數(shù)本身可以被當(dāng)做一個(gè)對象來看待,但遺憾的是函數(shù)的原型鏈上也沒有this這個(gè)屬性:
javascript基礎(chǔ)修煉(2)——What's this(上)

綜上所述,this可以直觀地理解為:

this與函數(shù)相關(guān),是函數(shù)在運(yùn)行時(shí)解釋器自動為其賦值的一個(gè)局部常量。

2.3 javascript代碼編寫方式
a.不使用this

這是有可能發(fā)生的。很多初學(xué)者會發(fā)現(xiàn),自己在編寫javascript代碼時(shí)并沒有用到this,但是也并不影響自己編寫代碼。前面提到過上下文信息的意義在于讓代詞明確其指向,那么如果一段話的上下文中并沒有使用代詞,在語文中我們就不需要聯(lián)系上下文就能理解這段話;同理,如果函數(shù)的函數(shù)體中并沒有使用this關(guān)鍵字來指代任何對象,或者不需要關(guān)注其調(diào)用對象,那實(shí)際上就算不確定this的指向,函數(shù)的執(zhí)行過程也不會有歧義。

/**
 *數(shù)據(jù)加工轉(zhuǎn)換類的函數(shù),對開發(fā)者來說更關(guān)注結(jié)果,而并不在乎是誰在調(diào)用。
*/
function addNumber(a,b) {
    return a + b;
}

無論是計(jì)算機(jī)對象調(diào)用addNumber方法,或是算盤對象調(diào)用addNumber方法,甚至是人類對象通過心算調(diào)用addNumber方法,都無所謂,因?yàn)槲覀冴P(guān)注的是結(jié)果,而不是它怎么來的。

b.不使用函數(shù)自帶的this

有時(shí)候我們編寫的代碼是需要用到一些關(guān)于調(diào)用對象的信息的,但由于不熟悉this的用法,許多開發(fā)者使用了另一種變通的方式,也就是顯式傳參。比如我們在一個(gè)方法中,需要打出上下文對象的名字,下面兩種編寫方式都是可以實(shí)現(xiàn)的。

//方式一.使用this
invoker.whoInvokeMe = function(){
    console.log(this.name);
}

//方式二.不使用this
function whoInvokeMe2(invoker){
    console.log(invoker.name);
}

方式二的方式并不是語法錯(cuò)誤,可以讓開發(fā)者避開了因?yàn)閷?code>this關(guān)鍵字的誤用而引發(fā)的混亂,同樣也避開了this所帶來的對代碼的抽象能力和簡潔性,同時(shí)會造成一些性能上的損失,畢竟這樣做會使得每次調(diào)用函數(shù)時(shí)需要處理更多的參數(shù),而這些參數(shù)本可以通過內(nèi)置的this獲取到。

c.面向?qū)ο蟮木幊?/strong>

提到this,必然會提到另一個(gè)詞語——面向?qū)ο?/strong>。"面向?qū)ο?是一種編程思想,請暫時(shí)拋開封裝,繼承,多態(tài)等高大上的修飾詞帶來的負(fù)擔(dān),純粹地感受一下這種思想本身。有的人說"面向?qū)ο?賦予了編程一種哲學(xué)的意義,它是使用程序語言的方式對現(xiàn)實(shí)世界進(jìn)行的一種簡化抽象,現(xiàn)實(shí)世界的一個(gè)用戶,一種策略,一個(gè)消息,某個(gè)算法,在面向?qū)ο蟮氖澜缋锞鶎⑵湟暈橐粋€(gè)對象,也就是哲學(xué)意義上的無分別,每一個(gè)對象都有其生命周期,它怎么來,要做什么,如何消亡,以及它與萬物之間的聯(lián)系。

面向?qū)ο?/code>的思想,是用程序語言勾勒現(xiàn)實(shí)世界框架的方式之一,它的出現(xiàn)不是用來為難開發(fā)者的,而是為了讓開發(fā)者能以更貼近日常生活的認(rèn)知方式來提升對程序語言的理解能力。

2.4 如果沒有this

我們來看一下如果javascript中不使用this關(guān)鍵字,對程序編寫會造成什么影響呢?
我們先來編寫一段簡單的定義代碼:

    //假設(shè)我們定義一個(gè)人的類
    function Person(name){

    }

    // 方法-介紹你自己(使用this編寫)
    Person.prototype.introduceYourselfWithThis = function () {
        if (Object.hasOwnProperty.call(this, 'name')) {
           return `My name is ${this.name}`;
        } 
        return `I have no name`;
    }

    // 方法-介紹你自己(不使用this編寫)
    Person.prototype.introduceYourself = function (invoker) {
        if (Object.hasOwnProperty.call(invoker, 'name')) {
            return `My name is ${invoker.name}`;
        }
        return `I have no name`;
    }

    //生成兩個(gè)實(shí)例,并為各自的name屬性賦值
    var liLei = new Person();
    liLei.name = 'liLei';
    var hanMeiMei = new Person();
    hanMeiMei.name = 'hanMeiMei';

在上面的簡單示例中,我們定義了一個(gè)不包含任何實(shí)例屬性的類,并使用不同的方式為其定義介紹你自己這個(gè)方法,第一種定義使用常規(guī)的面向?qū)ο髮懛?,使?code>this獲取上下文對象,獲取實(shí)例的name屬性;第二種定義不使用this,而是將調(diào)用者名稱作為參數(shù)傳遞進(jìn)方法。
我們在控制臺進(jìn)行一些簡單的使用:
javascript基礎(chǔ)修煉(2)——What's this(上)
那么這兩種不同的寫法區(qū)別到底是什么呢?

  • 函數(shù)實(shí)際功能的變化
    從上面的示例中不難看出,當(dāng)開發(fā)中不使用this時(shí),需要開發(fā)者自行傳入上下文對象,并將其以參數(shù)的形式在函數(shù)執(zhí)行時(shí)傳入,如果傳入的invoker 對象和 this的指向一致,那么結(jié)果就一致,如果不一致,則會造成混亂。

    • 從編碼角度來看
      introduceYourselfWithThis()方法只是introduceYourself(invoker)方法的特例(當(dāng)this === invoker時(shí))。
    • 從方法的含義來看
      定義者希望實(shí)現(xiàn)自我介紹功能而編寫了introduceYourself()方法,可是使用者在閱讀到introduceYourself()的源碼時(shí)看到的代碼表達(dá)的意義是:我告訴你一個(gè)名字,你把它填在'My name is __'這句話中再返回給我。而不是一個(gè)與調(diào)用對象有著緊密聯(lián)系的自我介紹動作。
  • 畫蛇添足的參數(shù)傳遞
    在正確的使用過程中,thisinvoker 的指向是一致的,形參invoker的定義不僅增加了函數(shù)使用的復(fù)雜度,也增加了函數(shù)運(yùn)行的負(fù)擔(dān),卻沒有為函數(shù)的執(zhí)行帶來任何新的附加信息。

  • 重復(fù)的雷同代碼
    如果編碼中不使用this,也就相當(dāng)于漢語中不使用代詞,那么我們就需要在每一個(gè)獨(dú)立的句子中使用完整的信息。為了使introduceYourself()方法能夠正確的執(zhí)行,我們需要在每一個(gè)實(shí)例生成后,為其綁定確切的實(shí)例方法,即:
    var liLei = new Person();
    liLei.name = 'liLei';
    //定義實(shí)例方法
    liLei.introduceYourself = function (){
        return `My name is liLei`;
    };

    var hanMeiMei = new Person();
    hanMeiMei.name = 'hanMeiMei';
    //定義實(shí)例方法
    hanMeiMei.introduceYourself = function (){
        return `My name is hanMeiMei`;
    }

即時(shí)不使用this,你也不會直接陷入無法編寫javascript代碼的境地,只是需要將所有的定義和使用場景全部具體化, 需要手動對所有的具體功能編寫具體實(shí)現(xiàn),也就是"面向過程"的編程。

================================我是華麗的分割線======================================

【輕松一刻】

話說赤壁之戰(zhàn)后,一日閑來無事,孔明與劉關(guān)張三兄弟一起喝酒。孔明說,我出三道題考考各位學(xué)識修養(yǎng),如何啊?三兄弟舉手贊同。
孔明:第一題,主公,赤壁之戰(zhàn)發(fā)生在哪里?
劉備:赤壁啊
孔明:答對了,主公果然厲害。第二題,關(guān)將軍,雙方有多少人參戰(zhàn)?
關(guān)羽:聯(lián)軍5萬,曹軍20余萬。
孔明:答對了,關(guān)將軍也是智勇雙全啊。最后一題,他們分別是誰?
張飛:我......我靠

愿你能夠掌握this,不要在自己的代碼里搞出他們分別是誰的尷尬,小心被隊(duì)友活埋。

================================我是華麗的分割線======================================

三. this的一般指向規(guī)則

javascript中有四條關(guān)于this指向的基本規(guī)則。今天,我們將一起通過【碼農(nóng)視角】【語文老師視角】來分別解讀這些規(guī)則,你會發(fā)現(xiàn)他們理解起來其實(shí)很自然。

規(guī)則1——作為函數(shù)調(diào)用時(shí),this指向全局對象

瀏覽器中的全局對象,指的是window對象。這一規(guī)則指的就是我們在全局作用域或者函數(shù)作用域中使用function關(guān)鍵字直接聲明或使用函數(shù)表達(dá)式賦值給標(biāo)識符的方式創(chuàng)建的函數(shù)。為了在調(diào)用時(shí)在內(nèi)存中找到所聲明的方法,我們需要一個(gè)標(biāo)識符來指向它的位置,具名函數(shù)可以通過它的名字找到,匿名函數(shù)則需要通過標(biāo)識符來找到。作為函數(shù)調(diào)用的實(shí)質(zhì),就是通過方法名直或標(biāo)識符找到函數(shù)并執(zhí)行它。

一般什么樣的函數(shù)我們會這樣定義呢?
就是那些不關(guān)注調(diào)用者的函數(shù),比如上面舉例的addNumber()方法,這類函數(shù)往往是將一步或幾步業(yè)務(wù)邏輯組合在一起,起一個(gè)新的名字便于管理和重用,而并不關(guān)注使用者到底是誰。

語文老師解讀版
很好理解,當(dāng)你想描述一個(gè)動作卻不知道或者不關(guān)注具體是誰做的,代詞就指向有的人。
比如臧克家同學(xué)在作文里寫的這樣:
有的人活著,但是他已經(jīng)死了;
有的人死了,但是他還活著;
上文中的指誰?指有的人;那有的人是誰?隨便,愛誰誰。

規(guī)則2——作為方法調(diào)用時(shí),this指向上下文對象

上文中我們看到函數(shù)的作用域鏈上是包含Object對象的,所以函數(shù)可以被當(dāng)做對象來理解。當(dāng)函數(shù)作為對象被賦值在另一個(gè)對象的屬性上時(shí),這個(gè)對象的屬性值里會保存函數(shù)的地址,因?yàn)橛煤瘮?shù)作為賦值運(yùn)算的右值時(shí)是一個(gè)引用類型賦值。如果這個(gè)函數(shù)正好又是一個(gè)匿名函數(shù),那么執(zhí)行時(shí)只能通過對象屬性中記錄的地址信息來找到這個(gè)函數(shù)在內(nèi)存中的位置,從而執(zhí)行它。所以當(dāng)函數(shù)作為方法調(diào)用時(shí),this中包含的信息的本質(zhì)是這個(gè)函數(shù)執(zhí)行時(shí)是怎么被找查找到的。答案就是:通過this所指向的這個(gè)對象的屬性找到的。

一般什么樣的函數(shù)我們會這樣定義呢?
作為方法定義的函數(shù),往往是另一個(gè)抽象合集的具體實(shí)現(xiàn)。比如前例的addNumber()這個(gè)方法,只是將兩個(gè)數(shù)字相加這樣一個(gè)抽象動作,至于是誰通過什么方式來執(zhí)行這個(gè)計(jì)算過程,無所謂,它可以概括所有對象將兩個(gè)數(shù)字相加并給出結(jié)果這一動作??扇绻鳛橐粋€(gè)對象方法來調(diào)用時(shí),就有了更明確的現(xiàn)實(shí)指向意義:

  • Computer.addNumber()表達(dá)了計(jì)算機(jī)通過軟硬件聯(lián)合作用而給出結(jié)果的過程
  • Calculator.addNumber()表達(dá)了計(jì)算器通過簡易硬件計(jì)算給出結(jié)果的過程
  • Abacus.addNumber()表達(dá)了算盤通過加減珠子的方式給出結(jié)果的過程
  • ...

語文老師解讀版
當(dāng)你想知道一個(gè)代詞具體指的是誰時(shí),當(dāng)然需要聯(lián)系上下文語境進(jìn)行理解。

規(guī)則3——作為構(gòu)造函數(shù)使用時(shí),this指向生成的實(shí)例

作為構(gòu)造函數(shù)使用,就是new + 構(gòu)造函數(shù)名的方式調(diào)用的情況。
js引擎在調(diào)用new操作符的邏輯可以用偽代碼表示為:

new Person('liLei') = {
    //生成一個(gè)新的空對象
    var obj = {}; 
    //空對象的原型鏈指向構(gòu)造函數(shù)的原型對象
    obj.__proto__ = Person.prototype; 
    //使用call方法執(zhí)行構(gòu)造函數(shù)并顯式指定上下文對象為新生成的obj對象
    var result = Person.call(obj,"liLei"); 
    // 如果構(gòu)造函數(shù)調(diào)用后返回一個(gè)對象,就return這個(gè)對象,否則return新生成的obj對象
    return typeof result === 'object'? result : obj;
}

暫不考慮構(gòu)造函數(shù)有返回值的情況,那么很容易就可以明白this為什么指向?qū)嵗?,因?yàn)轭惗x函數(shù)在執(zhí)行的時(shí)候顯式地綁定了this為新生成的對象,也就是調(diào)用new操作符后得到的實(shí)例對象。

語文老師解讀版
有些同學(xué)喜歡抄襲,抄襲這個(gè)動作可以描述為:"把一份作業(yè)Copy一遍,在最后寫上自己的名字。"。如果李雷是喜歡抄襲的人之一,那么他就掌握了"抄襲"這個(gè)方法,那你覺得他每次抄完作業(yè)后在署名的地方應(yīng)該寫自己的名字"李雷"還是寫這一類人的總稱"喜歡抄襲的人"呢?
抬杠的那個(gè)同學(xué),我記住你了!放學(xué)別走!

規(guī)則4——使用call/apply/bind方法顯式指定this

call/bind/apply這三個(gè)方法是javascript動態(tài)性的重要組成部分,后續(xù)的篇章會有詳細(xì)的講解。這里只看一下API用法,了解一下其對于this指向的影響:

  • func.call(this, arg1, arg2...)
  • func.apply(this, [arg1, arg2...])
  • func.bind(this[, arg1[, arg2[, ...]]])

這個(gè)規(guī)則很好理解,就是說函數(shù)執(zhí)行時(shí)遇到函數(shù)體里有this的語句都用顯式指定的對象來替換。

語文老師解讀版
就是直接告訴你下文中的代詞指什么,比如:×××憲法(以下簡稱"本法"),那讀者當(dāng)然就知道后面所說的"本法"指誰。

四. 基本規(guī)則示例

為了更清晰地看到上面兩條原則的區(qū)別,我們來看一個(gè)示例:

        var heroIdentity = '[Function Version]Iron Man';

        function checkIdentity(){
            return this.heroIdentity;
        } 

        var obj = {
            name:'Tony Stark',
            heroIdentity:'[Method Version]Iron Man',
            checkIdentityFromObj:checkIdentity
        }

        function TheAvenger(name){
            this.heroIdentity = name;
            this.checkIdentityFromNew = checkIdentity;
        }

        var tony = new TheAvenger('[New Verison]Iron Man');

        console.log('1.直接調(diào)用方法時(shí)結(jié)果為:',checkIdentity());
        console.log('2.通過obj.checkIdentityFromObj調(diào)用同一個(gè)方法結(jié)果為:',obj.checkIdentityFromObj());
        console.log('3.new操作符生成的對象:',tony.checkIdentityFromNew());
        console.log('4.call方法顯示修改this指向:',checkIdentity.call({heroIdentity:'[Call Version]Iron Man'}));

控制臺輸出的結(jié)果是這樣的:
javascript基礎(chǔ)修煉(2)——What's this(上)
同一個(gè)方法,同一個(gè)this,調(diào)用的方式不同,得到的結(jié)果也不同。

五. 后記

在基礎(chǔ)面前,一切技巧都是浮云。

如果認(rèn)為明白了this的基本規(guī)則就可以為所欲為,那你就真的too young too simple了。
了解了基本指向規(guī)則,只能讓你在開發(fā)中自己盡可能少挖坑或者不挖坑。但是想要填別人的坑或者讀懂大師級代碼中簡潔優(yōu)雅的用法,還需要更多的修煉和反思。實(shí)際應(yīng)用中許多復(fù)雜的使用場景是很難一下子搞明白this的指向以及為什么要指定this的指向的。
筆者將在《javascript基礎(chǔ)修煉(3)——What's this(下)》中詳細(xì)講述開發(fā)中千奇百怪的this。欲知后事如何,先點(diǎn)個(gè)贊先吧!

參考文章:
[1].js中的new()到底做了什么
[2].ECMA-262-3 in detail. Chapter 1. Execution Contexts


當(dāng)前題目:javascript基礎(chǔ)修煉(2)——What'sthis(上)
本文路徑:http://weahome.cn/article/gpjdcd.html

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部