摘要:第四點也要著重講下,記住構(gòu)造函數(shù)被操作,要讓正常作用最好不能在構(gòu)造函數(shù)里
4) this、new、call和apply的相關(guān)問題
講解this指針的原理是個很復(fù)雜的問題,如果我們從javascript里this的實現(xiàn)機(jī)制來說明this,很多朋友可能會越來越糊涂,因此本篇打算換一個思路從應(yīng)用的角度來講解this指針,從這個角度理解this指針更加有現(xiàn)實意義。
下面我們看看在java語言里是如何使用this指針的,代碼如下:
public class Person { private String name; private String sex; private int age; private String job; public Person(String name, String sex, int age, String job) { super(); this.name = name; this.sex = sex; this.age = age; this.job = job; } private void showPerson(){ System.out.println("姓名:" + this.name); System.out.println("性別:" + this.sex); System.out.println("年齡:" + this.age); System.out.println("工作:" + this.job); } public void printInfo(){ this.showPerson(); } public static void main(String[] args) { Person person = new Person("馬云", "男", 46, "董事長"); person.printInfo(); } } //姓名:馬云 //性別:男 //年齡:46 //工作:董事長
上面的代碼執(zhí)行后沒有任何問題,下面我修改下這個代碼,加一個靜態(tài)的方法,靜態(tài)方法里使用this指針調(diào)用類里的屬性,如下圖所示:
我們發(fā)現(xiàn)IDE會報出語法錯誤“Cannot use this in a static context”,this指針在java語言里是不能使用在靜態(tài)的上下文里的。
在面向?qū)ο缶幊汤镉袃蓚€重要的概念:一個是類,一個是實例化的對象,類是一個抽象的概念,用個形象的比喻表述的話,類就像一個模具,而實例化對象就是通過這個模具制造出來的產(chǎn)品,實例化對象才是我們需要的實實在在的東西,類和實例化對象有著很密切的關(guān)系,但是在使用上類的功能是絕對不能取代實例化對象,就像模具和模具制造的產(chǎn)品的關(guān)系,二者的用途是不相同的。
有上面代碼我們可以看到,this指針在java語言里只能在實例化對象里使用,this指針等于這個被實例化好的對象,而this后面加上點操作符,點操作符后面的東西就是this所擁有的東西,例如:姓名,工作,手,腳等等。
其實javascript里的this指針邏輯上的概念也是實例化對象,這一點和java語言里的this指針是一致的,但是javascript里的this指針卻比java里的this難以理解的多,究其根本原因我個人覺得有三個原因:
原因一:javascript是一個函數(shù)編程語言,怪就怪在它也有this指針,說明這個函數(shù)編程語言也是面向?qū)ο蟮恼Z言,說的具體點,javascript里的函數(shù)是一個高階函數(shù),編程語言里的高階函數(shù)是可以作為對象傳遞的,同時javascript里的函數(shù)還有可以作為構(gòu)造函數(shù),這個構(gòu)造函數(shù)可以創(chuàng)建實例化對象,結(jié)果導(dǎo)致方法執(zhí)行時候this指針的指向會不斷發(fā)生變化,很難控制。
原因二:javascript里的全局作用域?qū)his指針有很大的影響,由上面java的例子我們看到,this指針只有在使用new操作符后才會生效,但是javascript里的this在沒有進(jìn)行new操作也會生效,這時候this往往會指向全局對象window。
原因三:javascript里call和apply操作符可以隨意改變this指向,這看起來很靈活,但是這種不合常理的做法破壞了我們理解this指針的本意,同時也讓寫代碼時候很難理解this的真正指向
上面的三個原因都違反了傳統(tǒng)this指針使用的方法,它們都擁有有別于傳統(tǒng)this原理的理解思路,而在實際開發(fā)里三個原因又往往會交織在一起,這就更加讓人迷惑不解了,今天我要為大家理清這個思路,其實javascript里的this指針有一套固有的邏輯,我們理解好這套邏輯就能準(zhǔn)確的掌握好this指針的使用。
我們先看看下面的代碼:
在script標(biāo)簽里我們可以直接使用this指針,this指針就是window對象,我們看到即使使用三等號它們也是相等的。全局作用域常常會干擾我們很好的理解javascript語言的特性,這種干擾的本質(zhì)就是:
在javascript語言里全局作用域可以理解為window對象,記住window是對象而不是類,也就是說window是被實例化的對象,這個實例化的過程是在頁面加載時候由javascript引擎完成的,整個頁面里的要素都被濃縮到這個window對象,因為程序員無法通過編程語言來控制和操作這個實例化過程,所以開發(fā)時候我們就沒有構(gòu)建這個this指針的感覺,常常會忽視它,這就是干擾我們在代碼里理解this指針指向window的情形。
干擾的本質(zhì)還和function的使用有關(guān),我們看看下面的代碼:
上面是我們經(jīng)常使用的兩種定義函數(shù)的方式,第一種定義函數(shù)的方式在javascript語言稱作聲明函數(shù),第二種定義函數(shù)的方式叫做函數(shù)表達(dá)式,這兩種方式我們通常認(rèn)為是等價的,但是它們其實是有區(qū)別的,而這個區(qū)別常常會讓我們混淆this指針的使用,我們再看看下面的代碼:
這又是一段沒有按順序執(zhí)行的代碼,先看看ftn02,打印結(jié)果是undefined,undefined我在前文里講到了,在內(nèi)存的棧區(qū)已經(jīng)有了變量的名稱,但是沒有棧區(qū)的變量值,同時堆區(qū)是沒有具體的對象,這是javascript引擎在預(yù)處理(群里東方說預(yù)處理比預(yù)加載更準(zhǔn)確,我同意他的說法,以后文章里我都寫為預(yù)處理)掃描變量定義所致,但是ftn01的打印結(jié)果很令人意外,既然打印出完成的函數(shù)定義了,而且代碼并沒有按順序執(zhí)行,這只能說明一個問題:
在javascript語言通過聲明函數(shù)方式定義函數(shù),javascript引擎在預(yù)處理過程里就把函數(shù)定義和賦值操作都完成了,在這里我補(bǔ)充下javascript里預(yù)處理的特性,其實預(yù)處理是和執(zhí)行環(huán)境相關(guān),在上篇文章里我講到執(zhí)行環(huán)境有兩大類:全局執(zhí)行環(huán)境和局部執(zhí)行環(huán)境,執(zhí)行環(huán)境是通過上下文變量體現(xiàn)的,其實這個過程都是在函數(shù)執(zhí)行前完成,預(yù)處理就是構(gòu)造執(zhí)行環(huán)境的另一個說法,總而言之預(yù)處理和構(gòu)造執(zhí)行環(huán)境的主要目的就是明確變量定義,分清變量的邊界,但是在全局作用域構(gòu)造或者說全局變量預(yù)處理時候?qū)τ诼暶骱瘮?shù)有些不同,聲明函數(shù)會將變量定義和賦值操作同時完成,因此我們看到上面代碼的運行結(jié)果。由于聲明函數(shù)都會在全局作用域構(gòu)造時候完成,因此聲明函數(shù)都是window對象的屬性,這就說明為什么我們不管在哪里聲明函數(shù),聲明函數(shù)最終都是屬于window對象的原因了。
關(guān)于函數(shù)表達(dá)式的寫法還有秘密可以探尋,我們看下面的代碼:
運行結(jié)果我們發(fā)現(xiàn)ftn04雖然在ftn03作用域下,但是執(zhí)行它里面的this指針也是指向window,其實函數(shù)表達(dá)式的寫法我們大多數(shù)更喜歡在函數(shù)內(nèi)部寫,因為聲明函數(shù)里的this指向window這已經(jīng)不是秘密,但是函數(shù)表達(dá)式的this指針指向window卻是常常被我們所忽視,特別是當(dāng)它被寫在另一個函數(shù)內(nèi)部時候更加如此。
其實在javascript語言里任何匿名函數(shù)都是屬于window對象,它們也都是在全局作用域構(gòu)造時候完成定義和賦值,但是匿名函數(shù)是沒有名字的函數(shù)變量,但是在定義匿名函數(shù)時候它會返回自己的內(nèi)存地址,如果此時有個變量接收了這個內(nèi)存地址,那么匿名函數(shù)就能在程序里被使用了,因為匿名函數(shù)也是在全局執(zhí)行環(huán)境構(gòu)造時候定義和賦值,所以匿名函數(shù)的this指向也是window對象,所以上面代碼執(zhí)行時候ftn04的this也是指向window,因為javascript變量名稱不管在那個作用域有效,堆區(qū)的存儲的函數(shù)都是在全局執(zhí)行環(huán)境時候就被固定下來了,變量的名字只是一個指代而已。
這下子壞了,this都指向window,那我們到底怎么才能改變它了?
在本文開頭我說出了this的秘密,this都是指向?qū)嵗瘜ο?,前面講到那么多情況this都指向window,就是因為這些時候只做了一次實例化操作,而這個實例化都是在實例化window對象,所以this都是指向window。我們要把this從window變成別的對象,就得要讓function被實例化,那如何讓javascript的function實例化呢?答案就是使用new操作符。我們看看下面的代碼:
這是我上篇講到的關(guān)于this使用的一個例子,寫法一是我們大伙都愛寫的一種寫法,里面的this指針不是指向window的,而是指向Object的實例,firebug的顯示讓很多人疑惑,其實Object就是面向?qū)ο蟮念悾罄ㄌ柪锞褪菍嵗龑ο罅?,即obj和otherObj。Javascript里通過字面量方式定義對象的方式是new Object的簡寫,二者是等價的,目的是為了減少代碼的書寫量,可見即使不用new操作字面量定義法本質(zhì)也是new操作符,所以通過new改變this指針的確是不過攻破的真理。
下面我使用javascript來重寫本篇開頭用java定義的類,代碼如下:
看this指針的打印,類變成了Person,這表明function Person就是相當(dāng)于在定義一個類,在javascript里function的意義實在太多,function既是函數(shù)又可以表示對象,function是函數(shù)時候還能當(dāng)做構(gòu)造函數(shù),javascript的構(gòu)造函數(shù)我常認(rèn)為是把類和構(gòu)造函數(shù)合二為一,當(dāng)然在javascript語言規(guī)范里是沒有類的概念,但是我這種理解可以作為構(gòu)造函數(shù)和普通函數(shù)的一個區(qū)別,這樣理解起來會更加容易些。
下面我貼出在《javascript高級編程》里對new操作符的解釋:
new操作符會讓構(gòu)造函數(shù)產(chǎn)生如下變化:
1.創(chuàng)建一個新對象;
2.將構(gòu)造函數(shù)的作用域賦給新對象(因此this就指向了這個新對象);
3.執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性);
4.返回新對象
關(guān)于第二點其實很容易讓人迷惑,例如前面例子里的obj和otherObj,obj.show(),里面this指向obj,我以前文章講到一個簡單識別this方式就是看方法調(diào)用前的對象是哪個this就指向哪個,其實這個過程還可以這么理解,在全局執(zhí)行環(huán)境里window就是上下文對象,那么在obj里局部作用域通過obj來代表了,這個window的理解是一致的。
第四點也要著重講下,記住構(gòu)造函數(shù)被new操作,要讓new正常作用最好不能在構(gòu)造函數(shù)里寫return,沒有return的構(gòu)造函數(shù)都是按上面四點執(zhí)行,有了return情況就復(fù)雜了,這個知識我會在講prototype時候講到。
Javascript還有一種方式可以改變this指針,這就是call方法和apply方法,call和apply方法的作用相同,就是參數(shù)不同,call和apply的第一個參數(shù)都是一樣的,但是后面參數(shù)不同,apply第二個參數(shù)是個數(shù)組,call從第二個參數(shù)開始后面有許多參數(shù)。Call和apply的作用是什么,這個很重要,重點描述如下:
Call和apply是改變函數(shù)的作用域(有些書里叫做改變函數(shù)的上下文)
這個說明我們參見上面new操作符第二條:
將構(gòu)造函數(shù)的作用域賦給新對象(因此this就指向了這個新對象);
Call和apply是將this指針指向方法的第一個參數(shù)。
我們看看下面的代碼:
我們看到apply和call改變的是this的指向,這點在開發(fā)里很重要,開發(fā)里我們常常被this所迷惑,迷惑的根本原因我在上文講到了,這里我講講表面的原因:
表面原因就是我們定義對象使用對象的字面表示法,字面表示法在簡單的表示里我們很容易知道this指向?qū)ο蟊旧恚沁@個對象會有方法,方法的參數(shù)可能會是函數(shù),而這個函數(shù)的定義里也可能會使用this指針,如果傳入的函數(shù)沒有被實例化過和被實例化過,this的指向是不同,有時我們還想在傳入函數(shù)里通過this指向外部函數(shù)或者指向被定義對象本身,這些亂七八糟的情況使用交織在一起導(dǎo)致this變得很復(fù)雜,結(jié)果就變得糊里糊涂。
其實理清上面情況也是有跡可循的,就以定義對象里的方法里傳入函數(shù)為例:
情形一:傳入的參數(shù)是函數(shù)的別名,那么函數(shù)的this就是指向window;
情形二:傳入的參數(shù)是被new過的構(gòu)造函數(shù),那么this就是指向?qū)嵗膶ο蟊旧恚?/p>
情形三:如果我們想把被傳入的函數(shù)對象里this的指針指向外部字面量定義的對象,那么我們就是用apply和call
我們可以通過代碼看出我的結(jié)論,代碼如下:
結(jié)果如下:
最后再總結(jié)一下:
如果在javascript語言里沒有通過new(包括對象字面量定義)、call和apply改變函數(shù)的this指針,函數(shù)的this指針都是指向window的。
原文出處:javascript技術(shù)難點(三)之this、new、apply和call詳解
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/85551.html
摘要:自我學(xué)習(xí)目前有成千上萬的年輕人在學(xué)習(xí)和開發(fā),希望獲得一份工作。知道的綁定規(guī)則。知道和原型屬性是什么以及它們的作用。高階函數(shù)了解函數(shù)是中的一級對象,這意味著什么知道從另一個函數(shù)返回函數(shù)是完全合法的。了解閉包和高階函數(shù)允許我們使用的情況。 翻譯原文出處:10 JavaScript concepts you need to know for interviews 之前不是鬧得沸沸揚(yáng)揚(yáng)的大漠窮...
摘要:使用指定的參數(shù)調(diào)用構(gòu)造函數(shù),并將綁定到新創(chuàng)建的對象。由構(gòu)造函數(shù)返回的對象就是表達(dá)式的結(jié)果。情況返回以外的基本類型實例中只能訪問到構(gòu)造函數(shù)中的屬性,和情況完全相反,結(jié)果相當(dāng)于沒有返回值。 定義 new 運算符創(chuàng)建一個用戶定義的對象類型的實例或具有構(gòu)造函數(shù)的內(nèi)置對象的實例。 ——(來自于MDN) 舉個栗子 function Car(color) { this.color = co...
摘要:之前文章詳細(xì)介紹了的使用,不了解的查看進(jìn)階期。不同的引擎有不同的限制,核心限制在,有些引擎會拋出異常,有些不拋出異常但丟失多余參數(shù)。存儲的對象能動態(tài)增多和減少,并且可以存儲任何值。這邊采用方法來實現(xiàn),拼成一個函數(shù)。 之前文章詳細(xì)介紹了 this 的使用,不了解的查看【進(jìn)階3-1期】。 call() 和 apply() call() 方法調(diào)用一個函數(shù), 其具有一個指定的 this 值和分...
摘要:寫在前面深入系列共計篇已經(jīng)正式完結(jié),這是一個旨在幫助大家,其實也是幫助自己捋順底層知識的系列。深入系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 深入系列共計 15 篇已經(jīng)正式完結(jié),這是一個旨在幫助大家,其實也是幫助自己捋順 JavaScript 底層知識的系列。重點講解了如原型、作用域、執(zhí)行上下文、變量對象、this、...
閱讀 1533·2023-04-25 16:31
閱讀 2113·2021-11-24 10:33
閱讀 2806·2021-09-23 11:33
閱讀 2620·2021-09-23 11:31
閱讀 3025·2021-09-08 09:45
閱讀 2413·2021-09-06 15:02
閱讀 2730·2019-08-30 14:21
閱讀 2385·2019-08-30 12:56