# js 面试题汇总(上)

有关 js 面试题,持续更新中...

# 1、类型/变量/运算符

# 1.1 js 数据类型和值是怎么存储的?

JavaScript 数据类型分为两种:

  • 简单数据类型(也称为原始类型):一共有 7 种简单类型,String(字符串),Number(数字),BigInt(任意大整数),Boolean(布尔值),Null(空值),Undefined(未定义),Symbol(符号)。
  • 复杂数据类型(引用类型):Object(对象)。

存储方式:

  • 原始类型把数据名和值直接存储在栈当中
  • 引用类型在栈中存储数据名和一个堆的地址,在堆中存储属性及值。访问时先从栈获取地址,再到堆中拿出相应的值。

# 1.2 判断一个值是什么类型有哪些方法?

类型检查有四种方式,分别为 typeof、instanceof、constructor 和 Object.prototype.toString()。

// 通过 typeof 操作符来判断一个值属于哪种原始类型
typeof 'seymoe' // 'string'

// 通过 instanceof 操作符可以对引用类型进行判定
[] instanceof Array // true

// 通过 constructor 构造器返回数据类型
(2).constructor === Number // true

// 通过 Object.prototype.toString() 可以判定 JavaScript 中所有数据类型
Object.prototype.toString.call({}) // '[object Object]'
1
2
3
4
5
6
7
8
9
10
11

# 1.3 判断数组的方式有哪些?

// 1. 通过 instanceof 判定
arr instanceof Array

// 2. 通过 constructor 判定
arr.constructor == Array

// 3. Object.protype.toString.call 判定
Object.protype.toString.call(arr) == '[Object Array]'

// 4. 通过原型链做判断
arr.__proto__ === Array.prototype;

// 5. 通过 ES6 的 Array.isArray() 做判断
Array.isArrray(arr);

// 6. 通过 Array.prototype.isPrototypeOf 判定
Array.prototype.isPrototypeOf(arr)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 1.4 undefined 与 undeclared 的区别?

  • 已在作用域中声明但还没有赋值的变量,是 undefined。
  • 相反,还没有在作用域中声明过的变量,是 undeclared 的。
function foo() {
  "use strict";
  bar = true;
}
foo(); // ReferenceError: assignment to undeclared variable bar
1
2
3
4
5

对于 undeclared 变量的引用,浏览器会报引用错误,如 ReferenceError: b is not defined 。但是我们可以使用 typeof 的安全防范机制来避免报错,因为对于 undeclared(或者 not defined )变量,typeof 会返回 "undefined"。

# 1.5 null 和 undefined 的区别?

  • null 表示一个对象被定义了,值为“空值”,或表示"没有对象",即该处不应该有值。
      1. 作为函数的参数,表示该函数的参数不是对象。
      1. 作为对象原型链的终点。
  • undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义。
      1. 如果声明变量尚未初始化,则其值为 undefined。
      1. 对象没有赋值的属性,该属性的值为undefined。
      1. 函数没有返回值时,默认返回undefined。
      1. 函数有一个 return 没有参数的语句,则该函数隐式返回 undefined。
      1. 调用函数时,应该提供的参数没有提供,该参数等于 undefined。
      1. obj?.someProp 返回的可选链接,假值则返回 undefined。
null == undefined // true
null === undefined // false
1
2

# 1.6 typeof null 的结果是什么,为什么?

typeof null 的结果是 Object。

在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:

000: object   - 当前存储的数据指向一个对象。
  1: int      - 当前存储的数据是一个 31 位的有符号整数。
010: double   - 当前存储的数据指向一个双精度的浮点数。
100: string   - 当前存储的数据指向一个字符串。
110: boolean  - 当前存储的数据是布尔值。
1
2
3
4
5

如果最低位是 1,则类型标签标志位的长度只有一位;如果最低位是 0,则类型标签标志位的长度占三位,为存储其他四种数据类型提供了额外两个 bit 的长度。

