鲜为人知的JavaScript功能

在编程中,JavaScript通常被称为初学者最简单的语言,这是最难掌握的语言。 该材料的作者(我们发表其译文)说,他不得不同意这一说法。 事实是,JS是一种非常古老且非常灵活的语言。 它充满了神秘的语法结构和仍然支持的过时功能。

图片

今天,我们将讨论鲜为人知的JavaScript功能及其实际应用选项。

JavaScript总是新事物


我已经使用JavaScript多年了,并且不断遇到一些我从未怀疑过的东西。 在这里,我试图列出该语言的类似鲜为人知的功能。 在严格模式下,其中一些将不起作用,但在正常模式下,它们是完全正确的JS代码示例。 应该指出的是,我不建议读者将所有这些信息投入使用。 尽管我要讲的内容对您来说似乎很有趣,但是如果您在团队中工作,您可以开始使用所有这些内容,并且,温和地说,让您的同事感到惊讶。

→我们将在这里讨论代码

请注意,我不包括诸如引发变量,关闭,代理对象,原型继承,异步/等待,生成器之类的事情。 尽管该语言的这些功能可归因于难以理解,但它们并不是众所周知的。

虚空运算符


JavaScript具有一元void运算符。 您可能以void(0)void 0的形式遇到了它。 它的唯一目的是在右边计算表达式并返回undefined 。 此处使用0仅仅是因为它是习惯性的,尽管这不是必需的,但您可以在这里使用任何有效的表达式。 是的,此运算符无论如何都将返回undefined

 //  void void 0                  // undefined void (0)                // undefined void 'abc'              // undefined void {}                 // undefined void (1 === 1)          // undefined void (1 !== 1)          // undefined void anyfunction()      // undefined 

如果您只能使用标准值undefined ,为什么还要在用于返回undefined的语言中添加特殊关键字呢? 是不是,有一些冗余?

事实证明,在大多数浏览器中出现ES5标准之前,可以为undefined的标准值分配一个新值。 假设您可以成功执行以下命令: undefined = "abc" 。 结果, undefined值可能不是应该的。 在那些日子里,使用void使我们能够确保对使用real undefined信心。

调用构造函数时的括号是可选的


在类名之后添加的括号(调用构造函数)完全是可选的(除非构造函数需要传递参数)。

在以下示例中,括号的存在或不存在不影响程序的正确操作。

 //     const date = new Date() const month = new Date().getMonth() const myInstance = new MyClass() //     const date = new Date const month = (new Date).getMonth() const myInstance = new MyClass 

支架不能与IIFE一起使用


IIFE语法在我看来一直很奇怪。 为什么会有所有这些括号?

事实证明,仅需使用方括号告诉JavaScript解析器某些代码是函数表达式,而不是错误地声明函数。 知道了这一事实,我们便可以理解,有很多方法可以消除IIFE放在其中的括号,同时可以编写工作代码。

 // IIFE (function () { console.log('Normal IIFE called') })() // Normal IIFE called void function () { console.log('Cool IIFE called') }() // Cool IIFE called 

在这里, void运算符告诉解析器它后面的代码是一个函数表达式。 这使得摆脱函数声明周围的括号成为可能。 顺便说一下,在这里您可以使用任何一元运算符( void+!-等等),并且代码将继续工作。 那不是很好吗?

但是,如果您是一个细心的读者,那么您可能会想知道一元运算符会影响IIFE返回的结果。 其实就是这样。 但是,好处是,如果需要IIFE执行的结果(例如,将其保存在某个变量中),则不需要在IIFE周围加上括号。 这是一个例子。

 // IIFE,    let result = (function () { // ... -  return 'Victor Sully' })() console.log(result) // Victor Sully let result1 = function () { // ... -  return 'Nathan Drake' }() console.log(result1) // Nathan Drake 

第一个IIFE周围的花括号只会提高代码的可读性,而不会影响其操作。

如果您想更好地理解IIFE,请阅读材料。

建筑用


您知道JavaScript具有支持表达式块的with构造吗? 看起来像这样:

 with (object)  statement //       with (object) {  statement  statement  ... } 

with构造将在执行命令时使用的作用域链中添加传递给它的对象的所有属性。

 //    with const person = { firstname: 'Nathan', lastname: 'Drake', age: 29 } with (person) { console.log(`${firstname} ${lastname} is ${age} years old`) } // Nathan Drake is 29 years old 

with似乎是一个很好的工具。 看起来它甚至比JS的对象解构新功能要好,但实际上并非如此。

with结构已弃用,不建议使用。 在严格模式下,禁止使用它。 事实证明, with块会导致性能和安全性问题。

函数构造器


使用function关键字不是定义新函数的唯一方法。 您可以使用Function构造函数和new运算符动态定义函数。 这是它的外观。

 //  Function const multiply = new Function('x', 'y', 'return x*y') multiply(2,3) // 6 

传递给构造函数的最后一个参数是带有功能代码的字符串。 另外两个参数是函数参数。

