what the fuck javascript 你不知道的javaScript的怪癖(下)

前言

这篇文章是对最近在github上很流行的一篇文章What the f*ck JavaScript?的个人翻译。阅读本人翻译的上半部分,更推荐阅读英文原版

Examples

Math with true and false

true + true // -> 2
(true + true) * (true + true) - true // -> 3

这是因为在计算的过程中,true被强制转换成1。在上一篇文中说到的一元加操作符,也是像Number()转型函数一样,对一个值进行强制转换。

Number(true) // -> 1
+true // -> 1

HTML注释在JavaScript中有效

<!--   --!>

在JavaScript中是有效的注释。这是因为html类似的注释,可以在不了解<script>标签的浏览器中优雅的降级。
这些浏览器,例如Netscape 1.x不再受欢迎。所以,将HTML注释放在你的脚本标签中真的没有任何意义。由于Node.js基于V8引擎,Node.js运行时也支持类似HTML的注释。此外,它们是规范的一部分。

NaN is not a number

NaN 的类型是number

typeof NaN   //  -> 'number'

[]和null是对象

typeof []   // -> 'object'
typeof null // -> 'object'

// 然而
null instanceof Object // false

因为null会被认为是一个空对象引用。用toString去检查一下null的类型

Object.prototype.toString.call(null)
// -> '[object Null]'

神奇增加的数字

999999999999999  // -> 999999999999999
9999999999999999 // -> 10000000000000000

10000000000000000       // -> 10000000000000000
10000000000000000 + 1   // -> 10000000000000000
10000000000000000 + 1.1 // -> 10000000000000002

这是由IEEE 754-2008二进制浮点运算标准引起的。在这个尺度上,它会舍入到最接近的偶数。

0.1+0.2的精确度

一个很著名的笑话,0.1+0.2加起来“超级”精确。

0.1 + 0.2 // -> 0.30000000000000004
(0.1 + 0.2) === 0.3 // -> false

这是因为浮点数在保存时会变成二进制,无法准确的表示一个浮点数,只能逼近,因此会产生误差。这个问题不仅仅是在JavaScript中出现,他发生在使用浮点数的每种编程语言中。访问0.30000000000000004.com

Patching numbers 修补数字

你可以在String或是Number中扩展自己的方法

Number.prototype.isOne = function () {
  return Number(this) === 1
}

1.0.isOne() // -> true
1..isOne()  // -> true
2.0.isOne() // -> false
(7).isOne() // -> false

您可以像JavaScript中的任何其他对象一样扩展Number对象。但是,如果定义的方法的行为不是规范的一部分,则不建议。这是Number的属性列表

三个数字的比较

1 < 2 < 3 // -> true
3 > 2 > 1 // -> false

为什么会这样呢呢?那么问题在于表达式的第一部分。以下是它的工作原理:

1 < 2 < 3 // 1 < 2 -> true
true  < 3 // true -> 1
1     < 3 // -> true

3 > 2 > 1 // 3 > 2 -> true
true  > 1 // true -> 1
1     > 1 // -> false

有趣的数学

通常JavaScript中的算术运算结果可能是非常意想不到的。考虑这些例子:

 3  - 1  // -> 2
 3  + 1  // -> 4
'3' - 1  // -> 2
'3' + 1  // -> '31'

'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'

'222' - -'111' // -> 333

[4] * [4]       // -> 16
[] * []         // -> 0
[4, 4] * [4, 4] // NaN

前四个例子发生了什么?这是一个小表,以了解JavaScript中的加法:

Number  + Number  -> addition
Boolean + Number  -> addition
Boolean + Boolean -> addition
Number  + String  -> 字符串连接
String  + Boolean -> 字符串连接
String  + String  -> 字符串连接

RegExps的扩充

你知道能像这样添加数字吗?

// Patch a toString method
RegExp.prototype.toString = function() {
  return this.source
}

/7/ - /5/ // -> 2

详解

字符串并不是String的实例

'str' // -> 'str'
typeof 'str' // -> 'string'
'str' instanceof String // -> false

String的构造函数返回一个字符串

typeof String('str')   // -> 'string'
String('str')          // -> 'str'
String('str') == 'str' // -> true

我们用new来试一下:

new String('str') == 'str' // -> true
typeof new String('str')   // -> 'object'

一个对象?那是啥?

new String('str') // -> [String: 'str']

有关规范中的String构造函数的更多信息

用反引号调用函数

我们来声明一个将所有参数记录到控制台中的函数:

function f(...args) {
  return args
}

你当然知道你可以像这样调用这个函数

f(1, 2, 3) // -> [ 1, 2, 3 ]

但是你尝试过使用反引号来调用函数吗

f`true is ${true}, false is ${false}, array is ${[1,2,3]}`
// -> [ [ 'true is ', ', false is ', ', array is ', '' ],
// ->   true,
// ->   false,
// ->   [ 1, 2, 3 ] ]

如果你熟悉Tagged模板文字,这并不是很神奇。在上面的例子中,f函数是模板文字的标签。模板文字之前的标签允许您使用函数解析模板文字。标签函数的第一个参数包含字符串值的数组。其余的参数与表达式有关。

function template(strings, ...keys) {
  // do something with strings and keys…
}

这是一个著名的lib,styled component,在React社区很有名。

Call call call

console.log.call.call.call.call.call.apply(a => a, [1, 2]) // -> 2

这很可能会打乱你的思路,尝试在你头脑中重现:我们正在使用apply方法调用call方法。

