资讯专栏INFORMATION COLUMN

ES6新语法疑点简析

silencezwm / 2708人阅读

摘要:本文涵盖了一些新语法可能造成疑惑的地方和一些建议。新接口的迭代器参数的误调用接口及集合类构造器的参数,可以放入支持迭代器的内容,而不局限于数组兼容。新集合类容器的构造器集合类容器不可以通过非方式来构造。

本文涵盖了一些ES6新语法可能造成疑惑的地方和一些建议。

1# 箭头函数

箭头函数看起来像是匿名函数表达式function(){}的简写,然而它不是。

这个例子应该很容易看出来会有怎样的问题:

</>复制代码

  1. function Apple(){}
  2. Apple.prototype.check = ()=>{
  3. console.log(this instanceof Apple);
  4. };
  5. (new Apple()).check() // false

使用apply、call、bind改变箭头函数的this指向呢?

</>复制代码

  1. var i = 0;
  2. var xx = ()=>{ console.log(++i, this) };
  3. var yy = function(){ console.log(++i, this) };
  4. xx(); // 1 window
  5. xx.apply([]); // 2 window
  6. xx.bind([])(); // 3 window
  7. yy(); // 4 window
  8. yy.apply([]); // 5 []
  9. yy.bind([])(); // 6 []

显然apply、call、bind无法改变箭头函数的this指向,箭头函数的this确定后无法更改。

在这些场景中不要使用箭头函数:

当你需要正常使用this binding时,如函数构造器、prototype

当你需要动态改变this的时候

针对工作报酬和代码量呈反比的程序猿,在需要用到this binding的场景里,可能比较适合的简写形式是在新对象字面量语法里提供的:

</>复制代码

  1. var obj = {
  2. hello() { // 少写了一个function耶!
  3. console.log("world")
  4. }
  5. };
2# Promise 2.1# then

</>复制代码

  1. //1
  2. fetch(xx, oo).then(handleResultAndReturnsAnPromise(result));
  3. //2
  4. fetch(xx, oo).then(handleResultAndReturnsAnPromise);
  5. //3
  6. fetch(xx, oo).then((result) => handleResultAndReturnsAnPromise(result));
  7. //4
  8. fetch(xx, oo).then(function(result) { handleResultAndReturnsAnPromise(result) });

1与2、3、4均不等价:1同步调用了handleResultAndReturnsAnPromise;而2~4均会导致handleResultAndReturnsAnPromise在fetch之后完成

2与3/4则是运行时的调用栈有区别,3/4额外创建了一个匿名函数。

3与4除了this binding的区别,4的调用返回值没有进行返回,这样将导致promise链断裂。

1中需要注意的是,then(promise)里面传一个 Promise 对象是没有什么意义的,它会被当成then(null),在下面推荐的文章中,它被称作“Promise 穿透”

更多的令人混淆的案例,请继续阅读《谈谈使用 promise 时候的一些反模式》。

2.2# catch

在node的一些版本中,采用Promise并忘记给promise链增加catch(fn)then(null, fn),将导致代码中的异常被吞掉。

这个问题在新的v8中(node 6.6+,chrome最新版)会导致一个UnhandledPromiseRejectionWarning,防止开发遗漏。

</>复制代码

  1. node -e "Promise.reject()"
  2. # UnhandledPromiseRejectionWarning: Unhandled promise rejection
2.3# resolve

Promise接口和jQuery实现的接口不一样,resolve只接受单参数,then的回调也只能拿到单参数。

在Promise规范中的单参数链式调用场景下,可以利用解构、_.spread、访问自由变量等方式来处理多个过程中得到的值:

</>复制代码

  1. new Promise(function(resolve, reject){
  2. let something = 1,
  3. otherstuff = 2;
  4. resolve({something, otherstuff});
  5. }).then(function({something, otherstuff}){
  6. // handle something and otherstuff
  7. });

</>复制代码

  1. Promise.all([
  2. Promise.resolve(40), Promise.resolve(36)
  3. ]).then(
  4. _.spread(function(first, second){
  5. // first: 40, second: 36
  6. })
  7. );

</>复制代码

  1. let someMiddleResult;
  2. fetch()
  3. .then(function(fetchResult){
  4. someMiddleResult = fetchResult;
  5. })
  6. .then(otherHandleFn)
  7. .then(function(otherHandleFnResult){
  8. // use both someMiddleResult and otherHandleFnResult now
  9. })
