美文网首页js让前端飞Web前端之路
what the fuck javascript 你不知道的ja

what the fuck javascript 你不知道的ja

作者: mytac | 来源:发表于2017-08-24 09:55 被阅读75次

    前言

    这篇文章是对最近在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

    相关文章

      网友评论

        本文标题:what the fuck javascript 你不知道的ja

        本文链接:https://www.haomeiwen.com/subject/ixmsdxtx.html