乐趣区

关于javascript:深入浅出JavaScript中的隐式转换

// 问题 如何让该片段在控制台输入 "hello world"
if (a == 1 && a == 2 && a == 3) {console.log('hello world!');
}

/*
a == 1 ?
a == 2 ?
a == 3 ?
a 为什么能等于这么多值???带着这样的问题,咱们来看一下 JS 中的隐式转换
*/
JS 是一门弱类型语言,在申明变量的时候不须要申明变量的类型,并且不同类型的数据能够进行计算,这都得益于 JS 中在语言设计时的创立的隐式转换。

咱们晓得 JS 有 7 大数据类型

分为根本类型(原始值):String Number Boolean null undefined Symbol
简单援用类型(援用值):Object

隐式转换作为 JS 中的一个难点,因为 js 在一些特定操作符的状况下会调用隐式转换,这也是 js 灵便的中央,两个操作符 ”+” 和 ”==” 是比拟常见的呈现隐式转换的操作符

+ == 这两个符号能够是忽视类型并且针对多种类型能够转化成多种类型去计算。- * / 这三个只针对 Number 类型进行隐式转换,故而转化时只思考 Number 类型即可

一、既然是要隐式转换,那麽必然有一套约定俗成的规定,而不是轻易转化。

隐式转换次要分为三种形式

  1. 将值转化为原始值 => toPrimitive
  2. 将值转化为 String => ToString
  3. 将值转化为 Number => ToNumber
2.1 通过 toPrimitive 将值转化为原始值
  1. js 引擎外部有一个形象操作 ToPrimitive, 他有着这样的签名,ToPrimitive(input, PreferredType?) input 是传入的值,PreferredType 是可选参数,示意转化为什么类型,转化后的后果并不一定是这个参数所指定的后果,然而肯定是一个原始类型的值
  2. 如果 PreferredType 被标记为 Number,则会进行如下操作进行转化

    1. 如果输出的值曾经是一个原始值,则间接返回该值
    2. 否则如果是一个对象,则调用对象的 valueOf()办法,如果 valueOf()返回的是一个原始值,则间接返回失去的值
    3. 否则调用这个对象的 toString()办法,如果 toString()返回的是一个原始值,则间接返回该值
    4. 否则抛出 TypeError 异样
  3. 如果 PreferredType 被标记为 String,则会进行如下操作转化

    1. 如果输出的值是一个原始值,则间接返回该值
    2. 否则如果是一个对象,则调用对象的 toString()办法,如果 toString()办法返回的是一个原始值,则间接返回该值
    3. 否则调用这个对象的 valueOf()办法,如果 valueOf()返回的是一个原始值,则间接返回该值
    4. 否则抛出 TypeError 异样
  4. 既然 PreferredType 是一个可选值,那么不传默认会被标记为什么呢

    1. 如果传入值为 Date 类型,则标记为 String
    2. 如果传入的值为其余援用类型,则标记为 Number
  5. 下面次要提到了 valueOf 和 toString 办法,那么咱们是否保障任何状况下都有这两个办法呢,答案是必定的

    只有是援用类型的咱们能够间接顺着原型链找到 Object.prototype,咱们会发现在原型上就有 toString 和 valueOf 办法,故而是肯定能够找到的

  6. 先看看对象的 valueOf 函数,其转化的后果是什么

    对于 js 的常见内置对象:Date, Array, Math, Number, Boolean, String, Array, RegExp, Function

    1. Number、Boolean、String 这三种构造函数生成的根底值的对象模式,通过 valueOf 转换后会变成相应的原始值。如:

      var num = new Number('12');
      num.valueOf(); // 12
      
      var str = new String('44rr');
      str.valueOf(); // '44rr'
      
      var bool = new Boolean('xxx');
      bool.valueOf(); // true
    2. Date 是一个非凡的对象,通过 valueOf 会转化为毫秒值

      var a = new Date();
      a.valueOf(); // 1614700725143
    3. 其余的都返回 this, 也是援用自身

      var a = new Array();
      a.valueOf() === a; // true
      
      var b = new Object({});
      b.valueOf() === b; // true
  7. 再看看 toString 函数,其转换的后果是什么?对于 js 的常见内置对象:Date, Array, Math, Number, Boolean, String, Array, RegExp, Function

    1. Number、Boolean、String、Array、Date、RegExp、Function 这几种构造函数生成的对象,通过 toString 转换后会变成相应的字符串的模式,因为这些构造函数上封装了本人的 toString 办法。如:

      Number.prototype.hasOwnProperty('toString'); // true
      Boolean.prototype.hasOwnProperty('toString'); // true
      String.prototype.hasOwnProperty('toString'); // true
      Array.prototype.hasOwnProperty('toString'); // true
      Date.prototype.hasOwnProperty('toString'); // true
      RegExp.prototype.hasOwnProperty('toString'); // true
      Function.prototype.hasOwnProperty('toString'); // true
      
      var num = new Number(123)
      num.toString() // '123'
      
      var boolean = new Boolean('xxx')
      boolean.toString() // 'true'
      
      var string = new String('abc123')
      string.toString() // 'abc123'
      
      var array = new Array(1,2,3,4)
      array.toString() // '1,2,3,4'
      
      var date = new Date()
      date.toString() // "Wed Mar 03 2021 00:15:57 GMT+0800 (中国规范工夫)"
      
      var RegExp = new RegExp(/\w/g)
      RegExp.toString() // '/\w/g'
      
      var fun = new Function('console.log(1)')
      fun.toString() // "function anonymous() {console.log(1)}"
      
      var fun1 = function fun1() {}
      fun1.toString() // "function fun1() {}"
    
    2. 除了上述对象以外,其余的对象都是返回的该对象的类型,调用了原型链上的 Object.prototype.toString()办法
    

    var obj = {}
    obj.toString() // ‘[Object object]’

    Math.toString() // ‘[Object Math]’

  8. 一些思考

    从下面 valueOf 和 toString 两个函数对对象的转换能够看出为什么对于 ToPrimitive(input, PreferredType?),PreferredType 没有设定的时候,除了 Date 类型,PreferredType 被设置为 String,其它的会设置成 Number。

    1. 因为对于咱们不晓得要转换什么类型的时候,最好是返回其自身,标记为 Number 先调用 valueOf 的时候除了在 String、Number、Boolean 生成的对象字面量转化为对应的原始类型值,还有 Date 返回了对应的毫秒值以外,其余都返回了自身 this
    2. 为什么默认应用标记为 Number 呢,因为 Number 会先调用 valueOf(),而不是像标记为 String 无脑调用 toString 办法转化为字符串
    3. 为什么 Date 在没有标记的时候默认标记为 String 先调用 toString()办法,因为咱们不晓得要转化为什么,所以没必要转化为一个特地大的数字,所有调用 toString 转化为 String
