前 11 个(以及更多!)必须知道的 JavaScript 函数

代码聪明! 通过掌握语言中这些最重要和重复出现的功能,成为更快、更高效、更快乐的 JavaScript 开发人员。

无论是后端还是前端(甚至 宇宙飞船), JavaScript 无处不在。 它也是一种非常灵活的语言(这意味着它具有核心函数式编程模式以及良好的 ol’ 类),并且它与其他“类 C”语言的相似性使得开发人员可以轻松地从其他语言进行转换。

如果你想 升级你的 JS 游戏, 我建议你学习、实践并最终掌握语言中可用的以下核心功能。 并非所有这些都是解决问题的严格“必需”,但在某些情况下它们可以为您完成大量繁重的工作,而在其他情况下,这些可以减少您必须编写的代码量。

地图()

写一篇关于重要 JavaScript 函数的文章而不提及 map() 是异端邪说! 😆😆 与 filter() 和 reduce() 一起,map() 形成了一个神圣的三位一体。 这些是您将在职业生涯中反复使用的功能,因此非常值得一看。 让我们一一解决,从 map() 开始。

map() 是给学习 JavaScript 的人带来最大麻烦的函数之一。 为什么? 不是因为它本身就很复杂,而是因为这个函数的工作方式是从所谓的函数式编程中汲取的思想。 而且由于我们没有接触过函数式编程——我们的学校和行业充满了面向对象的语言——这些工作方式对我们有偏见的大脑来说似乎很奇怪甚至是错误的。

JavaScript 比面向对象的功能强大得多,尽管它的现代版本正在尽力隐藏这一事实。 但那是一整罐蠕虫,我也许改天再打开。 🤣 好的,所以, map() 。 . .

map() 是一个非常简单的函数; 它把自己附加到一个数组上,帮助我们将每个项目转换成其他东西,从而产生一个新数组。 如何准确地转换项目作为另一个函数提供,按照惯例,该函数是匿名的。

这里的所有都是它的! 语法可能需要一些时间来适应,但本质上这就是我们在 map() 函数中所做的。 为什么我们要使用 map()? 取决于我们要达到的目标。 例如,假设我们记录了上周每一天的温度并将其存储为一个简单的数组。 然而,现在我们被告知仪器不是很准确,报告的温度比应有的低 1.5 度。

我们可以使用 map() 函数进行修正,如下所示:

const weeklyReadings = [20, 22, 20.5, 19, 21, 21.5, 23];

const correctedWeeklyReadings = weeklyReadings.map(reading => reading + 1.5);

console.log(correctedWeeklyReadings); // gives [ 21.5, 23.5, 22, 20.5, 22.5, 23, 24.5 ]

另一个非常实用的示例来自 React 世界,其中从数组创建 DOM 元素列表是一种常见模式; 所以,这样的事情很常见:

export default ({ products }) => {
    return products.map(product => {
        return (
            <div className="product" key={product.id}>
                <div className="p-name">{product.name}</div>
                <div className="p-desc">{product.description}</div>
            </div>
        );
    });
};

在这里,我们有一个功能性的 React 组件,它接收一个产品列表作为它的 props。 然后从这个列表(数组)构建一个 HTML“div”列表,基本上将每个产品对象转换为 HTML。 原始产品对象保持不变。

你可以争辩说 map() 只不过是一个美化的 for 循环,你是完全正确的。 但是请注意,一旦你提出这个论点,说话的是你受过面向对象训练的头脑,而这些函数及其基本原理来自函数式编程,在函数式编程中,统一性、紧凑性和优雅性受到高度推崇。 🙂

筛选()

filter() 是一个非常有用的函数,您会发现自己在许多情况下都会反复应用它。 顾名思义,此函数根据您提供的规则/逻辑过滤一个数组,并返回一个包含满足这些规则的项目的新数组。

让我们重用我们的天气示例。 假设我们有一个数组,其中包含上周每一天的最高温度; 现在,我们想知道有多少天比现在更冷。 是的,“更冷”是一个主观术语,所以假设我们正在寻找温度低于 20 度的日子。我们可以使用 filter() 函数来做到这一点,如下所示:

const weeklyReadings = [20, 22, 20.5, 19, 21, 21.5, 23];

const colderDays = weeklyReadings.filter(dayTemperature => {
    return dayTemperature < 20;
});

console.log("Total colder days in week were: " + colderDays.length); // 1

请注意,我们传递给 filter() 的匿名函数必须返回一个布尔值:true 或 false。 这就是 filter() 将如何知道是否将该项目包含在过滤后的数组中。 您可以在此匿名函数中自由编写任意数量的复杂逻辑; 您可以进行 API 调用和读取用户输入等,只要您确保最终返回的是一个布尔值。

