day4: JS 代码执行题( == 与 隐式转化)

212 阅读8分钟

JS 代码执行题( == 与 隐式转化)

我们先来看个例子:

var a = [0];
if (a) {
  console.log(a == true);
} else {
  console.log(a);
}
// 写出执行结果,并解释原因

大家猜想结果会是什么呢?

  • 答案是: false;

为什么呢?

解析

(1) 当 a 出现在 if 的条件中时,被转成布尔值,而 Boolean([0]) true,所以就进行下一步判断 a == true,在进行比较时,[0]被转换成了 0,所以 0==truefalse 数组从非primitive 转为primitive的时候会先隐式调用 join 变成0string boolean 比较的时候,两个都先转为number类型再比较,最后就是 0==1 的比较了,至于为什么会调用 join 在后面剖析。

深度剖析

ToBoolean

我们先看看 ToBoolean 转化规则

ToBoolean 转化规则

当我们进行 if 判断时 会进行 Boolean([0]) 转化得到的结果是 true 在进行 == 判断

!![] //true 空数组转换为布尔值是 true, 
Boolean([]) // 空数组转换为布尔值是  true
!![0]//true 数组转换为布尔值是 true  
[0] == true;//false 数组与布尔值比较时却变成了 false  
Number([])//0  当 Number 中 为 Object 时还会调用 ToPrimitive
Number(false)//0  
Number(['1'])//1  当 Number 中 为 Object 时还会调用 ToPrimitive

我们看看 == 判断得隐式转化规则

== 判断规则

我们先看看几个简单的例子


console.log(1 == 1);
// expected output: true

console.log('hello' == 'hello');
// expected output: true

console.log('1' ==  1);
// expected output: true

console.log(0 == false);
// expected output: true

ECMA 对他的描述

ECMA对他的描述

比较x == y,其中xy是值,产生true或 false。如下进行这样的比较:

  1. 如果Type ( x ) 与Type ( y ) 相同,则

    1. 如果Type ( x ) 与Type ( y )未定义,则返回true

    2. 如果Type ( x ) 与 Type ( x )为 Null,则返回true

    3. 如果Typex ) 是数字,那么

      1. 如果xNaN,则返回false
      2. 如果yNaN,则返回false
      3. 如果x与 y是相同的 Number 值,则返回true
      4. 如果x+0y为**-0**,则返回true
      5. 如果x为**-0y+0,则返回true**。
      6. 返回
    4. 如果Type ( x ) 是字符串,则如果xy是完全相同的字符序列(相同长度和对应位置的相同字符),则返回true 。 否则,返回 false

    5. 如果Type ( x ) 是 Boolean ,如果xy都为 true或都为false ,则返回 true。否则,返回false

  2. 如果xy引用同一个对象,则返回true 。 否则,返回false。********

  3. 如果xnully 未定义,则返回true

  4. 如果x 未定义ynull ,则返回true

  5. 如果Type ( x ) 是 Number 并且Type ( y ) 是 String,则
    返回比较结果x == ToNumber ( y )。

  6. 如果Type ( x ) 是 String 并且Type ( y ) 是 Number ,则
    返回比较结果ToNumber ( x ) == y

  7. 如果Type ( x ) 是 Boolean ,则返回比较结果ToNumber ( x ) == y

  8. 如果Type ( y ) 是 Boolean,则返回比较结果x == ToNumber ( y )。

  9. 如果Type ( x ) 是 String 或 Number 并且Type ( y ) 是 Object,则
    返回比较结果x == ToPrimitive ( y )。

  10. 如果Type ( x ) 是 Object 并且Type ( y ) 是 String 或 Number ,
    则返回比较ToPrimitive ( x ) == y的结果。

  11. 返回

我们看看下面几个例子

"1" ==  1;            // true
1 == "1";             // true
0 == false;           // true
0 == null;            // false
0 == undefined;       // false
null == undefined;    // true

const number1 = new Number(3);
const number2 = new Number(3);
number1 == 3;         // true
number1 == number2;   // false

注意:

  • 等式运算符并不总是可传递的。例如,可能有两个不同的 String 对象,每个对象代表相同的 String 值;操作员会认为每个 String 对象都等于 String 值==,但两个 String 对象不会彼此相等。例如:
  • new String("a") == "a"并且"a" == new String("a")都是true
  • new String("a") == new String("a")false的。

ToPrimitive

image.png

image.png

当使用提示String调用O的[[DefaultValue]]内部方法时,采取以下步骤:

  1. toString为使用参数 " " 调用对象O的 [[Get]] 内部方法的结果toString

  2. 如果IsCallable ( toString),则

    1. str为调用toString的 [[Call]] 内部方法的结果,其中O作为 this值和一个空参数列表。
    2. 如果str是原始值,则返回str
  3. valueOf为使用参数 " " 调用对象O的 [[Get]] 内部方法的结果valueOf

  4. 如果IsCallable ( valueOf),则

    1. val为调用valueOf的 [[Call]] 内部方法的结果,其中O作为 this 值和一个空参数列表。
    2. 如果val是原始值,则返回val
  5. 抛出TypeError异常。

O的 [[DefaultValue]] 内部方法用提示 Number 调用时,采取以下步骤:

  1. valueOf为使用参数 " " 调用对象O的 [[Get]] 内部方法的结果valueOf

  2. 如果IsCallable ( valueOf),则

    1. val为调用valueOf的 [[Call]] 内部方法的结果,其中O作为 this值和一个空参数列表。
    2. 如果val是原始值,则返回val
  3. toString为使用参数 " " 调用对象O的 [[Get]] 内部方法的结果toString

  4. 如果IsCallable ( toString),则

    1. str为调用toString的 [[Call]] 内部方法的结果,其中O作为 this 值和一个空参数列表。
    2. 如果str是原始值,则返回str
  5. 抛出TypeError异常。

