WebWorker

webworker通信

# es6有什么新特性

# es6~es12

变量声明:let const ,块级作用域

promise

类:class

箭头函数:

解构赋值:数组对象快速取值

模块化:用import export(ES Module)

模板字符串:${name}

有更多的数据结构:map set

函数参数之类的 默认参数展开参数

对象属性简写

ES7

includes

指数操作符

ES8

async/await

# common.js和es6中模块引入的区别?

CommonJS:Node.js 最初采用的模块规范(require/module.exports),同步加载模块。

ES6 Module (ESM):ES6 原生支持的模块化标准(import/export),静态编译时确定依赖关系。

// CommonJS
const fs = require('fs');
module.exports = { read: fs.readFileSync };

// ES6 Module
import fs from 'fs';
export const read = fs.readFileSync;

特点 CommonJS ES6 Module
语法 require / module.exports import / export
加载时机 运行时 编译时
导出方式 值拷贝 引用绑定(live binding)
缓存机制 缓存执行结果,单例 无缓存,实时绑定
是否可动态引入 ✅ 可以 ❌ 必须静态
Tree Shaking ❌ 不支持 ✅ 支持
主要应用 Node.js 后端 前端 + Node (新版本)

# 开始的起点

# 浏览器输入一个网址之后按回车会发生什么样的变化

1 输入网址 & DNS 解析:浏览器检查缓存、没有的话发起DNS请求(解析域名-> 得到IP地址)

2 建立连接:浏览器根据协议发起连接:TCP连接

3 发送 HTTP 请求:请求报文,字段,请求发送服务端对应的IP+端口

4 服务器处理请求:服务器接收请求然后经过负载均衡、方向代理,再到应用服务器,应用服务器查数据生成响应内容

5 返回响应:服务端返回 响应报文(状态行、响应头、响应体)

阻塞点

  • 初始 HTML 需要先下载到浏览器才能开始解析。
  • CSS 被认为是渲染阻塞资源(必须下载并解析完成才可进行渲染)。
  • JS(未加 defer/async)遇到 <script> 会暂停 HTML 解析并立即执行(因为脚本可能修改 DOM/CSSOM)。

