JS02 - JS number 及比较原则

294 阅读22分钟

1. number 类型

number 类型的种类

  • 数字:十进制没有前缀,二进制前缀0b(例如 0b0111),八进制前缀0(例如 012),十六进制前缀0x(例如 0x12),字面量不论是哪种进制,输出都是十进制
  • 关键字 Infinity:表示无穷大的值
  • 关键词 NaN:表示不是一个有效数字(not a number),注意:NaN 和 NaN 本身不相等,即 NaN == NaN 的结果为false

Number() parseInt() parseFloat() 方法 - 转换为 number 类型

Number([value])

/* 基本数据类型 转换为 number类型 */
console.log(Number(true))       //1
console.log(Number(false))      //0
console.log(Number(null))       //0 => object转换为数字类型为NaN,但 null 不是object类型,只是null的二进制代码是000,跟object的开头一样,所以typeof的结果是object,因此null能够转换为数字类型为 0,同时null转换为字符串为 "null"
console.log(Number(undefined))  //NaN => undefined 不存在值,null是存在值的,即null值(空值)
console.log(Number(1n))         //1 说明:这里的1n表示BigInt类型
console.log(Number("12px"),Number("12"))   //NaN 12
console.log(Number(NaN))      //NaN
console.log(Number(Symbol()))   //报错 说明:Uncaught TypeError: cannot convert a Symbol value to number(报错,说明Symbol()不能转换为数字)
/* 引用数据类型(对象/函数) 转换为 number类型 */
//    步骤一:获取 Symbol.toPrimitive 属性值(Symbol.toPrimitive 是内置的 symbol 属性,被所有的强类型转换制算法优先调用,返回对象原始值),如果没有这个属性,则进入第二步
//      ->在该步骤转换为number,需要定义 [Symbol.toPrimitive]() 方法来手动设置 Symbol.toPrimitive 属性
        //方式一:借用其它对象的方法
        const arr = [3, 5, 4, 7, 2, 8];
        const obj = {
            //重写
            [Symbol.toPrimitive](hint){
                //设置如果调用Number则会转换为number类型,返回值为10
                if (hint === "number") {
                    return 10; 
                }
            }
        }
        obj[Symbol.toPrimitive] = obj[Symbol.toPrimitive]
        //方式二:重写自身构造函数原型的方法
        arr.__proto__[Symbol.toPrimitive] = function (hint) {
            var result = 0;
            if (hint === "number") {
                for (var i = 0; i < this.length; i++) {
                    result += this[i];
                    console.log(i)
                } // else none
                return result;
            }
        }
        console.log(Number(arr));       //29
//   步骤二:获取 valueOf() 原始值,就是基本数据类型的初始默值,如果返回的时引用值则进入下一步,例如:
        new String().valueOf();     //原始值:""
        new Number(3);              //引用值:Number {3}
        new Number(3).valueOf();    //原始值:3
        [].valueOf();                 //引用值:[]
        [3].valueOf();                //引用值:[3]
//   步骤三:最后,如果没有原始值,则通过toString()将其转换为字符串,然后再通过Number()方法转换为number类型转换为number类型
        [3].valueOf().toString();     //3 
        typeof [3].valueOf().toString();     //string
        Number([3].valueOf().toString());   //3 
        typeof  Number([3].valueOf().toString());   //number

parseInt([value]) / arseFloat([value])

