函數:菜鳥收割者
綜觀JavaScript中所有必須掌握的重點知識中,函數是我們在初學的時候最容易忽略的一個知識點。在學習的過程中,可能會有很多人、很多文章告訴你物件導向很重要,原型很重要,可是卻很少有人告訴你,物件導向中所有的重點難點,幾乎都與函數息息相關。
包括我之前幾篇文章介紹的執行上下文,變數對象,閉包,this等,都是圍繞函數來展開。
我知道很多人在學習中,很急切的希望自己快一點開始學習面向對象,學習模組,學習流行框架,然後迅速成為高手。但是我可以很負責的告訴你,關於函數的這些基礎東西沒理解到一定程度,那麼你的學習進度一定是舉步維艱的。
所以,大家一定要重視函數!
關於函數在實際開發中的應用,大體可以總結為函數宣告、函數表達式、匿名函數、自執行函數。
函數宣告
我們知道,JavaScript中,有兩種宣告方式,一個是使用var
的變數聲明,另一個是使用function
的函數宣告。
在前端基礎進階(三):變數物件詳解中我有提到過,在變數物件的建立過程中,函數宣告比變數宣告有更優先的執行順序,也就是我們常常提到的函數宣告提前。因此我們在執行上下文中,無論在什麼位置聲明了函數,我們都可以在同一個執行上下文中直接使用該函數。
1 2 3 4 5 |
|
函數表達式
與函數宣告不同,函數表達式使用了var進行聲明,那麼我們在確認他是否可以正確使用的時候就必須依照var的規則進行判斷,即變數宣告。我們知道使用var進行變數聲明,其實是進行了兩步驟操作。
1 2 3 4 5 6 |
|
同樣的道理,當我們使用變數宣告的方式來宣告函數###時,就是我們常常說的函數表達式。函數表達的提升方式與變數宣告一致。
1 2 3 4 |
|
1 2 3 4 5 |
|
因此,由於宣告方式的不同,導致了函數宣告與函數表達式在使用上的一些差異需要我們注意,除此之外,這兩種形式的函數在使用上並無不同。關於上面例子中,函數表達式中的賦值操作,在其他一些地方也會被經常使用,我們清楚其中的關係即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
匿名函數
在上面我們大概講述了函數表達式中的賦值運算。而匿名函數,顧名思義,就是指的沒有被顯示進行賦值運算的函數。它的使用場景,多作為一個參數傳入另一個函數。1 2 3 4 5 6 7 8 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
#回呼函數#。關於匿名函數更多的內容,我會在下一篇深入探討柯里化的文章中進行更加詳細講解。
匿名函數的這個應用場景幾乎承擔了函數的所有難以理解的知識點,因此我們一定要對它的這些細節了解的足夠清楚,如果對於變量對象的演變過程你還看不太明白,一定要回過頭去看這篇文章:前端基礎進階(三):變數物件詳解
函數自執行與區塊級作用域
在ES5中,沒有區塊級作用域,因此我們常常使用函數自執行的方式來模仿區塊級作用域,這樣就提供了一個獨立的執行上下文,結合閉包,就為模組化提供了基礎。
1 2 3 |
|
一个模块往往可以包括:私有变量、私有方法、公有变量、公有方法。
根据作用域链的单向访问,外面可能很容易知道在这个独立的模块中,外部执行环境是无法访问内部的任何变量与方法的,因此我们可以很容易的创建属于这个模块的私有变量与私有方法。
1 2 3 4 5 6 7 8 9 10 |
|
但是共有方法和变量应该怎么办?大家还记得我们前面讲到过的闭包的特性吗?没错,利用闭包,我们可以访问到执行上下文内部的变量和方法,因此,我们只需要根据闭包的定义,创建一个闭包,将你认为需要公开的变量和方法开放出来即可。
如果你对闭包了解不够,前端基础进阶(四):详细图解作用域链与闭包应该可以帮到你。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
当然,闭包在模块中的重要作用,我们也在讲解闭包的时候已经强调过,但是这个知识点真的太重要,需要我们反复理解并且彻底掌握,因此为了帮助大家进一步理解闭包,我们来看看jQuery中,是如何利用我们模块与闭包的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
在这里,我们只需要看懂闭包与模块的部分就行了,至于内部的原型链是如何绕的,为什么会这样写,我在讲面向对象的时候会为大家慢慢分析。举这个例子的目的所在,就是希望大家能够重视函数,因为在实际开发中,它无处不在。
接下来我要分享一个高级的,非常有用的模块的应用。当我们的项目越来越大,那么需要保存的数据与状态就越来越多,因此,我们需要一个专门的模块来维护这些数据,这个时候,有一个叫做状态管理器的东西就应运而生。对于状态管理器,最出名的,我想非redux莫属了。虽然对于还在学习中的大家来说,redux是一个有点高深莫测的东西,但是在我们学习之前,可以先通过简单的方式,让大家大致了解状态管理器的实现原理,为我们未来的学习奠定坚实的基础。
先来直接看代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
|
demo实例在线地址
我之所以说这是一个高级应用,是因为在单页应用中,我们很可能会用到这样的思路。根据我们提到过的知识,理解这个例子其实很简单,其中的难点估计就在于set方法的处理上,因为为了具有更多的适用性,因此做了很多适配,用到了递归等知识。如果你暂时看不懂,没有关系,知道如何使用就行了,上面的代码可以直接运用于实际开发。记住,当你需要保存的状态太多的时候,你就想到这一段代码就行了。
函数自执行的方式另外还有其他几种写法,诸如
!function(){}()
,+function(){}()
还记得基本数据类型与引用数据类型在复制上的差异吗?基本数据类型复制,是直接值发生了复制,因此改变后,各自相互不影响。但是引用数据类型的复制,是保存在变量对象中的引用发生了复制,因此复制之后的这两个引用实际访问的实际是同一个堆内存中的值。当改变其中一个时,另外一个自然也被改变。如下例。
1 2 3 4 5 6 7 8 9 |
|
当值作为函数的参数传递进入函数内部时,也有同样的差异。我们知道,函数的参数在进入函数后,实际是被保存在了函数的变量对象中,因此,这个时候相当于发生了一次复制。如下例。
1 2 3 4 5 6 7 8 |
|
1 2 3 4 5 6 7 8 |
|
正是由于这样的不同,导致了许多人在理解函数参数的传递方式时,就有许多困惑。到底是按值传递还是按引用传递?实际上结论仍然是按值传递,只不过当我们期望传递一个引用类型时,真正传递的,只是这个引用类型保存在变量对象中的引用而已。为了说明这个问题,我们看看下面这个例子。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
在上面的例子中,如果person是按引用传递,那么person就会自动被修改为指向其name属性值为Gerg的新对象。但是我们从结果中看到,person对象并未发生任何改变,因此只是在函数内部引用被修改而已。
虽然JavaScript并不是一门纯函数式编程的语言,但是它使用了许多函数式编程的特性。因此了解这些特性可以让我们更加了解自己写的代码。
函数是第一等公民
所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。这些场景,我们应该见过很多。
1 2 3 4 5 6 7 8 9 |
|
只用"表达式",不用"语句"
"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
了解这一点,可以让我们自己在封装函数的时候养成良好的习惯。借助这个特性,我们在学习其他API的时候,了解函数的返回值也是一个十分重要的习惯。
没有"副作用"
所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
即所谓的只要是同样的参数传入,返回的结果一定是相等的。
闭包
闭包是函数式编程语言的重要特性,我也在前面几篇文章中说了很多关于闭包的内容。这里不再赘述。
柯里化
理解柯里化稍微有点难,我在下一篇文章里专门单独来深入分析。
在我们自己封装函数时,最好尽量根据函数式编程的特点来编写。当然在许多情况下并不能完全做到,比如函数中我们常常会利用模块中的私有变量等。
普通封装
1 2 3 4 5 |
|
挂载在对象上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
修改数组对象的例子,常在面试中被问到类似的,但是并不建议在实际开发中扩展原生对象。与普通封装不一样的是,因为挂载在对象的原型上我们可以通过this来访问对象的属性和方法,所以这种封装在实际使用时会有许多的难点,因此我们一定要掌握好this。
以上是前端進階(七):函數與函數式編程的詳細內容。更多資訊請關注PHP中文網其他相關文章!