你不知道的JavaScript丛书总结(二)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 你不知道的JavaScript丛书总结(二)

该专栏总结自《你不知道的JavaScript》丛书的知识,易错易混点。


类型装换


类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型怨言的运行时。在js中统称为强制类型转换。


抽象值操作


ToString:他负责处理非字符串到字符串的强制类型转换。


  • 如果转化的是极大极小值时,转化后的字符串是指数形式。


const a = 100000000000000000000000000000000000000000000;
    console.log(a.toString()) //1e+44


  • 对于普通对象来说,除非自定义toString方法,否则都调用Object.prototype.toString(),然后返回"[object Object]"


  • JSON字符串化在对象中遇到undefined, function, symbol时会自动将其忽略,在数组中则会返回null,用来保证数组中元素对应的位置不变。并且对包含循环引用的对象JSON字符串化时报错。


console.log(JSON.stringify([1, undefined, function() {}, 4])) //[1,null,null,4]
    console.log(JSON.stringify({
      name: "zh",
      age: undefined,
      foo: function() {}
    })) // {"name":"zh"}


如果对象中定义了toJSON方法,JSON字符串化时会首先调用该方法,然后用他的返回值来进行序列化。


ToNumber: 将非非数字值转化为数字值。


  • 注意这两个值的转换。undefined转换为NaN, null转换为0。


console.log(Number(undefined)) //NaN
    console.log(Number(NaN)) //NaN
    console.log(Number(null)) // 0
    console.log(Number("")) // 0
    console.log(Number([])) // 0
    console.log(Number({})) // NaN


  • 为了将值转换为相应的基本类型值,抽象操作ToPrimitive会首先检查该值是否有valueOf方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用toStrin()的返回值来进行强制类型转换。如果valueOf(), toString()均不返回基本类型值,会产生TypeError错误。


ToBoolean: 将非boolean值转换为boolean类型。


  • undefined, null, false, +0, -0, NaN, ""会被强制转化为false, 其他任何值都将被转化为true。


显示强制类型转换


显示强制类型转换是那些显而易见的类型转换。


字符串和数字之间的显示转换


  • String,Number两个内建函数来完成上面的操作。并且遵守ToString,ToNumber规则。


  • 还有很多方法可以实现二者之间的显示转换。


const a = 42;
    const b = a.toString(); // "42"
    +"3.14" // 3.14


  • 一元运算符+可以将日期对象强制转换为数字。返回结果为Unix时间戳。但是一般通过Date.now()来获取当前时间戳,使用new Date().getTime()来获取指定时间时间戳。


console.log(+new Date())


字符串转换为数字的解析和转换


  • 解析(parseInt, parseFloat)允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。


  • 转换(Number)不允许出现非数字字符,否则会失败并返回NaN。


  • 注意:parseInt,parseFloat传入的是字符串,所以我们将其他类型的数据传入后,他们会先被转换为字符串,然后再按照ToPrimitive规则来进行转化,然后再转换为数字。


console.log(parseInt([1,2,3])) // 1


上面的方法在es5之前会有一个bug,如果不指定第二个参数,那么它将根据第一个参数第一个字符来确定,将要将字符串数字转化为几进制数字。但是在es5之后,第二个参数默认为10。所以在es5之前环境下,我们需要手动指定第二个参数为10。


  • parseInt解析非字符串类型。很可能会出现意想不到的结果。但是都是先将该类型计算后转换为字符串,然后再根据第二个参数来转化。


console.log(parseInt(1 / 0, 19)) // parseInt("Infinity", 19) => 18
    console.log(parseInt(0.000008)) // 0 ("0" 来自于"0.000008")
    console.log(parseInt(0.0000008)) // 8 ("8" 来自于"8e-7" )
    console.log(parseInt(false, 16)) // 250 ("fa" 来自于 "false")
    console.log(parseInt("0x10")) // 16
    console.log(parseInt("103", 2)) // 2


显示转换为布尔值


  • 通过调用Boolean,并且遵循ToBoolean规则。


  • 或者通过!!


隐式强制类型转换


隐式强制类型转换指的是那些隐蔽的强制类型转换,副作用也不是很明显。


数字转化为字符串


  • +操作符。 如果操作符两边有一个可以转换为字符串,那么就是字符串拼接操作。如果都是数字,则为数字加法操作。


const a = [1,2];
    const b = [3,4];
    console.log(a + b) // 1,23,4


上面的结果就是数组通过ToPrimitive后转化为字符串,然后再进行字符串拼接。


  • (3) + ""String(3)的差别。前者是通过ToPrimitive规则转换的,后者通过ToString规则转换的。


const a = {
      valueOf(){
        return 3
      },
      toString() {
        return 5
      }
    }
    console.log(a +''); // "3"
    console.log(String(a)) // "5"