//步骤一:判断[value]是否为string,不是,则通过[value].toString()隐式地转换为字符串 
//步骤二:从字符串左侧开始,向右逐个字符进行查找,遇到一个则转换一个,如果遇到一个非有效数字,则停止转换,不管后面还有没有数字,如果开头就是一个非有效数字,则结果就是 NaN
console.log(Number(""))             //0
console.log(parseInt(""))           //NaN
console.log(parseFloat(""))         //NaN
console.log(parseInt("12px"))       //12
console.log(parseInt("12px13"))     //12
console.log(parseFloat("12.13"))    //12.13
console.log(parseInt("12.13"))      //12
console.log(parseInt(true))         //NaN 说明:true.toString() -> "true"
console.log(parseFloat(true))       //NaN 说明:true.toString() -> "true"
console.log(parseFloat(false))      //NaN 说明:false.toString() -> "false"
console.log(parseFloat(null))       //NaN 说明:尤其注意
console.log(parseFloat(undefined))  //NaN
console.log(parseInt([12,23]))      //12 说明:[12,23].toString() -> "12,23"
console.log(parseFloat([23,12]))    //23 说明:[23,12].toString() -> "23,12"

巧用运算符:除了加法以外的数学运算 -0 /1 *1

var 待转换变量 = "108"
console.log(待转换变量*1)
console.log(待转换变量/1)
console.log(待转换变量-0)

isNaN() 方法

  • 功能:由于NaN不能等于任何值,因此设置一个专门方法检测是否为 "有效数字",如果不是有效数字则返回 true,反之返回 false
  • 原理:如果 [value] 不是数字类型,则浏览器会默认将其转换为数字类型(这个过程称为隐式转换,是基于Number([value])方法实现的,因此 isNaN(null) 的结果是 false,),如果在转换[value]过程中遇到一个非数字,都会返回 NaN

toFixed() 方法 - 保留小数

  • 语法[number].toFixed()
  • 返回:string,如果需要number,则在后面减0即可
let height = 189.58
console.log(height.toFixed(1))  //189.6
console.log(height.toFixed(2))  //189.58
console.log(height.toFixed(3))  //189.580
console.log(typeof height.toFixed(2))   //string
var res = height.toFixed(2)-0
console.log(res,typeof res)   //189.58 'number'

Number 安全存储范围

  • 安全存储:能够准确区分两个不同的值,例如Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2得到的结果是true,这在数学上是明显错误的,因为加上1和2之后超过了安全存储范围。
  • 存储范围:在JavaScript中Number数字存储采用的是 IEEE 754 中规定的双精度浮点数数据类型,这一数据类型能够安全存储 -(2^53-1) 到 2^53-1 之间的数值(包括边界值)。
    //输出最大安全值
    Number.MAX_SAFE_INTEGER;    //9007199254740991 number静态属性,直接调用
    console.log(max) //9007199254740991 说明:9,007,199,254,740,991(2^53-1) 
    
    //输出最小安全值
    Number.MIN_SAFE_INTEGER;    //-9007199254740991
    console.log(max) //9007199254740991 说明:9,007,199,254,740,991(2^53-1) 

科学计数法

//极大或极小的数字可以通过科学计数法来书写
var number1 = 123e15;
var number2 = 123e-15;
console.log(number1) //123000000000000000
console.log(number2) //1.23e-13

运算误差

//小数运算有可能会丢失精度,导致结果有细小误差
console.log(0.1+0.2)    //0.30000000000000004 -> 计算机在把十进制的0.1或0.2转换成二进制时丢失了“精度”,就好像我们在把1/3转换成0.333333333333333333时丢失了一点点数据一样
console.log(0.02+0.1)   //0.12000000000000001
console.log(0.15+0.2)   //0.35
console.log(0.15+0.25)  //0.4
console.log(1+2)        //3

2.字符串类型 string

  • 双引号/单引号/反引号:引号包起来的都是字符串,例如 var str = "[10,20]"; //字符串
  • 反引号: ES6 模板字符串,有助于字符串的拼接
  • 加号+:加号的左右两边,会先通过 Number 转换类型,转换之后如果有一边是 string,结果就是字符串拼接,因此,10+"10" 结果是 101010+undefined 结果是 NaN10+null 结果是 10,减号没有拼接字符串的功能,而是直接进行计算,因此,10-"10" 结果是 0
  • 单边加号:加号如果只出现在单边,那么这一边即使是字符串/对象,也是实行数学运算,例如 +"10" 结果是 number 类型的10
  • 特殊情况2:如果是一个{}加内容,且{}在前,那么{}不参与运算,只是相当于一个代码块,但如果用()包起来,则会当成一个运算整体