当心:根据我作为 JavaScript 开发人员的经验,这是我不得不提供的旁注。 无论是由于粗心大意还是错误的基础,许多程序员在使用 filter() 时都会在他们的程序中产生细微的错误。 让我们重写之前的代码以包含错误:

const weeklyReadings = [20, 22, 20.5, 19, 21, 21.5, 23];

const colderDays = weeklyReadings.filter(dayTemperature => {
    return dayTemperature < 20;
});

if(colderDays) {
    console.log("Yes, there were colder days last week");
} else {
    console.log("No, there were no colder days");
}

注意到什么了吗? 如果你做到了,那就太好了! 最后的 if 条件检查 colderDays,它实际上是一个数组! 您会惊讶于人们在赶时间赶时间或情绪低落(无论出于何种原因)编写代码时犯了多少次这种错误。 这种情况的问题在于,JavaScript 在很多方面都是一种奇怪且不一致的语言,事物的“真实性”就是其中之一。 尽管 [] == true 返回 false,让你认为上面的代码没有被破坏,实际情况是在 if 条件中, [] 评估为真! 换句话说,我们编写的代码永远不会说上周没有更冷的日子。

  如何在新 Mac 上打开启动铃声

修复非常简单,如上述代码之前的代码所示。 我们检查 colderDays.length,它保证给我们一个整数(零或更大),因此在逻辑比较中始终如一地工作。 请注意,filter() 将始终、始终、始终返回一个数组,无论是空的还是非空的,因此我们可以依赖它并自信地编写我们的逻辑比较。

这比我计划的绕了更长的弯路,但像这样的错误值得用一万字来强调,如果需要的话全部大写。 我希望您不要被这个问题困扰,并为自己节省数百小时的调试工作! 🙂

减少()

在本文以及标准 JavaScript 库中的所有函数中,reduce() 位居“令人困惑和怪异”桂冠的领跑者之列。 虽然此功能非常重要并且在许多情况下会产生优雅的代码,但大多数 JavaScript 开发人员都避免使用它,他们更愿意编写更冗长的代码。

原因是——老实说! — reduce() 在概念和执行方面都很难理解。 当你读到它的描述时,你已经重读了好几遍,你仍然怀疑自己是否读错了; 当您看到它在运行并尝试想象它是如何工作的时,您的大脑就会扭成一千个结! 🤭

现在,不要害怕。 reduce() 函数在复杂性和威胁性方面远不及,比如说, B+树 和他们的算法。 只是这种逻辑在普通程序员的日常工作中很少遇到。

所以,在把你吓得半死然后立即告诉你不要担心之后,我最后想向你展示这个功能是什么以及我们为什么需要它。

顾名思义,reduce() 用于减少某些东西。 它减少的是一个数组,它减少给定数组的东西是一个单一的值(数字、字符串、函数、对象,等等)。 这是一种更简单的表达方式——reduce() 将数组转换为单个值。 请注意,reduce() 的返回值不是数组,map() 和 filter() 就是这种情况。 了解了这么多就已经成功了一半。 🙂

现在,很明显,如果我们要转换(减少)一个数组,我们需要提供必要的逻辑; 根据您作为 JS 开发人员的经验,您很可能已经猜到我们使用函数来做到这一点。 这个函数就是我们所说的 reducer 函数,它构成了 reduce() 的第一个参数。 第二个参数是一个起始值,例如数字、字符串等(稍后我会解释这个“起始值”到底是什么)。

根据我们目前的理解,我们可以说对 reduce() 的调用看起来像这样:array.reduce(reducerFunction, startingValue)。 现在让我们来处理整个事情的核心:reducer 函数。 如前所述,reducer 函数告诉 reduce() 如何将数组转换为单个值。 它有两个参数:一个用作累加器的变量(别担心,我也会解释这一点),以及一个存储当前值的变量。

我知道我知道 。 . . 这是一个单一函数的大量术语,在 JavaScript 中甚至不是强制性的。 😝😝 这就是人们避开 reduce() 的原因。 但是如果你一步一步地学习它,你不仅会理解它,而且会在你成为更好的开发人员时欣赏它。

好的,那么,回到手头的话题。 传递给 reduce() 的“起始值”是 . . . 好吧,您要使用的计算的起始值。 例如,如果您要在 reducer 函数中进行乘法运算,则起始值为 1 是有意义的; 对于加法,你可以从 0 开始,依此类推。