有两种特殊数据类型:

  • undefined 的值是 (-2)30(一个超出整数范围的数字);
  • null 的值是机器码 NULL 指针空对象(null 指针的值全是 0)

那也就是说 null 的类型标签也是 000,和 Object 的类型标签一样,所以会被判定为 Object。

# 1.7 js的数据类型的转换

在 JS 中类型转换只有三种情况,分别是:

  • 转换为布尔值(调用Boolean()方法)
  • 转换为数字(调用Number()、parseInt()和parseFloat()方法)
  • 转换为字符串(调用.toString()或者String()方法)

null和underfined没有.toString方法

# 1.8 {}和[]的valueOf和toString的结果是什么?

  • {} 的 valueOf 结果为 {} ,toString 的结果为 "[object Object]"
  • [] 的 valueOf 结果为 [] ,toString 的结果为 ""

# 1.9 Object.is() 与比较操作符 “===”、“==” 的区别?

  • ==,当且仅当两个运算数相等时,它返回 true,即不检查数据类型
  • ===,只有在无需类型转换运算数就相等的情况下,才返回 true,需要检查数据类型
  • 使用 Object.is 来进行相等判断时,一般情况下和 === 的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的。

# 1.10 var、let、const 区别?

  • var 存在变量提升。
  • let 只能在块级作用域内访问。
  • const 用来定义常量,必须初始化,不能修改(对象特殊)

# 1.11 intanceof 操作符的实现原理及实现

instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。

function myInstanceof(left, right) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left)
  // 获取构造函数的 prototype 对象
  let prototype = right.prototype; 
 
  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 1.12 为什么0.1+0.2 ! == 0.3,如何让其相等

因为使用了 IEEE 754 数值,这种错误并非 ECMAScript 所独有。其他使用相同格式的语言也有这个问题。

解决办法:

// 换成别的数字
(0.1*10+0.2*10)/10 === 0.3

// 保留小数位数
parseFloat((0.1+0.2).toFixed(10)) === 0.3
1
2
3
4
5

# 1.13 如何获取安全的 undefined 值?

因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。

# 1.14 typeof NaN 的结果是什么?

NaN 指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。

typeof NaN; // "number"
1

NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 为 true。

# 1.15 isNaN 和 Number.isNaN 函数的区别?

  • 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。
  • 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

# 1.16 == 操作符的强制类型转换规则?

对于 == 来说,如果对比双方的类型不一样,就会进行类型转换。假如对比 x 和 y 是否相同,就会进行如下判断流程:

  1. 首先会判断两者类型是否相同,相同的话就比较两者的大小;
  2. 类型不相同的话,就会进行类型转换;
  3. 会先判断是否在对比 null 和 undefined,是的话就会返回 true
  4. 判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
  5. 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
  6. 判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断

1 == '1' => 1 == 1

'1' == true => '1' == 1 => 1 == 1

'1' == { name: 'js' } => '1' == '[object Object]'

类型转换流程图

# 1.17 其他值到字符串的转换规则?

  • Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined"。
  • Boolean 类型,true 转换为 "true",false 转换为 "false"。
  • Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。
  • Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
  • 对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。

# 1.18 其他值到数字值的转换规则?

  • Undefined 类型的值转换为 NaN。
  • Null 类型的值转换为 0。
  • Boolean 类型的值,true 转换为 1,false 转换为 0。
  • String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。
  • Symbol 类型的值不能转换为数字,会报错。
  • 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。

为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有valueOf()方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。

如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。

# 1.19 其他值到布尔类型的值的转换规则?

以下这些是假值: • undefined • null • false • +0、-0 和 NaN • ""

假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。

# 1.20 || 和 && 操作符的返回值?

|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。

  • 对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。
  • && 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。

|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果

# 1.21 什么是 JavaScript 中的包装类型?

为了方便操作原始值,JavaScript 提供了 3 种特殊的引用类型:Boolean、Number 和 String。

每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。