image.png

(4)转换为字符串

  • 方案 1:String() 方法

    String(123);    //"123"
    String(true);   //"true"
    String(false);  //"false"
    String(undefined);  //"undefined"
    String(null);       //"null"
    String("str");      //"str"
    String(function fn(){}); //"function fn(){}"
    var before = 100
    var after = String(before)
    console.log("before:"+before+"-"+typeof before,"after:"+after+"-"+typeof after)   //before:100-number after:100-string
    
    //情况1:并不是全部直接用引号括起来,而是对其中的返回值括起来
    String(Number(1));      //"1"
    String(Number(0));      //"0"
    String(Bigint(4));      //"4"
    String([10,20]);        //"10,20" 数组转化为字符串,是把其中每一项用逗号分隔
    String(new Number(1));     //"1"
    String(new Boolean(true)); //"true"
    
    //情况2:对于普通对象,转换为字符串,无论里面包含哪些内容,结果都是输出"[object Object]"
    String({});     //"[object Object]"
    
  • 方案 2:toString() 方法

    var digit = 100
    console.log(digit.toString())   //方法是对象所具有的,之所以基本数据类型也可使用,在于存在js的强制转换,但是这种强制转换对于null和string都会报错
    //注意:null和undefined使用toString()方法会报错,因此在使用toString()方法的时候,需要判断是否为null或undefined
    var targetNull = null
    console.log(targetNull.toString()) //报错:Cannot read property 'toString' of null
    var targetUndefined
    console.log(targetUndefined.toString()) //报错:Cannot read property 'toString' of undefined
    
  • 方案 3:加号,只有两边都是数字的时候,才会进行数学运算,只要有一边是字符串,就会进行字符串拼接

    //加上一个空字符串即可 
    var digit = 100 
    var text = digit.toString() //加上一个空字符串,即可转换为字符串
    console.log(digit,text)    //100 "100"
    

3.布尔类型 Boolean

(1)2个布尔类型值

  • 布尔类型只有两个值:true / false

(2)转换为布尔类型

  • 方式一:Boolean([value])

    console.log(Boolean(1))          //true
    console.log(Boolean(0))          //false
    console.log(Boolean(10086))      //true
    console.log(Boolean(undefined))  //false
    console.log(Boolean(""))         //false
    console.log(Boolean(null))       //false
    console.log(Boolean(NaN))        //false
    console.log(Boolean(-10086))     //true
    console.log(Boolean([]))         //true
    console.log(Boolean({}))         //true
    
    /* 只有 0/""/null/undefined/NaN 转换为布尔类型是false,其余都是true */
    
  • 方式二:!![value]

    /* *
     * 作用机制:
       步骤1:首先,执行`![value]`将value值转换为布尔类型,然后取反
       步骤2:其次,执行`!![value]`将之前转换为布尔类型同时又取反的值,再次取反,那么输出的结果就是两次取反得到原本对应的布尔类型值(负负得正)
     * */
    
    console.log(!!true)       //true
    console.log(!![])         //true
    console.log(!!0)          //false
    console.log(!!undefined)  //false
    console.log(!!null)       //false
    console.log(!!"string")   //true
    console.log(!!"")         //false
    console.log(!!-100)       //true
    console.log(!!NaN)        //false
    
    console.log(typeof new String(""))     //object
    console.log(!!new String(""))          //true 
    console.log(typeof String(""))         //string
    console.log(!!String(""))               //false
    

(3)逻辑运算符

  • 逻辑或 ||A||B 首先看A是真还是假,如果A是真返回A的值,如果A是假返回B的值

  • 逻辑与 &&A&&B 首先看A是真还是假,如果A是假返回A的值,如果A是真返回B的值

//注意1:只有 0/NaN/空字符串/null/undefined 是假,其余都是真
//注意2:逻辑与 && 的优先级高于逻辑或 ||

