wtfjs:一个接近20k关注的有趣JS项目(下)

本文探讨了JavaScript中的一些不直观的行为,如多重继承的误解、逗号运算符的用法、类内部的类声明、非强制对象的创建、箭头函数的特点、返回语句的陷阱、对象链式赋值的细节、数组访问对象属性、浮点数的表示问题、Math函数的怪异行为以及Promise解析的规则。通过这些例子,深入了解JavaScript的微妙之处。
摘要由CSDN通过智能技术生成

这是多重继承吗?

看下面的例子:

new class F extends (String, Array) {}(); // -> F []

这是多重继承吗?不。

???? 说明:

有趣的部分是 extends 子句的值((String,Array))。分组运算符总是返回其最后一个参数,所以 (String,Array) 实际上只是 Array。这意味着我们刚刚创建了一个扩展 Array 的类。

  • 14.5 类定义

  • 12.16 逗号运算符 (,)

考虑一下这个 yield 自身的生成器例子:

(function* f() {
  yield f;
})().next();
// -> { value: [GeneratorFunction: f], done: false }

如您所见,返回的值是一个值等于 f 的对象。那样的话,我们可以做这样的事情:

(function* f() {
  yield f;
})()
  .next()
  .value()
  .next()(
    // -> { value: [GeneratorFunction: f], done: false }

    // 再一次
    function* f() {
      yield f;
    }
  )()
  .next()
  .value()
  .next()
  .value()
  .next()(
    // -> { value: [GeneratorFunction: f], done: false }

    // 再一次
    function* f() {
      yield f;
    }
  )()
  .next()
  .value()
  .next()
  .value()
  .next()
  .value()
  .next();
// -> { value: [GeneratorFunction: f], done: false }

// 以此类推
// …

???? 说明:

要理解为什么这样工作,请阅读规范的这些部分:

  • 25 Control Abstraction Objects

  • 25.3 Generator Objects

一个类的类

考虑这个混淆语法:

typeof new class {
  class() {}
}(); // -> 'object'

似乎我们在类内部声明了一个类。应该是个错误,然而,我们得到一个 'object' 字符串。

???? 说明:

ECMAScript 5 时代以来,关键字允许访问属性。所以请考虑一下这个简单的对象示例:

const foo = {
  class: function() {}
};

还有 ES6 标准速记方法定义。此外,类可能是匿名的。因此,如果我们放弃 : function 部分,我们将得到:

class {
  class() {}
}

默认类的结果总是一个简单的对象。其类型应返回 'object' 。

在这里阅读更多

  • 14.3 Method Definitions

  • 14.5 Class Definitions

非强制对象

有着名的符号,有一种方法可以摆脱类型的强制。看一看:

function nonCoercible(val) {
  if (val == null) {
    throw TypeError("nonCoercible should not be called with null or undefined");
  }

  const res = Object(val);

  res[Symbol.toPrimitive] = () => {
    throw TypeError("Trying to coerce non-coercible object");
  };

  return res;
}

现在我们可以这样使用:

// objects
const foo = nonCoercible({ foo: "foo" });

foo * 10; // -> TypeError: Trying to coerce non-coercible object
foo + "evil"; // -> TypeError: Trying to coerce non-coercible object

// strings
const bar = nonCoercible("bar");

bar + "1"; // -> TypeError: Trying to coerce non-coercible object
bar.toString() + 1; // -> bar1
bar === "bar"; // -> false
bar.toString() === "bar"; // -> true
bar == "bar"; // -> TypeError: Trying to coerce non-coercible object

// numbers
const baz = nonCoercible(1);

baz == 1; // -> TypeError: Trying to coerce non-coercible object
baz === 1; // -> false
baz.valueOf() === 1; // -> true

???? 说明:

  • A gist by Sergey Rubanov

  • 6.1.5.1 Well-Known Symbols

棘手的箭头功能

考虑下面的例子:

let f = () => 10;
f(); // -> 10

好吧,但是这是怎么说的呢?

let f = () => {};
f(); // -> undefined

???? 说明:

你可能期待 {} 而不是 undefined。这是因为花括号是箭头函数语法的一部分,所以 f 会返回未定义的。然而要从箭头函数直接返回 {} 对象也是可能的,要通过用括号把返回值括起来。

箭头函数不能作为构造器

考虑下面的例子:

let f = function() {
  this.a = 1;
};
new f(); // -> { 'a': 1 }

现在,试着用箭头函数做同样的事情:

let f = () => {
  this.a = 1;
};
new f(); // -> TypeError: f is not a constructor

???? 说明:

箭头函数不能作为构造器并且会在被 new 时抛出错误。因为它有一个词域的 this,而且也没有 prototype 属性,所以这样做没什么意义。

arguments 和箭头函数

考虑下面的例子:

let f = function() {
  return arguments;
};
f("a"); // -> { '0': 'a' }

现在,试着用箭头函数做同样的事情:

let f = () => arguments;
f("a"); // -> Uncaught ReferenceError: arguments is not defined

