关于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作为一门弱类型的语言,存在着这样的隐式转换,这些能够给咱们提供一些便利性,然而在日常的开发中,并不举荐应用这种隐式转换,因为它令人感到艰涩难懂不够间接,日常工作中还是要防止应用==,采纳===进行判断值的相等于否。



评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理