有趣的是, Function构造函数是JavaScript中所有构造Function的“父”。 甚至Object构造函数都是Function构造函数。 本机Function构造Function也是Function 。 结果,对任何JS对象进行足够多次的类型object.constructor.constructor...的调用将返回Function构造Function

特征属性


我们都知道JavaScript中的函数是一流的对象。 因此,没有人阻止我们向函数添加新属性。 这是完全正常的,但很少使用。

什么时候需要?

实际上,在许多情况下此功能可能会派上用场。 考虑他们。

▍自定义功能


假设我们有一个greet()函数。 我们需要她根据所使用的区域设置显示不同的欢迎消息。 这些设置可以存储在函数外部的变量中。 此外,该功能可以具有定义这些设置(特别是用户的语言设置)的属性。 我们将使用第二种方法。

 //  ,   function greet () { if (greet.locale === 'fr') {   console.log('Bonjour!') } else if (greet.locale === 'es') {   console.log('Hola!') } else {   console.log('Hello!') } } greet() // Hello! greet.locale = 'fr' greet() // Bonjour! 

with带静态变量的功能


这是另一个类似的例子。 假设我们需要实现一个生成一系列有序数字的生成器。 通常在这种情况下,为了存储有关最后生成的数字的信息,将使用类或IIFE中的静态计数器变量。 通过这种方法,我们限制了计数器的访问,并防止了带有其他变量的全局范围的污染。

但是,如果我们需要灵活性,如果我们需要读取甚至修改此类计数器的值而不阻塞全局范围,该怎么办?

当然,您可以使用相应的变量和允许您使用它的方法来创建一个类。 或者,您不能为此类事情而烦恼,而只是使用函数的属性。

 //  ,   function generateNumber () { if (!generateNumber.counter) {   generateNumber.counter = 0 } return ++generateNumber.counter } console.log(generateNumber()) // 1 console.log(generateNumber()) // 2 console.log('current counter value: ', generateNumber.counter) // current counter value: 2 generateNumber.counter = 10 console.log('current counter value: ', generateNumber.counter) // current counter value: 10 console.log(generateNumber()) // 11 

参数对象属性


我相信大多数人都知道函数有一个arguments对象。 这是在所有函数中均可访问的类似数组的对象(箭头函数除外,后者没有自己的arguments对象)。 它包含调用时传递给函数的参数列表。 此外,它还具有一些有趣的属性:

  • arguments.callee包含指向当前函数的链接。
  • arguments.caller包含对调用当前函数的函数的引用。

考虑一个例子。

 //  callee  caller  arguments const myFunction = function () { console.log('Current function: ', arguments.callee.name) console.log('Invoked by function: ', arguments.callee.caller.name) } void function main () { myFunction() } () // Current function: myFunction // Invoked by function: main 

ES5标准禁止在严格模式下使用callee callercaller属性,但是在许多JavaScript编译的程序文本中(例如在库中)仍然广泛使用它们。 因此,了解它们很有用。

标记模板文字


当然,如果您与JavaScript编程有任何关系,就听说过模板文字 。 模板文字是ES6标准的许多重大创新之一。 但是,您了解带标签的模板文字吗?

 //    `Hello ${username}!` //    myTag`Hello ${username}!` 

带有标记的模板文字使开发人员可以控制模板文字如何变成字符串。 这是通过使用特殊标签来完成的。 标签只是解析器函数的名称,该函数接收由字符串模式解释的字符串和值的数组。 使用标记函数时,预期它将返回完成的字符串。

在以下示例中,我们的标记highlight解释了模板文字的数据并将其嵌入到最后一行中,并将其放置在HTML <mark>标记中以在网页上显示此类文本时将其选中。

 //    function highlight(strings, ...values) { //  i -      let result = '' strings.forEach((str, i) => {   result += str   if (values[i]) {     result += `<mark>${values[i]}</mark>`   } }) return result } const author = 'Henry Avery' const statement = `I am a man of fortune & I must seek my fortune` const quote = highlight`${author} once said, ${statement}` // <mark>Henry Avery</mark> once said, <mark>I am a man of fortune // & I must seek my fortune</mark> 

在许多库中都可以找到使用此功能的有趣方式。 以下是一些示例:


ES5中的获取器和设置器


大多数情况下,JavaScript对象非常简单。 假设我们有一个user对象,并且我们正在尝试使用user.age构造访问它的age属性。 使用这种方法,如果定义了此属性,则将获取其值,如果未定义,则将获取undefined 。 一切都非常简单。

但是使用属性根本不需要那么原始。 JS对象实现了getter和setter的概念。 代替直接返回对象某些属性的值,我们可以编写我们自己的getter函数,该函数返回我们认为必要的值。 这同样适用于使用setter函数将新值写入属性的情况。

