js
JS由哪三部分组成
ECMAScript、BOM、DOM
内置对象
String、Number、Boolean、Array、Date、RegExp、Object、Math、JSON、Error
数组的常用方法
forEach、map、filter、reduce、reduceRight、some、every、indexOf、lastIndexOf、concat、slice、splice、join、sort、reverse、push、pop、shift、unshift
数据类型检测
- typeof
- instanceof
- Object.prototype.toString.call
闭包
在函数内部,可以访问函数外的变量
- 重复利用变量
- 不会全局污染
- 存在内存当中,提升性能
function fn(x) {
return function () {
console.log(' =====', x)
}
}
const y = fn('abcd')内存泄露
已经分配地址的对象,长时间占用或者无法清除导致
事件委托
利用事件冒泡实现,把子元素的事件委托给父元素,减少事件监听,提升性能
阻止事件冒泡,event.stopPropagation()
基本、引用数据类型(存放堆栈的不同)
- Number、String、Boolean、Null、undefined、Symbol
- Object、Array、Function
原型链
构造函数的实例共享属性和方法
class Person {
sayName = function () {
console.log('this is function sayName')
}
}
const p = new Person()
console.log(' =====', p.__proto__ === Person.prototype)__proto__可以理解成一个指针(实例对象当中的一个属性),指向原型对象(构造函数的原型prototype)
一个实例对象在调用属性/方法的时候,执行过程:实例本身--> 构造函数的原型 --> 原型的原型
ES6原型链加了什么东西
- ES6核心是给原型链加了
class/extends/super语法糖,简化继承 - 新增标准化原型操作方法,替代
__proto__Object.getPrototypeOf()和Object.setPrototypeOf()
static关键字区分静态方法与原型方法,优化原型链使用逻辑
__proto__和prototype的区别
prototype:仅函数(含类)独有,是一个对象,用于存放所有实例共享的方法 / 属性,是原型链的 “模板”__proto__:所有对象(含函数实例)都有,指向该对象的原型(即其构造函数的 prototype),是实例连接原型链的 “桥梁”
new做了什么
- 创建空对象
- 空对象的构造函数通过原型链进行连接
- 把构造函数的this绑定到空对象
- 根据构造函数返回类型判断
- 值类型 --> 返回对象
- 引用类型 --> 返回该引用类型
function newFun(Fun, ...args) {
let newObj = {}
newObj.__proto__ = Fun.prototype
const result = Fun.apply(newObj, args)
return result instanceof Object ? result : newObj
}
function Person(name) {
this.name = name
}
Person.prototype.sayName = function () {
console.log(this.name)
}
const p = newFun(Person, 'abc')
p.sayName()
console.log(' =====', p)继承
- 原型链
- 构造函数
- 组合式
- class
js设计原理
- js引擎(V8引擎)
- 运行上下文
- 调用栈
- 事件循环
- 回调
this指向
- 全局this、普通函数、匿名函数都指向window
- apply、call、bind改变了的this指向
- 箭头函数看外层是否改变了this,否则指向window
- 非箭头函数,this指向最后调用它的对象
- new改变this的指向
script标签 async defer区别
未指定直接运行js
- async 加载DOM树的时候,同时执行js
- defer 加载DOM树的时候,等DOM树加载完毕后,再执行js
setTimeout、setInterval最小执行时间
- 4ms
- 10ms
ES新特性
- 块级作用域
- class
- set/map
- 解构
- async/await
- 数组的新API
- 扩展运算符
- 模块化
- Promise
- 箭头函数
- 函数参数默认值
apply、call、bind区别
都是用来改变this指向和函数调用
- call 传一个参数列表,call方法和apply方法调用一致,只是传参不同,其中call的性能更好,
- apply 传一个数组
- bind 传参不会立即执行,会返回一个改变this指向的函数,这个函数还可以继续传参
bind()()
递归当中的问题
在递归当中,需要指定对应的终止条件(return)避免栈溢出
深拷贝
- JSON.parse(JSON.stringify(obj))
- 扩展运算符
- 递归+遍历
事件循环
浏览器是单线程,为了不阻塞,用任务队列来调度代码
- 微任务(microtask)
Promise.then/catch/finallyasync/await后面queueMicrotask
- 宏任务(macrotask)
setTimeout/setInterval- 事件点击、AJAX、I/O
requestAnimationFrame、script 标签
执行顺序
- 先执行所有同步代码
- 执行当前所有微任务(清空微任务队列)
- 然后取一个宏任务执行
- 再清空微任务
- 循环往复
Ajax
- 创建XmlHttpRequest对象
- xhr.open()建立连接
- xhr.send()发送请求
- 接收响应
- 渲染
get、post区别
- 安全性
- 获取/提交
- 缓存方式
- 请求退回,get无影响,post会重新提交
- 浏览记录
- get参数只能url编码、post可以传文件等其他格式
Promise内部原理
Promise是用来解决回调地狱问题的
pendding初始化fulfilled成功rejected失败
其中当使用了Promise之后,是无法取消的,如果没有给Promise设置回调,那么其内部的错误无法反馈到外部
Promise、async、await区别
都是用于处理异步请求,分别在ES6和ES7被加入,其中async、await也是基于Promise进行实现的
- Promise通过then、catch捕获异常、并且Promise是链式调用、不方便维护
- async和await是通过try、catch捕获异常,让异步代码看起来和同步一样,易于维护
Promise 的方法
reject resole catch all
- allSettled在其中一个promise返回错误时还可以继续等待结果
- race 接收一个数组 得到最快返回的 这是一个新的promise
- any 只要有一个reject就会得到一个失败的promise,但是会等全部promise执行完
手写Promise
实现resolve和reject
关键知识点
- 闭包的应用:
resolve和reject函数形成了闭包,可以访问构造函数内部的变量。 - 立即执行函数模式:Promise 的执行器函数会在创建时立即执行,这是 Promise 与其他异步 API 的重要区别。
- 异常捕获:通过
try-catch块捕获执行器中的同步错误,并自动转为 rejected 状态。 - 状态不可逆:通过检查
this.#state === PENDING确保状态只能改变一次。
相关资源
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
// 私用变量(es6的symbol)
#state = PENDING;
#result = undefined;
constructor(executor) {
const resolve = (data) => {
this.#changeState(FULFILLED, data);
};
const reject = (error) => {
this.#changeState(REJECTED, error);
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
#changeState(state, result) {
if (this.#state === PENDING) {
this.#state = state;
this.#result = result;
console.log(this.#state, '----', this.#result);
}
}
}
const p = new MyPromise((resolve, reject) => {
resolve(1);
});实现then方法
- then方法:链式调用的关键(注册回调函数,返回新的 Promise)
- 接收两个参数:
onFulfilled(成功回调)、onRejected(失败回调) - 返回新的 Promise 实例(支持链式调用)
- 将回调函数和新的 resolve/reject 打包推入队列
- 尝试执行
#run()立即处理
- 接收两个参数:
- run方法:回调队列调度器(根据当前状态执行队列中的回调函数)
- PENDING状态:直接返回,等待状态改变
- 状态已改变:循环处理队列中的所有回调
- 状态分支:FULFILLED 执行成功回调,REJECTED 执行失败回调
- 出队处理:使用
shift()从队列头部取出
- handleCallback方法:回调执行器(处理回调函数的执行和结果传递)
- 非函数回调(值穿透)
- 直接将结果传递给下一个 Promise
- 使用
queueMicrotask异步执行 - 保持 Promise 链的正常传递
- 函数回调
- 微任务调度(异步执行)
- 执行回调函数获取返回值
- 用返回值 resolve 下一个 Promise
- try-catch 捕获异常并 reject
- 非函数回调(值穿透)
实现Promise的then方法
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
// 私用变量(es6的symbol)
#state = PENDING;
#result = undefined;
#thenQueue = [];
constructor(executor) {
const resolve = (data) => {
this.#changeState(FULFILLED, data);
};
const reject = (error) => {
this.#changeState(REJECTED, error);
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
#changeState(state, result) {
if (this.#state === PENDING) {
this.#state = state;
this.#result = result;
console.log(this.#state, '----', this.#result);
this.#run();
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#thenQueue.push({onFulfilled, onRejected, resolve, reject});
this.#run();
});
}
#run() {
if (this.#state === PENDING) return;
while (this.#thenQueue.length) {
const {onFulfilled, onRejected, resolve, reject} = this.#thenQueue.shift();
if (this.#state === FULFILLED) {
this.#handleCallback(onFulfilled, resolve, reject);
} else {
this.#handleCallback(onRejected, resolve, reject);
}
}
}
#handleCallback(callback, resolve, reject) {
if (typeof callback !== 'function') {
queueMicrotask(() => {
const settled = this.#state === FULFILLED ? resolve : reject;
settled(this.#result);
});
return;
}
queueMicrotask(() => {
try {
const data = callback(this.#result);
resolve(data);
} catch (e) {
reject(e);
}
});
}
}
const p = new MyPromise((resolve, reject) => {
resolve(1);
});
p.then(res => {
console.log(res);
return 2;
}).then(res => {
console.log(res);
return 3;
}).then(res => {
console.log(res);
return 4;
});多次点击按钮发送请求,只允许发送最后一次请求(请求取消)
- Axios:内置了
CancelToken - Fetch API:依赖浏览器原生的
AbortController - 按钮禁用
游览器的存储方式
- cookie
- 兼容好,请求头会自动携带,
- 存储小,使用麻烦
- localStorage
- 操作方便,永久存储,兼容好
- 值的类型被限定,并且在隐私模式下不可读取,不能进行爬虫
- sessionStorage
- 会话级的,页面关闭后失效
- indexedDB
- 键值存储,可快速读取
token存localStorage、cookie的区别
- localStorage 后续请求发送都需要手动添加token
- cookie 会自动发送token,不能跨域
token登录流程
- 请求登录
- 服务端验证,下发token
- 前端存储token
- 后续请求携带token
- 服务端验证token
- 响应
页面渲染流程
- DNS解析
- 建立TCP连接
- 发送HTTP请求
- 服务器响应
- 页面渲染
- 获取HTML、CSS
- 把HTML解析成DOM树、把CSS解析成样式表CSSOM树
- DOM+CSSOM树生成渲染树
- 布局 把渲染树进行渲染
- 断开TCP连接
DOM树和渲染树的区别
- DOM树和HTML标签一一对应,包含了head和隐藏元素
- 渲染树不包含head和隐藏元素
精灵图与Base64
- 精灵图:把图片整合到一张图片中,减少HTTP请求次数,提升性能
- Base64:将图片转换成字符串,然后通过url的形式进行展示
svg (Xml语法格式的图像)
- 可直接插入页面,是DOM树的一部分
- 可作为文件进行引入
<img src="xxx.svg" /> - 可转成base64引入
JWT
json web token
- 获取后端响应的JWT(token)携带了用户信息
- HTTP请求头携带Authorization
- 后端解析token校验
- 解析出用户信息做后续逻辑操作
npm底层环境原理
node project manager
- 网站
npmjs.com - 注册表 用来管理所有的包
- 命令行工具
HTTP请求头与响应头的区别
请求头
- Accept 支持的类型
- Host 主机地址
- User-Agent 浏览器信息
- Referer 来源
- Cookie 认证信息
- Date 时间
- connection 连接方式
- x-request-with 与服务器通信的协议
响应头
- Content-Type 内容类型
- Server 服务器信息
- Location 服务器地址
- Refresh 重定向
缓存类型
- 强缓存(本地缓存)不发送请求,直接从缓存中读取
- 弱缓存(协商缓存)发送请求判断是否使用缓存,服务器返回304,从缓存中读取
同源策略
协议+域名+端口(一致表示同源)
其中:img、link、script标签是允许跨域的
解决跨域的方案
- JSONP
- CORS 后端允许跨域
- nginx反向代理
- webSocket
防抖、节流
- 防抖:在 n 时间以后触发函数,若时间未到再次被触发,则会重置执行函数的倒计时。(多用于搜索框优化)
- 节流:在 n 时间内无论触发多少次函数,只会执行第一次触发。从而稀释出发频率。 (多用于按钮,以及页面滚动)
后端接口无数据响应
- 填充默认数据
- if判断数据是否为空
无感登录(无感刷新token)
在token过期的时候,不需要用户操作,后台自动刷新token
- 在响应器当中拦截,判断token返回过期之后,重新调用获取token
- 后端返回过期时间,前端根据超时时间判断是否过期,何时发送请求获取token
- 前端定时请求获取token
大文件上传
- 分片上传
- 断点续传
- 服务端返回,从哪里重新开始上传,在浏览器端判断
函数中定义变量,返回一个立即执行函数,外部怎么修改这个变量
可以通过返回一个对象,该对象包含一个用于修改变量的方法
function myFunction() {
let myVar = 0;
return {
getVar: function () {
return myVar;
},
setVar: function (value) {
myVar = value;
}
};
}
const obj = myFunction();
obj.setVar(10);实现const
- object.freeze({})
- Object.defineProperty
Object.defineProperty(obj, 'value', {
value: value,
writable: false,
enumerable: true,
configurable: false
});实现instanceof
function myInstanceOf(obj, constructor) {
let prototype = Object.getPrototypeOf(obj);
while (prototype !== null) {
if (prototype === constructor.prototype) {
return true;
}
prototype = Object.getPrototypeOf(prototype);
}
return false
}requestAnimationFrame和requestIdleCallback在渲染优化中的执行时机差异
| 特性 | requestAnimationFrame | requestIdleCallback |
|---|---|---|
| 执行时机 | 每帧重绘前(paint 前) | 每帧结束后,空闲时 |
| 优先级 | ⭐⭐⭐⭐⭐(最高) | ⭐⭐(最低) |
| 执行频率 | ~60 次/秒(60fps) | 不固定,可能不执行 |
| 是否阻塞渲染 | 是(如果耗时过长) | 否(自动切片) |
| 适用场景 | 动画、关键更新 | 日志、预加载、非关键任务 |
| 是否与屏幕同步 | ✅ 是 | ❌ 否 |
| 超时机制 | ❌ 无 | ✅ 有 timeout 选项 |
parseInt输出
["1", "2", "3"].map(parseInt)等价于:["1", "2", "3"].map((item, index) => parseInt(item, index))
- parseInt(item, index)
- item 数组当前项
- index 数组当前项的索引(进制)
故输出结果为:[1, NaN, NaN]
parseInt('1', 0)= 1 (默认进制为 10)parseInt('2', 1)= NaN (不存在1进制)parseInt('3', 2)= NaN (2 进制没有 3 这个数字)