???? 说明:

箭头函数是注重短小和词域下的 this 的常规函数的轻量级版本。同时箭头函数不提供 arguments 对象的绑定。作为一个有效的替代选择使用 rest parameters 来得到同样的结果:

let f = (...args) => args;
f("a");
  • Arrow functions at MDN.

棘手的返回

return 语句是很棘手的. 看下面的代码:

(function() {
  return
  {
    b: 10;
  }
})(); // -> undefined

???? 说明:

return 和返回的表达式必须在同一行:

(function() {
  return {
    b: 10
  };
})(); // -> { b: 10 }

这是因为一个叫自动插入分号的概念,它会在大部分换行处插入分号。第一个例子里,有一个分号被插入到 return 语句和对象字面量中间。所以函数返回 undefined 而对象字面量不会被求值。

  • 11.9.1 Rules of Automatic Semicolon Insertion

  • 13.10 The return Statement

对象的链式赋值

var foo = { n: 1 };
var bar = foo;

foo.x = foo = { n: 2 };

foo.x; // -> undefined
foo; // -> {n: 2}
bar; // -> {n: 1, x: {n: 2}}

从右到左,{n: 2} 被赋值给 foo,而此赋值的结果 {n: 2} 被赋值给 foo.x,因此 bar 是 {n: 1, x: {n: 2}} 因为 bar 是 foo 的一个引用。但为什么 foo.x 是 undefined 而 bar.x 不是呢?

???? 说明:

foo 和 bar 引用同一个对象 {n: 1},而左值在赋值前解析。foo = {n: 2} 是创建一个新对象,所以 foo 被更新为引用那个新的对象。这里的戏法是 foo.x = ... 中的 foo 作为左值在赋值前就被解析并依然引用旧的 foo = {n: 1} 对象并为其添加了 x 值。在那个链式赋值之后,bar 依然引用旧的 foo 对象,但 foo 引用新的没有 x 的 {n: 2} 对象。

它等价于:

var foo = { n: 1 };
var bar = foo;

foo = { n: 2 }; // -> {n: 2}
bar.x = foo; // -> {n: 1, x: {n: 2}}
// bar.x 指向新的 foo 对象的地址
// 这不等价于:bar.x = {n: 2}

使用数组访问对象属性

var obj = { property: 1 };
var array = ["property"];

obj[array]; // -> 1

那关于伪多维数组创建对象呢?

var map = {};
var x = 1;
var y = 2;
var z = 3;

map[[x, y, z]] = true;
map[[x + 10, y, z]] = true;

map["1,2,3"]; // -> true
map["11,2,3"]; // -> true

???? 说明:

括号操作符将传递给字符串的表达式转换为字符串。将一个元素数组转换为字符串,就像将元素转换为字符串:

["property"].toString(); // -> 'property'`

Null 和关系运算符

null > 0; // false
null == 0; // false

null >= 0; // true

???? 说明:

长话短说,如果 null 小于 0 是 false,那么 null >= 0 则是 true。请阅读这里的详细解释。

Number.toFixed()显示不同的数字

Number.toFixed() 在不同的浏览器中会表现得有点奇怪。看看这个例子:

(0.7875).toFixed(3);
// Firefox: -> 0.787
// Chrome: -> 0.787
// IE11: -> 0.788
(0.7876).toFixed(3);
// Firefox: -> 0.788
// Chrome: -> 0.788
// IE11: -> 0.788

???? 说明:

尽管你的第一直觉可能是 IE11 是正确的而 Firefox/Chrome 错了,事实是 Firefox/Chrome 更直接地遵循数字运算的标准(IEEE-754 Floating Point),而 IE11 经常违反它们(可能)去努力得出更清晰的结果。

你可以通过一些快速的测试来了解为什么它们发生:

// 确认 5 向下取证的奇怪结果
(0.7875).toFixed(3); // -> 0.787
// 当你展开到 64 位(双精度)浮点数准确度限制时看起来就是一个 5
(0.7875).toFixed(14); // -> 0.78750000000000
// 但如果你超越这个限制呢?
(0.7875).toFixed(20); // -> 0.78749999999999997780

浮点数在计算机内部不是以一系列十进制数字的形式存储的,而是通过一个可以产生一点点通常会被 toString 或者其他调用取整的不准确性的更复杂的方法,但它实际上在内部会被表示。

在这里,那个结尾的 "5" 实际上是一个极其小的略小于 5 的分数。将其以任何常理的长度取整它都会被看作一个 5,但它在内部通常不是 5。

IE11,尽管如此,描述这个数字时只是加上一些 0,甚至在 toFixed(20) 的时候也是这样,因为它看起来强制取整了值来减少硬件限制带来的问题。

详见 ECMA-262 中 NOTE 2 的 toFixed 的定义。

  • 20.1.3.3 Number.prototype.toFixed (fractionDigits)

Math.max() 小于 Math.min()