使用Getter和Setter可以实现用于处理属性的高级方案。 读取或写入属性时,可以使用虚拟字段的概念,可以检查字段的值,并且在写入或读取时,可能会发生一些有用的副作用。

 //    const user = { firstName: 'Nathan', lastName: 'Drake', // fullname -    get fullName() {   return this.firstName + ' ' + this.lastName }, //      set age(value) {   if (isNaN(value)) throw Error('Age has to be a number')   this._age = Number(value) }, get age() {   return this._age } } console.log(user.fullName) // Nathan Drake user.firstName = 'Francis' console.log(user.fullName) // Francis Drake user.age = '29' console.log(user.age) // 29 // user.age = 'invalid text' // Error: Age has to be a number 

吸气剂和吸气剂不是ES5标准的创新。 它们始终以该语言出现。 在ES5中,仅添加了方便的语法工具来使用它们。 可以在这里找到有关获取器和设置器的细节。

使用吸气剂的示例包括流行的Node.js Colors库。

该库扩展了String类并向其添加了许多getter方法。 这样,您就可以将字符串转换为其“彩色”版本,以便将该字符串用于日志记录。 这是通过使用字符串属性完成的

逗号运算符


JS有一个逗号运算符。 它允许您在一行中用逗号分隔编写多个表达式,并返回计算最后一个表达式的结果。 这就是这种设计的样子。

 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 } 

设计小箭头功能时,此功能很有用。

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

加号运算符


如果您需要快速将字符串转换为数字,则加号运算符对您很有用。 他能够处理各种数字,而且不仅可以处理积极的数字。 我们正在谈论负数,八进制数,十六进制数和指数表示法中的数字。 而且,它能够将Date对象和Moment.js库对象转换为时间戳。

 //  "" +'9.11'          // 9.11 +'-4'            // -4 +'0xFF'          // 255 +true            // 1 +'123e-5'        // 0.00123 +false           // 0 +null            // 0 +'Infinity'      // Infinity +'1,234'         // NaN +new Date      // 1542975502981 ( ) +momentObject    // 1542975502981 ( ) 

双感叹号


应该注意的是,有时被称为“双感叹号运算符”(Bang Bang或Double Bang)实际上不是运算符。 这是一个逻辑NOT运算符,或者是一个逻辑惊叹号,看起来像一个重复两次的感叹号。 双重感叹号是好的,因为它允许您将任何表达式转换为布尔值。 从JS的角度来看,如果该表达式为true,则在使用双感叹号对其进行处理后,将返回true 。 否则,将返回false

 //     !!null            // false !!undefined       // false !!false           // false !!true            // true !!""              // false !!"string"        // true !!0               // false !!1               // true !!{}              // true !![]              // true 

按位求反运算符


让我们面对现实:没有人关心按位运算符。 我不是在谈论使用它们。 但是,按位取反运算符可以在许多情况下使用。

当将此运算符应用于数字时,它将按以下方式进行转换:从数字N得出-(N+1) 。 如果N-1则该表达式为0

当此功能用于检查数组或字符串中元素的存在时,可以与indexOf()方法一起使用,因为如果找不到该元素,此方法将返回-1

 //      indexOf let username = "Nathan Drake" if (~username.indexOf("Drake")) { console.log('Access denied') } else { console.log('Access granted') } 

应当注意,分别在ES6和ES7标准中,对于字符串和数组,出现了includes()方法。 与使用按位求反运算符和indexOf()相比,确定元素的存在绝对更加方便。

命名块


JavaScript具有标签的概念,您可以使用它为循环分配名称(标签)。 然后,在应用breakcontinue语句时,可以使用这些标签来引用适当的循环。 标签也可以分配给常规代码块。

带标签的循环在使用嵌套循环时很有用。 但是,它们也可以用于方便地在块中组织代码,或者在创建可以中断代码的块时使用它们。

 //    declarationBlock: { //       //     var i, j } forLoop1: //     - "forLoop1" for (i = 0; i < 3; i++) {       forLoop2: //     -  "forLoop2"  for (j = 0; j < 3; j++) {       if (i === 1 && j === 1) {        continue forLoop1     }     console.log('i = ' + i + ', j = ' + j)  } } /* i = 0, j = 0 i = 0, j = 1 i = 0, j = 2 i = 1, j = 0 i = 2, j = 0 i = 2, j = 1 i = 2, j = 2 */ //      loopBlock4: { console.log('I will print') break loopBlock4 console.log('I will not print') } // I will print 

请注意,与某些其他语言不同,JS中没有goto 。 因此,标签仅与breakcontinue语句一起使用。

总结


在本文中,我们讨论了鲜为人知的JavaScript功能,这些知识对于任何JS程序员都是有用的,至少是为了准备好迎接其他人代码中的异常。 如果您对“未知的JS”主题感兴趣,则可以阅读我们的出版物。

亲爱的读者们! 如果您了解JS的一些鲜为人知的功能并查看其实际应用的选项,请向我们介绍。

Source: https://habr.com/ru/post/zh-CN431878/


All Articles