Bootstrap

常见类型、类型转化、四则运算【JavaScript基础面试题】

类型

JS数据分为两大类型:

 1. 原始类型
 2. 对象类型
 

原始类型 / 基本类型(Primitive)

在 JS 中,有 7 种原始值,分别是:

  • boolean
  • null
  • undefined
  • number
  • string
  • symbol
  • bigint

原始类型常见考点

1.原始类型有没有方法属性

答:原始类型没有方法和属性

2.如果原始类型没有方法属性,请说出以下代码的执行结果。

console.log('ABC'.length); // 输出 3

答:当我们调用字符串的方法时,JavaScript 会将字符串值转换为字符串对象(String object),并在对象上调用方法。这是 JavaScript 在需要时进行的一种隐式类型转换

因此,在调用字符串方法时,JavaScript 首先将字符串值转换为字符串对象,然后调用相应的方法。在调用完方法后,字符串对象会被销毁,而原始字符串值保持不变。在这个例子中,字符串常量 'ABC' 被转换为字符串对象,并通过 .length 属性获取字符串的长度。

3.在JavaScript中,0.1 + 0.2 !== 0.3,请解释其原理

答:请看如下视频。

面试官:0.1+0.2≠0.3? 请解释原理

复杂类型(Object)

对象类型和原始类型不同的是,始类型的值存储在栈(stack)中,而对象类型的值存储在堆(heap)中。栈中存储的是对象在堆中的引用(指针)。

因此,当我们创建一个对象的时候,计算机会在堆中开辟一个空间存放这个对象。

const codereasy = [];

在上述代码中,我们创建了一个对象condereasy。实际上在堆中开辟了一个内存地址,假设该内存地址为#666 。那么在 #666 这个位置就存放了一个空数组 [ ].

对象类型常见考点

1. 看代码说结果

const a = []
const b = a
b.push('codereasy')

当我们将变量 a 给另外一个变量 b 时,复制的是原变量(也就是 )的地址(指针)。换而言之,当前变量 b 地址(指针)也是 #666。因此,不管我们修改 a 还是修改 b,等同于修改存放在地址(指针) #666 上的值,最终都会导致两个变量同时发生改变。

2. null 是不是对象类型?

null 不是对象类型。但是执行以下代码会得到这种结果。

console.log(typeof null); // "object"

这是因为在 JavaScript 的底层实现中,它们使用了一种称为“标签”的机制来存储不同类型的值。JavaScript 的不同数据类型都有相应的标签值。在早期的 JavaScript 实现中,对于对象类型,其标签值的二进制表示的低三位都是 0。而 null 值在内存中被表示为全 0,因此其低三位也都是 0。这导致了 typeof 操作符将 null 错误地识别为对象类型。

因此,在 JavaScript 中当你需要检查一个值是否为 null 时,最好直接使用 value === null,而不是依赖 typeof 操作符。

3.请说出以下代码的执行结果,并解释原理。

function change(obj) {
  obj.age = 26
  obj = {
    name: 'otherName',
    age: 99
  }

  return obj
}
const p1 = {
  name: 'codereasy',
  age: 18
}
const p2 = change(p1)
console.log(p1) 
console.log(p2) 

答:执行结果如下

{name: 'codereasy', age: 26}
{name: 'otherName', age: 99}
  1. obj参数接收到传入的 p1 对象的引用,即 objp1 都指向同一个对象
  2. obj.age = 26:通过 obj 改变了对象的 age 属性值,由于 objp1 指向同一个对象,因此 p1age 属性值也会变为 26
  3. obj = { name: 'otherName', age: 99 }:将 obj 的引用指向一个新的对象,这个新对象包含 name 属性值为 'otherName' 和 age 属性值为 99。此时,objp1 不再指向同一个对象。
  4. return obj:函数返回新创建的对象,该对象的引用被赋值给 p2

类型转化

在JavaScript中,类型转化可以根据转化结果分为以下三种情况:

  • 转化为布尔值
  • 转化为数字
  • 转化为字符串

