2024前端面试题之js篇

283 阅读7分钟

写在前面,收集面试题,祝每一个面试者都能找到好工作

js基本数据类型

JavaScript共有⼋种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt

  • Symbol 和 BigInt 是ES6 中新增的数据类型:

    • Symbol 代表创建后独⼀⽆⼆且不可变的数据类型,它主要是为了解决可能出现的全 局变量冲突的问题。

    • BigInt 是⼀种数字类型的数据,它可以表⽰任意精度格式的整数,使⽤ BigInt 可以安 全地存储和操作⼤整数,即使这个数已经超出了 Number 能够表⽰的安全整数范围。

  • 可以分为原始数据类型和引⽤数据类型:

    • 栈:原始数据类型(Undefined、Null、Boolean、Number、String、Symbol、BigInt)
    • 堆:引⽤数据类型(对象、数组和函数)
  • 类型检测

    • typeof 数组、对象、null都会被判断为object,其他判断都正确。
    • instanceof 可以正确判断对象的类型,其内部运⾏机制是判断在其原型链中能否找到该类
    • Object.prototype.toString.call() 使⽤ Object 对象的原型⽅法 toString 来判断数据类型

var、let、const的区别

简记: 快死复生(块级作用域、暂时性死区、不能重复声明、不存在变量提升)

  • 1. 块级作用域
    • var 定义的变量没有块的概念,可以跨块访问,不能跨函数访问
    • let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问
    • const 定义的变量,只能在块作用域里访问
{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1
  • 2. 不存在变量提升

    • var 命令会发生变量提升现象,即变量可以在声明之前使用,值为undefined
    • let和 const 命令不会发生变量提升
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
  • 3.暂时性死区

    • 暂时性死区:在代码块内,使用letconst声明变量之前,该变量是不可用的
var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}
  • 4.不可以被重复声明

    • let、const 命令在相同作用域中, 不能重读声明
// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}
  • const

    • const声明一个只读的常量,一旦声明,常量的值不能改变
    • 声明的基本数据类型不能被修改,声明的引用数据类型可以通过修改对象属性和方法,本质上,基本数据类型的数据值保存在变量指向的那个内存地址,因此等同于常量。但是引用数据类型的数据,变量指向的内存地址保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了

内存泄漏和内存溢出

  • 内存泄漏指的是程序中不再使用的内存没有被及时释放,导致内存占用越来越高,最终可能导致系统性能下降或崩溃。内存泄漏通常是由于程序中的对象被引用,但不再被使用,或者由于循环引用等原因导致无法被垃圾回收器回收。

  • 预防内存泄漏的方法

    • 使用 letconstvar 声明变量。
    • 在不再需要定时器、事件监听器或回调时,及时清除它们。
    • 小心使用闭包,确保闭包中不必要的引用能够被正确清理。
    • 避免不必要地持有对 DOM 元素的引用。
  • 以下可能会导致内存泄漏

    • 意外的全局变量:没有使用 varletconst 声明变量时,变量会被自动添加到全局作用域,导致内存泄漏。
    
    function foo() {
      bar = "I am a global variable"; // 意外地成为全局变量
    }
    
    • 被遗忘的定时器或回调:未能清除不再需要的定时器或回调会导致内存泄漏。
    let intervalId = setInterval(() => {
      console.log('This will run forever unless cleared');
    }, 1000);
    
    // 忘记 clearInterval(intervalId);
    
    • 闭包:不正确使用闭包可能会导致不必要的引用,从而导致内存泄漏。
    function outer() {
      let largeData = new Array(1000).fill('*');
      return function inner() {
        console.log(largeData[0]);
      };
    }
    let myFunc = outer();
    
    • DOM 参照:保留对已移除的 DOM 元素的引用会导致这些元素及其附带的事件处理程序不能被垃圾回收。
    let elements = [];
    function createElement() {
      let el = document.createElement('div');
      elements.push(el); // 持有引用
      document.body.appendChild(el);
    }
    
  • 内存溢出指的是程序申请的内存超过了系统能够提供的最大内存,导致程序无法继续运行。内存溢出通常是由于程序中存在大量的对象或数据,或者由于程序中存在无限循环等原因导致。

  • 常见的内存溢出原因

  1. 大数据集合:一次性加载或处理过大的数据集合,会导致超出可用内存。

    let largeArray = new Array(1e10).fill('*'); // 超大数组
    
  2. 无限递归:函数调用自身无限次,最终导致堆栈溢出。

    function recurse() {
      return recurse();
    }
    recurse(); // 无限递归
    

