2024前段高频面试题

数据类型

1.js基本数据类型以及它们的区别

javascript共有8种数据类型

  • 原始数据类型
    • undefined
    • null
    • boolean
    • number
    • string
    • symbol
    • bigint
  • 引用数据类型(对象、数组、函数)
    • object

symbol和bigint

symbol和bigint都是es6新增的数据类型

  • symbol代表创建后独一无二且不可变的数据类型
    • 主要为了解决可能出现的全局变量冲突问题
  • bigint可以用来表示任意精度格式的整数
    • 可以安全的存储和操作大整数

原始数据类型和引用数据类型的区别是什么?

  • 区别是:存放的位置不同
  • 原始数据类型直接存放在栈(stack)中
    • 占据空间小,大小稳定,需要频繁使用
  • 引用数据类型存放在堆(heap)中
    • 占据空间大,大小不稳定
    • 在栈中存储指针,指针指向堆中该实体的起始地址
    • 解释器首先寻找栈中存放的地址,再从堆中获得实体

栈和堆的区别是什么?

  • 操作系统中,内存被分为堆区和栈区
  • 栈中数据为先进后出
    • 内存由编译器自动分配释放
  • 堆是一个优先队列,按照优先级进行排序,优先级可以按照大小来规定
    • 一般由开发者分配释放

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

  • typeof
    • 判断null/数组/对象类型时,结果都会是object
  • instanceof
    • 机制:判断在原型链中是否能找到该类型的原型
    • 可以用来测试对象原型链中是否存在指定的prototype属性
    • 仅对引用类型有效,无法判断原始数据类型
  • constructor
    • 可以使用constructor来判断数据类型,但是如果修改对象的原型就行不通了
  • Object.prototype.toString.call()
    • 可以获取属性类型名称字符串

问:为什么obj.toString()和Object.prototype.toString()的返回值不一样

答:因为后者调用的时Object的原型方法,而使用实例进行调用,toString方法都被重写了

数组

3.判断数组的方式有哪些

  1. Array.isArray(obj)
  2. obj.proto === Array.prototype
  3. Object.prototype.toString().call(obj).slice(8,-1) === ‘Array’
  4. obj instanceof Array
  5. Array.prototype.isPrototypeOf(obj)

9.数组有哪些原生方法

  • toString
  • join,将数组元素连接成字符串
  • push/pop/shift/unshift
  • reverse
  • sort 回调函数返回值为整数时,交换两个参数的位置
  • concat,不影响原数组
  • slice,不影响原数组
  • splice,影响原数组
  • indexOf、lastIndexOf
  • every/some/filter/forEach
  • reduce/reduceRight

11.数组的遍历方法有哪些

  • forEach
  • map
  • filter
  • for…of
    • 返回数组元素/对象的属性值
  • every/some
  • find/findIndex
  • reduce/reduceRight

12.forEach和map的区别

都是用来遍历数组的

  • forEach主要是针对每一个元素执行提供的函数,返回值没有意义
  • map会根据回调函数的返回值得到一个新的数组

语法特性

4.请简述JavaScript中的this

  • this指向当前执行上下文中的对象
  • 调用模式
    • 函数调用模式:全局定义函数直接被调用,this指向全局对象
    • 方法调用模式:函数作为对象的方法被调用,this指向对象
    • 构造器调用模式:函数作为构造器被new调用,执行前会新建一个对象,this指向对象
    • apply、call、bind:可以指定this的指向
      • apply:传入2个参数
        • 参数1:this绑定对象
        • 参数2:其它参数(数组格式)
      • call:
        • 参数1:this绑定对象
        • 其它参数(必须逐个例句出来)
      • bind
        • 参数1:this指向
        • 返回值:新函数
  • 调用模式优先级:构造器 > apply/bind/call > 方法 > 函数

箭头函数和普通函数的区别

  • 箭头函数的写法更加简洁
  • 箭头函数的this指向函数定义的上下文
  • 箭头函数继承的this指向不会改变
  • 箭头函数不能作为构造函数使用
  • 箭头函数没有自己arguments对象
  • 箭头函数没有prototype
  • 箭头函数不能用作generator函数,不能使用yield关键字

5.AMD和CommonJs的区别

都是实现模块化的方式

  • Commonjs
    • 同步加载,由于运行在服务器端,从本地磁盘读取数据,速度很快
    • 更适合服务器,nodejs使用commonjs
    • module/exports/require/global
    • module.exports输出,require加载
  • AMD/require.js
    • 异步方式加载模块,模块加载不影响后面语句运行
    • 更适合浏览器
    • 使用require.config()指定引用路径
    • define()定义模块,require()加载模块

6.ES6模块与CommonJs模块有什么异同

两者都能对引入的对象的内部属性值进行改变

  • ES6模块
    • 语法简单,适用浏览器和服务器
    • export导出,import导入
    • 是对模块的引用,模块本身指向不能改变
  • commonjs
    • 是对模块的浅拷贝
    • 模块本身可以被重新赋值

7.let、const、var的区别

  • 块级作用域
    • let、const具有块级作用域,var没有
    • 块级作用域使用花括号包裹,用于解决2个问题:
      • 内层变量可能覆盖外层变量
      • 用来计数的循环变量泄露为全局变量
  • 变量提升
    • 变量只能在声明后使用,否则会报错
    • var存在变量提升
    • let/const不存在变量提升
  • 全局添加属性
    • 浏览器全局变量:window,Node全局变量global
    • var声明变量会成为全局对象的属性
    • let和const不会
  • 重复声明
    • var可以重复声明,后声明的变量会覆盖前声明的
    • const、let不允许重复声明
  • 暂时性死区
    • let/const声明之前无法使用
  • 初始值设置
    • var/let可以不设置初始值
    • const必须设置初始值
  • 指针指向
    • let/var创建的变量都可以更改指针指向
    • const不能

