
纯功能
满足以下两个要求的函数称为pure:
- 当使用相同的参数调用它时,总是返回相同的结果。
- 执行该功能时,没有副作用。
让我们考虑一个例子:
function circleArea(radius){
return radius * radius * 3.14
}
如果此函数传递相同的值
radius
,则始终返回相同的结果。同时,在函数执行期间,其外部没有任何变化,即没有副作用。所有这些意味着这是一个纯函数。
这是另一个例子:
let counter = (function(){
let initValue = 0
return function(){
initValue++;
return initValue
}
})()
让我们在浏览器控制台中尝试此功能。

在浏览器控制台中测试功能
如您所见,
counter
实现计数器的函数每次调用都会返回不同的结果。因此,它不能被称为纯粹的。
这是另一个示例:
let femaleCounter = 0;
let maleCounter = 0;
function isMale(user){
if(user.sex = 'man'){
maleCounter++;
return true
}
return false
}
这里显示的是一个函数
isMale
,当传递相同的参数时,始终返回相同的结果。但是它有副作用。即,我们正在谈论更改全局变量maleCounter
。结果,该函数不能称为纯函数。
▍为什么需要纯函数?
为什么我们在常规函数和纯函数之间划清界限?关键是纯函数有很多优势。它们的使用可以提高代码的质量。让我们谈谈纯函数的使用会给我们带来什么。
1.纯函数的代码比普通函数的代码更清晰,更易于阅读
每个纯功能都针对特定任务。使用相同的输入调用它时,总是返回相同的结果。这大大提高了代码的可读性,并使其更易于记录。
2.纯函数在编译代码时更适合优化
假设您有一段这样的代码:
for (int i = 0; i < 1000; i++){
console.log(fun(10));
}
如果
fun
-这不是一个纯函数,则在执行此代码期间,必须将该函数调用fun(10)
1000次。
如果它
fun
是纯函数,则编译器可以优化代码。它可能看起来像这样:
let result = fun(10)
for (int i = 0; i < 1000; i++){
console.log(result);
}
3.纯功能更易于测试
纯功能测试不应是上下文相关的。在编写用于纯函数的单元测试时,它们只是将一些输入值传递给此类函数并根据特定要求检查它们返回的值。
这是一个简单的例子。pure函数将数字数组作为参数,并向该数组的每个元素加1,返回一个新数组。这是简写形式:
const incrementNumbers = function(numbers){
// ...
}
要测试这样的功能,只需编写类似于以下内容的单元测试:
let list = [1, 2, 3, 4, 5];
assert.equals(incrementNumbers(list), [2, 3, 4, 5, 6])
如果一个函数不是纯函数,则要对其进行测试,您需要考虑许多可能影响其行为的外部因素。结果,测试这种功能将比测试纯功能更加困难。
高阶函数。
高阶函数是具有至少以下能力之一的函数:
- 它能够接受其他函数作为参数。
- 它可以返回一个功能,作为其工作的结果。
使用高阶函数可以提高代码的灵活性,从而帮助您编写更紧凑,更高效的程序。
假设有一个整数数组。有必要在其基础上创建相同长度的新数组,但是这样,其每个元素都将表示将原始数组的相应元素乘以2的结果。
如果不使用高阶函数的功能,则此问题的解决方案可能如下所示:
const arr1 = [1, 2, 3];
const arr2 = [];
for (let i = 0; i < arr1.length; i++) {
arr2.push(arr1[i] * 2);
}
如果您考虑这个问题,事实证明
Array
JavaScript中的类型对象有一个method map()
。此方法称为map(callback)
。它创建一个新数组,其中填充了要调用的数组元素,并使用传递给它的函数进行处理callback
。
这是使用该方法解决此问题的方式,如下所示
map()
:
const arr1 = [1, 2, 3];
const arr2 = arr1.map(function(item) {
return item * 2;
});
console.log(arr2);
方法
map()
是高阶函数的一个示例。
正确使用高阶函数有助于提高代码质量。在本材料的以下各节中,我们将不止一次返回这些功能。
缓存功能结果
假设您有一个看起来像这样的纯函数:
function computed(str) {
// ,
console.log('2000s have passed')
// ,
return 'a result'
}
为了提高代码的性能,诉诸于缓存在函数中执行的计算结果不会损害我们。当使用与调用时相同的参数调用该函数时,您将不必再次执行相同的计算。相反,它们先前存储在缓存中的结果将立即返回。
如何为功能配备缓存?为此,您可以编写一个特殊的函数用作目标函数的包装器。我们将为这个特殊功能命名
cached
。该函数将目标函数作为参数并返回一个新函数。在函数中,cached
您可以使用常规对象(Object
)或使用作为数据结构的对象来组织对其所包装函数的调用结果的缓存。Map
...
这就是功能代码的样子
cached
:
function cached(fn){
// , fn.
const cache = Object.create(null);
// fn, .
return function cachedFn (str) {
// - fn
if ( !cache[str] ) {
let result = fn(str);
// , fn,
cache[str] = result;
}
return cache[str]
}
}
这是在浏览器控制台中试用此功能的结果。