# 介绍一下闭包和闭包常用场景?

  • 闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包常见方式,就是在一个函数的内部创建另一个函数
  • 使用闭包主要为了设计私有的方法和变量,闭包的优点是可以避免变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念。
  • 闭包有三个特性:
    • 函数嵌套函数
    • 函数内部可以引用外部的参数和变量
    • 参数和变量不会被垃圾回收机制回收
  • 应用场景,设置私有变量的方法
  • 不适用场景:返回闭包的函数是个非常大的函数
  • 闭包的缺点就是常驻内存,会增大内存使用量,使用不当会造成内存泄漏

# 对象

# 函数

# 迭代器与生成器

# 迭代器与生成器

# DOM和BOM

# eval是做什么的?

  • 它的功能是把对应的字符串解析成 JS 代码并运行;
  • 应该避免使用 eval,不安全,非常耗性能(2次,一次解析成 js 语句,一次执行)。

# 箭头函数有哪些特点?

  • 不需要function关键字来创建函数
  • 省略return关键字
  • 改变this指向

# new操作符具体干了什么呢?

  1. 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
  2. 属性和方法被加入到 this 引用的对象中。
  3. 新创建的对象由 this 所引用,并且最后隐式的返回 this 。

# JSON 的了解?

  • JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
  • 它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小 {'age':'12', 'name':'back'}

# documen.write 和 innerHTML 的区别?

  • document.write 只能重绘整个页面
  • innerHTML 可以重绘页面的一部分

# ajax过程?

  • (1)创建XMLHttpRequest对象,也就是创建一个异步调用对象.
  • (2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
  • (3)设置响应HTTP请求状态变化的函数.
  • (4)发送HTTP请求.
  • (5)获取异步调用返回的数据.
  • (6)使用JavaScript和DOM实现局部刷新.

# 请解释一下 JavaScript 的同源策略?

概念:同源策略是客户端脚本,其目的是防止某个文档或脚本从多个不同源装载。

这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。

指一段脚本只能读取来自同一来源的窗口和文档的属性。

# javascript的内存(垃圾)回收机制?

  • 垃圾回收器会每隔一段时间找出那些不再使用的内存,然后为其释放内存
  • 一般使用标记清除方法(mark and sweep), 当变量进入环境标记为进入环境,离开环境标记为离开环境垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
  • 还有引用计数方法(reference counting), 在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
  • 在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的, 也就是说只要涉及BOM及DOM就会出现循环引用问题。

# JavaScript原型,原型链 ? 有什么特点?

  • 每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
  • 关系:instance.constructor.prototype = instance.proto
  • 特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

# 数组合并

(1)for

会变更原数组,当然也可以写成生成新数组的形式。

let arr = [1, 2]
let arr2 = [3, 4]

for (let i in arr2) {
    arr.push(arr2[i])
}

console.log(arr)
// [1, 2, 3, 4]
1
2
3
4
5
6
7
8
9

(2)concat

会生成新的数组。

let arr = [1, 2]
let arr2 = [3, 4]

arr = arr.concat(arr2)

console.log(arr)
// [1, 2, 3, 4]
1
2
3
4
5
6
7

(3)arr.push.apply

会变更原数组。

let arr = [1, 2]
let arr2 = [3, 4]

arr.push.apply(arr, arr2)

console.log(arr)
// [1, 2, 3, 4]
1
2
3
4
5
6
7

(4)[…arr, …arr2]

会生成新的数组。

let arr = [1, 2]
let arr2 = [3, 4]

arr = [...arr, ...arr2]

console.log(arr)
// [1, 2, 3, 4]
1
2
3
4
5
6
7

(5)push(…arr)

会变更原数组。

let arr = [1, 2]
let arr2 = [3, 4]

arr.push(...arr2)

console.log(arr)
// [1, 2, 3, 4]
1
2
3
4
5
6
7

参考:

https://blog.csdn.net/github_35780607/article/details/110528481

上次更新: 5/10/2022, 10:31:54 PM