JavaScript通常被认为是最容易入门却最难以掌握的编程语言。我完全赞同这种说法。这是因为JavaScript是一门非常古老却又非常灵活的语言。它有着各种各样神秘的语法和古老的特性。我是一个JavaScript老用户,直到现在,我仍然会时不时地发现一些我从来都不知道的隐藏语法或技巧。
我将在这篇文章中列出一些鲜为人知的JavaScript特性。虽然其中一些在strict模式下是无效的,但它们仍然是有效的JavaScript代码。但请注意,我不建议你使用所有这些特性。虽然它们看起来很酷,但如果你真的使用了这些特性,很有可能会让你的队友吐血。
所有相关的代码都可以在这里找到,祝你编程愉快!
注意:我不会提及诸如Hoisting、闭包、代理、原型继承、async/await、生成器,等等。虽然这些特性可能不太好理解,但它们其实是众所周知的。
void运算符
JavaScript提供了一个一元运算符void,你可能已经看到过它的这种用法,比如void(0)或void 0。它的作用只有一个——计算其右边的表达式并返回undefined。使用“0”只是一种惯例,你不一定要使用“0”,它可以是任何有效的表达式,如void ,它仍然会返回undefined。
为什么要创建一个特殊的关键字来返回undefined,而不是直接返回undefined?这似乎有点多余,不是吗?
实际上,在ES5之前,你可以在大多数浏览器中为给undefined赋值,比如undeunfined = “abc”。在那个时候,使用void是一种确保总是能够返回undefined的方法。
构造函数的括号是可选的
在调用构造函数时,类名后面的括号是可选的(前提是你不需要传递任何参数)!
下面的代码都是有效的JS语法,并且会给你完全相同的结果!
可以跳过IIFE的括号
IIFE(立即调用函数表达式)的语法对我来说总是有点奇怪。那些括号都有什么作用?
那些额外的括号只是为了告诉JavaScript解析器,后面的代码是一个函数表达而不是一个函数。知道了这一点,我们就有很多方法可以跳过这些额外的括号,并仍然可以使用有效的IIFE。
void运算符告诉解析器后面的代码是函数表达式。因此,我们可以跳过函数定义周围的括号。我们还可以使用任何一元运算符(void、+、!、-,等等),它们都是有效的!
你可能会想,一元运算符不会影响IIFE返回的结果吗?
它确实会影响返回的结果。但如果你关心结果,并希望将结果赋给在某个变量,那么首先你就不需要额外的括号。
我们添加这些括号只是为了更好的可读性。
with语句
JavaScript也支持with块?with实际上是JS的一个关键字。with块的语法如下:
with (object) statement // for multiple statements add a blockwith (object) { statement statement ...}
with将“对象”的所有属性添加到用于计算语句的作用域链中。
with块看起来非常酷,它甚至比对象解构更好,但其实并不尽然。
通常不鼓励使用with语句,因为它已经被弃用。在strict模式下是被完全禁止的。事实证明,使用with块会带来一些性能和安全方面的问题。
Function构造函数
function语句并不是定义新函数的唯一方法,你可以使用Function()构造函数和new运算符动态定义函数。
最后一个参数是函数的字符串化代码,前面的其他参数是函数的参数。
Function构造函数是JavaScript中所有构造函数的祖先。甚至Object的构造函数也是Function。而Function自己的构造函数也是Function本身。因此,如果调用object.constructor.constructor…足够多的次数,最后将获得Function构造函数。
函数属性
我们都知道,函数是JavaScript的一等对象。因此,我们当然可以向函数添加自定义属性。这样做是完全有效的。然而,它很少被这样使用。
那么,我们什么时候会这么做呢?
可配置的函数
假设我们有一个叫作greet的函数。我们希望它能够根据不同的区域设置打印出不同的问候语。区域设置也应该是可配置的。我们可以在某处维护一个全局区域环境变量,或者我们也可以使用函数属性来实现这个函数,如下所示:
具有静态变量的函数
另一个类似的例子,假设你想要实现一个生成一系列有序数字的数字生成器。通常,你会使用Class或IIFE,并使用一个静态计数器变量来跟踪最后一个值。这样我们就可以限制对计数器的访问,并避免使用额外的变量来污染全局命名空间。
但是,如果我们希望能够灵活地读取甚至是修改计数器,并且不污染全局命名空间呢?
我们仍然可以创建一个Class,带有一个计数器变量和一些额外的方法来读取它,或者我们可以使用函数的属性。
参数属性
我相信大多数人都知道函数的arguments对象。它是一种类似于数组的对象,所有函数都包含了它。它包含了在调用函数时传给函数的所有参数,但它也有一些其他有趣的属性:
arguments.callee:指当前调用的函数;
arguments.callee.caller:指调用当前函数的函数。
注意:尽管ES5禁止在strict模式下使用callee和caller,但在很多编译库中仍然很常见。
标记模板字面量
除非你与世隔绝,否则你一定听说过模板字面量。模板字面量是ES6的众多很酷的补充特性之一。但是,你知道标记模板字面量吗?
在使用标记模板字面量时,你可以通过向模板字面量添加自定义标记来更好地控制如何将模板字面量解析为字符串。标记只是一个解析器函数,它获取字符串模板中所有的字符串和值。标记函数负责返回最终的字符串。
在下面的示例中,我们的自定义标记——highlight,解释模板字面量的值,并使用元素将解释的值包装在结果字符串中,以突出显示。
Getter和Setter
JavaScript对象的大部分东西是很简单的。假设我们有一个user对象,并且我们使用user.age来访问它的age属性,如果定义了age属性,我们就会得到它的值,如果没有,我们就会得到undefined。
但是,它也可能不会这么简单。JavaScript对象也有Getter和Setter的概念。我们可以编写自定义的Getter函数来返回我们想要的任何东西,而不是直接返回对象的值。设置值也是一样的。
这样我们在获取或设置字段时就拥有了一些强大的概念,如虚拟字段、字段验证、副作用,等等。
Getter和Setter并不是ES5的新增功能,它们一直都存在。ES5只是为它们添加了方便的语法。
逗号运算符
JavaScript提供了一个逗号运算符,我们可以用它在一行中编写由逗号分隔的多个表达式,并返回最后一个表达式的结果。
let result = expression1, expression2,... expressionN
这里所有的表达式都会被计算,并将expressionN返回的值赋给result变量。
你可能已经在for循环中使用了逗号运算符:
for (var a = 0, b = 10; a \u0026lt;= 10; a++, b--)
有时候,在一行中编写多个语句会有所帮助:
function getNextValue() { return counter++, console.log(counter), counter}
或者用它编写很短的lamda表达式:
const getSquare = x =\u0026gt; (console.log (x), x * x)
加号运算符
你是否曾经想过快速将字符串转换为数字?
只需在字符串前面加上加号即可。
加号运算符也适用于负数、八进制、十六进制、指数。它甚至可以将Date或Moment.js对象转换为时间戳!
!!运算符
从技术上讲,它并不是一个单独的JavaScript运算符。它的效果与使用两次JavaScript否定运算符是一样的。
!!是将任何表达式转换为布尔值的一个巧妙的技巧。
如果表达式是真值,则返回true,否则返回false。
~运算符
没有人会关心位运算符,因为我们几乎很少会用它!但它确实有一些使用场景!
当与数字一起使用时,比如~N =\u0026gt; -(N + 1)。这个表达式只在N == -1时结果为“0”。
我们可以在indexOf(…)函数前面加一个~来进行布尔检查,看看一个项是否存在于String或Array中。
注意:ES6和ES7分别在String和Array中添加了一个新的.includes()方法。当然,它比使用~运算符检查项目是否存在于Array或String中更清晰一些。
标签语句
JavaScript也有标签语句的概念。我们可以在JavaScript中使用标签来命名循环和代码块。然后,我们可以在break或continue时通过这些标签返回到之前的代码。
在嵌套循环中使用标签语句会非常方便,我们也可以使用它们来将代码组织成代码块或创建可break的代码块。
注意:与其他一些语言不同,JavaScript中没有goto。因此,我们只能使用在break和continue中使用标签。
英文原文:
https://blog.usejournal.com/little-known-features-of-javascript-901665291387
更多内容,可关注前端之巅(ID:frontshow)