前端面试之JS

145 阅读11分钟

js 中继承实现的几种方式

原型链继承 :

缺点:

  1. 引用值共享问题。
  2. 只能继承一个
  3. 新实例无法向父类构造函数传参。

构造函数继承

特点

  1. 只继承了父类构造函数的属性、没有继承父类原型上的属性。
  2. 可以继承多个构造函数属性。
  3. 在子实例中可以向父实例传参。

缺点:

  1. 只能继承父类构造函数的属性、拿不到原型上的方法。
  2. 无法实现构造函数的复用。(每次使用都需要重新调用)

组合继承

特点:

  1. 可继承父类原型上的方法、属性, 可传参可复用。
  2. 每个新实例引入的构造函数属性是私有的。

缺点:

  1. 调用了两次父类构造函数 Sub.prototype = new Super(); // 执行两次

寄生组合继承:

缺点

  1. 原型上的方法访问不到

this关键字 指向问题

是什么

我们都知道函数调用时内部自动生成的上下文对象。而我们都知道对象是一系列属性的集合、所以 this 为当前执行上下文对象的一个属性。(属性 = 属性名 + 属性值)

绑定规则

  1. 默认绑定: 一般 this 是全局的 Window。 严格模式下是 undefined。
  2. 隐式绑定: 函数的 this 是上下文对象。(上下文必须包含我们的函数)
  3. new 绑定:this 就是实例对象
  4. 显式绑定:
  • apply( 参数1: 上下文对象、即指向的this,参数2:为一个数组 )、
    写法:test.apply({},[1,2])
    
  • call(参数1: 上下文对象、即指向的this,具体的每个参数1...)、 
    写法:test.call({},1,2)
    
  • bind   函数中的 this 指向最终要执行的函数、并同时返回一个新的匿名函数。
           而该匿名函数作用就是执行 this 指向的函数、并改变其 this 指向
    写法: test.bind({})()
    

常见情况下的 this 指向:

  1. 全局上下文:在全局作用域中,this指向全局对象。在浏览器环境中,全局对象是window
  2. 函数调用上下文:当一个函数被普通调用时,this通常指向全局对象。例如:myFunction()
  3. 方法调用上下文:当一个函数作为对象的方法被调用时,this指向调用该方法的对象。例如:obj.myMethod()
  4. 构造函数上下文:当使用new关键字调用一个构造函数时,this指向新创建的实例对象。
  5. 事件处理上下文:在事件处理函数中,this通常指向触发事件的元素。
  6. 自定义上下文:可以使用callapplybind方法改变函数的上下文,从而改变this的指向。

执行上下文

种类

  1. 全局上下文: 一个程序只有一个全局上下文、它会执行两件事。

    • 创建全局的 window 对象。
    • 设置 this 值等于这个全局对象
  2. 函数执行上下文:在函数每次调用的时候就会创建一个函数上下文,每当创建一个函数上下文它就会按照被创建的顺序执行。(函数上下文可以有多个)

  3. Eval 函数上下文

创建上下文的阶段

创建阶段

一般会发生以下三件事

  1. this 绑定

  2. 创建词法环境组件(内部有两个组件)

    • 环境记录器: 存储变量和函数声明的实际位置
    • 外部环境的引用:意味着它可以访问父级的词法环境(作用域)
  3. 创建变量环境组件:

    • 词法环境和变量环境不同之处是
      • 词法环境 是被用来存储 函数声明和变量的 (let 和 const)。
      • 变量环境 只能存储 var 变量的绑定
执行阶段

将这些变量进行分配、最后执行代码

防抖节流

防抖

是什么

  1. 当事件被触发时、 相应的函数不会立即触发、而是被推迟执行。
  2. 如果该事件在一定的时间内频繁触发、 那么相应的函数会被一直推迟执行。
  3. 只有该事件在一定的时间内没有被触发、 才是真正的执行这个函数

简而言之 : 防抖就是将函数的执行延迟一定的时间、如果在该时间内重新触发、该时间就会被重置; 只有真正达到延迟时间、才会执行回调函数。

我们可以把它理解为游戏里的回城。点击回城如果我们不移动英雄、那么一段时间后它就会回去、反之就会一直被刷新回城时间。

应用场景

  1. 输入框内频繁输入、搜索或者提交信息
  2. 频繁点击按钮、触发某个事件
  3. 监听浏览器滚动时间
  4. 监听用户缩放浏览器的 resize 时间

节流

是什么

  1. 当事件触发时、 相应的函数就会立即执行。
  2. 如果该事件一直被触发时、那么相应的函数会按照一定的频率进行执行

我们可以把它理解为技能cd,不管你按了多少次,必须等到cd结束后才能释放技能。也就是说在如果在cd时间段,不管你触发了几次事件,只会执行一次。只有当下一次cd转换,才会再次执行。

应用场景

  1. DOM 元素的拖拽功能实现
  2. 计算鼠标移动的距离
  3. 监听 scorll 事件

事件代理(事件委托)

将事件绑定到父元素上、让父元素担当事件监听的职务。它的原理是DOM元素的事件冒泡。

优点: 可以大量节省内存占用、减少事件注册、新增子元素无需再次对其绑定

事件模型

在JavaScript中,事件模型(Event Model)是一种处理用户交互和浏览器行为的机制。事件模型基于事件监听器(Event Listeners)和事件流(Event Flow)的概念。