Math.min(1, 4, 7, 2); // -> 1
Math.max(1, 4, 7, 2); // -> 7
Math.min(); // -> Infinity
Math.max(); // -> -Infinity
Math.min() > Math.max(); // -> true

???? 说明:

  • Why is Math.max() less than Math.min()? by Charlie Harvey

比较 null 和 0

下面的表达式似乎有点矛盾:

null == 0; // -> false
null > 0; // -> false
null >= 0; // -> true

null 怎么既不等于也不大于 0,如果null >= 0 实际上是 true?(这也适用于少于同样的方法。)

???? 说明:

执行这三个表达式的方式各不相同,并负责产生这种意外行为。首先,抽象相等比较 null == 0。通常情况下,如果这个运算符不能正确地比较两边的值,则它将两个数字转换为数字,并对数字进行比较。然后,您可能会期望以下行为:

// 事实并非如此
(null == 0 + null) == +0;
0 == 0;
true;

然而,根据对规范的仔细阅读,数字转换实际上并没有发生在 null 或 undefined 的一侧。因此,如果在等号的一侧有 null,则另一侧的表达式必须为 null 或 undefined,以返回 true。既然不是这样,就会返回 false

接下来,关系比较 null > 0 。这里的算法不同于抽象的相等运算符,将 null 转换为一个数字。因此,我们得到这样的行为:

null > 0
+null = +0
0 > 0
false

最后,关系比较 null >= 0。你可以认为这个表达式应该是 null > 0 || null == 0 的结果;如果是这样,那么以上的结果将意味着这也是false。然而,>= 操作符实际上以一种非常不同的方式工作,这基本上与 < 操作符相反。因为我们的例子中,大于运算符的例子也适用于小于运算符,也就是说这个表达式的值是这样的:

null >= 0;
!(null < 0);
!(+null < +0);
!(0 < 0);
!false;
true;
  • 7.2.12 抽象的关系比较

  • 7.2.13 比较抽象的平等

相同变量重复声明

JS 允许重复声明变量:

a;
a;
// 这也是有效的
a, a;

严格模式也可以运行:

var a, a, a;
var a;
var a;

???? 解释:

所有的定义都被合并成一条定义。

  • 13.3.2 Variable Statement

Array.prototype.sort() 的默认行为

想象你需要排序数组中的数字。

[ 10, 1, 3 ].sort() // -> [ 1, 10, 3 ]

???? 说明:

默认排序基于将给定元素转换为字符串,然后比较它们的 UTF-16 序列中的值。

  • 22.1.3.25 Array.prototype.sort ( comparefn )

提示

传入一个 compareFn 比较函数如果你想对字符串以外的内容排序。

[ 10, 1, 3 ].sort((a, b) => a - b) // -> [ 1, 3, 10 ]

resolve() 不会返回 Promise 实例

const theObject = {
  "a": 7,
};
const thePromise = new Promise((resolve, reject) => {
  resolve(theObject);
}); // -> Promise 实例对象

thePromise.then(value => {
  console.log(value === theObject); // -> true
  console.log(value); // -> { a: 7 }
})

thePromise接收到的value值完全就是theObject

那么,如果向resolve传入另外一个Promise会怎样?

const theObject = new Promise((resolve, reject) => {
  resolve(7);
}); // -> Promise 实例对象
const thePromise = new Promise((resolve, reject) => {
  resolve(theObject);
}); // -> Promise 实例对象

thePromise.then(value => {
  console.log(value === theObject); // -> false
  console.log(value); // -> 7
})

???? 说明:

此函数将类promise对象的多层嵌套展平。

– Promise.resolve() on MDN

官方规范是 ECMAScript 25.6.1.3.2 Promise Resolve Functions,由于是机械思维,所以难以读懂。

其他资源

  • wtfjs.com — 这是一组非常特别的不规范,不一致的地方,以及那些对于网络语言来说非常痛苦的不直观的时刻。

  • Wat — A lightning talk by Gary Bernhardt from CodeMash 2012

  • What the... JavaScript? — 凯尔。辛普森一家谈到了前两次试图从 JavaScript 中“拉出疯狂”的尝试。他希望帮助您生成更干净、更优雅、更可读的代码,然后鼓励人们为开源社区做出贡献。

???? License

http://www.wtfpl.net https://npmjs.org/package/wtfjs

© Denys Dovhan

相关文章

  1. 由浅入深,66条JavaScript面试知识点

  2. Javascript 里的奇葩知识

  3. 使用JavaScript的一些小技巧

最后

关注公众号:前端开发博客,后台回复以下关键字:

  1. 回复「1024」领取前端进阶资料

  2. 回复「电子书」领取海量面试和JS资料

  3. 回复「资料」领取前端群分享及培训机构的资料

  4. 回复「Vue」获取 Vue 精选文章

  5. 回复「面试」获取 面试 精选文章

  6. 回复「JS」获取 JavaScript 精选文

  7. 回复「CSS」获取 CSS 精选文章

“在看”吗?在看就点一下吧

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值