10.for in 和 for of的区别

  • for_of
    • 遍历对象的键值,遍历数组元素值
    • 只遍历当前对象上的属性
  • for_in
    • 遍历对象的键名,遍历数组索引
    • 会遍历对象的整个原型链,性能比较差

23、ES6有哪些新特性

  1. 箭头函数
  2. 解构赋值
  3. 模版字符串
  4. promise
  5. symbol,用于创建一个独一无二的值,不能与其他数据类型进行运算
  6. let/const
  7. 模块化(import/export)
  8. for..of循环刻碟带对象
  9. 扩展运算符…
  10. Map/Set
  11. Proxy,允许在对象和函数调用等操作前后添加自定义的行为
  12. Class类
  13. 函数默认参数

24、匿名函数的典型应用场景是什么?

  • 需要立即调用执行的函数,封装局部作用域的代码,避免声明的变量暴露到全局作用域
1
(function(){})()
  • 用于一些函数作为参数的情况,比如setTimeout或者forEach

引用类型

8.new操作符的实现原理

  • 首先创建了一个新的空对象
  • 将对象的原型设置为函数的prototype对象
  • 让函数的this指向这个对象,执行构造函数的代码
  • 判断函数返回类型,如果是值类型,返回创建的对象,如果是引用类型,返回引用类型的对象

13.原型和原型链

  • js中的构造函数内部存在一个prototype属性
  • 新建的对象内部存在一个指针,指向构造函数的prototype属性的对应值,即原型
  • 浏览器中能够通过proto属性访问这个值
  • 当访问一个对象的属性时,对象内部不存在这个属性,那么就会沿着原型链向上寻找该属性/方法
  • 原型链的终点:
    • Object.prototype.proto,也就是null
  • 可以通过hasOwnProperty方法判断属性到底是原型链上的还是本身定义的

全局变量&&作用域

14.对执行上下文、作用域链、闭包的理解

  • 闭包

    • 指的是有权访问另一个函数作用域中变量的函数
    • 最常见的方式:在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量
      • 比如A函数内有B函数,B函数能访问A函数中的变量,B函数就是闭包
    • 用途:
      • 在函数外部访问函数内部的变量(间接访问函数内部的变量)
      • 使运行已经结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用
  • 作用域

    • 全局作用域
      • window对象的属性拥有全局作用域
      • 最外层函数和变量都拥有全局作用域
    • 函数作用域
      • 函数内部的变量
    • 块级作用域
      • es6的新特性
      • let/const有效
      • 循环比较适合块级作用域
  • 作用域链

    • 在当前作用域查找不到变量时,就去父级作用域查找,直到全局作用域为止
    • 保证对环境中的变量和函数进行有序访问

15.实现call、apply及bind函数

16.call、apply函数的区别

区别在于传参不同:

  • apply:第二个参数为数组或类数组,作为函数的参数
  • call,第一个参数往后全是函数的参数

闭包

  • 什么是闭包(内层函数中访问到外层函数的作用域)
    • 一个函数与其内部创建的函数的作用域的连接
    • 阻止了函数调用结束时对函数内部变量的销毁,因为变量被闭包函数引用
  • 解决的问题
    • 内部函数可以访问到外部函数的内容
  • 使用场景
    • 防抖节流
    • 创建私有变量/方法
    • 延长变量的生命周期
  • 闭包的缺点:
    • 变量驻留在内存中,造成内存损耗问题
    • 解决方法:手动清空闭包函数

异步模式

17.异步编程的实现方式

  1. 回调函数
  2. Promise方式
  3. generator(不熟)
  4. async,promise的语法糖

18、setTimeout、Promise、Async/Await的区别

19、Promise.all和Promise.race的区别以及使用场景

  • Promise.all 将多个promise实例包装成一个新的promise实例
    • 成功:返回结果数组
    • 失败:返回最先被reject失败状态的值
    • 使用场景:需要多个ajax请求全部回来之后才能正常显示,且返回数组顺序和传入时的顺序一样
  • Promise.race
    • 传入的请求中,返回请求响应最快的结果

20、对async/await的理解

  • 是promise的一种语法糖,能够解决回调地狱
  • async修饰的函数,返回值是promise对象
  • await用于修饰一步操作
  • async函数中,会等待await代码执行结束后,才会继续执行下面的语句

宏任务和微任务

  • js是单线程语言
  • js代码会先执行完同步任务,再进入事件循环
  • 事件循环:请求、定时器、事件
    • 检测是否有可执行的微任务
      • 有可执行的微任务,执行微任务
    • 微任务清空后执行宏任务
  • 任务类型
    • 宏任务
      • setTimout、setInterval
      • 执行前提:清空了所有的微任务
    • 微任务
      • promise

性能优化

21、浏览器的垃圾回收机制

22、哪些情况会导致内存泄漏

原理相关

25、你能举出一个柯里化函数的例子吗?它有哪些好处

26、什么是事件循环?调用堆栈和任务队列之间有什么区别

  • 事件循环是一个单线程循环
  • 用于监视调用堆栈并检查是否有工作即将在任务队列中完成
  • 如果调用堆栈为空并且任务队列中有回调函数,则将回调函数出队并推送到调用堆栈中执行

js设计模式有哪些

几个比较要命的问题:

  • 如何解决跨域问题
  • http握手
  • 如何区分null和空数组
  • Map的定义方式
  • Object哪些方法用的比较多
  • 防抖节流
  • 同源策略