14个你不知道的 JavaScript 技巧

1,969 阅读10分钟

本文翻译自《Little known features of JavaScript》

人们通常认为 JavaScript 是一门很容易上手的语言,但是要做到精通却不简单。是的,这是因为 JavaScript 是一种非常古老且非常灵活的语言。它充满了神秘的语法和过时的功能。到目前为止,我已经使用 JavaScript 多年了,但是还是时不时就会发现一些我不知道的隐藏语法或技巧。

我试图列出一些鲜为人知的JavaScript功能。尽管其中一些功能在严格模式下无效,但它们仍然是完全有效的JavaScript代码。但是请注意,我不建议您开始使用所有这些功能。尽管它们绝对酷,但如果您开始使用它们,很有可能会让你的队友很生气。

此处提供了所有使用的源代码。Happy Coding!

注意:本文并不包括诸如提升(Hoisting),闭包,代理,原型继承,async-await,生成器之类的东西。虽然这些功能可能不被大部分人所了解,但它们仍然是众所周知的。

void 操作符

JavaScript 具有一元void运算符。您可能已经看到它用作void(0)void 0。它的生命中只有一个目标——在表达式的正确位置返回**undefined**。使用“0”只是一个约定。您不一定必须使用“ 0”,它可以是任何有效的表达式void,并且仍返回**undefined。**

img
img

为什么创造了一个特殊的关键字来返回 undefined 而不是直接返回 undefined呢?

听起来好像有一点冗余对吧?

🚩 趣闻

好吧,事实证明,在ES5之前,您实际上可以在大多数浏览器中为原始的 undefined 分配一个新值,像是 undefined =“ abc”。

定义 undefined?!

因此在那个时候,使用 void 是确保您始终返回原始 undefined 的一种方法。

构造函数后面的括号是可选的

是的,在调用构造函数时,我们在类名后添加的括号是完全可选的!😮(前提是您不需要将任何参数传递给构造函数)

以下两种代码样式均被视为有效的 JS 语法,并且结果是相同的!

img
img

IIFE 函数的括号可以省略

对于我来说,IIFE(立即调用功能表达式)的语法总是有点奇怪。

那些括号我到底应该怎么用?

事实证明,这些额外的括号仅仅是为了告诉 JavaScript 解析器即将发布的代码是函数表达式,而不是函数。可以想象,知道了这一点,有很多方法可以跳过那些多余的括号,并且仍然可以制作出有效的IIFE。

IIFE(without return)
IIFE(without return)

void 运算符告诉解析器代码是函数表达式。因此,我们可以跳过函数定义的括号。你猜怎么着?我们可以使用任何一元运算符(void,+,!,-等)来让它奏效!

太酷了!

但是,如果您是一个敏锐的观察者,您可能会想,

一元运算符不会影响IIFE返回的任何结果吗?

好吧,这会影响结果。但是,好消息是,如果您关心结果并说要将其存储在某个变量中,那么首先就不需要多余的括号。

确实如此!

IIFE(with return)
IIFE(with return)

我们添加这些括号只是为了提高人类可读性。

想要了解有关IIFE结帐的更多信息,可以去看看 Chandra Gundamaraju的这篇很酷的文章

with 声明

您知道吗,JavaScript有一个with语句块?实际上它是JS中的关键字。编写with块的语法如下:

with (object)
   statement 
// for multiple statements add a block
with (object) {
   statement
   statement
   ...
}

with 把传入的对象的所有属性添加到当前作用域链中:

img
img

🚩趣闻

with听起来非常酷,对吧?它甚至比对象解构更好。

好吧,不是这样的。

通常不建议使用with语句,因为它已被弃用。在严格模式下完全禁止这样做。事实证明,使用块会增加该语言的性能和安全性问题!

译者注:

不建议使用with,因为with语句将对象的成员添加到当前作用域,从而无法说出块内的变量实际指的是什么。由于难以调试和读取这些类型的功能,因此许多人认为这是一种不好的做法,可以参考以下信息了解更多:

  • https://eslint.org/docs/rules/no-with
  • https://eslint.org/docs/rules/no-new-func#disallow-function-constructor-no-new-func

Function 构造函数

函数声明不是定义新函数的唯一方法;您可以使用Function()构造函数以及new运算符动态地定义函数。

img
img

最后一个构造函数参数是函数的字符串化代码以及之前的其他参数为函数参数。

🚩趣闻

Function 构造函数是 JavaScript 中所有构造函数的母亲。甚至Object的构造函数都是Function构造函数。Function自己的构造函数也是Function本身。因此,调用object.constructor.constructor ...足够的次数最终将返回JavaScript中任何对象上的Function构造函数。

函数属性

众所周知,函数是JavaScript中的「一等公民」。因此,没有人阻止我们向函数添加自定义属性。这在JS中是完全正确的事情。但是,它很少使用。

那么我们什么时候想要这样做呢?

嗯,有一些很好的用例。例如:

可配置函数

假设我们有一个叫做greet的函数。我们希望我们的函数根据不同的语言环境打印不同的问候消息。此语言环境也应该是可配置的。我们可以在某个地方维护全局语言环境变量,也可以使用如下所示的函数属性来实现函数:

img
img

带有静态变量的函数