2.4# reject / throw

出现reject接口,应该是第一次前端有机会拿异常处理流程做正常流程(比如*)。不要这样做。

由于reject(new Error(""))throw new Error("")都能作为catch的入口,一些不可预知的错误被抛出的时候,这样的处理方式将会复杂化catch内的代码。不要用异常处理逻辑来做正常处理流程,这个规则保证了代码可读性与可维护性。

throwreject都可以作为catch的入口,它们更加详细的区别如下:

</>复制代码

  1. new Promise((resolve, reject) => {
  2. setTimeout(function(){
  3. reject(new Error("hello"));
  4. });
  5. }).catch(() => console.log("reject"));
  6. // reject
  7. new Promise((resolve, reject) => {
  8. setTimeout(function(){
  9. throw new Error("hello");
  10. });
  11. }).catch(() => console.log("throw"));
  12. // Uncaught Error: hello

reject能够“穿透”回调;而throw限于函数作用域,无法“穿透”回调。

建议:

正常流程请选择在then的时候if..else,不要用reject替代

在需要走异常处理流程的时候封装Error抛出,可以最大化的化简catch回调里面的处理逻辑,类似于e instanceof MyDesignedError

由于回调函数里的throw无法被自动捕获到,如果需要在回调中reject当前 promise,那么我们需要用reject而不是throw

在使用Promise接口的 polyfill 的场景,应当在reject后加一个return

3# let & const & var

看起来letconst的组合就像是一个能完全灭掉var的新特性,但对旧代码不能简单的正则替换掉var,因为我们太习惯于滥用它的特性了——主要是声明提升。

一些情形下会造成语法错误:

</>复制代码

  1. try {
  2. let a = 10;
  3. if (a > 2) {
  4. throw new Error();
  5. }
  6. // ...
  7. } catch (err) {
  8. console.log(a);
  9. // 若为var声明,不报错
  10. // 若为const、let声明:Uncaught ReferenceError: a is not defined
  11. }

除了try..catch,隐式造就的块级作用域在forif..else中也将造成问题:

</>复制代码

  1. if(false) {
  2. let my = "bad";
  3. } else {
  4. console.log(my); // ReferenceError: my is not defined
  5. }

解决方案倒是很简单,将作用域内的let放在更靠外层的位置即可。

varletconst的区别如下(部分参考自stackoverflow*):

作用域:letconst将创造一个块级作用域,在作用域之外此变量不可见,作用域外访问将导致SyntaxErrorvar遵循函数级作用域

全局影响:全局作用域下的var使用等同于设置window/global之上的内容,但letconst不会

提升行为:var声明有提升到当前函数作用域顶部的特性,但constlet没有,在声明前访问变量将导致SyntaxError

重新赋值:对const变量所做的重新赋值将导致TypeError,而varlet不会

重新声明:var声明的变量使用var再次声明不会出现SyntaxError,但constlet声明的变量不能被重新声明,也不能覆盖掉之前任何形式的声明:

</>复制代码

  1. var vVar = 1;
  2. const vConst = 2;
  3. let vLet = 3;
  4. var vVar = 4; // success
  5. let vVar = 5; // SyntaxError
  6. const vVar = 6; // SyntaxError
  7. var vConst = 7; // SyntaxError
  8. let vConst = 8; // SyntaxError
  9. const vConst = 9; // SyntaxError
  10. var vLet = 10; // SyntaxError
  11. let vLet = 11; // SyntaxError
  12. const vLet = 12; // SyntaxError
4# 边界

本篇章集结 ES6 给予的不同边界条件,部分编译自 You don"t know JS

4.1# 函数默认参数值

</>复制代码

  1. function before(a) { var a = a || 1; console.log(a); }
  2. function after(a = 1) { console.log(a); }
  3. before(NaN) // 1
  4. after(NaN) // NaN

新的写法的fallback逻辑只针对undefined有效。

4.2# Object.assign

Object.assign将赋予所有的可枚举值,但不包含从原型链继承来的值:

</>复制代码

  1. let arr = [1, 2, 3],
  2. obj = {};
  3. Object.assign(obj, arr);
  4. obj[1] // 2
  5. obj.length // undefined
  6. Object.getOwnPropertyDescriptors(arr).length.enumerable // false

此外:Object.assign仅仅进行浅拷贝:

</>复制代码

  1. var orig = {
  2. a: [1, 2, 3]
  3. },
  4. nObj = {};
  5. Object.assign(nObj, orig);
  6. orig.a.push(4);
  7. nObj.a // [1, 2, 3, 4]
4.3# NaN

Number.isNaN和全局空间中的isNaN的区别在于不存在隐式转换:

</>复制代码

  1. isNaN("number") // true
  2. Number.isNaN("number") // false

Object.is除了区分正负零这个非常小众的边界,这个接口相对===更大的意义是判断NaN:

</>复制代码

  1. Object.is(NaN, NaN); // true
  2. NaN === NaN; // false

</>复制代码

  1. Object.is(+0, -0); // false
  2. +0 === -0; // true

同样的,arr.includes(xx)arr.lastIndexOf(xx) > -1好的地方也包括对于NaN的处理:

</>复制代码

  1. [1, 2, NaN].includes(NaN); // true
4.4# Number

isFiniteNumber.isFinite的区别也是后者不存在隐式转换:

</>复制代码

  1. isFinite("42"); // true
  2. Number.isFinite("42"); // false

Number.isInteger表示一个数是不是小数,和x === Math.floor(x)的区别在于对Infinity的处理

</>复制代码

  1. Number.isInteger(Infinity); // false
  2. Infinity === Math.floor(Infinity); // true

Number.isSafeInteger表示传入的数值有没有精度损失,它比较的是数字是否在Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER之间:

</>复制代码

  1. Number.isSafeInteger(Math.pow(2, 53) - 1); // true
  2. Number.isSafeInteger(Math.pow(2, 53)); // false

我曾整理过Number的数轴(*),也写过JavaScript中的一些数字内存模型的demo,其中有一部分值没有直接的量来表示,但现在有了。

从负无穷往正无穷来看,是这样的:

Number.NEGATIVE_INFINITY 负无穷

-Number.MAX_VALUE 能表示的最小数字,更小被视为负无穷,等于-(2^53-1)*(2^971)

Number.MIN_SAFE_INTEGER(新) 没有精度误差的最小数,等于-(2^53-1)

0 正负零

Number.EPSILON(新) IEEE 754规范下的精度位允许的最小差异值,等于2^-52

Number.MIN_VALUE 能表示的最小正整数,这是一个IEEE 754规范下的反规格化值,等于2^-1074

Number.MAX_SAFE_INTEGER(新) 没有精度误差的最大数,,等于2^53-1

Number.MAX_VALUE 能表示的最大数字,更大被视为正无穷,等于(2^53-1)*(2^971)

Number.INFINITY 正无穷

比较令人混淆的是Number.EPSILONNumber.MIN_VALUE,前者为精度位允许的最小差异值,考虑的是浮点数的精度位;而后者考虑的是利用到浮点数的所有位置能够表示的最小正数值。

5# 怪奇错误展

本节收集了一些奇奇怪怪的错误提示,正常写出的代码不会导致它们,没有兴趣可以略过。

5.1# 新接口的迭代器参数

</>复制代码

  1. Array.from(1, 2, 3) // Array.of(1,2,3)的误调用
  2. // 2 is not a function

Array.fromPromise.all接口及集合类构造器的参数,可以放入支持迭代器的内容,而不局限于数组(node 0.12+兼容)。这里其实尝试去调用了参数的迭代器Symbol.iterator

5.2# 新集合类容器的构造器

</>复制代码

  1. Array(); // []
  2. Set(); // Uncaught TypeError: Constructor Set requires "new"

集合类容器Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array Set不可以通过非new方式来构造。

5.3# Tagged Template

</>复制代码

  1. var x = 30
  2. `abcdefg`
  3. // Uncaught TypeError: 30 is not a function

模版语法可能是ES6最为显然的语法,但它的扩展形式Tagged Template在极端场景可能造成一个奇怪的报错,算是对不写分号党造成的又一个暴击*。

6# 欺负新来的

本篇章集结一些被滥用的特性。

6.1

解构特性很棒,它可以在promise这样的单参数链式调用场景或是正则匹配场景中大方光芒,更为经典的是python风格的[y, x] = [x, y]

但如果一个人铁了心要疯狂解构,新来维护这份代码的人就要默默流下痛苦的眼泪了:

</>复制代码

  1. // 新人:是什么阻止了你用 a2 = [o1[a], o1[b], o1[c]] ……
  2. var o1 = { a: 1, b: 2, c: 3 },
  3. a2 = [];
  4. ( { a: a2[0], b: a2[1], c: a2[2] } = o1 );

</>复制代码

  1. // 老人:看得爽吗
  2. var { a: { b: [ c, d ], e: { f } }, g } = obj;

</>复制代码

  1. // 主管:写到一半这个程序猿已经被打死了
  2. var x = 200, y = 300, z = 100;
  3. var o1 = { x: { y: 42 }, z: { y: z } };
  4. ( { y: x = { y: y } } = o1 );
  5. ( { z: y = { y: z } } = o1 );
  6. ( { x: z = { y: x } } = o1 );

一个可以尝试的保持代码可读性的方法,是尽量保证解构的层次低。

6.2

新对象字面量也很不错,新的rest操作符也很实用,但是如果你们把它们混在一起……下面进一段代码赏析(*):

</>复制代码

  1. export const sharePostStatus = createReducer( {}, {
  2. [ PUBLICIZE_SHARE ]: ( state, { siteId, postId } ) => ( { ...state, [ siteId ]: { ...state[ siteId ], [ postId ]: {
  3. requesting: true,
  4. } } } ),
  5. [ PUBLICIZE_SHARE_SUCCESS ]: ( state, { siteId, postId } ) => ( { ...state, [ siteId ]: { ...state[ siteId ], [ postId ]: {
  6. requesting: false,
  7. success: true,
  8. } } } ),
  9. [ PUBLICIZE_SHARE_FAILURE ]: ( state, { siteId, postId, error } ) => ( { ...state, [ siteId ]: { ...state[ siteId ], [ postId ]: {
  10. requesting: false,
  11. success: false,
  12. error,
  13. } } } ),
  14. [ PUBLICIZE_SHARE_DISMISS ]: ( state, { siteId, postId } ) => ( { ...state, [ siteId ]: {
  15. ...state[ siteId ], [ postId ]: undefined
  16. } } ),
  17. } );

尽可能的保持代码的可读性,一行只用不超过2个ES6特性或许是一个可操作的方案。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81332.html

相关文章

  • Loader学习,简析babel-loader

    摘要:用来转换内容,内部调用了方法进行转换,这里简单介绍一下的原理将代码解析成,对进行转译,得到新的,新的通过转换成,核心方法在中的方法,有兴趣可以去了解一下。将函数传入参数和归并,得到。通常我们是用不上的,估计在某些中可能会使用到。 什么是Loader? 继上两篇文章webpack工作原理介绍(上篇、下篇),我们了解到Loader:模块转换器,也就是将模块的内容按照需求装换成新内容,而且每...

    wpw 评论0 收藏0
  • Immer.js简析

    摘要:所以整个过程只涉及三个输入状态,中间状态,输出状态关键是是如何生成,如何应用修改,如何生成最终的。至此基本把上的模式解析完毕。结束实现还是相当巧妙的,以后可以在状态管理上使用一下。 开始 在函数式编程中,Immutable这个特性是相当重要的,但是在Javascript中很明显是没办法从语言层面提供支持,但是还有其他库(例如:Immutable.js)可以提供给开发者用上这样的特性,所...

    Aceyclee 评论0 收藏0
  • Immer.js简析

    摘要:所以整个过程只涉及三个输入状态,中间状态,输出状态关键是是如何生成,如何应用修改,如何生成最终的。至此基本把上的模式解析完毕。结束实现还是相当巧妙的,以后可以在状态管理上使用一下。 开始 在函数式编程中,Immutable这个特性是相当重要的,但是在Javascript中很明显是没办法从语言层面提供支持,但是还有其他库(例如:Immutable.js)可以提供给开发者用上这样的特性,所...

    dackel 评论0 收藏0
  • 前端计划——JavaScript正则表达式快速入门

    摘要:前言正则表达式时处理字符串中常用的手法,本文以简单的方式,快速展示了中正则相关的基础知识点。文末还提供了几个简单的正则相关面试题。接下来是正则部分,注意后面的并不匹配,也就是比如,实际匹配的值是和,在和后面加上,就完成了预期。 前言:正则表达式时处理字符串中常用的手法,本文以简单的方式,快速展示了JavaScript中正则相关的基础知识点。文末还提供了几个简单的正则相关面试题。个人总结...

    Xufc 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<