现在让我们看看 reducer 函数的签名。 传递给 reduce() 的 reducer 函数具有以下形式:reducerFunction(accumulator, currentValue)。 “累加器”只是收集和保存计算结果的变量的别称; 这就像使用一个名为 total 的变量来使用 total += arr 之类的东西对数组中的所有项目求和[i]. 这正是 reduce() 中的 reducer 函数的应用方式:累加器最初设置为您提供的起始值,然后一个一个地访问数组中的元素,执行计算,并将结果存储在蓄能器等。 . .

那么,reducer 函数中的“当前值”是多少? 如果我要求您遍历一个数组,您会在脑海中想象出同样的想法:您将一个变量从索引零开始,然后一次向前移动一步。 当你这样做的时候,如果我让你突然停下来,你会发现自己在数组的元素之一上,对吧? 这就是我们所说的当前值的意思:它是用于表示当前正在考虑的数组项的变量的值(如果有帮助,可以考虑遍历数组)。

话虽如此,是时候看一个简单的例子,看看所有这些行话是如何在实际的 reduce() 调用中组合在一起的。 假设我们有一个包含前 n 个自然数 (1, 2, 3 . . n) 的数组,我们有兴趣找到 n 的阶乘。 我们知道要找到 n! 我们只需要将所有东西相乘,这就导致了我们的实现:

const numbers = [1, 2, 3, 4, 5];
const factorial = numbers.reduce((acc, item) => acc * item, 1);
console.log(factorial); // 120