console.log(10||20)             //10
console.log(0||20)              //20
console.log(10&&20)             //20
console.log(0&&20)              //0
console.log(10||20&&30||40)     //10
console.log(0||10&&20||30)      //20

4.ES6 唯一值类型 Symbol

  • 创建方法: Symbol();
  • 使用场景:一般可以用于定义对象的属性名,因为对象属性名不能重名,如果在定义对象属性名的时候,不知道已经存在该属性,很有可能会覆盖原有的对象属性,如果是方法,则会将方法给覆盖,导致未知问题
  • Symbol 用于创建唯一值,相互之间是不相等的,可以在方法内加入标识,但即使是同样的标识,唯一值也不相等,因为标识仅仅用于开发人员区别
    //创建两个唯一值
    console.log(Symbol() == Symbol());  //false,两个唯一值是不相等的
    //创建两个唯一值,加上标识
    console.log(Symbol("F1") == Symbol("F1"));  //false,加上标识同样也是不相等的
    //防止对象变量名重复
    let name = Symbol("name");
    let age = Symbol("age");
    let work = Symbol("work");
    let obj = {[name]:"James", [age]:20, [work]:"developer"};
    console.log(obj);   //{Symbol(name): 'James', Symbol(age): 20, Symbol(work): 'developer'}
    

5.大数类型 BigInt

  • 概念:BigInt数据类型提供了一种方法来表示大于 2^53-1 的整数。

  • 缘由:JavaScript数字的安全存储的范围是有限的

  • 解决:如果需要使用超出JavaScript安全存储范围的数字,就需要采用Bigint数据类型,具体使用方法如下:

    //方法1:在整数的末尾追加 n
    //方法2:调用Bigint()构造函数
    
    var grassCount = 12000n
    console.log(typeof grassCount)  //bigint
    
    var squareCount = BigInt(13000)
    console.log(typeof squareCount) //bigint
    
    var levelCount = 13000
    console.log(typeof levelCount)  //number
    
    console.log(levelCount == squareCount)  //true
    console.log(levelCount === squareCount) //false
    
    /* 安全存储范围 */
    var max = Number.MAX_SAFE_INTEGER  //9007199254740991
    console.log(max+1)  //9007199254740994 说明:偶然正确
    console.log(max+10)  //9007199254741000 说明:数学上应该9007199254741001,说明超过安全存储,计算不精确
    
    var beyond = 9007199254740993   //超过安全存储范围
    console.log(beyond)     //9007199254740992 说明:数学上应该9007199254740993
    console.log(beyond+1)   //9007199254740992 说明:数学上应该9007199254740994
    console.log(beyond+4)   //9007199254740996 说明:数学上应该9007199254740997
    
    console.log(BigInt(beyond))     //9007199254740992n 说明:数学上应该9007199254740993n,但因为已经将9007199254740993赋值给了beyond,此时已经是number类型,数据已经不正确了,从而就算使用bigint转换,得到的也是错误结果
    console.log(BigInt(9007199254740993))   //9007199254740992n 说明:9007199254740993虽然没有赋值,但其本身也超过了安全存储范围
    console.log(BigInt(9007199254740993n)) //9007199254740993n
    console.log(9007199254740993n)  //9007199254740993n 总结:如果需要超过安全存储范围的值,则在数值后面添加n,此时才是bigint类型数值
    console.log(typeof BigInt(9007199254740993n)) //bigint
    

6.undefined 类型

  • 如果变量没有被赋值,其类型就是 undefined
  • 一个函数如果没有使用 return 语句指定返回值,就会返回一个 undefined 值

(1)严格相等和 undefined

你可以使用 undefined 和严格相等或不相等操作符来决定一个变量是否拥有值。在下面的代码中,变量 x 是未定义的,if 语句的求值结果将是 true

var x;
if (x === undefined ) {
    console.log("x is undefined")  //执行
} else {
    console.log("x isn't undefined")
}

