函數是javascript中非常重要的一部分,用途也非常的多,可作為參數、返回值、回調等等,下面有一些函數式編程的重要概念和定義
純函數
純函數屬於程序設計的名詞,其它語言中也是存在的,而在javascript中,符合以下規則即為純函數。
- 函數有相同的輸入,必定有相同的輸出
- 函數的輸出僅與輸入有關,與其他隱藏信息無關
- 不得產生任何的副作用,如 觸發事件等
副作用:除了返回函數值以外,還對調用函數產生了其他的影響,如修改全局變量、修改參數或者改變外部存儲。
以下幾個示例來區分一下純函數和非純函數
1、數組方法
var arr1 = [1, 2, 3, 4]
var arr2 = [10, 20, 30, 40]
const newArr1 = arr1.splice(1, 3)
const newArr2 = arr2.slice(1, 3)
console.log(arr1, newArr1) // [1] [2, 3, 4]
console.log(arr2, newArr2) // [10, 20, 30, 40] [20, 30]
splice 通過下標值和長度操作原數組本身,修改了入參,所以不是純函數。
slice 通過下標值截取數組,返回一個新的數組,沒有副作用,是純函數。
2、修改入參
var obj = { name: 'alice' }
function foo(info) {
info.name = 'kiki'
return info
}
foo(obj)
function bar(info) {
return {
...info,
name: 'kiki'
}
}
bar(obj)
上面兩個函數的作用都是返回一個新的對象,但foo修改了入參,所以foo不是純函數,bar沒有產生任何副作用,滿足純函數的定義。
使用純函數在開發當中具有一些優點
- 不用考慮傳入的參數是否已經發生了變化
- 不用考慮函數的操作是否會修改全局、外部的一些變量
這樣函數的功能就更加的單一和純凈,可以放心的編寫和使用
柯里化
通過函數返回函數的方式,實現多次接收參數並進行統一處理的函數編碼形式
function sum(a, b, c) {
return a + b + c
}
// 柯里化寫法
function add(a) {
return function (b) {
return function (c) {
return a + b + c
}
}
}
sum(1, 2, 3)
add(1)(2)(3)
以上兩個函數的執行結果相同,但是調用方式不同,使用柯里化的優點在於使每一個函數的功能更加單一,及邏輯復用
function foo(type, time, message){
console.log(`${type}:${[time]}:${message}`)
}
foo('error', '2021/10/24', '接口返回數據異常')
foo('error', new Date(), '頁面顯示錯誤')
// 柯里化寫法
function log(type) {
return function (time) {
return function (message) {
console.log(`${type}:${[time]}:${message}`)
}
}
}
var error = log('error')
error('2021/10/24')('接口返回數據異常')
error(new Date())('頁面顯示錯誤')
如以上柯里化代碼,復用了【獲取類型】部分的函數
組合函數
組合函數不是一種函數類型,只是創建一個新函數將多種功能的函數合並起來
function mul(m) {
return m * 2
}
function square(n) {
return n * n
}
// 組合函數
function compose(m, n) {
return function (i) {
return m(n(i))
}
}
var fn = compose(mul, square)
console.log(fn(5))
通過組合函數,就可以將不同功能的函數組合使用
with語句
with語句可以創建一個獨立的作用域
with語句的語法是 with(){},with小括號內需要傳遞一個值,一般為對象,with語句內的變量會從這個小括號里先查找
var message = 'global'
var obj = { name: 'alice', message: 'obj' }
function foo() {
console.log(message)
with (obj) {
console.log(message)
}
}
foo()
以上代碼的執行結果如下
目前with語句已經不推薦使用了
eval
eval用於解析字符串及執行代碼
var str = "var message = 'global'; console.log(message)"
eval(str)
以上代碼執行完成會在控制台上輸出 global
目前已經不推薦使用eval,從上述代碼,我們可以看出它存在的問題
- 可讀性非常差,也不便於維護
- eval字符串很可能會在執行的過程中被篡改,容易遭到攻擊
- js代碼在解析的過程中可以經過js引擎優化,如果是字符串,就沒辦法優化
嚴格模式
在全局或者函數中通過 "use strict" 來開啟嚴格模式,嚴格模式下,不允許編寫一些松散的代碼,瀏覽器會對代碼進行更嚴格的檢測
嚴格模式對javascript語義做了一些限制
1、嚴格模式通過【拋出錯誤】來消除一些原有的【靜默】錯誤
靜默錯誤:比如定義變量沒有關鍵字聲明,如 num = 1
2、嚴格模式在js引擎在執行代碼的時候可以進行更多的優化(不需要對特殊的語法進行處理)
如以下代碼,通過屬性描述符定義name屬性為不可修改
var obj = {}
Object.defineProperty(obj, "name", { writable: false })
obj.name = "alice"
非嚴格模式直接忽略賦值,嚴格模式下會報錯
3、嚴格模式禁用了在ECMAScript未來版本可能會定義的一些語法
如:
關鍵字 function var new
保留字 class let const (ES5以前)
保留字在未來有可能升級為關鍵字
非嚴格模式下以前可以使用保留字作為變量如var let = "abc"
嚴格模式下會報錯
具體來說,嚴格模式做了以下限制
1、無法意外的創建全局變量
在非嚴格模式下,沒有通過關鍵字定義的變量會被添加到全局
嚴格模式下這樣的代碼執行會拋出異常 message is not defined
message = "hello"
2、嚴格模式會使引起靜默失敗的賦值操作拋出異常
靜默失敗:silently fail,不報錯也沒有任何效果
非嚴格模式下不報錯,嚴格模式下直接拋出異常 Cannot create property 'message' on boolean 'true'
true.name = "alice"
3、嚴格模式下刪除不可刪除的屬性會拋出異常
非嚴格模式下不報錯,忽略刪除操作,嚴格模式下直接拋出異常 Cannot delete property 'address' of #