在这短短的三行代码中发生了很多事情,所以让我们在我们迄今为止进行的(非常长的)讨论的背景下一一解开它。 很明显,numbers 是一个数组,它包含我们想要相乘的所有数字。 接下来,查看 numbers.reduce() 调用,它表示 acc 的起始值应为 1(因为它不会影响或破坏任何乘法)。 接下来,检查 reducer 函数体,`(acc, item) => acc * item,它只是说每次迭代数组的返回值应该是该项目乘以累加器中已有的值。 迭代并将乘法显式存储在累加器中是在幕后发生的事情,这也是 reduce() 成为 JavaScript 开发人员绊脚石的最大原因之一。

为什么要使用 reduce()?

这是一个非常好的问题,老实说,我没有确定的答案。 无论 reduce() 做什么,都可以通过循环、forEach() 等来完成。但是,这些技术会产生更多的代码,使其难以阅读,尤其是在您赶时间的时候。 然后是不可变性的问题:使用 reduce() 和类似的函数,你可以确定你的原始数据没有被改变; 这本身就消除了整类错误,尤其是在分布式应用程序中。

最后,reduce() 更加灵活,因为累加器可以是一个对象、一个数组,如果需要的话甚至可以是一个函数; 起始值和函数调用的其他部分也是如此——几乎任何东西都可以进入,几乎任何东西都可以出来,因此在设计可重用代码时具有极大的灵活性。

如果您仍然不相信,那也没关系; JavaScript 社区本身在 reduce() 的“紧凑性”、“优雅”和“强大”方面存在明显分歧,所以如果你不使用它也没关系。 🙂但是一定要看一些 简洁的例子 在你决定 bin reduce() 之前。

  什么是包围曝光?

一些()

假设您有一个对象数组,每个对象代表一个人。 你想知道数组中是否有 35 岁以上的人。请注意,不需要计算有多少这样的人,更不用说检索他们的列表了。 我们在这里所说的相当于“一个或多个”或“至少一个”。

你怎么做到这一点?

是的,您可以创建一个标志变量并遍历数组来解决这个问题,如下所示:

const persons = [
    {
        name: 'Person 1',
        age: 32
    },
    
    {
        name: 'Person 2',
        age: 40
    },
];

let foundOver35 = false;

for (let i = 0; i < persons.length; i ++) {
    if(persons[i].age > 35) {
        foundOver35 = true;
        break;
    }
}

if(foundOver35) {
    console.log("Yup, there are a few people here!")
}

问题? 在我看来,代码太像 C 或 Java。 “冗长”是我想到的另一个词。 有经验的 JS 可能会想到“丑陋”、“可怕”等。😝 我认为这是正确的。 改进这段代码的一种方法是使用类似 map() 的方法,但即便如此,解决方案也有点笨拙。

事实证明,我们有一个相当简洁的函数,称为 some() 已经在核心语言中可用。 此函数适用于数组并接受自定义“过滤”函数,返回布尔值 true 或 false。 本质上,它正在做我们在过去几分钟一直在尝试做的事情,只是非常简洁和优雅。 下面是我们如何使用它:

const persons = [
    {
        name: 'Person 1',
        age: 32
    },
    
    {
        name: 'Person 2',
        age: 40
    },
];

if(persons.some(person => {
    return person.age > 35
})) {
    console.log("Found some people!")
}

同样的输入,和以前一样的结果; 但请注意代码的大量减少! 还要注意认知负担是如何显着减少的,因为我们不再需要像自己是解释器一样逐行解析代码! 代码现在几乎像自然语言一样读起来。

每一个()

就像 some() 一样,我们还有另一个有用的函数,叫做 every()。 您现在可以猜到,这也将返回一个布尔值,具体取决于数组中的所有项目是否都通过了给定的测试。 当然,要通过的测试大部分时间都是作为匿名函数提供的。 我会让你免去代码的幼稚版本的痛苦,所以这里是如何使用 every() 的:

const entries = [
    {
        id: 1
    },
    
    {
        id: 2
    },
    
    {
        id: 3  
    },
];

if(entries.every(entry => {
    return Number.isInteger(entry.id) && entry.id > 0;
})) {
    console.log("All the entries have a valid id")
}

很明显,代码会检查数组中的所有对象是否具有有效的 id 属性。 “有效”的定义取决于问题上下文,但如您所见,对于这段代码,我考虑了非负整数。 再一次,我们看到代码阅读起来是多么简单和优雅,这是这个(和类似的)函数的唯一目标。

包含()

你如何检查子字符串和数组元素的存在? 好吧,如果您像我一样,您会很快找到 indexOf() 然后查找文档以了解其可能的返回值。 这是一个相当大的不便,并且返回值很难记住(快速 – 进程返回 2 到操作系统是什么意思?)。

但是我们可以使用一个不错的替代方法:includes()。 用法和名字一样简单,生成的代码非常暖心。 请记住,由 includes() 完成的匹配是区分大小写的,但我想这无论如何都是我们直觉所期望的。 现在,是时候编写一些代码了!

const numbers = [1, 2, 3, 4, 5];
console.log(numbers.includes(4));
const name = "Ankush";
console.log(name.includes('ank')); // false, because first letter is in small caps
console.log(name.includes('Ank')); // true, as expected

但是,不要对这种不起眼的方法抱有太大期望:

const user = {a: 10, b: 20};
console.log(user.includes('a')); // blows up, as objects don't have a "includes" method

它无法查看对象内部,因为它根本没有为对象定义。 但是,嘿,我们确实知道它适用于数组,所以也许我们可以在这里做些小把戏。 . . 🤔。

const persons = [{name: 'Phil'}, {name: 'Jane'}];
persons.includes({name: 'Phil'});

那么,当您运行这段代码时会发生什么? 它没有爆炸,但输出也令人失望:false。 😫😫 实际上,这与对象、指针以及 JavaScript 如何查看和管理内存有关,这是它自己的一个世界。 如果您想更深入地潜水,请随时尝试一下(也许开始 这里), 但我会在这里停下来。

如果我们将上面的代码重写如下,我们可以使上面的代码正常运行,但在这一点上,它或多或少成了一个笑话,在我看来:

const phil = {name: 'Phil'};
const persons = [phil, {name: 'Jane'}];
persons.includes(phil); // true

尽管如此,它确实表明我们可以使 includes() 对对象起作用,所以我想这并不是一场彻底的灾难。 😄

  修复 Uverse Code 0 加载资源失败

片()

假设我们有一个字符串,我要求您返回其中以“r”开头并以“z”结尾的部分(实际字符并不重要)。 你会如何处理它? 也许您会创建一个新字符串并使用它来存储所有必需的字符并返回它们。 或者如果你像大多数程序员一样,你会给我两个数组索引作为回报:一个指示子字符串的开始,另一个指示结束。

这两种方法都很好,但是有一个称为切片的概念在这种情况下提供了一个巧妙的解决方案。 值得庆幸的是,没有深奥的理论可循; 切片的意思和它听起来的一样——从给定的字符串/数组创建一个更小的字符串/数组,就像我们创建水果切片一样。 让我们看看我的意思,借助一个简单的例子:

const headline = "And in tonight's special, the guest we've all been waiting for!";
const startIndex = headline.indexOf('guest');
const endIndex = headline.indexOf('waiting');
const newHeadline = headline.slice(startIndex, endIndex);
console.log(newHeadline); // guest we've all been

当我们 slice() 时,我们为 JavaScript 提供了两个索引——一个是我们想要开始切片的地方,另一个是我们想要它停止的地方。 slice() 的问题在于结束索引未包含在最终结果中,这就是为什么我们看到上面代码中的新标题中缺少“等待”一词的原因。

像切片这样的概念在其他语言中更为突出,尤其是 Python。 如果你问那些开发人员,他们会说他们无法想象没有这个功能的生活,当语言为切片提供非常简洁的语法时,这是正确的。

切片非常简洁,非常方便,没有理由不用它。 它也不是充满性能损失的语法糖,因为它创建了原始数组/字符串的浅表副本。 对于 JavaScript 开发人员,我强烈建议熟悉 slice() 并将其添加到您的武器库中!

拼接()

方法 splice() 听起来像是 slice() 的表亲,在某些方面,我们可以说它是。 两者都从原始数组/字符串创建新的数组/字符串,有一个小但重要的区别 — splice() 删除、更改或添加元素但修改原始数组。 如果您不小心或不理解深层复制和引用,这种对原始数组的“破坏”可能会产生巨大的问题。 我想知道是什么阻止了开发人员使用与 slice() 相同的方法并保持原始数组不变,但我想我们可以对一种语言更宽容 仅用十天创建.

尽管有我的抱怨,但让我们看看 splice() 是如何工作的。 我将展示一个示例,其中我们从数组中删除一些元素,因为这是此方法最常见的用法。 我也不会提供添加和插入的示例,因为这些示例很容易查找并且也很简单。

const items = ['eggs', 'milk', 'cheese', 'bread', 'butter'];
items.splice(2, 1);
console.log(items); // [ 'eggs', 'milk', 'bread', 'butter' ]

上面对 splice() 的调用说:从数组的索引 2(即第三位)开始,并删除一项。 在给定的数组中,’cheese’ 是第三个项目,因此它被从数组中删除并且项目数组被缩短,正如预期的那样。 顺便说一句,删除的项目由 splice() 以表单或数组的形式返回,因此如果我们愿意,我们可以在变量中捕获“奶酪”。

以我的经验,indexOf() 和 splice() 有很好的协同作用——我们找到一个项目的索引,然后从给定的数组中删除它。 但是,请注意,它并不总是最有效的方法,而且通常使用对象(相当于哈希映射)键要快得多。

转移()

shift() 是一种方便的排序方法,用于删除数组的第一个元素。 请注意,同样的事情可以用 splice() 完成,但是当您需要做的只是砍掉第一个元素时,shift() 更容易记住和直观。

const items = ['eggs', 'milk', 'cheese', 'bread', 'butter'];
items.shift()
console.log(items); // [ 'milk', 'cheese', 'bread', 'butter' ]

取消移位()

就像 shift() 从数组中删除第一个元素一样,unshift() 将一个新元素添加到数组的开头。 它的使用同样简单紧凑:

const items = ['eggs', 'milk'];
items.unshift('bread')
console.log(items); // [ 'bread', 'eggs', 'milk' ]

也就是说,我忍不住警告那些新手:与流行的 push() 和 pop() 方法不同,shift() 和 unshift() 效率极低(因为底层算法的工作方式)。 因此,如果您在大型数组(例如 2000 多个项目)上操作,过多的此类函数调用会使您的应用程序陷入停顿。

充满()

有时您需要将几个项目更改为单个值,或者甚至可以说“重置”整个数组。 在这些情况下,fill() 可以避免循环和差一错误。 它可用于用给定值替换部分或全部数组。 让我们看几个例子:

const heights = [1, 2, 4, 5, 6, 7, 1, 1];
heights.fill(0);
console.log(heights); // [0, 0, 0, 0, 0, 0, 0, 0]

const heights2 = [1, 2, 4, 5, 6, 7, 1, 1];
heights2.fill(0, 4);
console.log(heights2); // [1, 2, 4, 5, 0, 0, 0, 0]

其他值得一提的功能

虽然上面的列表是大多数 JavaScript 开发人员在他们的职业生涯中最终遇到和使用的,但它绝不是完整的。 JavaScript 中还有许多次要但有用的函数(方法),不可能在一篇文章中涵盖所有这些。 也就是说,我想到的一些如下:

  • 撤销()
  • 种类()
  • 条目()
  • 充满()
  • 寻找()
  • 平坦的()

我鼓励您至少查阅一下这些内容,以便您了解存在此类便利。

结论

JavaScript 是一门庞大的语言,尽管需要学习的核心概念很少。 我们可用的许多功能(方法)构成了这个大尺寸的主体。 然而,由于 JavaScript 是大多数开发人员的第二门语言,我们没有深入研究,错过了它提供的许多漂亮和有用的功能。 实际上,函数式编程概念也是如此,但那是另一天的话题! 🤗

只要有可能,花一些时间探索核心语言(如果可能的话,探索著名的实用程序库,例如 Lodash). 即使花几分钟来完成这项工作,也会带来巨大的生产力提升和更简洁、更紧凑的代码。