JS 的数据类型有哪些
-
原始类型 / 基本类型:Number、String、Boolean、Null、Undefined、Symbol、BigInt
-
引用类型 / 对象类型:Object
原始类型 / 基本类型
原始/基本类型的值存储在栈内存 ,没有方法和属性
引用类型/ 对象类型
引用/对象类型的值存储在堆(heap)中。栈中存储的是对象在堆中的引用(指针)。可以添加方法和属性
Symbol 类型
通过 Symbol()生成的 symbol 变量,永远不会重复 。
由于 symbol 是原始类型,所以不能添加方法和属性。
基本包装类型
Number 是一个基本类型,但是它却能调用 toString() 的方法。
let number = 42;
console.log(number.toString()); // 输出:"42"
JavaScript 引擎的工作
-
自动创建 Number 类型的一个实例(和基本类型的值不同,这个实例就是一个基本包装类型的对象)
-
调用实例(对象)上指定的方法
-
销毁这个实例
基本类型的值虽然没有方法可以调用,但是后台临时创建的包装对象上有内置方法可以让我们调用方法,因此这样我们就可以对字符串、数值、布尔值这三种基本数据类型的数据进行更多操作。
由于基本包装类型的对象仅在执行瞬间存在,因此无法向其添加属性。相反,引用类型的对象会持续存在,直到它们不再被引用并被垃圾收集器回收,所以你可以向它们添加属性。
let number = 42;
number.myProperty = "hello";
console.log(number.myProperty); // 输出:undefined
隐式类型转换
转化为原始类型
在 JavaScript 中,当对象需要进行类型转换(例如转换为字符串、数字或布尔值)时,会调用内置的 [[ToPrimitive]]函数。
对象如何转原始类型?
如果对象拥有
[Symbol.toPrimitive]
方法,调用该方法。 若该方法能得到原始值,使用该原始值; 若得不到原始值,抛出异常调用对象的
valueOf
方法若该方法能得到原始值,使用该原始值; 若得不到原始值,进入下一步
调用对象的
toString
方法若该方法能得到原始值,使用该原始值; 若得不到原始值,抛出异常
const obj = {
[Symbol.toPrimitive]() {
console.log('Symbol.toPrimitive');
return 1;
},
valueOf() {
console.log("Calling valueOf");
return 42;
},
toString() {
console.log("Calling toString");
return "I am an object";
},
};
console.log(obj + 1); // 输出 "Symbol.toPrimitive",结果为 2(obj 被转换为数字 1)
console.log("Hello, " + obj); // 输出 "Symbol.toPrimitive",结果为 "Hello, 1"(obj 被转换为字符串 "1")
const myObject = {
[Symbol.toPrimitive]() {
return {};
},
valueOf() {
return {}
},
toString() {
return "2"
}
}
console.log(myObject == 2) // true
严格相等(===)、非严格相等( == )
对于 == 来说,如果两个值的类型一样,则直接比较。如果类型不一样,则先进行类型转换,再比较。 === 在比较两个值时都不会进行类型转换。
-
宽松相等(==):
-
如果两个操作数类型相同,执行严格比较。
-
如果两个操作数类型不同,则进行类型转换后再进行比较。规则如下:
-
如果一个操作数是数值(number),另一个操作数是字符串,则将字符串转换为数值,然后进行比较。
-
如果一个操作数是布尔值,则将布尔值转换为数值,然后进行比较。
-
如果一个操作数是对象,另一个操作数是数值或字符串,则将对象转换为原始值,然后进行比较。
-
-
-
严格相等(===):
-
如果两个操作数类型不同,直接返回
false
。 -
如果两个操作数类型相同,执行严格比较。(需要注意的是,对于两个对象类型,会比较两个对象的地址是否相同,如果地址不同,则返回 false)
-
NaN === NaN 返回 false。因为 NaN 是不等于任何值,包括它自身。
-
对于+0 和-0,+0 === -0 返回 true,因为严格相等不区分正负。
-
Object.is() 和 严格相等(===)的区别
-
Object.is
-
当比较 NaN 时,Object.is(NaN,NaN)返回 true。
-
当比较+0 和-0 时,Object.is(+0,-0)返回 false,因为它能区分正零和负零(所以我们在日常开发中,如要区分正负,应该用 object.is().
-
-
===
-
NaN === NaN 返回 false。因为 NaN 是不等于任何值,包括它自身。
-
对于+0 和-0,+0 === -0 返回 true,因为严格相等不区分正负。
-
null 和 undefined 和NaN 的区别
null 表示「无」的对象,转为数值时为0,undefined 表示「无」的原始值,转为数值时为NaN。
如果一个变量将来可能是任何值,此时此刻还没有值,可以赋值 undefined
如果一个变量将来可能是对象,此时此刻还不是对象,可以赋值 null
Number(null) //0
Number(undefined) //NaN
项目实践中的区别
null和undefined几乎是同义的
-
null 表示"没有对象",即该处不应该有值。典型用法是:
-
作为函数的参数,表示该函数的参数不是对象或者没有值。
-
作为对象原型链的终点。
-
-
undefined表示“缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:
-
变量被声明了,但没有赋值时,就等于undefined。
-
调用函数时,应该提供的参数没有提供,该参数等于undefined。
-
对象没有赋值的属性,该属性的值为undefined。
-
函数没有返回值时,默认返回undefined。
-
为什么typeof null 的结果是Object
数据类型 | 机器码标识 |
---|---|
对象(Object) | 000 |
整数 | 1 |
浮点数 | 010 |
字符串 | 100 |
布尔 | 110 |
undefined | -2^31(即全为1) |
null | 全为0 |
这是因为在 JavaScript 的底层实现中,它们使用了一种称为“标签”的机制来存储不同类型的值。JavaScript 的不同数据类型都有相应的标签值。在早期的 JavaScript 实现中,对于对象类型,其标签值的二进制表示的低三位都是 0。而 null 值在内存中被表示为全 0,因此其低三位也都是 0。这导致了 typeof 操作符将 null 错误地识别为对象类型。
使用 void 0 替代 undefined
undefined是JavaScript 的一个全局变量,也就是挂载在window对象上的一个变量并不是不是关键字,可以使用undefined作为一个变量名,在非全局环境中可以被重新定义
NaN 以及判断
-
NaN 表示不是一个数字
-
NaN 和任何值相加都是 NaN
-
NaN 与任何值都不相等包括自身
判断
isNaN() 先尝试转换为数字,若无法转换为数字,则返回 true,否则返回 false
Number.isNaN() 直接检查一个值是否是 NaN
面试
等式成立a ==1 && a ==2 & a ==3
从上到下按照规则比较,直到能得到确切结果为止
-
两端存在 NaN,返回 false
-
undefined 和 null只有与自身比较,或者互相比较时,才会返回 true
-
两端类型相同,比较值
-
两端都是原始类型,转换成数字重新比较
-
一端是原始类型,一端是对象类型,把对象转换成原始类型后重新比较
const a = {
count: 1,
valueOf() {
return this.count++
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('等式成立')
}
判断数组的方法
-
Object.prototype.toString.call()对象原型链判断方法
-
原型链判断
obj.__proto__ === Array.prototype
Object.getPrototypeOf(obj) === Array.prototype obj.__proto__ === Array.prototype
-
instanceof
-
👍 Array.isArray()
总结方法 1.2.3 有缺陷可以被人为修改原型,且方法 3 不适合有iframe
的情况
0.1 + 0.2 !== 0.3,请解释其原理
计算机用二进制存储数据。
整数用二进制没有误差,如 9
表示为 1001
。
而有的小数无法用二进制表示,如 0.2
用二进制表示就是 1.10011001100...
所以,累加小数时会出现误差。
null 是不是对象类型
null 不是对象类型。但是执行以下代码会得到这种结果。
console.log(typeof null); // "object"
在早期的 JavaScript 实现中,对于对象类型,其标签值的二进制表示的低三位都是 0。而 null 值在内存中被表示为全 0,因此其低三位也都是 0。这导致了 typeof 操作符将 null 错误地识别为对象类型。
['1', '2', '3'].map(parseInt)
答案:[1,NaN,NaN]
第一循环
parseInt('1',0)
// 参数:'1',进制值为默认的十进制(0)
// 返回结果:1
第二次循环
parseInt('2',1)
// 参数:'2',进制值为 1
// 无效的1进制 返回 NaN
第三次循环
parseInt('3',2)
// 参数:'3',进制值为 2
// 无效的2进制数字 返回 NaN
手写深拷贝
注意循环引用 、正则表达式、日期类型、Symbol
-
weakMap
-
正则表达式、日期类型
-
Symbol属性 和 undefined
基础版本 只考虑数组和普通对象
function deepClone(source) {
if (typeof source !== 'Object' || source == null) {
return source
}
let target = Array.isArray(source) ? [] : {}
for (key in source) {
if (typeof source[key] !== 'Object' || source[key] == null) {
target[key] = source[key]
} else {
target[key] = deepClone(source[key])
}
}
return target
}
加强版本
// 利用weakMap 解决循环引用 , weakMap的键必须是对象
function deepClone(source, map = new WeakMap()) {
if (typeof source !== 'Object' || source == null) {
return source
}
if (map.has(source)) {
return map.get(source)
}
let target = Array.isArray(source) ? [] : {}
map.set(source, target)
for (key in source) {
if (typeof source[key] !== 'Object' || source[key] == null) {
target[key] = source[key]
} else {
target[key] = deepClone(source[key], map)
}
}
return target
}
// 考虑 正则表达式、日期类型、Symbol
function deepClone(source, map = new WeakMap()) {
if (typeof source !== 'Object' || source == null) {
return source
}
if (map.has(source)) {
return map.get(source)
}
let target;
if (Array.isArray[source]) {
target = []
} else if (source instanceof Date) {
target = new Date(source)
} else if (source instanceof RegExp) {
target = new RegExp(source.source, source.flags)
} else if (source instanceof Map) {
target = new Map()
source.forEach((v, k) => {
const v1 = deepClone(v, map)
const k1 = deepClone(k, map)
target.set(v1, k1)
})
} else if (source instanceof Set) {
target = new Set()
source.forEach(v => {
const v1 = deepClone(v, map)
target.add(deepClone(v1))
})
} else {
target = {}
}
map.set(source, target)
for (key in source) {
if (typeof source[key] !== 'Object' || source[key] == null) {
target[key] = source[key]
} else {
target[key] = deepClone(source[key], map)
}
}
const symbolKeys = Object.getOwnPropertySymbols(source)
for (const s in symbolKeys) {
target[s] = deepClone(source[s], map)
}
return target
}
获取数据类型
-
typeof,最常见的判断方法
-
instanceof ,用于已知对象类型
-
Object.prototype.toString.call(),对象原型链判断方法
typeof 的特点
-
对于基本类型,除 null 以外,均可以返回正确的结果。
-
对于引用类型,除 function 以外,一律返回 object 类型。
-
对于 null ,返回 object 类型。
-
对于 function 返回 function 类型。
console.log(typeof "helloworld") ------------------>"string"
console.log(typeof 123) ------------------>"number"
console.log(typeof [1,2,3]) ------------------>"object"
console.log(typeof new Function()) ------------------>"function"
console.log(typeof new Date()) ------------------>"object"
console.log(typeof new RegExp()) ------------------>"object"
console.log(typeof Symbol()) ------------------>"symbol"
console.log(typeof true) ------------------>"true"
console.log(typeof null) ------------------>"object"
console.log(typeof undefined) ------------------>"undefined"
console.log(typeof 'undefined') ------------------>"string"
[1,2,3] instanceof Array -------->true
new Date() instanceof Date -------->true
new Function() instanceof Function -------->true
new Function() instanceof function -------->false
null instanceof Object -------->false
console.log(Object.prototype.toString.call("123")) -------->[object String]
console.log(Object.prototype.toString.call(123)) -------->[object Number]
console.log(Object.prototype.toString.call(true)) -------->[object Boolean]
console.log(Object.prototype.toString.call([1, 2, 3])) -------->[object Array]
console.log(Object.prototype.toString.call(null)) -------->[object Null]
console.log(Object.prototype.toString.call(undefined)) -------->[object Undefined]
console.log(Object.prototype.toString.call({name: 'Hello'})) -------->[object Object]
console.log(Object.prototype.toString.call(function () {})) -------->[object Function]
console.log(Object.prototype.toString.call(new Date())) -------->[object Date]
console.log(Object.prototype.toString.call(/\d/)) -------->[object RegExp]
console.log(Object.prototype.toString.call(Symbol())) -------->[object Symbol]