前言
这篇文章是对最近在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']
用反引号调用函数
我们来声明一个将所有参数记录到控制台中的函数:
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
- 我们定义了x,但并没有赋值,所以它为
undefined
- 之后,我们把x的值放到对象中,作为键值
- 之后我们通过解构提取x的值,并把它赋值给y。如果未定义该值,那么我们将使用1作为默认值。
- 返回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
阴险的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
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'
网友评论