2.2 通过 ToNumber 将值转换为数字
undefined => NaN
null => +0
boolean => false: 0, true: 1 
string => '': 0, 有字符串数字解析为数字,e.g.'12.3': 12.3,'xxx': NaN
number => number 无需转换
object => 先 ToPrimivetive(obj, preferredType=Number),而后再进行     ToNumber
2.3 通过 ToString 将值转化为字符串
undefined => 'undefined'
null => 'null'
boolean => false: 'false', true: 'true'
string => string 无需转化
number => 123: '123'
object => 先 ToPrimitive(obj, preferredType=String), 而后进行 Tostring

二、从实践到实际去看 JS 的隐式转换

1. 从第一章理论知识和规定咱们曾经理解了 js 是如何对不同数据类型进行隐式转换的,上面咱们来几道题目实战一下
({}+{}) 
/*
1. 剖析两个数据类型为 object,再看操作符为“+”,则须要转化为原始类型才能够进行加和
2. 采纳形象函数 ToPrimitive(input), 因为没有明确指定须要转化的值,故而默认为 Number,先调用外部的 valueOf()进行转化,({}).valueOf(), 因为是 object 对象则返回 this,没有转化为原始值持续调用({}).toString, 转化为 '[Object object]',转化为了原始值完结返回
3. 则式子变成 '[Object object]'+'[Object object]' = '[Object object][Object object]'
*/

({}+[]).length
/*
1. 操作符优先级 () 优先级大于点,先执行括号内的
2. 发现外部为两个援用类型的数据,操作符为 "+", 则须要调用形象函数 ToPrimitive(input), 进行转化
3. 没有明确指定转化的指标类型,默认为 Number,则先调用 valueOf()
4. ({}).valueOf() => 返回 this {},([]).valueOf() => 返回 this
5. 没有转化为原始值类型,对 valueOf()的返回后果持续调用 toString()
6. ({}).toString() => '[Object object]', ([]).toString() => 数组的原型实现了本人的 toString()办法,会将数据通过相似 join()的形式转化为字符串,[] 其实和 new Array() 一样, 外部没有传值为空,转化为 ''7. 都转化为了原始值,则式子变为('[Object object]'+"").length
8. '[Object object]'.length => 15
*/
{}+[].length