字符串转换为数字


  • 通过-, *, /等等数字运算符,即可。


  • 由于算术运算符两边都是数字,所以需要先将两边操作数转换为数字。如果是对象类型需要通过ToPrimitive规则转换为字符串,然后再转化为数字。 在编辑器中输出


console.log({} + []) // [object Object]
    console.log([] + {}) // [object Object]


在浏览器控制台中直接输出


{} + [] // 0
    [] + {} // [object Object]


为什么上面会产生不同的结果呢?[] + {}: 他先将[]转化为"" => 0, {} 转换为[object Object]{} + []: 他将{}看作是一个空代码块会被忽略。所以最后转换为+ [],然后被转换为0。


隐式强制类型转换为布尔值


下面的情况会发生布尔值隐式强制类型转换:


  • if()语句中的条件判断表达式


  • for()语句中的条件判断表达式(第二个)


  • while()和do...while()循环中的条件判断表达式


  • ? :中的条件判断表达式


  • 逻辑运算符 ||, &&。 转换的时候,遵循ToBoolean抽象操作规则。


  • &&, ||运算符的返回值并不一定是布尔值,而是两个操作数其中一个值。


const a = 42;
    const b = "abc";
    const c = null;
    console.log(a || b); // 42
    console.log(a && b); // abc
    console.log(c || b); // abc
    console.log(c && b); // null


Symbol的强制类型转换


  • 不能被强制类型转换为数字(显示隐式都报错),但是可以强制类型转换为布尔值(显式和隐式都可以)。可以显式强制类型转换为字符串,但是不能隐式强制类型转换为字符串。


console.log(Symbol("cool") + "") // TypeError: Cannot convert a Symbol value to a string
    console.log(String(Symbol("cool"))) // Symbol(cool)


宽松相等和严格相等


  • 一般对于二者的理解就是:==检查值是否相等,===检查值和类型是否相等。


  • 但是正确的理解应该是:==允许在相等比较中进行强制类型转换,而===不允许。


  • ==在比较两个不同类型的值时会发生隐式强制类型转换,会将其中之一或二者都转换为相同的类型后在进行比较。


  • 字符串和数字做==比较时,会将字符串做ToNumber规则转换。


"2n" == 2 // false  =>  Number("2n") = NaN
"2" == 2 // true  => Number("2") = 2


  • 布尔值和数字做==比较时,会将boolean值做ToNumber规则转换


false == 0   // true  => Number(false) = 0
false == 2   // false


  • 字符串和boolean做==比较时,会将二者都做ToNumber规则转换。


"n" == true     // false
"n" == false    // false
"2" == true     // false
"2" == false    // false
"1" == false    // false
"1" == true     // true


比较时并没有做ToBoolean操作。所以尽量不要使用== true, == false


  • null和undefined两者永远相等,二者和其他任何值做==比较时都为false。


  • 对象和字符串,数字,布尔值做==表示时,会将对象做ToPrimitive规则转换,然后==操作符两边的操作数转换为数字。


console.log(42 == [42]) // true  42 == "42" => 42 == 42 => true


  • 基本类型数据通过对应的基本包装类型封装后与原基本类型做==操作时,依旧是相等的。但是null, undefined, NaN则不符合。


const a = "abc";
    const b = Object(a);
    console.log(a == b) // true
    console.log(null == Object(null)) // false
    console.log(undefined == Object(undefined)) //false
    console.log(NaN == Object(NaN)) // false


  • [null], [undefined], [NaN]通过ToPromitive规则转换后的值。


console.log([undefined].toString()) // ""
    console.log([null].toString()) // ""
    console.log([NaN].toString()) // NaN


  • 安全运用隐式强制类型转换的原则:


  • 如果两边值其中有true, false,千万不要使用==


  • 如果两边的值有[], "", 0,千万不要使用==


  • 以上请使用===注意:使用==来做比较时,两边的操作数最终都转为数字来判断的。即调用Number()。


语句和表达式


  • 语句想等于句子,表达式相当于短语,运算符则相当于标点符号和连接词。


  • 由单个表达式构成的语句被称为"表达式语句"。


  • 每一条语句都有结果值。我们在代码中是没办法获得这个结果值的。在控制台中输入var a = 1;会返回一个undefined。 代码块的结果值就如同一个隐式的返回,即返回最后一个语句的结果值。我们可以通过eval获取一个代码块的返回值。


var a, b;
    a = eval("if(true) { b = 4 + 10 }");
    console.log(a) // 14


es7有一个do表达式提案。


a = do {
      if(true) {
        b = 4 + 10;
      }
    }
    console.log(a) // 14


其目的是将语句当做表达式来处理,从而不需要将语句封装为函数再调用return来返回值。现在测试,会报错,应该是没有被采纳。


  • 语句系列逗号运算符:将多个独立的表达式语句串联成一个语句。表示将最后一个表达式的值,赋值给变量。