构造函数的属性

const c = 'constructor'
c[c][c]('console.log("WTF?")')() // > WTF?

让我们把他分解来看

// Declare a new constant which is a string 'constructor'
const c = 'constructor'

// c is a string
c // -> 'constructor'

// Getting a constructor of string
c[c] // -> [Function: String]

// Getting a constructor of constructor
c[c][c] // -> [Function: Function]

// Call the Function constructor and pass
// the body of new function as an argument
c[c][c]('console.log("WTF?")') // -> [Function: anonymous]

// And then call this anonymous function
// The result is console-logging a string 'WTF?'
c[c][c]('console.log("WTF?")')() // > WTF?

对象作为key

{ [{}]: {} } // -> { '[object Object]': {} }

这里我们使用Computed属性名称。当您在这些括号之间传递对象时,强制把一个对象转成字符串,所以我们得到属性键'[object Object]'和值{}。

${{Object}}

`${{Object}}` // -> '[object Object]'

我们使用Shorthand属性表示法定义了一个带有属性Object的对象:{Object:Object}然后我们将这个对象传递给模板文字,所以toString方法调用该对象。这就是为什么我们得到字符串'[object Object]'

使用默认值进行结构化

let x, { x: y = 1 } = { x }; y;  // -> y为1

分析一下:

let x, { x: y = 1 } = { x }; y;
//  ↑       ↑           ↑    ↑
//  1       3           2    4
  1. 我们定义了x,但并没有赋值,所以它为undefined
  2. 之后,我们把x的值放到对象中,作为键值
  3. 之后我们通过解构提取x的值,并把它赋值给y。如果未定义该值,那么我们将使用1作为默认值。
  4. 返回y

Dots and spreading

有趣的例子可以由阵列的扩展组成。考虑这个:

[...[...'...']].length // -> 3

为什么是3?当我们使用...扩展操作符时,调用@@ iterator方法,并使用返回的迭代器来获取要迭代的值。字符串的默认迭代器将字符串扩展为字符;扩展后,我们将这些字符打包成一个数组。

一个'...'字符串由三个字符组成,所以生成的数组的长度是3。

现在我们来分步去看:

[...'...']             // -> [ '.', '.', '.' ]
[...[...'...']]        // -> [ '.', '.', '.' ]
[...[...'...']].length // -> 3

显然,我们可以像我们想要的那样扩展和包装数组的元素:

[...'...']                 // -> [ '.', '.', '.' ]
[...[...'...']]            // -> [ '.', '.', '.' ]
[...[...[...'...']]]       // -> [ '.', '.', '.' ]
[...[...[...[...'...']]]]  // -> [ '.', '.', '.' ]

标签

很多程序员都不知道javaScript的标签:

foo: {
  console.log('first');
  break foo;
  console.log('second');
}

// > first
// -> undefined

带标签的语句与break或continue语句一起使用。您可以使用标签来标识循环,然后使用break或continue语句来指示程序是否应该中断循环或继续执行循环。

在上面的例子中,我们确定了一个标签foo。之后的console.log('first');执行,然后中断执行。

嵌套标签

a: b: c: d: e: f: g: 1, 2, 3, 4, 5; // -> 5

关于label

阴险的try...catch

看下这个表达式将返回什么?

(() => {
  try {
    return 2;
  } finally {
    return 3;
  }
})()

答案是3

如果从finally块中返回一个值,那么这个值将会成为整个try-catch-finally的返回值,无论是否有return语句在try和catch中。这包括在catch块里抛出的异常。

这是多重继承?

看下下面的例子

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

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

A generator which yields itself

考虑下面的例子

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

如您所见,返回的值永远是一个值等于f的对象。在这种情况下,我们可以这样做:

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

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

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

A class of class

考虑这个模糊的语法

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

我们正在类中声明一个类。应该会报错,但是,我们得到字符串“对象”。

这是由于在ES5时代,关键字允许作为对象的属性名。看一下这个简单的例子:

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

和ES6的缩写定义。并且,类可以是匿名的。如果我们不使用function部分,我们将得到:

class {
  class() {}
}

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

不能强制转换的对象

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

不明白Symbol的建议阅读这篇文章

Tricky arrow functions

看一下下面的例子:

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

ok,那下面这个呢

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

你可能希望返回的是{}而不是undefined 这是因为大括号是箭头函数语法的一部分,所以f将返回undefined。

Tricky return

return语句也很棘手。考虑这个:

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

这是因为return和返回的表达式必须在同一行,下面为正确的操作:

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

使用数组访问对象属性

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

这是因为[]操作符通过toString转换表达式。将单元素数组转换为字符串,就像将元素转换为字符串一样

['property'].toString() // -> 'property'

一些有趣的链接

What the... JavaScript?

Wat

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,196评论 0 4
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 云海仙境中的学校 重走长征路 暖冬万里行 第32天 走访参观云南省双柏县妥甸镇九石(dan 4声)小学…… 10年...
    寒冰0601阅读 124评论 0 0
  • “不要说话!不要说话!马上就是我们“小太阳”的分享了,我要专心听他的成长故事”。我紧张的对身边的家人说。 他们一脸...
    月小姐的花园阅读 438评论 2 3
  • 我这辈子最大的心愿,就是嫁一个像父亲那样的男人。 前不久,我去一个亲戚家做客。上午,女主人在家张罗一些琐事,午饭后...
    拂塵阅读 396评论 0 5