预防内存溢出的方法

  • 对于大数据集合,考虑分页加载或数据流处理。
  • 处理递归时,确保有退出条件,并控制递归的深度。
  • 使用优化的算法和数据结构,以有效利用内存

对原型、原型链的理解

构造函数

先举个构造函数的例子

function AA(arr) {
    this.arr = arr || [];
}

AA.prototype.list = [0];
AA.prototype.add = function (a) {
    this.arr.push(a);
    this.list.push(a);
};

var a1 = new AA();
var a2 = new AA();
a1.add(1);
console.log(a1, "a1"); 
console.log(a2, "a2");

打印结果如图,对实例来说,可以看出prototype上的数据是公用的,构造函数里的数据不共用的

image.png

js 加载

<script type="text/javascript" src='./index.js'></script> 加载完立即执行 
<script defer type="text/javascript" src='./index.js'></script>
<script async type="text/javascript" src='./index.js'></script> 

下图很直观的表现出来,蓝色代表JavaScript脚本加载时间,红色代表JavaScript脚本执行时间,绿色代表HTML解析

  • 普通js 加载和执行阻塞dom解析
  • defer 资源加载不阻塞dom解析,需要等到文档所有元素解析完成之后才执行,DOMContentLoaded事件触发执行之前
  • async 资源加载不阻塞dom解析,执行会阻塞dom解析

image.png

浏览器渲染原理

  • DOM树构建:渲染引擎使用HTML解析器(调用XML解析器)解析HTML文档,将各个HTML元素逐个转化成DOM节点,从而生成DOM树;

  • CSSOM树构建:CSS解析器解析CSS,并将其转化为CSS对象,将这些CSS对象组装起来,构建CSSOM树;

    • css样式不会阻塞html解析(link是异步加载)
    • 样式要放在头部,如果样式放在底部,可能会导致重绘效果
  • 渲染树构建:DOM 树和 CSSOM 树都构建完成以后,浏览器会根据这两棵树构建出一棵渲染树;

  • 页面布局:渲染树构建完毕之后,元素的位置关系以及需要应用的样式就确定了,这时浏览器会计算出所有元素的大小和绝对位置;

  • 页面绘制:页面布局完成之后,浏览器会将根据处理出来的结果,把每一个页面图层转换为像素,并对所有的媒体文件进行解码。

优化策略

  • 减少DOM操作:避免不必要的DOM读写操作,可以缓存常用DOM对象到变量中,以减少直接访问DOM的次数。

  • 利用CSS优化:尽量使用CSS动画代替JavaScript动画,因为CSS动画在浏览器层面进行优化,通常比JavaScript动画性能更好。同时,通过优化CSS选择器的性能,减少样式计算的开销。

  • 批量修改:如果需要对多个元素进行样式修改或DOM操作,尽量将其合并成一次操作,以减少回流和重绘的次数。

  • 使用请求动画帧(requestAnimationFrame):这个函数可以在浏览器的下一次重绘之前执行代码,从而确保动画的流畅性,并减少不必要的回流和重绘。

  • 通过以上优化措施,可以有效地减轻回流和重绘对网页性能的影响,提升用户体验。

写在结尾,以上有错误的,欢迎批评指正。大家有好的面试题链接,可以发我整理,持续更新中