前端Javascript面试题(1)

✨✨✨目录

1.JavaScript有哪些数据类型及区别?

2.数据类型检测的方式有哪些?

3.null与undefined的区别?

4.instanceof操作符的实现原理及实现?

5.为什么0.1+0.2!==0.3,如何让其相等?

6.isNaN和Number.isNaN函数的区别?

[7.JavaScript 中的类型转换机制?](#7.JavaScript 中的类型转换机制?)

8.Object.is()与比较操作符"==="、"=="的区别?

9.什么是JavaScript中的包装类型?

10.为什么会有BigInt的提案?

11.object.assign和扩展运算法是深拷贝还是浅拷贝,深浅拷贝两者区别?

12.如何判断一个对象是空对象?

13.const、let、var的区别?

14.如果new一个箭头函数会怎么样?

15.new操作符的实现流程?


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用于检测基本数据类型(如 numberstringbooleanundefinedsymbolbigint)和函数(返回 "function")。但对 null 返回 "object",对数组和对象均返回 "object",存在局限性。

instanceof 用于检测对象是否为某个构造函数的实例,适用于引用类型(如 ArrayDate、自定义类等)。

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.10.2)在二进制中可能是无限循环小数,导致存储时被截断,产生精度误差。例如:0.1 的二进制表示为 0.0001100110011...(无限循环)。0.2 的二进制表示为 0.001100110011...(无限循环)。当 0.10.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.isNaNNumber 对象的静态方法,用于严格检测一个值是否为 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()(注意:nullundefined 没有 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
布尔值 true1false0
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

规则(假值列表):false0""(空字符串)、nullundefinedNaN 会转换为 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)条件语句中的隐式转换

ifwhilefor 等语句的条件表达式会被转换为布尔值。

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)块级作用域

  • letconst:具有块级作用域,由 {} 包括。这解决了ES5中内层变量可能覆盖外层变量和循环变量泄露为全局变量的问题。

  • var:不具备块级作用域,只有函数作用域或全局作用域。

(2)变量提升(var特有)

  • letconst:不存在变量提升,必须在声明后才能使用,否则报错。
  • var:存在变量提升,即变量可以在声明之前使用,但值为 undefined

(3)全局属性

  • letconst:不会将声明的全局变量添加到全局对象的属性上。
  • var:声明的全局变量会成为全局对象(浏览器中是 window,Node中是 global)的属性

(4)重复声明

  • var:允许在同一作用域内重复声明变量,后声明的会覆盖前面声明的。
  • letconst:不允许在同一作用域内重复声明变量。

(5)暂时性死区(let、const特有)

  • letconst:在声明前使用会报错,因为这段时间称为暂时性死区。
  • var:不存在暂时性死区。

(6)初始值设置

  • varlet:可以不设置初始值。
  • 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'

💕💕💕持续更新中......

若文章对你有帮助,点赞❤️、收藏⭐加关注➕吧!