函數(shù)進階
定義函數(shù)的方式
- 聲明函數(shù)
function xxx(){}這種方式是最直接的聲明方式,js執(zhí)行時這種方式會被預解析(聲明提升) - 函數(shù)表達式
var fn = function() {};這種方法是通過賦值,將函數(shù)賦值給變量,預解析只有變量和函數(shù)本身,賦值操作不會,所以這種方式函數(shù)不會被預解析 - 構(gòu)造函數(shù)方式
var fn = new Function('參數(shù)','參數(shù)','內(nèi)部代碼');這種定義方式不會用,執(zhí)行速度慢,只做了解 - 另外在新版瀏覽器中,
if語句中的函數(shù)聲明不會被預解析,但老版瀏覽器又會。所以如果想通過判斷條件來聲明函數(shù),又要顧及兼容性,可以用表達式的方式
偽變量this
-
this是函數(shù)內(nèi)部的偽變量,用于指向調(diào)用者。關于它的指向其實完全不會混淆,誰調(diào)用了該函數(shù),那么this就指向誰,記住這一點就行
函數(shù)對象的成員進階
- 前面已經(jīng)說過了函數(shù)就是一個對象,它有默認的屬性和方法。下面說下call、apply、bind的進階用法
-
.call(改變指向?qū)ο?,方法對應參?shù))這個方法的作用是調(diào)用函數(shù),并修改函數(shù)內(nèi)部this的指向- 進階用法舉例:比如一個偽數(shù)組(數(shù)組的形式,但沒有通過Array構(gòu)造函數(shù)創(chuàng)建,所有不能使用原型對象中的默認方法)。如果我們想要讓這個偽數(shù)組也使用數(shù)組的方法,那么可以
Array.prototype.push.call(偽數(shù)組);,這樣實際上是讓構(gòu)造函數(shù)調(diào)用方法,但是通過call來改變指向,達到目的。這只是一個用法,不是唯一
- 進階用法舉例:比如一個偽數(shù)組(數(shù)組的形式,但沒有通過Array構(gòu)造函數(shù)創(chuàng)建,所有不能使用原型對象中的默認方法)。如果我們想要讓這個偽數(shù)組也使用數(shù)組的方法,那么可以
-
.apply(改變指向?qū)ο?數(shù)組)這個方法的作用是調(diào)用函數(shù),并修改函數(shù)內(nèi)部this的指向,并且參數(shù)是數(shù)組的形式傳入,它會在自動拆分數(shù)組將值依次傳入- 進階用法舉例:
Math.max()這個方法可以返回一組數(shù)字中的最大值,但是它的參數(shù)只能是一組數(shù)字,不能是數(shù)組。這個時候就可以Math.max.apply(Math, 數(shù)組),通過apply方法自動拆分數(shù)組后,就能直接實現(xiàn)了。注意這里第一個參數(shù)因為不涉及改變指向,所以傳它本身調(diào)用者即可
- 進階用法舉例:
-
.bind(改變指向?qū)ο?,方法對應參?shù))這個方法的作用是修改函數(shù)內(nèi)部this的指向,并返回一個新的函數(shù),不會直接調(diào)用- 進階用法:它主要的應用場景是在函數(shù)不需要立刻調(diào)用的時候。比如函數(shù)作為一個參數(shù),或者作為一個賦值對象時。當時并不需要立即執(zhí)行函數(shù),就可以用它
-
aguments函數(shù)中默認有這個變量,也有這個屬性(不是同一個),但是作用都是相同。用來記錄傳入的實參,它本身是一個偽數(shù)組。在不確定傳入實參的情況下,可以用它直接獲取到,較常用需要掌握 -
caller屬性記錄該函數(shù)的調(diào)用者 -
name屬性記錄函數(shù)名 -
length屬性記錄形參個數(shù)
高階函數(shù)
- 當一個函數(shù)作為參數(shù)或者作為返回值的是,它就是高階函數(shù)
- 函數(shù)作為參數(shù)可以通過sort方法的參數(shù)(函數(shù))調(diào)整排序方式來理解
- 把函數(shù)作為返回值也是很常用的。舉例:很多場景下,我們需要一個函數(shù)來接受參數(shù),但是因為參數(shù)的動態(tài)變化,如果只有一個函數(shù),那么參數(shù)會被定死。所以我們可以返回一個函數(shù),讓返回的函數(shù)再接收值并執(zhí)行邏輯,這樣就是可以讓參數(shù)動態(tài)變化
- 具體可以看案例代碼理解
閉包
- 閉包是一個很抽象的概念,準確來說它是一種程序現(xiàn)象也是一種組合環(huán)境。在js中創(chuàng)建函數(shù)時,會自動生成一個作用域環(huán)境。其他部分語言中該作用域中的局部變量或函數(shù)會隨著作用域執(zhí)行完后銷毀而變得不可訪問,但在js中卻不是這樣
- 哪怕作用域銷毀,但是只要其他作用域訪問其中局部變量或函數(shù)(內(nèi)部函數(shù)或者本身),依然能夠訪問到
- 所以閉包也能理解了,它是由函數(shù)和創(chuàng)建該函數(shù)的詞法環(huán)境組合而成,它包含了該函數(shù)中所有能訪問的局部變量或函數(shù)(內(nèi)部函數(shù)或者本身),讓它們在函數(shù)執(zhí)行完銷毀后仍然可以被訪問
- 閉包的作用,簡單來說就是延展作用域,在某些情景下可以讓功能實現(xiàn)更方便。但不是所有場景都一定要用閉包
定時器工作原理
- js代碼在執(zhí)行時,是從上往下執(zhí)行的。這是因為執(zhí)行是,會把所有代碼放到執(zhí)行棧當中。但是定時器的參數(shù)函數(shù)并不會在執(zhí)行棧中處理,而是把函數(shù)放到任務隊里中進行排隊。當執(zhí)行棧中的代碼執(zhí)行完畢后,根據(jù)消息循環(huán)(就是觸發(fā)條件),執(zhí)行任務隊列中對應的函數(shù)
- 不光是定時器,事件觸發(fā)函數(shù)也是同樣的原理,也會把函數(shù)放到事件隊列中。當觸發(fā)事件后,會從事件隊列中取出對應函數(shù)并執(zhí)行
關于多次調(diào)用容易發(fā)生的理解誤區(qū)
- 假設一個對象
obj它有一個方法fun,fun中返回值是一個函數(shù) - 那么輸出
obj.fun()()時,返回函數(shù)中的this指向的誰呢?答案是window,而不是obj - 因為
obj.fun()這段代碼執(zhí)行時,this指向的是obj,執(zhí)行完畢后此時obj.fun()就是返回的一個函數(shù)。再次調(diào)用返回的函數(shù),注意返回值函數(shù)是沒有對象調(diào)用的,所以默認是由window來調(diào)用的
遞歸
- 遞歸實際上就是函數(shù)再內(nèi)部不停的調(diào)用自己,在使用遞歸時一般都會加上一個結(jié)束條件,不然遞歸就是一個死循環(huán),最終導致內(nèi)存不足報錯
- 具體執(zhí)行過程,可以通過斷點的方式查看分析
- 遞歸在使用時沒必要完全想通數(shù)學邏輯,實際上只需要明白要實現(xiàn)的功能該怎么寫,并套用對應公式即可
對象拷貝
- 淺拷貝
- 正常情況下如果想要拷貝一個對象,使用
for in遍歷對象并依次賦值給新對象即可 - 但是這樣會出現(xiàn)一個問題,如果拷貝的成員中,有一個屬性是對象。那么實際上拷貝的是該對象的引用地址,而不管哪個對象修改值,都會通過引用地址修改對象成員的值,那么就不是我們想要的結(jié)果了
- 這種拷貝也叫做淺拷貝
- 正常情況下如果想要拷貝一個對象,使用
- 深拷貝
- 深度拷貝就是將對象成員也進行拷貝,而不再是拷貝引用地址,這樣上面的問題也就解決了
- 具體深度拷貝實現(xiàn)代碼查看案例,也是遞歸的一種應用場景