当O的 [[DefaultValue]] 内部方法在没有提示的情况下被调用时,它的行为就像提示是数字一样,除非O是 Date 对象(参见 15.9.6),在这种情况下,它的行为就像提示是细绳。

本机对象的上述 [[DefaultValue]] 规范只能返回原始值。如果宿主对象实现了自己的 [[DefaultValue]] 内部方法,它必须确保其 [[DefaultValue]] 内部方法只能返回原始值。

数组中的toString

image.png

当数组调用toString 方法的时候 ,会调用内部的join 方法, 所以 [0].join("") 时变成 "0"

当我们进行 == 判断是 此时 的 "0" == 1 对比时会遵循规则

image.png

此时又会将 "0" 转化成 0 在与1 对比 , 所以最终 返回的结果为false

解释一下Number(['1']) 执行过程

image.png

image.png

剖析Number([1]) 转化

当我们使用Number(['1']) 时调用的是valueOf 方法 、toString 、join

我们来重写valueOf,toString,join看看


const orginValueOf = Array.prototype.valueOf // 保留原来的valueOf 方法 
Array.prototype.valueOf = function () {
    console.log("调用 valueOf")
    return orginValueOf.call(this)
}

const orginTostring = Array.prototype.toString // 保留原来的 toString 方法 
Array.prototype.toString = function () {
    console.log("调用 toString")
    return orginTostring.call(this)
}

const orginJoin = Array.prototype.join;  // 保留原来的join 方法 

Array.prototype.join = function () {
    console.log("调用 Array.join() ")
    return orginJoin.call(this)
}

const res = Number([1])
console.log(typeof res, res)// number 1

image.png

在线测试地址: 剖析Number([1]) 转化

从上述例子中我们可以看出 Number([1]) 依次执行 valueOftoStringjoin

我们看看 valueOf 的返回值 JavaScript调用valueOf方法将对象转换为原始值。你很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。

默认情况下,valueOf方法由Object后面的每个对象继承。 每个内置的核心对象都会覆盖此方法以返回适当的值。如果对象没有原始值,则valueOf将返回对象本身。

JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法的返回值和返回值类型均可能不同。

对象返回值
Array返回数组对象本身。
Boolean布尔值。
Date存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function函数本身。
Number数字值。
Object对象本身。这是默认情况。
String字符串值。
 Math 和 Error 对象没有 valueOf 方法。

你可以在自己的代码中使用valueOf将内置对象转换为原始值。 创建自定义对象时,可以覆盖Object.prototype.valueOf()来调用自定义方法,而不是默认Object方法。

剖析 [1] == 1 转化规则



const orginValueOf = Array.prototype.valueOf // 保留原来的valueOf 方法 
Array.prototype.valueOf = function () {
    console.log("调用 valueOf")
    return orginValueOf.call(this)
}

const orginTostring = Array.prototype.toString // 保留原来的 toString 方法 
Array.prototype.toString = function () {
    console.log("调用 toString")
    return orginTostring.call(this)
}

const orginJoin = Array.prototype.join;  // 保留原来的join 方法 

Array.prototype.join = function () {
   
    const result = orginJoin.call(this)
     console.log("调用 Array.join() =  ",typeof result ,result)
    return result
}

const orginToNumber = Number;  // 保留全局的 Number

window.Number = function (...arg) {
    console.log("调用 Number ")
    return orginToNumber(arg)
}
console.log([1] == 1)   // true



在线测试地址:剖析 [1] == 1 转化规则 剖析 [1] == 1  转化规则

toNumber

从上面的结果我们可以看出[1] == 1 的过程 也会 依次执行 valueOftoStringjoin 最终变成 "1" 在执行内部的 ToNumber 方法 最终将 [1] ==> 1

"true" == true 的结果

肯定很多小伙伴都会觉得这个的答案是 true

image.png 然而结果并不是我们想的那样, 我们来看看他是如何转化的呢?

  1. true ==> Number(true) ==> 1

If Type(y) is Boolean, return the result of the comparison x == ToNumber(y). 2. 1 == "true" ==> 将 "true" => Number("true") ==> NAN If Type(x) is String and Type(y) is Number,
return the result of the comparison ToNumber(x) == y.

  1. 1 == NAN ==> false

所以最终的结果为 false

剖析 Number(1) == 1

const n = new Number(2)

const orginValueOf = Number.prototype.valueOf // 保留原来的valueOf 方法 
Number.prototype.valueOf = function () {
    
    const value = orginValueOf.call(this)
    console.log("调用 valueOf","value = "+value,"typeof: "+typeof value)
    return value
}

const orginTostring = Number.prototype.toString // 保留原来的 toString 方法 
Number.prototype.toString = function () {
    console.log("调用 toString")
    return orginTostring.call(this)
}

const orginJoin = Number.prototype.join;  // 保留原来的join 方法 


console.log(n == 2)


在线测试地址:剖析 Number(1) == 1

剖析 Number(1) == 1

我们可以 发现和前面的 Array 不同的是 Number 重写了 valueOf 方法 所以不会调用 内部的 Tostring 方法,

Array.prototype.valueOf 剖析 Number(1) == 1

我们可以试着修改 NumbervalueOf 改成原来的 Object.prototype.valueOf看看结果如何?

image.png

所以我们可以看到 Number 中是重写了valueOf方法,所以不会调用 ToString ,直接讲 number 类型的数据返回

在线测试地址:修改valueOf

总结

我们在进行 == 判断 转化式遵循以下规则即可,其实我们可以从中发现,类型不同时,最终主要都还是进行Number转化 ECMA对他的描述

喜欢我的小伙伴可以 关注我的博客哦: 白鹤之家