✨✨✨目录
[7.JavaScript 中的类型转换机制?](#7.JavaScript 中的类型转换机制?)
8.Object.is()与比较操作符"==="、"=="的区别?
11.object.assign和扩展运算法是深拷贝还是浅拷贝,深浅拷贝两者区别?
1.JavaScript有哪些数据类型及区别?
JavaScript 中的数据类型主要分为两大类:基本数据类型和引用数据类型。
基本数据类型:Undefined、Null、Boolean、Number、String、Symbol、BigInt
引用数据类型:Object、Array、Function、Date、RegExp
两种数据类型的区别:栈------原始数据类型,堆------引用数据类型,区别在于存储位置的不同。
原始数据类型直接存储在栈中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;引用数据类型存储在堆中的对象,占据空间大,大小不固定。
2.数据类型检测的方式有哪些?
typeof、instanceof、Object.prototype.toString.call()、Array.isArray()、constructor
typeof用于检测基本数据类型(如 number
、string
、boolean
、undefined
、symbol
、bigint
)和函数(返回 "function"
)。但对 null
返回 "object"
,对数组和对象均返回 "object"
,存在局限性。
instanceof
用于检测对象是否为某个构造函数的实例,适用于引用类型(如 Array
、Date
、自定义类等)。
Object.prototype.toString.call()
通过调用对象的 toString
方法并解析返回的字符串,可以精确检测所有类型。如:Object.prototype.toString.call(null); // "[object Null]" Object.prototype.toString.call([]); // "[object Array]"
Array.isArray()专门用于检测数组。
constructor有两个作用,一是判断数据的类型,二是对象实例通过constructor对象访问它的构造函数。如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了。
3.null与undefined的区别?
null表示一个显式赋值的空值,通常用于表示"无对象"或"无值"。
undefined表示一个未初始化的变量或未赋值的属性,通常由 JavaScript 引擎自动赋予。
特性 | null |
undefined |
---|---|---|
语义 | 显式空值(人为设定) | 未初始化或未定义 |
typeof 结果 |
"object" |
"undefined" |
是否可显式赋值 | 是 | 是(但通常由引擎自动赋予) |
严格相等性 | null !== undefined |
undefined !== null |
常见使用场景 | 清空变量、函数参数默认值 | 未初始化变量、函数无返回值、不存在的属性 |
console.log(null == undefined); // true
console.log(null === undefined); // false
4.instanceof操作符的实现原理及实现?
instanceof
操作符是 JavaScript 中用于检测对象是否为某个构造函数(或其原型链上的构造函数)的实例的运算符。它的实现原理基于原型链继承机制,通过检查对象的原型链中是否存在构造函数的 prototype
属性来实现类型判断。
instanceof
的实现原理:
instanceof
会从对象的原型链(__proto__
)开始,逐级向上查找,直到找到匹配的 prototype
或到达原型链的末端(null
)。如果对象的原型链中存在构造函数的 prototype
,则返回 true
,否则返回 false
。对于 obj instanceof Constructor
,其逻辑等价于:
function isInstanceOf(obj, Constructor) {
let prototype = Constructor.prototype;
let currentProto = Object.getPrototypeOf(obj);
while (currentProto !== null) {
if (currentProto === prototype) {
return true;
}
currentProto = Object.getPrototypeOf(currentProto);
}
return false;
}
5.为什么0.1+0.2!==0.3,如何让其相等?
在 JavaScript 中,0.1 + 0.2 !== 0.3
的问题源于浮点数在计算机中的二进制表示方式导致的精度丢失。十进制的小数(如 0.1
、0.2
)在二进制中可能是无限循环小数,导致存储时被截断,产生精度误差。例如:0.1
的二进制表示为 0.0001100110011...
(无限循环)。0.2
的二进制表示为 0.001100110011...
(无限循环)。当 0.1
和 0.2
相加时,它们的二进制表示误差会累积,导致结果略大于 0.3
。
解决方法:通过 toFixed()
或数学运算将结果四舍五入到指定小数位;将小数转换为整数进行计算;定义一个极小的误差范围(如 Number.EPSILON
),判断差值是否在范围内;使用第三方库。
6.isNaN和Number.isNaN函数的区别?
isNaN
是全局函数,用于检测一个值是否"不是数字"(即无法转换为有效数字),存在隐式类型转换。
console.log(isNaN("hello")); // true("hello" 无法转换为数字)
console.log(isNaN("123")); // false("123" 可以转换为数字 123)
console.log(isNaN(true)); // false(true 转换为数字 1)
console.log(isNaN(NaN)); // true(直接是 NaN)
Number.isNaN
是 Number
对象的静态方法,用于严格检测一个值是否为 NaN
。
console.log(Number.isNaN("hello")); // false(字符串不是 NaN) console.log(Number.isNaN("123")); // false(字符串不是 NaN) console.log(Number.isNaN(true)); // false(布尔值不是 NaN) console.log(Number.isNaN(NaN)); // true(直接是 NaN)
特性 | isNaN |
Number.isNaN |
---|---|---|
隐式类型转换 | 是(尝试转换为数字) | 否(直接比较) |
适用场景 | 检测值是否"无法转换为数字" | 严格检测值是否为 NaN |
非数字但可转换的值 | 返回 false (如 "" 、" " 、[] ) |
返回 false (严格匹配 NaN ) |
直接传递 NaN |
返回 true |
返回 true |
7.JavaScript 中的类型转换机制?
JavaScript 类型转换分为两类:显式类型转换-开发者通过函数或方法主动将一种类型转换为另一种类型(如 Number()
、String()
、Boolean()
);隐式类型转换-JavaScript 引擎在特定操作(如算术运算、比较、逻辑判断)中自动将一种类型转换为另一种类型。
显式类型转换:
(1)转换为字符串(String)
String(value)
value.toString()
(注意:null
和 undefined
没有 toString()
方法)
console.log(String(123)); // "123"
console.log(String(true)); // "true"
console.log(String(null)); // "null"
console.log(String(undefined));// "undefined"
console.log((123).toString()); // "123"(数字调用 toString)
(2)转换为数字(Number)
Number(value)
parseInt(value, radix)
(解析整数)
parseFloat(value)
(解析浮点数)
一元加号运算符 +value
规则:
原始类型 | 转换为数字的结果 |
---|---|
字符串 | 解析为数字(如 "123" → 123 ),无法解析时为 NaN |
布尔值 | true → 1 ,false → 0 |
null |
0 |
undefined |
NaN |
对象/数组 | 调用 valueOf() 或 toString() 后再转换 |
console.log(Number("123")); // 123
console.log(Number("123abc")); // NaN
console.log(Number(true)); // 1
console.log(Number(null)); // 0
console.log(Number(undefined));// NaN
console.log(Number([1, 2])); // NaN(调用
valueOf()
或toString()
后再转换→ "1,2" → NaN)console.log(+[]); // 0(空数组调用 toString() → "" → 0)
(3)转换为布尔(Boolean)
Boolean(value)
双感叹号 !!value
规则(假值列表):false
、0
、""
(空字符串)、null
、undefined
、NaN
会转换为 false
,其他值均为 true
。
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean([])); // true(空数组是对象,非假值)
console.log(Boolean({})); // true(空对象是非假值)
console.log(!!"hello"); // true
隐式类型转换:
(1)算术运算中的隐式转换
加法运算符 +
:如果任意操作数是字符串,则进行字符串拼接。否则,尝试将操作数转换为数字进行加法。
其他运算符(-
、*
、/
、%
):总是将操作数转换为数字。
console.log("123" + 456); // "123456"(字符串拼接)
console.log(123 + true); // 124(true → 1)
console.log(123 - "456"); // -333(字符串 → 数字)
console.log(123 - true); // 122(true → 1)
console.log([] + 1); // "1"(空数组 → "" → "1")
console.log({} + 1); // "[object Object]1"(对象 → "[object Object]")
(2) 比较运算符中的隐式转换
宽松相等 ==
:如果类型相同,直接比较。如果类型不同,尝试转换为相同类型后再比较。
console.log(0 == false); // true(false → 0)
console.log("" == false); // true("" → 0,false → 0)
console.log(null == undefined); // true(特殊规则)
console.log("123" == 123); // true(字符串 → 数字)
console.log([] == false); // true([] → "" → 0,false → 0)
console.log({} == {}); // false(对象比较引用)
严格相等 ===
:不进行类型转换,类型或值不同则返回 false
。
推荐:始终使用 ===
避免隐式转换问题。
(3)逻辑运算符中的隐式转换
&&
和 ||
:操作数会被转换为布尔值,但返回的是原始值(非布尔值)。
||(逻辑或)和&&(逻辑与)操作符在JavaScript中的返回值机制如下:
逻辑或(||)操作符:从左到右依次检查操作数。如果第一个操作数为真值(Truthy),则直接返回第一个操作数;如果第一个操作数为假值(Falsy),则返回第二个操作数。不会强制转换为布尔值,直接返回原始值。
常见用途:用于设置默认值、避免访问未定义的属性等。
逻辑与(&&)操作符:从左到右依次检查操作数。如果第一个操作数为假值,则直接返回第一个操作数;如果第一个操作数为真值,则返回第二个操作数。不会强制转换为布尔值,直接返回原始值。
console.log(0 && "hello"); // 0(0 是假值,直接返回)
console.log("hello" && 0); // 0("hello" 是真值,返回第二个操作数)
console.log(null || "world"); // "world"(null 是假值,返回第二个操作数)
(4)条件语句中的隐式转换
if
、while
、for
等语句的条件表达式会被转换为布尔值。
if ("") { console.log("不会执行"); // 空字符串是假值 }
if ([]) { console.log("会执行"); // 空数组是真值 }
8.Object.is()与比较操作符"==="、"=="的区别?
Object.is() 是 JavaScript 中用于严格比较两个值是否相等的方法,与 === 运算符的主要区别在于对有符号零(+0/-0)和 NaN 的处理,其中 Object.is(+0, -0) 返回 false,Object.is(NaN, NaN) 返回 true。
严格相等判断:Object.is() 接收两个参数,返回布尔值,仅在以下情况返回 true:
两个值类型相同且值相等(包括对象引用比较)
两个值均为 undefined 或 null
两个值均为 NaN(唯一能正确识别 NaN 相等性的方法)
两个值分别为 +0 和 -0 时返回 false(区别于 ===)
与其他比较运算符的差异对比:
比较方式 | 类型转换 | NaN === NaN | +0 === -0 |
---|---|---|---|
==(宽松相等) | 有 | false | true |
===(严格相等) | 无 | false | true |
Object.is() | 无 | true | false |
应用场景分析
精确值匹配:在需要严格区分 +0 和 -0 的科学计算或图形处理中替代 ===
NaN 检测:替代 x === x
的 NaN 检查方式(因 Object.is(x, NaN) 更直观)
React 性能优化:用于 shouldComponentUpdate 生命周期方法中判断 props/state是否变化
9.什么是JavaScript中的包装类型?
概念:JavaScript中的包装类型是指为了便于操作基本数据类型(如String、Number、Boolean等),JavaScript在后台将这些基本类型的值临时包装成对象,从而允许调用这些对象的方法和属性。
自动装箱:当读取一个基本类型的值时,JavaScript会自动将其包装成一个对象。例如,当访问字符串"hello".length
时,JavaScript会在后台将"hello"
包装成一个String对象,然后访问其length
属性。
显式创建:可以使用Object()
函数显式地将基本类型转换为包装类型。例如,Object("hello")
会将"hello"
显式地转换为String对象。
拆箱:当操作完成后,这些对象会被拆箱回基本类型。例如,通过valueOf()
方法可以将包装类型转换回基本类型。
生命周期和内存管理:基本包装类型的对象在执行完一行代码后会被立即销毁,这意味着在运行时为基本包装类型值添加属性和方法是无效的。这是因为它们的生命周期非常短暂,仅在代码执行的一瞬间存在。
10.为什么会有BigInt的提案?
BigInt的提案主要是由于JavaScript中Number类型在处理大数时的精度问题。
JavaScript中的Number类型有一个最大安全整数限制,即Number.MAX_SAFE_INTEGER
,其值为9007199254740991
。在这个范围内,计算结果不会出现精度丢失,但一旦超过这个范围,就会出现计算不准确的情况。这在需要进行大数计算时,不得不依靠一些第三方库来解决,因此官方提出了BigInt来解决此问题。
11.object.assign和扩展运算法是深拷贝还是浅拷贝,深浅拷贝两者区别?
Object.assign和扩展运算符都是浅拷贝工具,而不是深拷贝工具。
Object.assign(target, ...sources)
,将一个或多个源对象的属性复制到目标对象中。会直接修改目标对象,覆盖同名属性,且只复制对象的可枚举属性,嵌套对象是引用复制。
const target = {a: 1};
const source = {b: 2, c: 3};
const result = Object.assign(target, source);
console.log(result); // { a: 1, b: 2, c: 3 }
console.log(target); // { a: 1, b: 2, c: 3 }
扩展运算符{...source}
,将一个对象的属性展开到另一个对象中。同样只复制对象的可枚举属性,嵌套对象是引用复制。
const obj1 = {a: 1, b: {c: 2}};
const obj2 = {...obj1};
obj2.b.c = 3;
console.log(obj1.b.c); // 3
浅拷贝和深拷贝的定义及区别
浅拷贝:只复制对象的第一层属性,嵌套对象是引用复制。修改嵌套对象的属性会影响原对象。
深拷贝:递归复制对象及其所有嵌套对象,确保每个层级的内容都被独立复制,不再共享引用地址。深拷贝可以使用JSON.parse(JSON.stringify())
或第三方库如Lodash的_.cloneDeep()
实现。
12.如何判断一个对象是空对象?
Object.keys(obj).length===0:仅检查可枚举字符串属性,忽略Symbol属性。
JSON.stringify(obj) === '{}':会忽略undefined和函数类型属性,存在误判风险。
13.const、let、var的区别?
(1)块级作用域
-
let
和const
:具有块级作用域,由{}
包括。这解决了ES5中内层变量可能覆盖外层变量和循环变量泄露为全局变量的问题。 -
var
:不具备块级作用域,只有函数作用域或全局作用域。
(2)变量提升(var特有)
let
和const
:不存在变量提升,必须在声明后才能使用,否则报错。var
:存在变量提升,即变量可以在声明之前使用,但值为undefined
。
(3)全局属性
let
和const
:不会将声明的全局变量添加到全局对象的属性上。var
:声明的全局变量会成为全局对象(浏览器中是window
,Node中是global
)的属性
(4)重复声明
var
:允许在同一作用域内重复声明变量,后声明的会覆盖前面声明的。let
和const
:不允许在同一作用域内重复声明变量。
(5)暂时性死区(let、const特有)
let
和const
:在声明前使用会报错,因为这段时间称为暂时性死区。- var:不存在暂时性死区。
(6)初始值设置
var
和let
:可以不设置初始值。const
:必须设置初始值。
(7)指针指向
let、var
:创建的变量可以重新赋值,即可以更改指针指向。const
:声明的变量不允许改变指针的指向,但如果是对象或数组,可以修改其内部属性或元素。
总结
区别 | let | const | var |
---|---|---|---|
有无块级作用域 | √ | √ | × |
有无变量提升 | × | × | √ |
能否添加全局属性 | × | × | √ |
能否重复声明变量 | × | × | √ |
有无暂时性死区 | √ | √ | × |
必须设置初始值 | × | √ | × |
能否改变指针指向 | √ | × | √ |
14.如果new一个箭头函数会怎么样?
箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。
new操作符的实现步骤如下:
(1)创建一个空的简单JavaScript对象(即{});
(2)为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
(3)将步骤1新创建的对象作为this的上下文 ;
(4)如果该函数没有返回对象,则返回this。
所以,上面的第二、三步,箭头函数都是没有办法执行的。
15.new操作符的实现流程?
在JavaScript
中,new
操作符用于创建一个给定构造函数的实例对象。
-
创建一个新的对象
obj
-
将对象与构造函数通过原型链连接起来
-
将构造函数中的
this
绑定到新建的对象obj
上 -
根据构造函数中返回值,如果是值类型,返回创建的对象;如果是引用类型,就返回这个引用类型对象。
function Test(name) {
this.name = name
return 1
}
const t = new Test('xxx')
console.log(t.name) // 'xxx'function Test1(name) {
this.name = name
console.log(this) // Test { name: 'xxx' }
return { age: 26 }
}
const t1 = new Test1('xxx')
console.log(t1) // { age: 26 }
console.log(t1.name) // 'undefined'function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(this.name)
}
const person1 = new Person('Tom', 20)
console.log(person1) // Person {name: "Tom", age: 20}
person1.sayName() // 'Tom'
💕💕💕持续更新中......
若文章对你有帮助,点赞❤️、收藏⭐加关注➕吧!