本文基于: Bilibili - 自由的加百利
成都創(chuàng)新互聯(lián)公司從2013年創(chuàng)立,先為太平等服務(wù)建站,太平等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為太平企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
來看一下一個(gè)函數(shù)的基本屬性:
我們可以將一個(gè)普通函數(shù)去掉它的名字,這樣就成功的創(chuàng)建了一個(gè)匿名函數(shù),并且編譯器不會(huì)報(bào)錯(cuò)。
那么這個(gè)函數(shù)既然沒有名字,我們又該怎么調(diào)用它呢?這時(shí)只需要使用一個(gè)小括號包裹住整個(gè)函數(shù),再在函數(shù)體的末尾添加一個(gè)小括號就可以在創(chuàng)建函數(shù)之后立即執(zhí)行這個(gè)函數(shù)。
這種寫法,也叫作 匿名函數(shù)的自運(yùn)行
其與直接在外部書寫函數(shù)體內(nèi)部的語句相比,優(yōu)點(diǎn)就是不會(huì)造成變量污染,會(huì)在匿名函數(shù)內(nèi)形成一個(gè) 封閉的作用域
在匿名函數(shù)的外部加上一個(gè)小括號,實(shí)際的作用是 將該函數(shù)的聲明變成了一個(gè)優(yōu)先計(jì)算的表達(dá)式
( function(){...} )()
而表達(dá)式的運(yùn)算結(jié)果就是這個(gè) 匿名函數(shù) 本身。拿到了函數(shù)本身之后,就可以在其后面加上一個(gè)小括號來調(diào)用它了。
既然小括號的作用是將函數(shù)的聲明變成表達(dá)式,那么在函數(shù)周圍加上運(yùn)算符會(huì)不會(huì)有同樣的效果呢?
+function(){...}()
!function(){...}()
~function(){...}()
void function(){...}()
delete function(){...}()
以上的幾種寫法都可以成功執(zhí)行匿名函數(shù),而且使用 +function(){...}()
這種方式執(zhí)行函數(shù)自運(yùn)行的效率是最高的。
遞歸函數(shù) 是指一個(gè)函數(shù)直接或間接的調(diào)用自身,并在特定的情況下結(jié)束并放回運(yùn)行結(jié)果
這里我們舉一個(gè) 階乘 的例子:
function F(N) {
return N * F(N - 1);
}
表面看上去,這個(gè)函數(shù)可以接收一個(gè)參數(shù),并計(jì)算出這個(gè)數(shù)的階乘。但是仔細(xì)想想就會(huì)發(fā)現(xiàn)不對勁,當(dāng) N = 1
時(shí)函數(shù)并沒有停止自身的繼續(xù)傳遞,也就是說這個(gè)函數(shù)沒有停止條件,最終便會(huì)陷入一個(gè)死循環(huán)。結(jié)果就是 會(huì)在某一時(shí)刻,大量的函數(shù)將內(nèi)存空間占滿導(dǎo)致內(nèi)存溢出。
也就是說我們上面寫的這個(gè)函數(shù),只有 遞 沒有 歸
我們嘗試改變一下上面的 遞歸函數(shù)
首先要弄清楚,我們需要計(jì)算的是一個(gè)數(shù) 它的階乘是多少。計(jì)算一個(gè)數(shù)字的階乘便是讓這個(gè)數(shù)每次乘以比他自身小 1 的數(shù),直到乘到1。(說得不是很清楚,大家自行理解)
那么關(guān)鍵點(diǎn)就在于這個(gè) 直到
我們不能讓它無止境的傳遞下去,在上面的例子中,參與遞歸的 N
為 1 時(shí)還在繼續(xù)向內(nèi)傳遞,0, -1, -2, -3...
我們所要做的就是當(dāng)函數(shù)傳遞到 N = 1
時(shí)停止向內(nèi)傳遞,直接返回 1 自身,將其自己交給外部的函數(shù)來調(diào)用,代碼更改如下:
function F(N) {
if (N == 1) return 1;
return N * F(N - 1);
}
上面 if
語句的作用是:當(dāng) N 為 1 時(shí),直接返回 1
這時(shí)運(yùn)行一下就會(huì)發(fā)現(xiàn),函數(shù)不報(bào)錯(cuò)了,而且也得到了我們想要的結(jié)果。
回調(diào)函數(shù),并不是指一種特殊的函數(shù),而是指函數(shù)的使用方式
看一下下面的代碼:
function f1(){
console.log(111);
}
function f2(){
console.log(222);
}
f1();
f2();
輸出結(jié)果的順序自然是先輸出 111,再輸出 222
但是如果我們給 f1()
添加一個(gè)定時(shí)器呢?
function f1(){
setTimeout(function(){
console.log(111);
}, 1000)
}
function f2(){
console.log(222);
}
f1();
f2();
這時(shí)便會(huì)先輸出 222,一秒后輸出 111。這種含有異步操作的函數(shù)就被稱為 異步函數(shù) ,異步函數(shù)最大的特點(diǎn)就是 后續(xù)的代碼不需要排隊(duì),異步函數(shù)時(shí)可以和后續(xù)的代碼并行的。f1()
就是一個(gè)典型的異步函數(shù),你無法知道 f1()
和 f2()
哪一個(gè)會(huì)先結(jié)束。
那么在有異步函數(shù)的情況下,如果我希望先輸出 111,再輸出222,要怎么做呢?
目前看來,唯一的辦法是 把函數(shù) f2()
放在 f1()
的內(nèi)部調(diào)用
function f1(){
setTimeout(function(){
console.log(111);
f2();
}, 1000)
}
function f2(){
console.log(222);
}
f1();
假設(shè)有這樣一個(gè)場景,項(xiàng)目組里有小白、小黃、小綠三個(gè)人,有一個(gè)工具函數(shù) getToken()
function getToken(){
//異步函數(shù)......
}
它是一個(gè)異步函數(shù),大家都在使用這個(gè)函數(shù)完成自己的業(yè)務(wù),并且每個(gè)人都希望在 getToken()
結(jié)束后執(zhí)行自己的代碼,于是它們將函數(shù)寫成了下面這樣:
但是這種寫法顯然是錯(cuò)誤的,因?yàn)楫惒胶瘮?shù)保證不了函數(shù)的執(zhí)行順序。那么現(xiàn)在只能想辦法將自己所寫的函數(shù)放在異步函數(shù)內(nèi)部,才有機(jī)會(huì)在其后面執(zhí)行。
首先,我們給 getToken()
函數(shù)增加一個(gè)參數(shù) callback
function getToken(callback){
//異步函數(shù)......
}
之后,三個(gè)人的代碼就可以改成這樣:
把自己的函數(shù)傳進(jìn)去,最后在 getToken()
的最后調(diào)用這個(gè) callback
function getToken(callback){
//異步函數(shù)......
callback();
}
現(xiàn)在,所有人的代碼都會(huì)在異步函數(shù)最后執(zhí)行,這極大的提高了代碼的可復(fù)用性,降低了開發(fā)維護(hù)的成本。
這種函數(shù)調(diào)用的方式就叫回調(diào)
字面意思就是:把自己的函數(shù)交給別人,回頭再調(diào)。
一個(gè)函數(shù)除了可以被當(dāng)作函數(shù),還可以被當(dāng)作
class
function fn(){
}
let obj = new fn();
console.log( typeof obj );
我們可以直接使用 new
關(guān)鍵字來聲明一個(gè)對象,這個(gè)時(shí)候,我們就說 fn()
是一個(gè)構(gòu)造函數(shù)
那么 fn()
明明是一個(gè)空函數(shù),這個(gè)對象是怎么來的呢?
問題的關(guān)鍵就在于這個(gè) new
關(guān)鍵字。當(dāng)你調(diào)用函數(shù)時(shí)在前面加上了 new
關(guān)鍵字,瀏覽器就會(huì)啟動(dòng) 構(gòu)造函數(shù) 的執(zhí)行流程:
function fn(){
this = {}
// 創(chuàng)建一個(gè)空對象,將其保存在this關(guān)鍵字中
...... //your code
return this;
}
let obj = new fn();
當(dāng)然了,上面部分代碼是不可見的。一個(gè)函數(shù)到底是普通函數(shù)還是構(gòu)造函數(shù),取決于你來怎么使用它。
但是通常,按照習(xí)慣,我們會(huì)將構(gòu)造函數(shù)的首字母大寫,普通函數(shù)的首字母小寫。也就是說,如果你看到一個(gè)函數(shù)的首字母是大寫的,在絕大多數(shù)的時(shí)候,它不應(yīng)該被直接調(diào)用。
function User() {
......
}
let user = User(); ×
let user = new User(); √
在最新版的 JavaScript
已經(jīng)支持了 class
關(guān)鍵字,你可以像 Java
一樣定義一個(gè)類,并通過構(gòu)造方法來生成對象。
function a(){
let x = 1;
function b(){
console.log(x);
}
}
函數(shù) b()
是一個(gè)定義在函數(shù) a()
內(nèi)部的函數(shù),所以其可以訪問到變量 x
,變量 x
相對于函數(shù) b()
來說就是一個(gè)全局變量。
如果我們把函數(shù) b()
作為函數(shù) a()
的返回值:
function a(){
let x = 1;
return function b(){
console.log(x);
}
}
let c = a();
c();
我們已知,函數(shù) c()
就是函數(shù) b()
,有由于函數(shù) c()
是全局變量,因此,相當(dāng)于在全局范圍調(diào)用了函數(shù) b()
,打破了函數(shù) b()
只能在局部使用的限制,最終我們打印出了變量 x
在這里,函數(shù) a()
所形成的作用域,叫做 閉包,函數(shù) b()
被稱作 閉包函數(shù)
這一節(jié)來源于知乎:https://zhuanlan.zhihu.com/p/#:~:text=函數(shù)柯里化,就是,后,才執(zhí)行原函數(shù)
function add(a, b) {
return a + b
}
function curry(fn) {
return function (a) {
return function (b) {
return fn(a, b)
}
}
}
let fn = curry(add)(1)(2)