6 浏览器渲染:

  • 解析 HTML → 构建 DOM 树。

    解析 CSS → 构建 CSSOM 树。

    合并成 Render Tree

    计算布局(Reflow)绘制(Repaint)

    遇到 <script> 会执行 JS(可能阻塞渲染)

  • 为什么 CSS 放头部,JS 放尾部?

    CSS 放头部:避免页面闪烁,保证 DOM 渲染时有样式。

    JS 放尾部:避免阻塞 DOM 解析(除非用 defer/async

  • 回流和重绘

    回流(Reflow):布局变化,DOM 的几何尺寸发生改变(增删 DOM、改变宽高、字体等)。开销大。

    重绘(Repaint):外观样式改变(颜色、背景),不影响布局。

    优化:减少频繁操作 DOM,合并修改,使用 requestAnimationFrame,开启 GPU 加速。

7 JS 执行 & 事件循环:JS 引擎解析并执行代码、遇到异步任务时,进入 事件循环机制(宏任务/微任务队列)

8 页面展示:渲染完成后,用户看到页面

# 浏览器渲染

浏览器拿到 HTML 后边解析边构建 DOM,同时下载并解析 CSS 构建 CSSOM,合并成 Render Tree,然后做布局(layout/reflow)计算每个元素的位置尺寸,再做绘制(paint)把像素画出来,最后合成图层(composite)交给 GPU 显示。JS(尤其同步脚本)和 CSS 都可能阻塞渲染;常见优化是最小化关键 CSS/JS,使用 defer/async、预加载资源、懒加载图片和用 transform/opacity 做动画以避免回流。

# 浏览器解析js是怎么样的过程

1 加载阶段:HTML解析器遇到<script>标签时,会暂停构建DOM,然后js会被下载或者走缓存

2 编译阶段:浏览器的js引擎V8对js源代码进行编译,把源码转成 AST,然后做预处理

3 执行阶段:解释器(Ignition)把 AST 转换成 字节码 并执行

4 事件循环:执行同步代码时,遇到异步任务会挂起、微任务队列(Promise.then、MutationObserver)和 宏任务队列(setTimeout、setInterval)交替执行

为什么 JS 会阻塞渲染? defer 和 async 的区别

  • JS 可能修改 DOM 结构(比如 document.write、DOM 操作),浏览器必须等 JS 执行完,才能继续解析 HTML,否则会错乱。
  • defer:JS 异步下载,等 DOM 解析完成后再按顺序执行(不会阻塞渲染)。
  • async:JS 异步下载,下载完立即执行(可能打断 DOM 构建,顺序不保证)。

async/await 的本质

  • 本质是 Promise 的语法糖
  • await 会把后面的表达式包装成一个微任务,等当前宏任务执行完后立即执行。
  • async 函数默认返回 Promise,方便链式调用。

AST 有什么用?

  • **AST(抽象语法树)**是 JS 源码的结构化表示。
  • 用途
    • Babel:把新语法(ES6+)转译成 ES5。
    • ESLint:检查代码风格、规范。
    • Webpack/Uglify:代码压缩、优化。

# 事件循环

js是单线程语言,但是不意味着单线程就是阻塞,实现单线程非阻塞的方法就是事件循环

js中的任务:

  • 同步任务:立即执行的任务,同步任务一般会直接进入主线程中执行
  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数

image-20250720163937346

image-20250720164418657

微任务

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

  • Promise.then

  • MutaionObserver

  • Object.observe(已废弃;Proxy对象替代)

  • process.nextTick

宏任务

这个宏任务的时间颗粒度比较大,这样的话执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务:

  • script
  • setTimeout/setInterval
  • UI rendering/UI事件
  • postMessage、MessageChannel
  • setImmediate、I/O (node.js)

微队列和宏队列的区别:

宏任务是事件循环的基本单位,比如 setTimeout、script 整体;微任务优先级更高,在每个宏任务执行完后立即执行,比如 Promise.then。区别在于:一次宏任务 → 清空所有微任务 → 再下一个宏任务。所以微任务的执行时机比宏任务要早。

为什么有宏任务微任务之分呢?

其实浏览器把任务分成宏任务和微任务,主要是出于性能和实时性的平衡。宏任务负责大的调度,比如整体 script、setTimeout;而微任务可以在一次宏任务执行后立即插入,保证像 Promise 回调、DOM 更新这种小任务能尽快执行,从而让页面表现更流畅。

async await

async函数是返回一个promise对象

function f() {
    return Promise.resolve('TEST')
}

// asyncF is equivalent to f
async function asyncF() {
    return 'TEST'
}

await

正常情况下,await后面是一个Promise对象,返回的是该对象的一个结果。如果不是promise就直接返回对应的值

需要注意的是await后面跟着的是什么,await都会阻塞后面的代码

image-20250722141421824

输出结果:1、fn2、3、2

下面上例子

image-20250722141843263

script start

async1 start

async2

promise1

script end

async1 end

promise2

settimeout

# 事件

# js的事件模型

事件:浏览器中发生的一种交互操作

事件流:

image-20250720154909658

事件模型

  • 原始事件模型(DOM0)

    速度优势但可能太快了导致页面没加载完有问题,并且只支持冒泡不支持捕获,同一个类型的事件只能绑定一次

    <input type="button" id="btn" onClick="fun1()">
        
    var btn = document.getElementById('.btn')
    btn.onclick = fun2 // 小心被覆盖
    
    // 删除DOM0级事件处理程序只需要将对应事件属性设置为null即可
    btn.onclick = null
    
  • 标准事件模型(DOM2)

    这个要经历三个过程了

    事件绑定监听函数

    addEventListener(eventType, handler, userCapture)
    

    移除绑定监听函数

    removeEventListener(eventType, handler, userCapture)
    
    //userCapture是一个boolean用于指定是否在捕获阶段进行处理一般设置为false
    

    可以在DOM元素上绑定多个事件

  • IE事件模型(基本不用)

补充两个概念:

  • event.stopPropagation():阻止事件继续传播(不冒泡/不捕获)
  • event.preventDefault():阻止默认行为(如表单提交)

JS 的事件模型通过捕获、目标、冒泡三个阶段来实现事件的分发和控制

# 解释一下什么是事件代理?应用场景?

事件代理:事件代理是指不在每个子元素上绑定事件监听器,而是把事件绑定在其父元素上,利用事件冒泡机制统一处理子元素的事件。

当事件触发时,它会冒泡到父元素,在父元素的事件回调中通过 event.target 判断实际触发的子元素。

应该也可以叫事件委托吧

例子:

image-20250720163111632

应用场景:

列表项点击(如菜单、评论列表)

表格操作按钮统一绑定事件

动态生成的元素(如无限滚动加载)

image-20250720163318624

image-20250720163328675

还有就是动态添加这种的

适合事件委托的事件有:clickmousedownmouseupkeydownkeyupkeypre

优点:

  • 减少事件绑定数量,减少内存提升性能

  • 支持动态添加的子元素也能响应事件,减少重复工作

不能所有事件都用事件代理,有些事件是不支持的

# 什么是事件传播?

事件在 DOM 树中传递的过程。这个过程主要分为三个阶段:

  1. 捕获阶段(Capture Phase): 事件从 document 顶部节点开始,向下捕获到目标元素

  2. 目标阶段(Target Phase): 事件到达我们点击的目标元素

  3. 冒泡阶段(Bubble Phase): 事件再从目标元素向上传播到父元素,一直到 document

事件发生在DOM元素上时,该事件并不完全发生在那个元素上。

# 什么是事件冒泡?

事件冒泡是指当一个元素上的事件被触发后,事件会从该元素逐级向上传播到它的祖先元素,直到根节点(通常是 document)。 这就好像你把石头扔进水中,波纹会向外扩散一样 —— 事件从你点击的元素开始,一层层“冒”到外面。

默认情况下,事件监听器都是在冒泡阶段执行

# 什么是事件捕获?

事件捕获是事件传播的第一阶段,它的过程是:document 根节点开始,向下经过每一层 DOM 元素,一直到目标元素

也就是说,事件先“捕捉”到路径上的每一个父元素,然后再到目标。

不过,默认情况下我们监听事件时是不会在捕获阶段触发的,除非我们显式设置捕获为 true

# 什么是事件代理?

事件代理:事件代理是指不在每个子元素上绑定事件监听器,而是把事件绑定在其父元素上,利用事件冒泡机制统一处理子元素的事件。

当事件触发时,它会冒泡到父元素,在父元素的事件回调中通过 event.target 判断实际触发的子元素。

应该也可以叫事件委托吧

场景:

列表项点击(如菜单、评论列表)

表格操作按钮统一绑定事件

动态生成的元素(如无限滚动加载)

# event.preventDefault() 和 event.stopPropagation()方法之间有什么区别?

event.preventDefault() 方法可防止元素的默认行为。比如:在表单元素中使用,它将阻止其提交、在锚元素中使用,它将阻止其导航

event.stopPropagation()方法用于阻止捕获和冒泡阶段中当前事件的进一步传播。

使用event.defaultPrevented属性。它返回一个布尔值用来表明是否在特定元素中调用了event.preventDefault()

# 异步

# Promise 是什么?

Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。promise有三种状态:pending(等待态)fulfiled(成功态)rejected(失败态);状态一旦改变,就不会再变。创造promise实例后,它会立即执行

# 什么是 async/await 及其如何工作?

async/await是 JS 中编写异步或非阻塞代码的新方法,本质是想让异步代码看起来像同步写法

  • async 用来声明一个函数为异步函数,返回值会被自动包装成 Promise

  • await 用来等待一个 Promise 完成,它会暂停函数执行,直到 Promise 状态变为 fulfilled/rejected

实现原理:

其实 async/await 是 Generator + Promise 的语法糖,由 JS 引擎(或 Babel 转译器)来实现。

  • Generator 函数:可以通过 yield 暂停函数执行。
  • async/await:把 await 当成 yield,配合 Promise,实现暂停和恢复。

例如:

async function foo() {
  const data = await fetchData();
  console.log(data);
}

编译后类似于

function foo() {
  return new Promise((resolve, reject) => {
    const gen = function* () {
      const data = yield fetchData();
      console.log(data);
    }();

    function step(nextF, arg) {
      let next;
      try {
        next = nextF(arg);
      } catch (err) {
        return reject(err);
      }
      if (next.done) return resolve(next.value);
      Promise.resolve(next.value).then(
        v => step(gen.next.bind(gen), v),
        e => step(gen.throw.bind(gen), e)
      );
    }

    step(gen.next.bind(gen));
  });
}

为什么 async/await 能简化异步?

  • Promise 需要 .then 链式调用,代码层层嵌套。
  • async/await 用同步写法组织异步逻辑,底层还是 Promise + 事件循环

await 会阻塞主线程吗?(不会,只阻塞当前 async 函数,底层还是事件循环调度)

如果有多个异步任务需要并行执行怎么办?Promise.allPromise.allSettled

async 函数里 return 普通值会怎样?(自动包装成 Promise.resolve(value)

# 原型相关的

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

就是要明白这几个东西代表什么意思

  • prototype是函数才有的属性(包括构造函数),每个函数都有一个 prototype 属性,指向一个对象,这个对象就是实例的原型(__proto__ 指向的对象),用来实现共享方法和属性

  • _proto_[[Prototype]],是对象才有的属性,每个对象都有一个 __proto__,它指向创建该对象的构造函数的 prototype

  • constructor,是 prototype 对象上的一个属性,指回构造函数本身

以一个例子来

function Persion(name) {
    this.name = name
    this.age = 18
    this.sayName = function(){
        console.log(this.name)
    }
}

var person = new Person('person')

有图:

             ┌───────────────────────────────┐
             │         Function              │
             │       (构造函数 Persion)      │
             └───────────────────────────────┘
                         │
                         │  .prototype
                         ▼
             ┌───────────────────────────────┐
             │    Persion.prototype          │◄────────────┐
             │  (共有的原型对象,默认有个   │             │
             │   constructor 指向 Persion)  │             │
             └───────────────────────────────┘             │
                         ▲                                │
                         │                                │
                         │ __proto__                      │ constructor
             ┌───────────────────────────────┐             │
             │          person               │─────────────┘
             │   (实例对象)                 │
             └───────────────────────────────┘
                         │
                         │  __proto__
                         ▼
             ┌───────────────────────────────┐
             │    Persion.prototype          │  (同上)
             └───────────────────────────────┘

工作机制:

当你执行:person.sayName() 时,查找过程:

  1. 在 person 自身找 sayName → 找到了,直接调用
  2. 如果找不到:
    • 会通过 person.__proto__Persion.prototype
  3. 如果再没有,会继续往上:
    • Persion.prototype.__proto__Object.prototype
    • 最后是 null(原型链尽头)

特点:

名称 属于谁 指向 用途
prototype 构造函数 原型对象 定义共享属性和方法
__proto__ 实例对象 构造函数的 prototype 实现原型链查找
constructor 原型对象 构造函数 指回构造函数本身

回答:

在 JavaScript 中,每个对象都有一个内部属性 __proto__,它指向该对象的原型。这个原型本身也是对象,也有自己的 __proto__,这样一层层向上连接,最终形成原型链

当我们访问一个对象的属性时,JS 引擎会先在自身查找,找不到就沿着原型链向上查找,直到 Object.prototype,如果还找不到就返回 undefined

# Function.prototype.apply && Function.prototype.call方法

apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。

# 闭包

说说你对闭包的理解?闭包的使用场景?

闭包可以让你在一个内层函数中访问到其外层函数的作用域

在js中每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁

function outer() {
  let count = 0;  // 外部函数的局部变量
  return function inner() {  // 内部函数
    count++;  // 访问并修改外部函数的局部变量
    console.log(count);
  };
}

const counter = outer();  // 调用 outer 函数,返回的是一个内部函数
counter();  // 1
counter();  // 2
counter();  // 3

这里,inner 函数就是一个闭包,因为它能访问并修改外部函数 outercount 变量,即使 outer 函数已经执行完毕并返回了。

这里如何理解闭包呢?

外部函数执行完成后,它的执行环境就被销毁了,通常其内部的局部变量会被清除。但因为闭包的存在inner 函数依然持有对 outer 中变量 count 的引用,这个引用让 count 的值保持不变,因此每次调用 counter() 时,它都会打印一个递增的值。

任何闭包的使用场景都离不开这两点:

  • 创建私有变量
  • 延长变量的生命周期

image-20250720170433220

柯里化函数

柯里化的目的在于避免频繁调用具有相同参数函数的同时,又能轻松的重用

image-20250720170630390

使用闭包模拟私有方法

js中是没有支持声明私有变量的,但我们可以用闭包来模拟私有方法

image-20250720170720475

闭包的应用场景

  • 闭包可以用来创建私有变量,实现数据封装

  • 闭包可以用来生成动态创建函数的工厂

  • 防抖和节流

# 细说new操作符

new用于创建一个给定构造函数的实例对象(将实例和构造函数通过原型链连接起来了)

new关键字的工作流程:

  • 创建一个新的对象obj
  • 将对象与构造函数通过原型链相连
  • 将构造函数中的this绑定到新建的对象obj上
  • 根据构建函数返回类型作判断,如果原始值则被忽略,如果是返回对象,需要正常处理
function Person(name, age) {
    this.name = name
    this.age = age
}

const person1 = new Person('Tom',20)
console.log(person1) // Person {name: "tom", age:20}
t.sayName() // 'Tom'

image-20250720140823494

下面来手写new操作符:

function mynew(Func, ...args){
	// 1.创建一个新对象
    const obj = {}
    // 2. 将对象原型指向构造函数元素对象
    obj._proto_ = Func.prototype
    // 3. 将构造函数的this指向新对象,并执行构造函数
    let result = Func.apply(obj,args)
    // 4. 根据返回值判断
    return result instanceof Object ? result : obj
}

使用:

function Person(name ,age){
    this.name = name
    this.age = age
}

Person.prototype.say = function() {
    console.log(this.name)
}

let p = mynew(Person, "huihui",123)
console.log(p) // 
p.say() // huihui

回答:

new 是 JavaScript 用来创建对象实例的关键字,它在调用构造函数时,会执行以下四个步骤:

  1. 创建一个空对象
  2. 将这个对象的原型指向构造函数的 prototype
  3. 将构造函数的 this 绑定到这个新对象上并执行构造函数
  4. 如果构造函数返回的是对象类型,就返回这个对象,否则返回步骤1创建的新对象

# 作用域

# 说说你对作用域链的理解

作用域就是js中能够有效访问变量或者函数的区域

js有三种作用域:

  • 全局作用域:在全局命名空间中声明的变量或者函数位于全局作用域中,在代码中的任何的地方都可以访问他们,使用var
  • 函数作用域:就是命名在函数里面的,在函数中可以访问,在函数外不能访问
  • 块作用域{}:在块{}中声明的变量(let,const)只能在其中访问。

答:

作用域链是 JavaScript 查找变量的一种机制。

当访问一个变量时,JS 引擎会从当前作用域开始查找,如果找不到,就向上层作用域查找,直到全局作用域为止。如果仍然找不到,就报 ReferenceError

# js执行上下文和执行栈是什么?

执行上下文就是js代码执行环境嘛

还有生命周期

  • 创建阶段,函数被调用但未执行内部代码之前

    • 确定this
    • 词法环境
    • 变量环境
  • 执行阶段,执行变量赋值(找不到值就分配undifned),代码执行

  • 回收阶段,执行上下文出栈等待虚拟机回收执行上下文

执行栈(调用栈)

具有LIFO结构,用于存储在代码执行期间创建的所有执行上下文

JS 引擎就是通过执行栈按顺序执行函数调用的。

运行流程:

  1. 脚本开始时,创建全局上下文,压入栈顶;
  2. 调用函数时,创建对应的函数上下文,压入栈顶;
  3. 函数执行完后,该上下文从栈中弹出;
  4. 最终栈清空,程序执行结束。

回答:

执行上下文是 JavaScript 代码在执行时的运行环境,决定了变量、函数的访问权限。

每当一段代码(函数、全局、eval)被运行,都会创建一个对应的执行上下文。

执行上下文的类型:

  1. 全局执行上下文:整个 JS 脚本的最外层环境,只创建一次;
  2. 函数执行上下文:每次函数调用都会创建新的上下文;
  3. eval 执行上下文:极少使用。

# 什么是提升?

提升是用来定义的变量和函数移动到其(全局或函数)作用域顶部的术语。

执行上下文来讲比较清楚,执行上下文是当前正在执行的**“代码环境”**。执行上下文有两个阶段:编译执行

  • 编译阶段:在这个阶段,JS获取所有函数声明、变量声明并将其提升到其作用域的顶部,(只有使用var声明的变量或者函数才会被提升,相反函数表达式或箭头函数、let和const声明的变量都不会被提升),还会给没有初始值的变量提供默认值undefined
  • 执行阶段:在这个阶段中他将值赋给之前提升的变量,并执行或者调用函数(对象中的方法)

# JS内存泄漏、垃圾回收

# 说说js中内存泄漏的几种情况

全局变量 / 意外的全局引用

未清理的定时器、

未移除的事件监听器

闭包导致的引用保留

# this

# 谈谈对this对象的理解

this关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象,注意:this在函数执行过程中一旦被确定了,就不可以再更改了

一般比较多考虑它的指向问题

  • 默认(普通方法),定义函数的时候内部使用(不能将全局对象默认绑定)

  • 隐式(对象方法),方法调用的时候指向这个上级对象

  • new(构造函数),new生成的实例对象就指向这个实例对象

  • 显示修改,使用apply()、call()、bind()

箭头函数:这个应该比较多

箭头函数的this就是指向上一个作用域的对象,一般箭头函数不用this,所以箭头函数不能当构造函数

还有考虑优先级:new>显示>隐式>默认

答:

在 JavaScript 中,this 指的是函数运行时的上下文对象,它的指向取决于函数的调用方式,而不是函数定义的位置。

# bind、call、apply区别?如何实现一个bind?

bind、call、apply的作用是改变函数执行时候的上下文,显示的改变this的指向

(1)call

  • 语法fn.call(thisArg, arg1, arg2, ...)
  • 作用:立即调用函数,this 指向传入的 thisArg
  • 特点:参数是 逐个传递 的。
function greet(greeting, name) {
  console.log(`${greeting}, ${name}! 我是 ${this.role}`);
}

greet.call({ role: "学生" }, "你好", "mmx"); 
// 输出: 你好, mmx! 我是 学生

(2)apply

  • 语法fn.apply(thisArg, [argsArray])
  • 作用:立即调用函数,this 指向传入的 thisArg
  • 特点:参数必须是 数组或类数组
greet.apply({ role: "老师" }, ["Hello", "Tom"]); 
// 输出: Hello, Tom! 我是 老师

(3)bind

  • 语法const newFn = fn.bind(thisArg, arg1, arg2, ...)
  • 作用:不会立即执行,而是返回一个 新函数,新函数内部的 this 永远绑定到指定对象。
  • 特点
    • 可预先传入部分参数(柯里化)。
    • 返回的函数还可以再次传参。
const boundGreet = greet.bind({ role: "程序员" }, "Hi");
boundGreet("Jerry"); 
// 输出: Hi, Jerry! 我是 程序员

手写 bind 实现

实现一个简化版的 Function.prototype.bind

Function.prototype.myBind = function (context, ...args1) {
  if (typeof this !== "function") {
    throw new TypeError("Bind must be called on a function");
  }

  const fn = this; // 保存原函数

  return function (...args2) {
    // 判断是否作为构造函数使用(new)
    if (this instanceof fn) {
      return new fn(...args1, ...args2);
    }
    return fn.apply(context, [...args1, ...args2]);
  };
};

使用示例

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const obj = { role: "学生" };

const boundPerson = Person.myBind(obj, "mmx");
const p1 = boundPerson(22);

console.log(p1);        // Person { name: 'mmx', age: 22 }
console.log(obj.name);  // undefined (说明new时this还是指向新对象)
  • call/apply 立即调用函数,区别在于参数传递形式不同;
  • bind 返回一个新函数,可延迟调用,支持柯里化,且能作为构造函数保留原型链。

# DOM和BOM

DOM

  • DOM表示HTML和XML文档的标准的对象模型,将文档中的每个组件(比如:元素、属性、文本等)都看作是一个对象,开发者可以通过js操作这些对象从而动态地改变页面的内容结构和样式
  • DOM树状结构,根节点是document对象代表整个文档

BOM

  • BOM是表示浏览器窗口及其各个组件的对象模型,它提供了一组对象,用于访问和控制浏览器窗口及其各个部分比如地址栏、历史记录等
  • 核心对象是window对象,它表示浏览器窗口并且是js中的全局对象,就是对于浏览器窗口的各个方面,以及浏览器相关信息历史等

# JS的数据

# 数据类型

# 数据结构

Map

键值对

Set

类似于数组当时成员的值都是唯一的

# JS对象

# 如何在 JS 中创建对象?

使用对象字面量:

const o = {
  name: "前端小智",
  greeting() {
    return `Hi, 我是${this.name}`;
  }
};

o.greeting(); 

使用构造函数:

function Person(name) {
   this.name = name;
}

Person.prototype.greeting = function () {
   return `Hi, 我是${this.name}`;
}

const mark = new Person("前端小智");

mark.greeting(); // "Hi, 我是前端小智"

使用 Object.create 方法:

const n = {
   greeting() {
      return `Hi, 我是${this.name}`;
   }
};

const o = Object.create(n); 
o.name = "前端小智";

#

# 深拷贝和浅拷贝的区别?如何实现一个深拷贝?

注意拷贝的属性类型

如果属性是基本类型那拷贝的就是基本类型的值,如果是引用类型,拷贝的就是内存地址

即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

简单实现一个浅拷贝

function shallowClone(obj) {
    const newObj = {}
    for(let prop in obj) {
        if(obj.hasOwnProperty(prop)){
            newObj[prop] = bij[prop]
        }
    }
    return newObj
}

在js中,存在浅拷贝的现象有:

  • Object.assign()

    var obj = {
     age: 18,
     nature: ['smart', 'good'],
     names: {
     name1: 'fx',
     name2: 'xka'
     },
     love: function () {
     console.log('fx is a great girl')
     }
    }
    var newObj = Object.assign({}, fObj);
    
  • Array.prototype..slice(), Array.prototype.concat()

深拷贝

深拷贝开辟了一个新的栈,两个对象属性完全相同,但是对应两个不同的地址,两个对象互不干扰

常见的深拷贝方法:

// _.cloneDeep()
const _ = require('lodash')
const obj1 = {
    a: 1
    b: { f:{g:1}}
    c: [1,2,3]
}
const ob2 = _.cloneDeep(obj1)
console.log(obj1.b.f) // false


// jQuery.extend()
const $ = require('jquery')
const ob3 = $.extend(true, {}, obj1)


// JSON.stringify()
// 但是这种方式存在弊端,会忽略undefined、symbol和函数
consst ob3 = JSON.parse(JSON.stringify(obj1))

手写一个深拷贝

function deepClone(obj, hash = new WeakMap()) {
	if (obj === null) return obj; //如果是 null或者是 undefined我就不进行拷贝操作
 	if (obj instanceof Date) return new Date(obj);
 	if (obj instanceof RegExp) return new RegExp(obj);
 	// 可能是对象或者普通的值,如果是函数的话是不需要深拷贝的
 	if (typeof obj !== "object") return obj;
 	// 是对象就要进行深拷贝
 	if (hash.get(obj)) return hash.get(obj);
 	let cloneObj = new obj.constructor();
 	// 找到的是所属类原型上的constructor,,而原型上的constructor指向是当前类本身
 	hash.set(obj, cloneObj);
 	for (let key in obj) {
 		if (obj.hasOwnProperty(key)) {
 			// 实现一个递归拷贝
 			cloneObj[key] = deepClone(obj[key], hash);
 		}
 	}
 	return cloneObj;
}

image-20250720174110012

# 说说js数字精度丢失的问题,如何解决?

# 什么是包装对象(wrapper object)?

在js的类型中,分为基本类型和引用(对象)类型

当我们使用比如下面的例子的时候会发现,怎么string基本类型会像对象一样使用属性或者函数的呢?

let name = "marko";

console.log(typeof name); // "string"
console.log(name.toUpperCase()); // "MARKO"

原因是基本类型在这个时候会临时转换或者强制转换为对象,因此name变量的行为类似于对象,这就是包装对象

除了null和undefined之外每个基本对象都有自己的包装对象

console.log(new String(name).toUpperCase()); // "MARKO"

在完成访问属性或调用方法之后,新创建的对象将立即被丢弃。

# 如何检查对象中是否存在某个属性?

检查对象中是否存在属性有三种方法。

第一种使用 in 操作符号:

const o = { 
  "prop" : "bwahahah",
  "prop2" : "hweasa"
};

console.log("prop" in o); // true
console.log("prop1" in o); // false

第二种使用 hasOwnProperty 方法,hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。

console.log(o.hasOwnProperty("prop2")); // true
console.log(o.hasOwnProperty("prop1")); // false

第三种使用括号符号obj["prop"]。如果属性存在,它将返回该属性的值,否则将返回undefined

console.log(o["prop"]); // "bwahahah"
console.log(o["prop1"]); // undefined

in 运算符和 Object.hasOwnProperty 方法有什么区别?

hasOwnPropert()方法指示对象自身属性中是否具有指定的属性,会忽略从原型链那里继承到的属性

in运算符会检查该对象及其原型链是否包含具有指定名称的属性

# Object.seal 和 Object.freeze 方法之间有什么区别?

Object.freeze() 方法可以冻结一个对象,属性都是不可写的不能被修改

Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置,如果属性是可写的,那么可以修改属性值

# 如何实现继承

# 防抖和节流?

# 什么是防抖和节流?有什么区别?如何实现?

优化高频率执行代码的一种方式

如:浏览器中的resize、scroll、keypress、mousemove等事件触发的时候,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能

我们要对此类事件进行调用次数的限制,我们就可以采用防抖节流的方式来减少频率

  • 节流:n秒内只运行一次,若在n秒内重复触发,只有一次生效(就像王者中的技能)
  • 防抖:n秒后执行该事件,若在n秒内被重复触发,则重新计时(就像王者中的回城)

代码实现

节流,可以时间戳与定时器的写法

时间戳写法的话事件会立即执行,停止触发后没有办法再次执行

function throttled (fn, delay = 500) {
    let oldtime = Date.now()
    return function (...args) {
        let newtime = Date.now()
        if (newtime - oldtime) >= delay {
            fn.apply(null, args)
        }
    }
} 

使用定时器的写法,delay毫秒后第一次执行没第二次事件停止触发后依然会再一次执行

function throttled (fn, delay = 500) {
    let timer = null
    return function(...args) {
        if(!timer) {
        	timer = setTimerout(() => {
            	fn.apply(null, args)
            	timer = null
        	},delay)
        }
    }
}

更精确版本

function throttled(fn, delay){
    let timer = null
    let startTime = Date.now()
    return function () {
        let curTimer = Date.now() //当前时间
        let remaining = delay - (curTimer - starttime) //从上一次到现在,还剩下多少多余时间
        let context = this
        let args = arguments
        claerTimer(timer)
        if(remianing <= 0){
            fn.apply(context, args)
            starttime = Date.now()
        }else{
            timer = setTimeout(fn, remianing)
        }
    }
}

防抖

function debounce(func, wait) {
    let timeout
    
    return function () {
        let context = this;// 保存this指向
        let args = arguments //拿到event对象
        
        clearTimeout(timeout)
        timeout = setTimeout(()=>{
            func.apply(context,args)
        }, wait)
    }
}

防抖

  • 搜索框搜索输入,只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小resize

节流

  • 滚动加载,加载更多或滚到底部监听
  • 搜索框的联想功能

# js本地存储

# js本地存储的方式有哪些?区别及应用场景?

很多啊有很多方式,下面四种应该是最常用的

  • cookie

    类型为小型文本文件,网站为了需要辨别用户身份而储存在用户本地的终端上的数据。为了解决HTTP无状态导致的问题

    一般不超过4kb,由一个名称name,一个值value和其他几个用于控制cookie有效期、安全性、使用范围的可选属性组成

    image-20250721104537704

    cookie的使用

    document.cookie = '名字=值'
    

    修改,需要确定dimain和path都是相同的才可以,如果有一个不同都会创建一个新的Cookie

    Set-Cookie:name = aa; domain=aa.net; path=/ # 服务端设置
    document.cookie = name = bb; domian = aa.net; path =/ #客户端设置
    
  • localStorage

    image-20250721105804316

    使用

    // 设置
    localStorage.setItem("username","xxx")
    
    // 获取
    localStorage.getItem("username")
    
    // 获取键名
    localStorage.key(0)
    
    // 删除
    localStorage.removeItem('username')
    
    // 一次性删除所有的存储
    loaclStorage.clear()
    

    它不能像Cookie一样设置过期时间

    只能存字符串,无法直接存对象

  • sesstionStorage

    sesstionStorage的使用方法基本和ocalStorage一致,唯一不用的是生命周期,一旦页面(会话)关闭,sesstionStorage就会删除数据

  • indexedDB

# js的数组和字符串

# js字符串的常用方法有哪些?

不是直接增,而是创建副本再操作

concat

slice

substr()

substrings()

这里改的意思也不是改变原字符串,而是创建字符串的一个副本然后再进行操作

常见

  • trim()、trimleft()、trimRight()
  • repeat()
  • padStart()、padEnd()
  • toLowerCase()、toUpperCase()

  • chatAt()
  • indexOf()
  • startWith
  • includes()

# 数组的常用方法有哪些?

需要留意有哪些方法会对原数组造成影响

  • push()
  • unshift()
  • splice(1,0,"yes") 开始的位置,要删除的元素的数量,插入元素
  • concat() 不会影响原数组

  • pop
  • shift()
  • splice()
  • slice() 不会影响数组

常用splice()

  • indexof()
  • includes()
  • find()

# 函数相关的

# 什么是函数式编程?

函数式编程(通常缩写为FP)是通过编写纯函数,避免共享状态、可变数据、副作用 来构建软件的过程

函数式编程是一种编程范式 ,这意味着它是一种基于一些基本的定义原则(如上所列)思考软件构建的方式

# 什么是高阶函数?

高阶函数只是将函数作为参数或返回值的函数。

function higherOrderFunction(param,callback){
    return callback(param);
}

# 什么是箭头函数?

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的thisargumentssupernew.target

const getCurrentDate = () => new Date();

# 开(spread )运算符和 剩余(Rest) 运算符有什么区别?

展开运算符(spread)是三个点(...),可以将一个数组转为用逗号分隔的参数序列

剩余运算符也是用三个点(...),一般作用到解构数组和对象

# 什么是默认参数?

默认参数是在 JS 中定义默认变量的一种新方法

//ES6 Version
function add(a = 0, b = 0){
  return a + b;
}
add(1); // returns 1 

#