类型
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 时,复制的是原变量(也就是 a )的地址(指针)。换而言之,当前变量 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}
obj
参数接收到传入的p1
对象的引用,即obj
和p1
都指向同一个对象。obj.age = 26
:通过obj
改变了对象的age
属性值,由于obj
和p1
指向同一个对象,因此p1
的age
属性值也会变为 26。obj = { name: 'otherName', age: 99 }
:将obj
的引用指向一个新的对象,这个新对象包含name
属性值为 'otherName' 和age
属性值为 99。此时,obj
和p1
不再指向同一个对象。return obj
:函数返回新创建的对象,该对象的引用被赋值给p2
。
类型转化
在JavaScript中,类型转化可以根据转化结果分为以下三种情况:
- 转化为布尔值
- 转化为数字
- 转化为字符串
接下来,我会对这三种情况进行详细的分析。
转换为布尔值
以下值在转换为布尔值时会被视为false:
undefined
null
false
NaN
''
(空字符串)0
(数字零)-0
(负零)
除了上述值以外,其他所有值在转换为布尔值时都会被视为true,包括:
- 字符串(包含空格或其他字符的非空字符串)
- 数字(除了0和-0之外的所有数字)
- 对象(包括数组、函数和其他对象)
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
被转换为 1
,false
被转换为 0
),然后按照前面的规则执行加法运算。
console.log(true + 3); // 输出 4(true 被转换为数字 1)
console.log(false + '3'); // 输出 "false3"
6.当一个操作数是 null
或 undefined
时,null
和 undefined
分别被转换为数字 0
和 NaN
,然后按照前面的规则执行加法运算。
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)
比较运算符
-
相等比较(
==
):- 如果两个操作数类型相同,执行严格比较。
- 如果两个操作数类型不同,则进行类型转换后再进行比较。规则如下:
- 如果一个操作数是数值(number),另一个操作数是字符串,则将字符串转换为数值,然后进行比较。
- 如果一个操作数是布尔值,则将布尔值转换为数值,然后进行比较。
- 如果一个操作数是对象,另一个操作数是数值或字符串,则将对象转换为原始值,然后进行比较。
-
严格相等(
===
):- 如果两个操作数类型不同,直接返回
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
。