(2)typeof 和 undefined

  • typeof 检测原理:所有的数据类型值在计算机中,都是按照二进制的值来进行存储的,而对象类型(不论是普通对象,还是数组、正则等对象)的二进制值都是以000开头的,null在计算机中存储的二进制值是000,所以typeof在检测null时候,检测到也是000开头的,因此就会被认为是对象了,但null并不是对象,是基本数据类型。

    数据类型二进制代码
    Object / null000
    整数01
    浮点数010
    字符串100
    布尔110
  • typeof 检测优点:使用 typeof 不会在一个变量没有被声明的时候抛出一个错误(但是,技术方面看来这样的使用方法应该被避免。JavaScript 是一个静态作用域语言,所以,一个变量是否被声明可以通过看它是否在一个封闭的上下文中被声明。唯一的例外是全局作用域,但是全局作用域是被绑定在全局对象上的,所以要检查一个变量是否在全局上下文中存在可以通过检查全局对象上是否存在这个属性(比如使用in操作符)。)

    // var x; 这里x没有被声明
    if (typeof x === undefined ) { //没有抛出错误
        console.log("x is undefined")  //执行
    } else {
        console.log("x isn't undefined")
    }
    
    //typeof num 和 typeof(num) 两种写法区分
    var num = 12
    console.log(typeof num + 100)   //number100
    console.log(typeof(num + 100))  //string
    
    if ('x' in window) {
      // 只有 x 被全局性的定义 才会执行这些语句
    }
    
  • typeof 检测缺点:typeof null 和typeof 对象/数组/正则/日期 的结果都是一样的object,因此基于typeof的检测,不能细分对象

    console.log(typeof 123)             //number
    console.log(typeof "")              //string
    console.log(typeof false)           //boolean
    console.log(typeof null)            //object
    console.log(typeof undefined)       //undefined
    console.log(typeof NaN)             //number
    console.log(typeof 0)               //number
    console.log(typeof [10,20])         //object
    console.log(typeof typeof [10,20])  //string
    typeof function () {}                // 返回 function
    typeof null                         // 返回 object
    

    案例分析 判断是否为一个对象

    //方案1
    var bulk = {};
    if(typeof bulk === "object"){  
        //缺陷:bulk 有可能是null,也有可能是object
        console.log("bulk is an Object.");
    }
    //改进方案
    if(bulk !== null && typeof bulk === "object"){
        console.log("bulk is an Object.");        
    }
    

(3)Void 和 undefined

void 运算符 对给定的表达式进行求值,然后返回 undefined

var y;
if(y === void 0){
    console.log("y is undefined")  //执行
}else{
    console.log("y isn't undefined")
}

(4)JavaScript 数据类型检测

数据类型检测

-   typeof [value]
-   [value] instanof [constructor]
        
        instanceof 只适用于引用类型
        子孙 instanceof 祖宗
        [anyValue] instanceof Object 永远都会是true
-   [value].constructor
-   Object.prototype.toString.call([value])

(5)? 防止报错

    //字面量没有 value 值,调用返回undefined
    console.log(false.value);
    console.log("0".value);
    console.log("undefined".value);
    //undefined 调用value 会报错,可以加上 ? 防止报错
    console.log(undefined?.value);
    console.log(null?.value);

7.null 类型

  • null特指对象的值未设置。它是JS基本类型之一,在布尔运算中被认为是falsy

  • 值 null 是一个字面量,不像 undefined,它不是全局对象的一个属性。

  • 在 API 中,null 返回的是 object ,而是因为null的二进制开头与对象的二进制开头一样,都是000,这一点也跟Eich设计语言时的想法有关系,他认为null可以作为一个暂时还没有放置对象的容器存在。

    // foo 不存在,它从来没有被定义过或者是初始化过:
    foo;
    "ReferenceError: foo is not defined"
    
    // foo 现在是存在的,但没有具体意义的值:
    var foo = null;
    console.log(foo);           //null
    console.log(null)           //null
    //检测类型
    console.log(typeof foo)    //object
    console.log(typeof null)    //object
    