事件监听器是用于处理特定类型事件的函数。当事件发生时,如点击、滚动、键盘按键等,事件监听器会被触发并执行相应的操作。在JavaScript中,可以使用addEventListener方法为元素添加事件监听器,使用removeEventListener方法移除事件监听器。

事件流描述了事件从发生到被处理的整个过程。主要包括三个阶段:

  1. 捕获阶段(Capturing Phase):事件从顶层元素(通常是document对象或window对象)开始向下传播,直到达到目标元素。在此过程中,事件处理程序可以捕获并处理事件。
  2. 目标阶段(Target Phase):事件到达目标元素,触发目标元素的事件处理程序。
  3. 冒泡阶段(Bubbling Phase):事件从目标元素开始向上回传到顶层元素。在此过程中,事件处理程序可以继续处理事件。

事件模型允许开发者在不同阶段对事件进行处理,实现灵活的事件处理逻辑。此外,JavaScript还提供了一些全局事件处理方法,如onclickonmouseover等,它们可以直接绑定到HTML元素上,但这种方式不如事件监听器灵活且可维护性较差。

总之,JavaScript的事件模型提供了一种处理用户交互和浏览器行为的机制,使得开发者能够根据不同的事件类型和阶段来编写相应的处理逻辑。

事件循环

事件循环是 JS 引擎中用于处理异步操作的的一种机制。它用于调度和管理 Js 代码中的事件、回调函数和任务队列以确保它们可以按照正确的顺序执行。 这种机制可以使 Javascript 具有非阻塞的特性,并能够处理大量的并发操作。

如果在 Js 中遇到异步操作时、它会被添加到任务队列中、而不会立即执行。而 JS 引擎不断的从任务队列取出任务并执行的过程叫做事件循环。 事件循环 = 任务队列 + 宏任务 + 微任务

执行过程

  1. 从宏任务队列中取出一个任务执行、 直到宏任务队列为空。
  2. 执行过程中如果遇到微任务、将添加到微任务队列中。
  3. 当从宏任务队列中取出的任务执行完毕后、 开始执行微任务队列中的任务、一直到微任务队列为空。
  4. 重复上述步骤、不断地从宏任务队列和微任务队列中取出任务执行,直到两个队列都为空。

垃圾回收机制

是什么

浏览器垃圾回收机制是指、浏览器在运行的过程中自动管理内存的一种机制。当我们使用浏览器打开网页时、会创建很多的对象和变量来存储页面的数据和功能。 但随着页面的加载、交互和关闭等一系列操作、这些对象和变量可能不再使用了。 为了避免内存泄露和性能优化、 浏览器会定期进行进行垃圾回收。

具体的来说它会标记哪些任在使用的对象并将其保留下来。而那些没有被引用的对象会当作垃圾并清除掉。

常见的垃圾回收算法

标记清除

通过从根节点开始遍历所有可达对象、 并对每个可达对象进行标记。 之后在第二次遍历的时候、未被标记的对象会被当作垃圾

引用计数

内部通过维护计数器来记录每个对象在其他地方被引用的次数。若一个对象没有任何引用时、就会把这个对象当作垃圾


在实际应用中、现代浏览器通常采用的更为复杂和高效的垃圾回收算法。如分代回收和增量式回收。这些算法可以根据对象的生命周期和内存分配等因素进行优化、 以提升浏览器性能和用户体验。

Null 和 undefined 的区别

nullundefined 都是属于基本数据类型(都保存在栈中)。ECMAScript认为undefinednull派生出来的。

区别

  1. undefined 代表的含义是未定义null 代表的含义是空对象
  2. undefined不是一个关键字(也就是它可以当作一个变量名),null 是一个关键字(表示一个空值)。
  3. Number(undefined) === NaNNumber(null) === 0
  4. 当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false

产生null的原因

  1. 访问不存在的DOM节点时。
  2. 原型链的终点。

产生undefined的原因

  1. 声明变量,但未赋值
  2. 函数无返回值,执行后返回undefined
  3. 函数中可选参数,没有传参时返回undefined
  4. 对象中不存在或未赋值的属性

堆和栈的区别

堆:

  • 内存的分配是程序员手动控制的
  • 内存管理依赖于垃圾回收机制、当一个对象不再被引用时它会自动回收
  • 内存空间相对较大
  • 主要用于存储复杂的数据结构

栈:

  • 内存的分配和释放由编译器或解释器自动处理的、每当一个函数执行完成之后、与之相关的栈内存就会被自动释放
  • 内存空间相对较小
  • 一般存储基本数据类型以及函数调用的上下文信息

深拷贝和浅拷贝

image.png 浅拷贝只复制属性指向某个对象的指针,而不复制对象本身

  1. 直接手动复制对象
  2. 使用扩展运算符
  3. 使用Concat() 方法合并多个数组,返回一个新的数组。原数组不变
  4. 使用Object.assign()

深拷贝会创建新的内存地址。可以在不改变原数组的情况下、修改元素

  1. 使用 JSON.stringify转为字符串再 JSON.parse
  2. 深度递归遍历
  3. 使用 Object.create()
  4. jQuery中的 $.extend()

settimeout setinterval 区别

setinterval

概念: 每隔一段时间就执行一次。重复执行 函数整体执行时间: 设置的固定时间,若异步函数执行时间大于设置的时间,则函数整体时间为异步函数执行时间

settimeout

概念: 一段时间后就执行一次。不重复执行 函数整体执行时间: 设置的固定时间 + 异步函数执行时间