/*
1. {}在第一位 js 会编译为作用域,则变为 +[].length
2. . 操作符优先级大于 +,[].length === 0 , +0 = 0
*/
{}+{}.length
/*
1. . 操作符优先级大于 +,则{} + undefied
2. {}在第一位会编译为作用域,则 +undefined 
3. undefined 转化为 number NaN
*/
({}+{}).length
/*
1. 下面问题 1 失去了一个后果 15,两个 15*2=30
*/
{}+[]
/*
1. 同样是作用域,+[]
2. 转化为 Number ([]).valueOf() => []  
3. 持续([]).toString() => ''4. +"", "" 要转化为 Number 转化为了 0 
5. +0 = 0
*/
({}+[])
/*
1. ()操作符优先级 21 最高,外部 {}+[] 不会编译为作用域
2. ({}).valueOf().toString() === '[Object object]'
3. ([]).valueOf().toString() === ''4.'[Object object]'
*/
[]+{}
/*
1. 同上 ""+"[Object object]"
*/
  1. == 运算符的隐式转换
    // 比拟运算 x ==y, 其中 x 和 y 是值,返回 true 或者 false。例如 es5 文档
    比拟运算 x==y, 其中 x 和 y 是值,返回 true 或者 false。这样的比拟按如下形式进行:1、若 Type(x) 与 Type(y) 雷同,则
    
        1* 若 Type(x) 为 Undefined,返回 true。2* 若 Type(x) 为 Null,返回 true。3* 若 Type(x) 为 Number,则
      
            (1)、若 x 为 NaN,返回 false。(2)、若 y 为 NaN,返回 false。(3)、若 x 与 y 为相等数值,返回 true。(4)、若 x 为 +0 且 y 为 −0,返回 true。(5)、若 x 为 −0 且 y 为 +0,返回 true。(6)、返回 false。4* 若 Type(x) 为 String, 则当 x 和 y 为完全相同的字符序列(长度相等且雷同字符在雷同地位)时返回 true。否则,返回 false。5* 若 Type(x) 为 Boolean, 当 x 和 y 为同为 true 或者同为 false 时返回 true。否则,返回 false。6*  当 x 和 y 为援用同一对象时返回 true。否则,返回 false。2、若 x 为 null 且 y 为 undefined,返回 true。3、若 x 为 undefined 且 y 为 null,返回 true。4、若 Type(x) 为 Number 且 Type(y) 为 String,返回比拟 x == ToNumber(y) 的后果。5、若 Type(x) 为 String 且 Type(y) 为 Number,返回比拟 ToNumber(x) == y 的后果。6、若 Type(x) 为 Boolean,返回比拟 ToNumber(x) == y 的后果。7、若 Type(y) 为 Boolean,返回比拟 x == ToNumber(y) 的后果。8、若 Type(x) 为 String 或 Number,且 Type(y) 为 Object,返回比拟 x == ToPrimitive(y) 的后果。9、若 Type(x) 为 Object 且 Type(y) 为 String 或 Number,返回比拟 ToPrimitive(x) == y 的后果。10、返回 false。
    总结一下:

    下面次要分为两类

    1. 类型雷同

      类型雷同时,没有类型转换,留神 NaN 不与任何值相等即可

    2. 类型不同

      1. x,y 为 null、undefined 两者中的一个 返回 true
      2. x,y 为 Number 和 String 类型时,转换为 Number 进行比拟
      3. 有 Boolean 时,转化为 Number 比拟
      4. 一个 Object 类型,一个 String 或者 Number 类型,将 Object 按类型转换为原始值后,再按下面三步比拟
    看一些例子:
    var a = {valueOf: function () {return 1;},
      toString: function () {return '123'}
    }
    true == a
    /*
    1. 首先得悉 true 和 a 不是一个类型,则须要类型转换
    2. 有 Boolean 则转化为 Number 进行比拟
    3. true => 1, a => Object 没有指定先调用 valueOf(), 返回了 1,失去原始值,则返回
    3. 1 == 1 // true
    */
    
    
    // 再看一段简单一点的比拟
    [] == !{}
    /*
    1. 操作符优先级!优先级 17,== 优先级 11,则先运算!{}
    2. !{} => false
    3. 式子变为 [] == false , 有 false 则转化为 Number
    4. false => 0, []转化为原始值再转 Number,([]).valueOf().toString() => '' => 0
    5. 0 == 0 // true
    */
    当初咱们来看看最开始的那道题目,咱们是不是有些思路了
    if (a == 1 && a == 2 && a == 3) {console.log('hello world!');
    }
    
    /*
    1. 剖析问题,a == 1,a == 2, a == 3, 能够得悉,a 是一个动静更新本人的变量,js 中没有这种变量,必定是一种比拟 trick 的写法,又看到 == 操作符,则想到了隐式转换中会顺次隐式调用两个函数,valueOf 和 toString
    2. 办法在原型上,咱们能够写在本身上进行拦挡从而达到目标。3. 可推出 a 是一个领有 valueOf 或者 toString 的办法,并且有一个值能够取出来并且随着从左到右顺次执行的 == 运算符顺次执行 +1
    4. 咱们可推算出 a 如下,每次调用隐式转换都将 ++,即 1,2,3 顺次相等,最终输入 hello world
    */
    let a = {
         value: 0,
         valueOf() {
        this.value++
        return this.value
      }
    }

总结

其实当咱们理解了隐式转换产生的规定当前,再看这些题目就会感觉非常简单,js 作为一门弱类型的语言,存在着这样的隐式转换,这些能够给咱们提供一些便利性,然而在日常的开发中,并不举荐应用这种隐式转换,因为它令人感到艰涩难懂不够间接,日常工作中还是要防止应用 ==,采纳 === 进行判断值的相等于否。
退出移动版