另一个类似的例子。假设您要实现一个数字生成器——该数字生成器生成一系列有序数字。通常,您将使用带有静态的counter变量的类或IIFE来跟踪上一个值。这样,我们可以限制访问counter,还可以避免因额外的变量污染全局空间。

但是,如果我们想灵活地读取甚至修改计数器而又不污染全局空间怎么办?

是的,我们仍然可以创建一个类,它带有counter变量和一些其他读取counter的方法;或者我们偷个懒,只在函数上使用属性。

img
img

这是一个很长的 list,我们刚刚写了一半。如果您想休息一下,现在是个好时机。如果您要继续看下去,好吧,我向您致敬!

让我们继续!

参数属性

我敢肯定你们大多数人都知道函数内的arguments对象。它是一个数组,类似于对象,可以在所有函数中使用。它具有在调用时传递给函数的参数列表。但是它还具有其他一些有趣的特性,

  • arguments.callee:引用当前调用的函数
  • arguments.callee.caller:引用已调用当前函数的函数
img
img

注意:尽管ES5禁止在严格模式下使用 calleecaller,但在许多已编译的库中仍然很常见。因此,值得学习。

标记模板字符串

除非您与世隔绝,否则您肯定听说过模版字符串。模版字符串是ES6提供的许多不错的功能之一。但是,您知道标记模版字符串吗?

img
img

带有标记的模板字符串可以通过向模板字符串添加自定义标记,来更好地控制将模板字符串解析为字符串的过程。Tag只是一个解析器函数,它获取由字符串模板解释的所有字符串和值的数组,并返回最终字符串。

在下面的示例中,我们的自定义标签highlight解释模板字符串的值,并且还将解释后的值使用<mark>元素包装在结果字符串中以突出显示。

img
img

在许多库都可以发现一些有趣的用例是利用此功能来实现的。

以下是一些很酷的例子,

Getters & Setters

在大多数情况下,JavaScript对象很简单。假设我们有一个user对象,并尝试使用user.age访问其年龄属性,如果定义了年龄属性,我们将获得年龄属性的值;否则,将获得未定义属性。简单。

但是,可以不必这么简单。JavaScript对象具有GetterSetter的概念。可以直接编写自定义Getter函数以返回所需的任何内容,而不是直接返回对象的值。设置值也一样。

这使我们在获取或设置字段时拥有虚拟字段,字段验证,副作用等强大的能力。

img
img

GettersSetters在 ES5 中不是新增功能。他们一直在那里。ES5 只是在现有功能中添加了方便的语法。要了解有关Getters&Setters的更多信息,请参阅这篇不错的[文章]( nice article)

Colors 是一个流行的node.js库,它是利用Getters的一个很好的例子。

该库扩展了String,并在其上添加了一堆Getter方法。这使我们能够通过简单地访问其属性,将任何字符串转换为其彩色版本,以便于打印。

逗号操作符

JavaScript 具有逗号运算符。它允许我们在一行中编写多个用逗号分隔的表达式,并返回最后一个表达式的结果

// syntax
let result = expression1, expression2,... expressionN

在上面的代码中,将对所有表达式进行求值,并将对expressionN 返回的值赋值给result变量。

您可能已经在for循环中使用了逗号运算符:

for (var a = 0, b = 10; a <= 10; a++, b--)

有时它可以用来帮助我们在同一行中写多个语句:

function getNextValue() {
    return counter++, console.log(counter), counter
}

或者写短的lamda 函数:

const getSquare = x => (console.log (x), x * x)

加号操作符

你是否曾经遇到过需要将字符串快速转换为数字的场景?

只需在字符串前面加上+运算符即可。

加号运算符还适用于负,八进制,十六进制,指数值。而且,它甚至可以将DateMoment.js对象转换为时间戳!

img
img

!! Bang Bang 操作符

好的,从技术上讲,它不是独立的 JavaScript 运算符。只是把 JavaScript 否定运算符使用了两次。

但是Bang Bang(!!)听起来很酷!Bang Bang或Double Bang是将所有表达式转换为布尔值的巧妙技巧。

如果表达式是 truthy 的值,则返回 true;否则返回 false。

img
img

~ 按位操作符

面对现实吧——没人在乎按位运算符。我们什么时候才能使用它!

好吧,按位非(~)运算符每天都有用例。

事实证明,当与数字一起使用时,按位非运算符才有效。比如:〜N =>-(N + 1)。仅当N == -1时,此表达式的计算结果才为“ 0”。

我们可以通过将放在indexOf(...)函数前面来检查某一项是否存在于一个字符串或者数组中:

img
img

注意:ES6和ES7分别在String和Array中添加了一个新的.includes()方法。无疑,这是一种比~运算符更简洁的方法来检查数组或字符串中是否存在项。

带标签的声明

JavaScript 具有label语句的概念。它允许我们在 JavaScript 中命名循环和块。然后,我们可以在以后使用breakcontinue时使用这些标签来引用代码。

带标签的语句在嵌套循环中特别方便。但是我们也可以使用它们将代码简单地组织成块或创建可退出的块。

img
img

注意:与其他某些语言不同,JavaScript没有goto构造。因此,我们只能使用带有break和continue的标签。

如果你还有一些 JavaScript 中鲜为人知的用法,可以在评论里分享您的经验~