接下来,我会对这三种情况进行详细的分析。

转换为布尔值

以下值在转换为布尔值时会被视为false

  1. undefined
  2. null
  3. false
  4. NaN
  5. ''(空字符串)
  6. 0(数字零)
  7. -0(负零)

除了上述值以外,其他所有值在转换为布尔值时都会被视为true,包括:

  1. 字符串(包含空格或其他字符的非空字符串)
  2. 数字(除了0和-0之外的所有数字)
  3. 对象(包括数组、函数和其他对象)
  4. true

当你需要将一个值转换为布尔值时,可以使用双感叹号 !! 操作符。例如:!!value

转化为原始类型

在JavaScript中,当对象需要进行类型转换(例如转换为字符串、数字或布尔值)时,会调用内置的 [[ToPrimitive]] 函数。

先看以下例子:

const obj = {
  valueOf() {
    console.log('Calling valueOf');
    return 42;
  },

  toString() {
    console.log('Calling toString');
    return 'I am an object';
  }
};

console.log(obj + 1); // 输出 "Calling valueOf",结果为 43(obj 被转换为数字 42)
console.log('Hello, ' + obj); // 输出 "Calling toString",结果为 "Hello, I am an object"(obj 被转换为字符串 "I am an object")

首先,我们看第一个console.log 。在加法计算中,所有的值会被转化为数字,因此,在这行代码中 obj 会被转化为数字。那么它如何被转化为数字呢?答案是,通过[[ToPrimitive]] 函数转化。

如果转化目标类型是数字,[[ToPrimitive]] 会按照以下顺序尝试调用方法:

  • 调用对象的 valueOf() 方法。如果返回的是原始类型值,则返回这个值。
  • 否则,调用对象的 toString() 方法。如果返回的是原始类型值,则返回这个值。
  • 如果以上两个方法都没有返回原始类型值,抛出一个类型错误(TypeError)。

然后,我们看第二个console.log 。在字符串拼接中,所有的值会被转化为字符串,因此,在这行代码中 obj 会被转化为字符串。那么它如何被转化为字符串呢?

  • 调用对象的 toString() 方法。如果返回的是原始类型值,则返回这个值。
  • 否则,调用对象的 valueOf() 方法。如果返回的是原始类型值,则返回这个值。
  • 如果以上两个方法都没有返回原始类型值,抛出一个类型错误(TypeError)。

最后,总结如下:在上述例子中,我们定义了一个对象 obj,它有 valueOf()toString() 方法。当我们尝试将 obj 与数字相加时,[[ToPrimitive]] 函数会优先调用 valueOf() 方法。当我们尝试将 obj 与字符串相加时,[[ToPrimitive]] 函数会优先调用 toString() 方法。

console.log(3 + 4); // 输出 7

四则运算

四则运算指的是加、减、乘、除四种运算方法。其中最为特殊的是加法,其余的三种(减、乘、除)运算规则完全一样。

加法运算规则

1.当两个操作数都是数字时,执行常规的数字相加。

console.log(3 + 4); // 输出 7

2.当两个操作数都是字符串时,执行字符串连接

console.log('Hello, ' + 'world!'); // 输出 "Hello, world!"

3.当一个操作数是数字,另一个操作数是字符串时,数字会先被转换为字符串,然后执行字符串连接。

console.log(3 + '4'); // 输出 "34"
console.log('3' + 4); // 输出 "34"

4.当一个操作数是对象时,对象会根据之前解释的 [[ToPrimitive]] 函数规则被转换为原始类型值(通常是字符串或数字),然后按照前面的规则执行加法运算。

const obj = {
  valueOf() {
    return 42;
  },

  toString() {
    return 'I am an object';
  }
};

console.log(obj + 1); // 43
console.log('Hello, ' + obj); // Hello, 42

5.当一个操作数是布尔值时,布尔值会先被转换为数字(true 被转换为 1false 被转换为 0),然后按照前面的规则执行加法运算。