实验一个其结果被缓存的函数
懒函数
在功能体内,通常有一些检查条件的说明。有时,与它们相对应的条件仅需要检查一次。每次调用函数时都没有必要检查它们。
在这种情况下,可以通过在第一次执行这些指令后“删除”这些指令来提高功能的性能。结果,该函数及其后续调用将不必执行检查,而不再需要检查。这将是“懒惰”功能。
假设我们要编写一个函数
foo
,该函数始终返回Date
第一次调用该函数时创建的对象。请注意,我们需要一个在第一次调用该函数时创建的对象。
其代码可能如下所示:
let fooFirstExecutedDate = null;
function foo() {
if ( fooFirstExecutedDate != null) {
return fooFirstExecutedDate;
} else {
fooFirstExecutedDate = new Date()
return fooFirstExecutedDate;
}
}
每次调用此函数时,都必须检查条件。如果此条件非常困难,则调用该函数将导致程序性能下降。在这里,我们可以使用创建“惰性”函数的技术来优化代码。
即,我们可以如下重写函数:
var foo = function() {
var t = new Date();
foo = function() {
return t;
};
return foo();
}
首次调用该函数后,我们用新函数替换了原始函数。这个新函数返回
t
由Date
第一次调用该函数创建的对象表示的值。结果,在调用此类函数时无需检查任何条件。这种方法可以提高代码的性能。
这是一个非常简单的条件示例。现在让我们看一些接近现实的东西。
将事件处理程序附加到DOM元素时,需要执行检查以确保解决方案与现代浏览器和IE兼容:
function addEvent (type, el, fn) {
if (window.addEventListener) {
el.addEventListener(type, fn, false);
}
else if(window.attachEvent){
el.attachEvent('on' + type, fn);
}
}
事实证明,每当我们调用一个函数时
addEvent
,都会在其中检查一个条件,这足以在第一次调用该条件时检查一次。让我们将此函数设为“惰性”:
function addEvent (type, el, fn) {
if (window.addEventListener) {
addEvent = function (type, el, fn) {
el.addEventListener(type, fn, false);
}
} else if(window.attachEvent){
addEvent = function (type, el, fn) {
el.attachEvent('on' + type, fn);
}
}
addEvent(type, el, fn)
}
结果,我们可以说,如果在某个函数中检查了某个条件(该条件只能执行一次),那么通过应用创建“惰性”函数的技术,可以优化代码。即,优化在于以下事实:在对条件进行第一次检查之后,将原始功能替换为新的功能,其中不再进行条件检查。
咖喱功能
Currying是对函数的这种转换,在应用该函数之后,必须在一次将几个参数传递给它之前将要调用的函数转换为可以通过一次传递一个所需的参数来调用的函数。
换句话说,我们正在谈论这样一个事实:需要多个参数才能正确工作的curried函数能够接受第一个参数,并返回一个能够接受第二个参数的函数。第二个函数依次返回一个带有第三个参数的新函数并返回一个新函数。这将继续,直到将所需数量的参数传递给函数为止。
这有什么用?
- 咖喱有助于避免需要通过反复传递相同的参数来调用函数的情况。
- 此技术有助于创建高阶函数。这对于处理事件非常有用。
- 多亏了curring,您可以组织用于执行某些操作的功能的初步准备,然后方便地在代码中重用这些功能。
考虑一个简单的函数,该函数将传递给它的数字相加。叫它
add
。它以三个操作数作为参数并返回它们的总和:
function add(a,b,c){
return a + b + c;
}
可以通过向其传递少于所需数量的参数来调用此函数(尽管这将导致它返回的结果与预期的结果完全不同)。也可以使用比创建时提供的更多的参数来调用它。在这种情况下,“不必要”的论点将被忽略。试验类似的功能可能如下所示:
add(1,2,3) --> 6
add(1,2) --> NaN
add(1,2,3,4) --> 6 // .
如何咖喱这种功能?
这是
curry
用于咖喱其他功能的功能代码:
function curry(fn) {
if (fn.length <= 1) return fn;
const generator = (...args) => {
if (fn.length === args.length) {
return fn(...args)
} else {
return (...args2) => {
return generator(...args, ...args2)
}
}
}
return generator
}
这是在浏览器控制台中试用此功能的结果。

在浏览器控制台中尝试咖喱
功能组成
假设您需要编写一个函数,以一个字符串为输入,例如
bitfish
返回一个string HELLO, BITFISH
。
如您所见,此功能有两个作用:
- 字符串的串联。
- 将结果字符串中的字符转换为大写。
这是该函数的代码如下所示:
let toUpperCase = function(x) { return x.toUpperCase(); };
let hello = function(x) { return 'HELLO, ' + x; };
let greet = function(x){
return hello(toUpperCase(x));
};
让我们尝试一下。

在浏览器控制台中测试功能
此任务包括组织为单独功能的两个子任务。结果,功能代码
greet
非常简单。如果有必要对字符串执行更多操作,则该函数greet
将包含类似的构造fn3(fn2(fn1(fn0(x))))
。
让我们简化问题的解决方案,并编写一个包含其他功能的函数。叫它
compose
。这是它的代码:
let compose = function(f,g) {
return function(x) {
return f(g(x));
};
};
现在
greet
可以使用函数创建函数compose
:
let greet = compose(hello, toUpperCase);
greet('kevin');
使用一个函数
compose
基于两个现有函数创建一个新函数意味着创建一个从左到右调用这些函数的函数。结果,我们得到了易于阅读的紧凑代码。
现在我们的函数
compose
只接受两个参数。并且我们希望它能够接受任意数量的参数。
著名的开源下划线库中提供了类似的功能,可以接受任意数量的参数。
function compose() {
var args = arguments;
var start = args.length - 1;
return function() {
var i = start;
var result = args[start].apply(this, arguments);
while (i--) result = args[i].call(this, result);
return result;
};
};
通过使用函数组合,可以使函数之间的逻辑关系更易理解,提高代码的可读性,并为将来的扩展和重构奠定基础。
您是否在JavaScript项目中使用任何特殊的方法来处理函数?

