[译]JS里我才知道的东西

2,849 阅读4分钟

我告诉大家一个写作心得,文章是靠改的。我的文章通常要改六七次,看上去像一气呵成,其实花费很多精力。

—— 《木心专访》

译文原链:air.ghost.io/js-things-i…

导读:2017年底,本文作者 Nick 通读了 MDN 文档,发现了一些之前不知道的 JS 特性和 API,于是写了一篇简短的小文。作者说:“这份简短的清单,对你可能并没有用,却阐释了 JS 学习之路永无止境的道理”。

注:文章翻译时,有删改。

Label 语句

在 JS 里可以为 for 循环和代码块标记名称。在 for 循环内部,可以使用 breakcontinue 指向此标记名称;而在代码块中,只能使用 break 终结某个标记块的进一步执行。

loop1: // 外层循环标记为 "loop1" 
for (let i = 0; i < 3; i++) { // "loop1"
   loop2: // 内层循环标记为 "loop2"
   for (let j = 0; j < 3; j++) { // "loop2"
      if (i === 1) {
         continue loop1; // 在此终止上层 "loop1" 循环的此次迭代、进入下一次迭代
         // break loop1; // 结束上层 "loop1" 循环
      }
      console.log(`i = ${i}, j = ${j}`);
   }
}

/*
输出:

i = 0, j = 0
i = 0, j = 1
i = 0, j = 2
i = 2, j = 0
i = 2, j = 1
i = 2, j = 2
*/

而标记了名称的代码块中只能使用 break 关键字:

foo: {
  console.log('第一条 log');
  break foo;
  console.log('这条 log 不会被打印出来的');
}
console.log('第二条 log');

/*
输出:

第一条 log
第二条 log
*/

void 操作符

作者在阅读 JS since 1996 的时候,知道了 void 操作符,受到所有浏览器的支持,MDN 的解释是:

void 操作符会计算后面给定的表达式,然后返回 undefined

这样我们就多了 IIFE 的另一种写法:

void function iife() {
    console.log('你好啊');
}();

// 跟下面是一样的

(function iife() {
    console.log('你好~');
})()

void 操作符不管后面表达式的计算结果如何,总是返回 undefined

const word = void function iife() {
	return 'hello';
}();

// word 的值是 `undefined`

const word = (function iife() {
	return 'hello';
})();

// 这里 word 的值是 `'hello'`

void 还可与 async 结合使用,可以使用它作为异步代码入口点。

void async function() { 
    try {
        const response = await fetch('air.ghost.io'); 
        const text = await response.text();
        console.log(text);
    } catch(e) {
        console.error(e);
    }
}()

// 或者还是坚持原来的写法 :)

(async () => {
    try {
        const response = await fetch('air.ghost.io'); 
        const text = await response.text();
        console.log(text);
    } catch(e) {
        console.error(e);
    }
})();

逗号运算符

来自于 MDN 的好解释:

逗号运算符会计算每一个操作数的结果(从左到右),然后返回最后一个操作数的计算结果。

function myFunc() {
  let x = 0;
  return (x += 1, x); // 等同于 return ++x;
}
console.log(myFunc()) // 1

y = false, true; // 这条语句的执行结果是 true
console.log(y); // y 的值是 false

z = (false, true); // 等号右侧表达式的值是 true
console.log(z); // z 的值是 true

/*
输出:

1
false
true
*/

与条件/三目操作符结合使用

逗号运算符表达式总是返回最后一个值,因此在条件运算符的判断中,我们可以在最后一个值之前添加任意数量的表达式。在下面的例子中,我们在返回布尔值之前,放了一个 console log 在前面。

const type = 'man';

const isMale = type === 'man' ? (
    console.log('嗨,先生们!'),
    true
) : (
    console.log('嗨,女士们!'),
    false
);

console.log(`isMale 的值是 "${isMale}"`);

/*
输出:

嗨,先生们!
isMale 的值是 "true"
*/

Internationalization API

国际化 API 现在已经得到很好的支持了,我最喜欢的特性之一就是格式化时间的 DateTimeFormat 了。

const date = new Date();

const options = {
  year: 'numeric', 
  month: 'long', 
  day: '2-digit',
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
  hour12: false
};

const formatter1 = new Intl.DateTimeFormat('zh-CN', { ...options, timeZone: 'Asia/Shanghai' });
console.log(formatter1.format(date));

const formatter2 = new Intl.DateTimeFormat('en-US', { ...options, timeZone: 'America/New_York' });
console.log(formatter2.format(date));

/*
输出:

2019年7月04日 10:26:03
July 03, 2019, 22:26:03
*/

Array.prototype.reduceRight

这个方法相当于是 Array.prototype.reverse() + Array.prototype.reduce() 的集合体。从右向左 reduce 数组。

const array = [[0, 1], [2, 3], [4, 5]]
const flattened = array.reduceRight(function(a, b) {
    return a.concat(b);
}, []);

// 扁平化后的 `array` (即 flattened)的值为 [4, 5, 2, 3, 0, 1]

setTimeout() 的参数

setTimeout 方法用于定时执行一段函数,方法的第一个参数指定触发函数执行的时间间隔(单位:毫秒)。

在调用函数时,我们可能会预先为要执行的函数,先指定前几个参数,你可能会感觉要使用 .bind(...) 方法实现此功能。然而我们不需要这么做。

setTimeout 方法第二个往后的参数会在调用函数时,会作为参数在函数上使用的。

setTimeout(alert, 1000, '你好,世界!');

/*
输出:(alert)

你好,世界!
*/

function log(text, textTwo) {
    console.log(text, textTwo);
}

setTimeout(log, 1000, '你好,世界!', '还有火星!');

/*
输出:

你好,世界! 还有火星!
*/

HTMLElement.dataset

这是查询 HTML 自定义属性 data-* 的时候用到一个很方便的 API。不过有一些命名限制,大家知道 HTML 属性是不区分大小写的,自定义属性的命名方式采用 dash-case 的形式,而在 JS 里查询时,则是使用对应的 camelCase 形式(忽略 data-* 前缀)。比如 data-birth-planet 在 JS 访问使用的是 birthPlanet

<div id='person' data-name='john' data-birth-planet='earth'></div>

现在用 JS 查询:

let personEl = document.querySelector('#person');

console.log(personEl.dataset) // DOMStringMap {name: "john", birthPlanet: "earth"}
console.log(personEl.dataset.name) // john
console.log(personEl.dataset.birthPlanet) // earth

// 使用编程的方式添加自定义属性
personEl.dataset.foo = 'bar';
console.log(personEl.dataset.foo); // bar
// 删除自定义属性
delete personEl.dataset.foo

(完)