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定时函数


微任务
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
Promise.thenMutaionObserverObject.observe(已废弃;Proxy对象替代)process.nextTick
宏任务
这个宏任务的时间颗粒度比较大,这样的话执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务:
scriptsetTimeout/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都会阻塞后面的代码

输出结果:1、fn2、3、2
下面上例子

script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout
# 事件
# js的事件模型
事件:浏览器中发生的一种交互操作
事件流:

事件模型
原始事件模型(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 判断实际触发的子元素。
应该也可以叫事件委托吧
例子:

应用场景:
列表项点击(如菜单、评论列表)
表格操作按钮统一绑定事件
动态生成的元素(如无限滚动加载)


还有就是动态添加这种的
适合事件委托的事件有:click、mousedown、mouseup、keydown、keyup、keypre
优点:
减少事件绑定数量,减少内存提升性能
支持动态添加的子元素也能响应事件,减少重复工作
不能所有事件都用事件代理,有些事件是不支持的
# 什么是事件传播?
事件在 DOM 树中传递的过程。这个过程主要分为三个阶段:
捕获阶段(Capture Phase): 事件从
document顶部节点开始,向下捕获到目标元素目标阶段(Target Phase): 事件到达我们点击的目标元素
冒泡阶段(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.all 或 Promise.allSettled)
async 函数里 return 普通值会怎样?(自动包装成 Promise.resolve(value))
# 原型相关的
# js原型,原型链?有什么特点?
就是要明白这几个东西代表什么意思
prototype,是函数才有的属性(包括构造函数),每个函数都有一个prototype属性,指向一个对象,这个对象就是实例的原型(__proto__指向的对象),用来实现共享方法和属性_proto_或[[Prototype]],是对象才有的属性,每个对象都有一个__proto__,它指向创建该对象的构造函数的prototypeconstructor,是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() 时,查找过程:
- 在 person 自身找 sayName → 找到了,直接调用
- 如果找不到:
- 会通过
person.__proto__去Persion.prototype找
- 会通过
- 如果再没有,会继续往上:
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 函数就是一个闭包,因为它能访问并修改外部函数 outer 的 count 变量,即使 outer 函数已经执行完毕并返回了。
这里如何理解闭包呢?
外部函数执行完成后,它的执行环境就被销毁了,通常其内部的局部变量会被清除。但因为闭包的存在,inner 函数依然持有对 outer 中变量 count 的引用,这个引用让 count 的值保持不变,因此每次调用 counter() 时,它都会打印一个递增的值。
任何闭包的使用场景都离不开这两点:
- 创建私有变量
- 延长变量的生命周期

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

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

闭包的应用场景
闭包可以用来创建私有变量,实现数据封装
闭包可以用来生成动态创建函数的工厂
防抖和节流
# 细说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'

下面来手写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 用来创建对象实例的关键字,它在调用构造函数时,会执行以下四个步骤:
- 创建一个空对象;
- 将这个对象的原型指向构造函数的
prototype;- 将构造函数的
this绑定到这个新对象上并执行构造函数;- 如果构造函数返回的是对象类型,就返回这个对象,否则返回步骤1创建的新对象。
# 作用域
# 说说你对作用域链的理解
作用域就是js中能够有效访问变量或者函数的区域
js有三种作用域:
- 全局作用域:在全局命名空间中声明的变量或者函数位于全局作用域中,在代码中的任何的地方都可以访问他们,使用var
- 函数作用域:就是命名在函数里面的,在函数中可以访问,在函数外不能访问
- 块作用域{}:在块
{}中声明的变量(let,const)只能在其中访问。
答:
作用域链是 JavaScript 查找变量的一种机制。
当访问一个变量时,JS 引擎会从当前作用域开始查找,如果找不到,就向上层作用域查找,直到全局作用域为止。如果仍然找不到,就报
ReferenceError
# js执行上下文和执行栈是什么?
执行上下文就是js代码执行环境嘛
还有生命周期
创建阶段,函数被调用但未执行内部代码之前
- 确定this
- 词法环境
- 变量环境
执行阶段,执行变量赋值(找不到值就分配undifned),代码执行
回收阶段,执行上下文出栈等待虚拟机回收执行上下文
执行栈(调用栈)
具有LIFO结构,用于存储在代码执行期间创建的所有执行上下文
JS 引擎就是通过执行栈按顺序执行函数调用的。
运行流程:
- 脚本开始时,创建全局上下文,压入栈顶;
- 调用函数时,创建对应的函数上下文,压入栈顶;
- 函数执行完后,该上下文从栈中弹出;
- 最终栈清空,程序执行结束。
回答:
执行上下文是 JavaScript 代码在执行时的运行环境,决定了变量、函数的访问权限。
每当一段代码(函数、全局、eval)被运行,都会创建一个对应的执行上下文。
执行上下文的类型:
- 全局执行上下文:整个 JS 脚本的最外层环境,只创建一次;
- 函数执行上下文:每次函数调用都会创建新的上下文;
- 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;
}

# 说说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有效期、安全性、使用范围的可选属性组成

cookie的使用
document.cookie = '名字=值'修改,需要确定dimain和path都是相同的才可以,如果有一个不同都会创建一个新的Cookie
Set-Cookie:name = aa; domain=aa.net; path=/ # 服务端设置 document.cookie = name = bb; domian = aa.net; path =/ #客户端设置localStorage

使用
// 设置 localStorage.setItem("username","xxx") // 获取 localStorage.getItem("username") // 获取键名 localStorage.key(0) // 删除 localStorage.removeItem('username') // 一次性删除所有的存储 loaclStorage.clear()它不能像Cookie一样设置过期时间
只能存字符串,无法直接存对象
sesstionStoragesesstionStorage的使用方法基本和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);
}
# 什么是箭头函数?
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target
const getCurrentDate = () => new Date();
# 开(spread )运算符和 剩余(Rest) 运算符有什么区别?
展开运算符(spread)是三个点(...),可以将一个数组转为用逗号分隔的参数序列
剩余运算符也是用三个点(...),一般作用到解构数组和对象
# 什么是默认参数?
默认参数是在 JS 中定义默认变量的一种新方法
//ES6 Version
function add(a = 0, b = 0){
return a + b;
}
add(1); // returns 1