null 和 undefined 区别

  1. null 和 undefined 的值相同,却属于原始类型,保存在栈(stack),但具体类型不同
console.log(isNaN(null))      //false 
console.log(null+1)           //1
console.log(null+"1")         //null1 
console.log(typeof null)                 //object 说明:null虽然返回的是object,却是基本数据类型null
console.log(typeof undefined)            //undefined
console.log(null === undefined)          //false
console.log(null == undefined)           //true
//以下代码说明 null 和 undefined 值相同
console.log(!null)                       //true
console.log(!!null)                      //false
console.log(!undefined)                  //true
console.log(!!undefined)                 //false
//以下两个代码说明 null undefined 不同于NaN
console.log(null === null)               //true
console.log(undefined === undefined)     //true
  1. null 表示已被声明,且已被赋值(赋值为null)的变量情况,赋值为null表示当前不指向任何对象地址,但将来可能会指向某一个对象地址,先暂时用null释放出内存,因为在js底层代码中,后台会伴随着当前程序同时进行,js引擎会定时自动调用垃圾回收器,因此如果一个比较大的变量使用完,想要释放内存的话,只有这个对象不再被任何变量引用的时候,才能被释放,所以可以将其设置为null,但此时这个变量还存在,只是值为null,而此时,垃圾回收器就完美地完成了专门释放对象内存的任务;undefined 表示变量已被声明,但还没有经过任何赋值,因此,它是所有未赋值变量的默认值
  2. 拓展分析:
    • null和undefined的相似性困惑:在JavaScript中,将一个变量赋值为undefined或null,老实说,几乎没区别。undefined和null在if语句中,都会被自动转为false,相等运算符甚至直接报告两者相等。既然undefined和null的含义与用法都差不多,为什么要同时设置两个这样的值,这不是无端增加JavaScript的复杂度,令初学者困扰吗?Google公司开发的JavaScript语言的替代品Dart语言,就明确规定只有null,没有undefined!
    • 历史原因追溯:1995年JavaScript诞生时,最初像Java一样,只设置了null作为表示"无"的值。根据C语言的传统,null被设计成可以自动转为0。但是,JavaScript的设计者Brendan Eich,觉得这样做还不够,有两个原因。首先,null像在Java里一样,被当成一个对象。但是,JavaScript的数据类型分成原始类型(primitive)和合成类型(complex)两大类,Brendan Eich觉得表示"无"的值最好不是对象。其次,JavaScript的最初版本没有包括错误处理机制,发生数据类型不匹配时,往往是自动转换类型或者默默地失败。Brendan Eich觉得,如果null自动转为0,很不容易发现错误。
    • 最初设计:JavaScript的最初版本是这样区分的:null是一个表示"无"的对象,转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN
    • 目前用法:但在实际应用中发现,不止如此。
      • null表示"没有对象",即该处不应该有值。典型用法是:

        (1)作为函数的参数,表示该函数的参数不是对象。

        (2)作为对象原型链的终点。

      • undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:

        (1)变量被声明了,但没有赋值时,就等于undefined。

        (2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。

        (3)对象没有赋值的属性,该属性的值为undefined。

        (4)函数没有返回值时,默认返回undefined。

console.log(isNaN(null))                 //false 0说明:null转换为数字为0
console.log(isNaN(null+1))               //false 1
console.log(isNaN(undefined))            //true
console.log(isNaN(undefined+1))          //true

console.log(Number(null))               //0
console.log(Number(undefined))          //NaN
/* 
 *注意 null 的特殊情况:object转换为数字类型为NaN, 
 *但null转换为数字类型为0,且null转换为字符串为"null" 
 */
console.log(typeof null)                //object 说明:这里并不说明null是对象,只是null开头的二进制代码与对象开头的二进制代码相同,在检测的时候被误认为是对象了,null是基本数据类型
console.log(isNaN(null))                //false 
console.log(typeof (null+1))            //number
console.log(null+1)                     //1
console.log(null+"1")                   //null1

//应用:可以通过将变量的值设置为 null 来清空变量
var x = 5566;	//赋值变量;输出 5566
var x = null;	//清空变量;输出 undefined

== VS ===

  • == 相等,只会比较值,就算两边的数据类型不相等,浏览器会通过隐式转换,然后再将值进行比较,因此,同类型比较,直接进行 "值" 比较,两者结果一样,但对于 Array,Object 等高级类型之内比较,== 和 === 是没有区别的
  • === 严格相等,要求两边的数据类型和值都要相等,只要有一个不相等,结果都是false,即不存在数据类型的隐式转换
console.log(10 === "10")        //false 
console.log(null === undefined)          //false

//应用场景:为了防止后端传过来的数据可能是number也可能是string,则会将数据进行转换,然后再比较
var transfer = "90"
console.log(parseInt(transfer) === 90)  //true

== 比较及转换规则

  • 规则 1:NaN 跟任何值都不相等
  • 规则 2:null == undefined 与其他值都不相等
  • 规则 3:字符串 == 对象 对象会转换为字符串,然后再比较
  • 规则 4:除了以上规则,都是先转换为字符串,再转换为数字,最后再比较
/* == 比较及隐式转换规则 */
//规则 1:NaN 跟任何一个值都不相等
console.log(NaN == NaN)                 //false

//规则 2:null与undefined两者之间的值相互相等,但类型不同,且除两者外与其它任何值都不相等
console.log(null == undefined)          //true 说明:规则记忆
console.log(null == null)               //true
console.log(undefined == undefined)     //true
console.log(null == 0)                  //false 说明:null在==比较的时候不会转换为number 0
console.log(null == "null")             //false 说明:null不是对象,与字符串比较的时候不会转换为字符串
console.log(undefined == 0)             //false
console.log(undefined == "undefined")   //false 说明:undefined不是对象

//规则 3:string == object,字符串与对象比较,会把对象object转换为字符串string
console.log("string" == ["string"]) //true

//规则 4:除开以上规则的比较,都是按照“先转换为number,然后再比较”进行处理
//eg.1
console.log(10 == "10")         //true 
/* * 
 * 转换规则:首先,这里是除 NaN==NaN null==undefined 字符串==对象 三种比较之外的情况,
 * 因此,是先将数据类型转换为数字,然后再将转换之后的数字进行比较,
 * 可以发现,"10"是字符串,通过Number("10")转换为数字10,
 * 另一边的已经是number类型的数字10,
 * 因此,不用转换,此时进行比较,发现完全相等,
 * 因此,结果是true
 * */
//eg.2
console.log("10" == [10])      //true
/* *
 * 转换规则:首先,这里是 对象 == 字符串 比较,
 * 因此 ,会先将对象转换为字符串[10].toString(); 即[10]转换为字符串"10";
 * 然后再将转换之后的字符串"10"与字符串"10"进行比较,
 * 可以发现,这个时候已经完全相等了,
 * 因此,比较的结果便是true 
 * */
//eg.3
console.log("1" == true)        //true
/* *
 * 转换规则:首先,这里是除 NaN==NaN null==undefined 字符串==对象 三种比较之外的情况,
 * 因此,先将数据类型转换为数字,然后再进行比较,
 * 这里"1"是字符串,通过Number("1")转换为数字1,
 * true是boolean类型,通过Number(true)转换为数字是1,
 * 此时,两边完全相等,结果是true
 * */
//eg.4
console.log(0 == [])    //true
/* *
 * 转换规则:首先,这里是除 NaN==NaN null==undefined 字符串==对象 三种比较之外的情况,
 * 因此,要先将数据类型转换为数字,然后进行比较,
 * 左边已经是数字0,不用转换,
 * 右边[]是数组对象,需要先转换为数字类型,Number([])的结果是0,
 * 因此隐式转换后相等
 * */


//拓展:数组对象 [] 转换为数字 0 的全过程:
//step1 : 
[][Symbol.toPrimitive] //-> undefined
//step2 : 
[].valueOf() //-> [] 这个是引用值,不是原始值
//step3 : 
[].toString() //-> ""
//step4 : 
Number("") //-> 0

8. 检测方式

typeof

instanceof

  • 定义instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
  • 语法object instanceof constructor
  • 说明:只适用于引用类型
//手写 instanceof 功能
function _instanceof(obj, con) {
    let result = { isInstance: undefined, details: undefined }
    if (typeof obj === "object" && obj !== null) {
        //1. 获取构造函数的原型
        let conPrototype = con.prototype;
        //2. 获取实例对象的原型链
        let objProtoChain = obj.__proto__;
        //3. 比对实例对象的原型链上是否有一层与构造函数的原型相等,直到null
        (function check(chain = objProtoChain) {
            //原型链顶层 Object.prototype
            if (chain) {
                if (conPrototype.__proto__ === null) {
                    result.isInstance = true;
                    result.details = `"${obj}" is belong to "${con}"`;
                    return result;
                } else if (chain === conPrototype) {
                    result.isInstance = true;
                    result.details = `"${obj}" is belong to "${con}"`;
                    return result;
                } else {
                    result.isInstance = false;
                    result.details = `"${obj}" isn't belong to "${con}"`;
                    return result;
                }
                chain = chain.__proto__;
                check(chain);
            }
        })();
        return result.isInstance;
    } else {
        result.isInstance = false;
        if(obj === null){
            result.details = `"${obj}" is null primitive`;
        } else if(typeof obj === "symbol") {    //symbol类型的值不能转换为字符串,会报错
            result.details = `Symbol() is ${typeof obj} primitive`;
        } else if(typeof obj === "bigint"){
            result.details = `${obj}n is ${typeof obj} primitive`;
        } else {
            result.details = `"${obj}" is ${typeof obj} primitive`;
        }
        return result.isInstance +" : "+ result.details;
    }

}

//引用数据类型
console.log(_instanceof(new Date(), Date));     //true
console.log(_instanceof([1, 2, 3, 4], Array));  //true
console.log(_instanceof([1, 2, 3, 4], Object)); //true
console.log(_instanceof(new Date(), String));   //false
console.log(_instanceof(new String(), Date));   //false
console.log(_instanceof(new Date(), Object));   //true

//基本数据类型
console.log(_instanceof(12345678, Number));     //false : "12345678" is number primitive
console.log(_instanceof("string", String));     //false : "string" is string primitive
console.log(_instanceof(undefined, Object));    //false : "undefined" is undefined primitive
console.log(_instanceof(null, Object));         //false : "null" is null primitive
console.log(_instanceof(12345678n, BigInt));    //false : 12345678n is bigint primitive
console.log(_instanceof(Symbol(), Object));     //false : Symbol() is symbol primitive
console.log(_instanceof(!!0, Boolean));         //false : "false" is boolean primitive

//基本数据类型是非对象实例,使用 instancof 检测,都是 false,因此不适用instanceof检测
console.log(123 instanceof Object);
console.log("string" instanceof Object);
console.log(undefined instanceof Object);
console.log(null instanceof Object);
console.log(12n instanceof Object);
console.log(Symbol() instanceof Object);
console.log(!!0 instanceof Object);
function myInstanceof(obj,constr){
    var isInstance;
    if((typeof obj === "object" || typeof obj === "function") && obj !== null ){
        var constrPro = constr.prototype;
        var objChain = obj.__proto__;
        if(constrPro.__proto__ === null){
            isInstance = true;
        } else if(objChain === constrPro){
            isInstance = true;
        } else {
            isInstance = false;
        }
    } else {
        isInstance = false;
    }
    return isInstance
}