var a = 42, b;
    b = (a++, a);
    a // 43
    b // 43


标签语句


{
        foo: bar()
    }


上面代码就是一个普通的代码块,但是内部是一个标签语句。我们可以在循环中使用continue 标签, break 标签来实现跳转执行。并且标签也能用于非循环代码块,但是只有break才可以。


function foo() {
      bar: {
        console.log("Hello");
        break bar;
        console.log("never runs");
      }
      console.log("World")
    }
    foo() // Hello World


运算符的优先级


具体请访问mdn文档


  • ,来连接一系列语句的时候,他的优先级最低,其他操作数的优先级都比他高。


const a = 42, b;
    b = a++, a;
    a // 43
    b // 42


  • && 运算符的优先级高于||


console.log(true || false && false) // true


  • ||的优先级高于?:


  • 关联:右关联不是指从右往左执行,而是指从右往左组合。但是执行顺序在任何时候都是从左往右。&&, ?:, =都是右关联。


var a, b, c;
    a = b = c = 42; // (a = (b = (c = 42)))
    var d = a && b || c ? c || b ? a : c && b : a;
    // ((a || b) || c) ? ((c || b) ? a : (c && b)) : a;


函数参数


  • arguments.length计算的是实参的个数。


  • 在非严格模式下,向函数传递参数时,arguments数组中的对应单元会和命名参数建立关联以得到相同的值,相反不传递参数就不会建立关联。


function foo(a) {
      a = 42;
      console.log(arguments[0]);
    }
    foo(2); // 42
    foo() // undefined


但是在严格模式下,传不传递参数都不会建立关联。


function foo(a) {
    "use strict"
      a = 42;
      console.log(arguments[0]);
    }
    foo(2); // 2
    foo() // undefined


因此,在开发中不要依赖这种关联机制。实际上,他是js语言引擎底层实现的一个抽象泄漏,并不是语言本身的特性。


这种关联机制与es没有关系,只是语言的一种缺陷,只有在严格模式下,才不会被关联。所以我们在使用只需要遵守一个原则:不要同时访问命名参数和其对应的arguments数组单元。


try...finally


  • finally中的return会覆盖try和catch中return的返回值。


function foo() {
      try {
        return 42;
      } finally {
        // 无返回值,所以没有覆盖
      }
    }
    function bar() {
      try {
        return 42;
      } finally {
        return ;
      }
    }
    function baz() {
      try {
        return 42;
      } finally {
        return "hello";
      }
    }
    console.log(foo()) // 42
    console.log(bar()) // undefined
    console.log(baz()) // hello


switch


  • 参数 和 case 表达式的匹配算法与===相同。 通常case语句中的switch都是简单值,所以并没有问题。


  • 然而,有时可能会需要通过强制类型转换来进行相等比较(即 == ),这是就需要做一些特殊处理。


const a = "42";
    switch (true) {
      case a == 10:
        console.log("10 or '10'")
        break;
      case a == 42:
        console.log("42 or '42'")
        break;
      default:
        break;
    }


  • default不一定要写在最后面,可以写在中间位置。效果都一样。


相关文章
|
JavaScript 前端开发 API
JavaScript数组API总结
JavaScript数组API总结
JavaScript数组API总结
|
JavaScript 前端开发
JavaScript总结:typeof与instanceof的区别,及Object.prototype.toString()方法
JavaScript总结:typeof与instanceof的区别,及Object.prototype.toString()方法
186 0
JavaScript总结:typeof与instanceof的区别,及Object.prototype.toString()方法
|
JavaScript 前端开发
JavaScript总结:let变量(弥补var的缺陷)
JavaScript总结:let变量(弥补var的缺陷)
144 0
JavaScript总结:let变量(弥补var的缺陷)
|
JavaScript 前端开发
JavaScript总结: javascript中使用var定义变量的变量提升问题
JavaScript总结: javascript中使用var定义变量的变量提升问题
134 0
JavaScript总结: javascript中使用var定义变量的变量提升问题
|
存储 前端开发 JavaScript
JavaScript总结:关于堆栈、队列中push()、pop()、shift()、unshift()使用方法的理解
JavaScript总结:关于堆栈、队列中push()、pop()、shift()、unshift()使用方法的理解
261 0
JavaScript总结:关于堆栈、队列中push()、pop()、shift()、unshift()使用方法的理解
|
JavaScript 前端开发 内存技术
JS入门到进阶知识总结(四)
JS入门到进阶知识总结(四)
JS入门到进阶知识总结(四)
|
JavaScript 前端开发 程序员
|
JavaScript 前端开发 API
JavaScript中字符串的API使用总结
JavaScript中字符串的API使用总结
|
JSON 编解码 JavaScript
【笔记】js URL编码解析总结
js URL编码解析总结
286 0