console.log(true + 3); // 输出 4(true 被转换为数字 1)
console.log(false + '3'); // 输出 "false3"

6.当一个操作数是 nullundefined 时,nullundefined 分别被转换为数字 0NaN,然后按照前面的规则执行加法运算。

console.log(null + 2); // 输出 2(null 被转换为数字 0)
console.log(undefined + 2); // 输出 NaN(undefined 被转换为数字 NaN)
console.log('Hello, ' + null); // 输出 "Hello, null"(null 被转换为字符串 "null")
console.log('Hello, ' + undefined); // 输出 "Hello, undefined"(undefined 被转换为字符串 "undefined")

其他运算符(减、乘、除)

当操作数不是数字时,它们会被转换为数字,然后执行减法运算。

//减法运算符的规则
console.log(5 - '2'); // 输出 3('2' 被转换为数字 2)
console.log('5' - 2); // 输出 3('5' 被转换为数字 5)
console.log(true - 3); // 输出 -2(true 被转换为数字 1)
console.log(null - 2); // 输出 -2(null 被转换为数字 0)
console.log(undefined - 2); // 输出 NaN(undefined 被转换为数字 NaN)


//乘法运算符的规则
console.log(5 * '2'); // 输出 10('2' 被转换为数字 2)
console.log('5' * 2); // 输出 10('5' 被转换为数字 5)
console.log(true * 3); // 输出 3(true 被转换为数字 1)
console.log(null * 2); // 输出 0(null 被转换为数字 0)
console.log(undefined * 2); // 输出 NaN(undefined 被转换为数字 NaN)


//除法运算符的规则
console.log(10 / '2'); // 输出 5('2' 被转换为数字 2)
console.log('10' / 2); // 输出 5('10' 被转换为数字 10)
console.log(true / 2); // 输出 0.5(true 被转换为数字 1)
console.log(null / 2); // 输出 0(null 被转换为数字 0)
console.log(undefined / 2); // 输出 NaN(undefined 被转换为数字 NaN)

比较运算符

  1. 相等比较(==):

    • 如果两个操作数类型相同,执行严格比较。
    • 如果两个操作数类型不同,则进行类型转换后再进行比较。规则如下:
      • 如果一个操作数是数值(number),另一个操作数是字符串,则将字符串转换为数值,然后进行比较。
      • 如果一个操作数是布尔值,则将布尔值转换为数值,然后进行比较。
      • 如果一个操作数是对象,另一个操作数是数值或字符串,则将对象转换为原始值,然后进行比较。
  2. 严格相等(===):

    • 如果两个操作数类型不同,直接返回false
    • 如果两个操作数类型相同,执行严格比较。(需要注意的是,对于两个对象类型,会比较两个对象的地址是否相同,如果地址不同,则返回false

上述情况的具体例子:

如果两个操作数类型相同,执行严格比较

console.log(3 == 3); // 输出:true
console.log('abc' == 'abc'); // 输出:true
console.log(false == false); // 输出:true

如果一个操作数是数值,另一个操作数是字符串,则将字符串转换为数值,然后进行比较

console.log('123' == 123); // 输出:true
console.log('3.14' == 3.14); // 输出:true

如果一个操作数是布尔值,则将布尔值转换为数值,然后进行比较

console.log(true == 1); // 输出:true
console.log(false == 0); // 输出:true

如果一个操作数是对象,另一个操作数是数值或字符串,则将对象转换为原始值(调用ToPrimitive,ToPrimitive的详细规则见上文),然后进行比较

console.log([1] == 1); // 输出:true
console.log([2] == '2'); // 输出:true

这里的 [1][2] 是数组对象。在进行比较时,首先会调用数组对象的 valueOf 方法尝试转换为原始值,但是数组的 valueOf 方法返回的还是数组本身,所以无法转换为原始值,然后会尝试调用 toString 方法,[1].toString()[2].toString() 分别返回 '1''2',所以 [1] == 1[2] == '2' 的结果都是 true

;