值类型(基本数据类型)
underfinedstringnumberbooleanSymbolnullbigint


引用类型(复杂数据类型)
objarrayfnDateRegExpMapSetWeakMapWeakSetSymbol
const obj = { a: 1 };
const arr = [1, 2, 3];
const a = null; // 特殊的引用类型, 指针指向空地址
function fn() {} // 特殊的引用类型,但不能用户存储数据, 所以没有 ‘神拷贝,复制函数’


typeof
typeof 可以判断值类型, 也可以判断引用类型(都是 object, 不能细分)


深拷贝
function deepClone(obj) {
// 先判断
if (typeof obj !== "object" || obj == null) {
return obj;
}
// 初始化数据
let result;
if (obj instanceof Array) {
result = [];
} else {
result = {};
}
// 进行拷贝
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
null 和 undefined 区别
- 在 JavaScript 中,null 和 undefined 都表示没有具体的值,但它们在使用上有所不同。null 是一个可以被赋予变量的值,表示变量故意被设置为空,而 undefined 表示变量已声明但尚未赋值。下面是它们之间的一些关键区别:
== 运算符
除了
==null之外,其他都一律===
const obj = { a: 1 };
if (obj.a == null) {}
相当于:obj.a === null || obj.a === underfined
if 语句 和 逻辑运算


class
class Student {
constructor(name, number) {
this.name = name;
this.number = number;
}
sayHi() {
console.info(`姓名:${this.name}, 学号:${this.number}`);
}
}
const zhooson = new Student("zhooson", 100);
console.info(zhooson.name);
console.info(zhooson.number);
console.info(zhooson.sayHi());
继承 extends
// 父类
class People {
constructor(name) {
this.name = name;
}
eat() {
console.info(`姓名为${this.name}吃东西了`);
}
}
// 子类
class Student extends People {
constructor(name, number) {
super(name);
this.number = number;
}
sayHi() {
console.info(`姓名:${this.name}, 学号:${this.number}`);
}
}
const zhooson = new Student("zhooson", 100);
console.info(zhooson.name);
console.info(zhooson.number);
console.info(zhooson.sayHi());
console.info(zhooson.eat());
类型判断 instanceof

显示原型/隐式原型
class Student {
constructor(name, number) {
this.name = name;
this.number = number;
}
sayHi() {
console.info(`姓名:${this.name}, 学号:${this.number}`);
}
}
const zhooson = new Student("zhooson", 100);
// 其中:zhooson.__proto__ === Student.prototype;
// 结论: 1. 每个class 都有 显示 原型
// 2. 每个实例 都有 隐式 原型
// 3. 实例的 __proto__ 指向对应 class 的 prototype
// 执行规则: 1. 现在自身规则寻找 2. 如果找不到去 隐式原型 寻找
原型链
。。。
手写一个 jQuery 插件
作用域
某个变量的合法的使用范围
- 全局作用域 window document 等
- 函数作用域 函数内部的变量
- 块级作用域 if for while 等有个花括号的 作用域 (const let 定义)
自由变量
- 一个变量在当前作用域没有定义,但是使用
- 向上级作用域一层一层找,直到找到为止
- 如果在全局域没有找到,则报错 xx is not underfined
闭包
函数可以访问其他函数内部变量
function create() {
const a = 100;
return function () {
console.info(a);
};
}
const fn = create();
const a = 200;
fn();
// 函数作为参数被传递
function print(fn) {
const a = 200;
fn();
}
const a = 100;
function fn() {
console.log(a);
}
print(fn); // 100
// 闭包: 自由变量的查找是在函数定义的地方,向上级查找, 不是在执行的地方
// 场景: 1. 隐藏数据 只提供 API
function createCache() {
const data = {};
return {
set: function (key, val) {
data[key] = val;
},
get: function (key) {
return data[key];
},
};
}
const c = createCache();
c.set("a", 100);
// c.get('a');
function f1() {
var n = 999;
nAdd = function () {
n += 1;
};
function f2() {
alert(n);
}
return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000
解释:在这段代码中,result 实际上就是闭包 f2 函数。它一共运行了两次,第一次的值是 999,第二次的值是 1000。这证明了,函数 f1 中的局部变量 n 一直保存在内存中,并没有在 f1 调用后被自动清除。 为什么会这样呢?原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。 这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在 nAdd 前面没有使用 var 关键字,因此 nAdd 是一个全局变量,而不是局部变量。其次,nAdd 的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以 nAdd 相当于是一个 setter,可以在函数外部对函数内部的局部变量进行操作。
this
this 取什么值, 是在函数执行的时候决定

异步
- 异步不会堵塞代码
- 同步会堵塞代码
场景: 1. ajax 调用 2. 定时器
单线程
- js 是单线程语言, 同时只能做一件事
- 浏览器和 nodejs 已经支持 js 启动进程,如 web work
- Js 和 Dom 渲染公用一个进程,因为 js 可以修改 dom 接口(做 dom 渲染则停止 js)
Promise
- 三种状态 pendding resolved rejected
- 变化不可逆 一次性的结果
节流/防抖
- 防抖
防抖的原理是在事件被触发后,在指定的时间内如果事件没有再次被触发,则执行一次函数;如果在这段时间内事件再次被触发,则重新计时。简单来说,就是将多次触发的事件合并为一次执行,常用于需要等待用户操作完全停止后再进行处理的场景,如输入框搜索、窗口大小调整等
理论:俗话说, 防止抖动, 你先抖动着,啥时候停止,在执行下一步 场景: input 输入框搜索 API,等输入停止后,在触发搜索 限制执行次数,多次密集的触发只执行一次
function debounce(fn, delay) {
let timer = 0;
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments); // 透传 this
timer = 0;
}, delay);
};
}
- 节流 节流的原理是在规定的时间间隔内,无论事件触发了多少次,都只执行一次函数。也就是说,它会确保函数以一定的频率执行,而不是无限制地响应事件触发。节流常用于需要频繁但有规律地执行某些操作的场景,如滚动监听、鼠标移动等。
理论: 节省交互沟通,流,你一定指流量, 俗话:别急,一个一个来,按照排队时间来,插队者无效 场景:drag/scroll 期间触发某个回调,要设置一个时间间隔 限制执行频率,有节奏的执行
function throttle(fn, delay) {
let timer = 0;
return function () {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, arguments); // 透传 this
timer = 0;
}, delay);
};
}
总结: 节流关注“过程”,防抖关注“结果”
箭头函数
缺点:
- 没有 arguments
- 无法通过 apply bind call 改变 this
- 某些箭头函数代码难以阅读
- 哪些场景不适用
- 对象方法
const obj = {
name: "zhooson",
getName() {
// getName: () => { // this 的指向则发生变化,并不是 obj了
console.info(4, this.name);
},
};
- 原型的方法
const obj = {
name: "双越",
};
obj.__proto__.getName = () => {
return this.name;
};
console.info(obj.getName());
- 构造函数
// const Foo = (name, city) => {
// this.name = name;
// this.city = city;
// };
// const f = new Foo('xxt', '北京!'); // 报错的
- call 方法
// const fn = () => {
// console.info(this); // window
// };
function fn() {
console.info(this); // { x: 1 }
}
fn.call({ x: 1 });
严格模式 use strict
- 全局变量必须实现声明
- 禁止使用 with
- 创建 eval 作用域
- 禁止 this 指向 window
- 函数参数不能重名
跨域
- 浏览器同源策略
- 同源厕率一般限制 ajax 网络请求, 不能跨域请求 server
- 不会限制 link img srcipt iframe 加载第三方资源

js 内存泄漏如何检测?场景有哪些?
- 闭包的变量/函数都是常驻内存中
检测方式: 浏览器 perfomance memory heap 属性,增长趋势, addEventListsner
方法: 标记清除
- 多次创建 addEventListsner, 没有 remove 掉
- 全局变量,定时器引用,组件销毁时候没有清楚
WeakMap WeakSet 弱引用
检测方法:
浏览器 和 nodejs 事件循环机制是什么? event loop
- js 是单线程,防止代码堵塞, 我们吧代码分为 同步和异步
- 同步代码给 js 引擎执行, 异步代码交给宿主环境
- 同步代码放入执行栈中, 异步代码等待时机成熟送入任务队列中
- 执行栈完毕后,去任务队列看是否有异步任务,有就送到执行栈执行,返回循环查看执行, 这个过程叫 事件循环
宏任务
宏任务 setTimeout setInterval ajax
微任务 promise await async
微任务在下一轮 QOM 渲染之前执行,宏任务在之后执行
微任务 比 宏任务快
vdom 真的很快吗?
- virtual dom 即 vdom
- 用 js 对象 模拟 dom 节点数据
- 有 react 最先提出
vue react 框架的价值
- 组件化
- 数据视图分离,数据驱动试图 - 这是核心
- vdom 并不快, js 直接操作 dom 是最快的
js bridge
- js 无法直接调用 native api
- 需要通过特定的格式来调用
移动 h5 click 有 300ms 延迟, 怎么解决?
- 以前的方案,faskclick 的方案
- 现在方案:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
token 和 cookie 有什么区别?
- cookie: HTTP 标准;跨域限制;配合 session 使用; (最大 4k), cookie 默认被浏览器存储
- token:无标准;无跨域限制;用于 JWT; token 需要自己手动存储
jwt 弊端
- 用户信息存储在客户端,无法快速封禁某用户
- 万一服务端秘钥被泄漏,则用户信息全部丢失
jwt session 那个更好?
- 如有严格管理用户信息的需求(保密、快速封禁)推荐 Session
- 如没有特殊要求,则使用 JWT(如创业初期的网站)
- 聊天类 session 可以快速封禁用户,jwt 需要等 token 失效才可以
- 万一 jwt 的 token 的密钥丢失就很轻松获取用户信息
如何实现单点登录(SSO)
基于 cookie cookie 默认是不共享的, 但是有些情况是可以设置共享的 如
a.baidu.com和b.baidu.com主域名是一样的就可以共享SSO(第三方后端方法)
script defer 和 async 的区别?
- defer HTML 继续解析,并行下载 JS,HTML 解析完再执行 JS
- async HTML 继续解析,并行下载 JS,执行 JS,再解析 HTML
websocket 和 http 有什么区别?
- WebSocket 协议名是 ws://,可双端发起请求
- WebSocket 没有跨域限制
网页 和 iframe 的通讯?
- 使用 postMessage 通讯
- 同源采用 cookie ,不同源采用 session
- websocket
首屏 优化?
- 路由懒加载
- ssr 技术
- APP 预获取
- 图片懒加载
- 分页
如果一个 H5 很慢, 如果排查性能问题?
Map 和 Set 的区别
- Map 是有序结构, key 是任意类型, Object 无序结构,key 俩种类型(字符串或者 symbol 类型)
- Set 可以去重
- Map 和 Set 比 Object/Array 执行要快
- new Map().set('1', 1) new Map().get('1')和 new Set().add(1)
为什么要禁用第三方浏览器 cookie

- 为了用户的隐私安全
- 第三方 cookie 会记录用户的行为和数据,方便做广告
- 有些浏览器默认禁止,chrome 增加 samesite(谷歌有广告)
setTimeout 和 setInterval 的区别?
区别 1: setInterval 不会等待你的函数执行完毕就开始计时下一个间隔, 而 setTimeout 会等待你的函数执行完毕后再开始计时。
例子:如果你设定了一个 setInterval,每隔 1 秒执行一次函数,但是这个函数需要 2 秒才能执行完毕,那么 setInterval 在函数还在执行的时候就开始计时下一个间隔,所以实际上函数会每隔 1 秒执行一次,而不是每隔 2 秒。 而 setTimeout 则不同,它会等待函数执行完毕后再开始计时,所以如果你设定了一个 setTimeout,每隔 1 秒执行一次函数,但是这个函数需要 2 秒才能执行完毕,那么实际上函数会每隔 3 秒执行一次,因为 setTimeout 会等待 2 秒的函数执行时间加上 1 秒的延迟时间。
区别 2: 错误处理:如果 setTimeout 或 setInterval 的回调函数中有错误,它们的处理方式是不同的。 setTimeout 在回调函数出错时,不会再次尝试执行该函数。 但是 setInterval 不管回调函数是否出错,都会继续按照设定的间隔时间执行。
区别 3: 返回值:setTimeout 和 setInterval 都会返回一个定时器 ID,可以用来在稍后取消定时器。 但是,这个 ID 在不同的环境中可能会有不同的表现。在浏览器中,它是一个数字;而在 Node.js 中,它是一个对象。
扩展: 如果页面卡住了, 后面过段时间,页面好了, setTimeout 和 setInterval 还能使用的吗? setTimeout 还可以用, 因为每隔一段时间, 向异步队列里面丢一个 setTimeout 事件
Promise.all 如果所有 Promise 都成功,返回的 Promise 会 resolve 一个包含所有结果的数组。如果其中任何一个 Promise 失败,返回的 Promise 会 reject 失败的原因。
const promise1 = fetch("https://api.example.com/data1");
const promise2 = fetch("https://api.example.com/data2");
Promise.all([promise1, promise2])
.then(([result1, result2]) => {
console.log(result1, result2);
})
.catch((error) => {
console.error(error);
});
追问:那如果一定要 then 怎么处理?
// 封装一层 promise
const promise1 = Promise.resolve("Success 1");
const promise2 = Promise.reject("Error 2");
const promise3 = Promise.resolve("Success 3");
const handlePromise = (promise) => {
return promise.then(
(result) => ({ status: "fulfilled", value: result }),
(error) => ({ status: "rejected", reason: error }),
);
};
Promise.all([
handlePromise(promise1),
handlePromise(promise2),
handlePromise(promise3),
]).then((results) => {
console.log(results);
// 处理结果
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log("Success:", result.value);
} else {
console.error("Error:", result.reason);
}
});
});
// 使用 promise.allSettled
const promise1 = new Promise((resolve, reject) =>
setTimeout(resolve, 100, "成功"),
);
const promise2 = new Promise((resolve, reject) =>
setTimeout(reject, 200, "失败"),
);
const promise3 = new Promise((resolve, reject) =>
setTimeout(resolve, 300, "成功"),
);
Promise.allSettled([promise1, promise2, promise3]).then((results) => {
console.info(333, results);
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`Promise ${index + 1} 成功:`, result.value);
} else {
console.log(`Promise ${index + 1} 失败:`, result.reason);
}
});
});
call apply bind 详解
js 正则
好的,下面是 JavaScript 正则表达式中常用符号的专业名词、解释及其用法:
基本符号
.(点号)- 名称:通配符
- 解释:匹配除换行符以外的任何单个字符。
- 用法:
a.b可以匹配aab、acb等。
^(脱字符)- 名称:行首匹配
- 解释:匹配输入的开始位置。
- 用法:
^abc匹配以abc开头的字符串。
$(美元符号)- 名称:行尾匹配
- 解释:匹配输入的结束位置。
- 用法:
abc$匹配以abc结尾的字符串。
*(星号)- 名称:零次或多次匹配
- 解释:匹配前一个表达式 0 次或多次。
- 用法:
bo*可以匹配b、bo、boo等。
+(加号)- 名称:一次或多次匹配
- 解释:匹配前一个表达式 1 次或多次。
- 用法:
a+可以匹配a、aa、aaa等。
?(问号)- 名称:零次或一次匹配
- 解释:匹配前一个表达式 0 次或 1 次。
- 用法:
a?可以匹配a或空字符串。
字符类
[abc]- 名称:字符类
- 解释:匹配方括号内的任意一个字符。
- 用法:
[abc]可以匹配a、b或c。
[^abc]- 名称:否定字符类
- 解释:匹配不在方括号内的任意字符。
- 用法:
[^abc]可以匹配d、e等。
[0-9]- 名称:数字字符类
- 解释:匹配任何数字。
- 用法:
[0-9]可以匹配0到9之间的任意数字。
元字符
\d- 名称:数字字符
- 解释:匹配任何数字字符,等价于
[0-9]。 - 用法:
\d可以匹配0到9。
\D- 名称:非数字字符
- 解释:匹配任何非数字字符,等价于
[^0-9]。 - 用法:
\D可以匹配字母、符号等非数字字符。
\w- 名称:单词字符
- 解释:匹配任何字母、数字或下划线字符,等价于
[A-Za-z0-9_]。 - 用法:
\w可以匹配a到z、A到Z、0到9以及_。
\W- 名称:非单词字符
- 解释:匹配任何非字母、数字或下划线字符,等价于
[^A-Za-z0-9_]。 - 用法:
\W可以匹配空格、符号等非单词字符。
\s- 名称:空白字符
- 解释:匹配任何空白字符,包括空格、制表符等。
- 用法:
\s可以匹配空格、制表符等。
\S- 名称:非空白字符
- 解释:匹配任何非空白字符。
- 用法:
\S可以匹配字母、数字、符号等非空白字符。
量词
{n}- 名称:精确匹配
- 解释:匹配前一个字符恰好 n 次。
- 用法:
a{3}匹配aaa。
{n,}- 名称:至少匹配
- 解释:匹配前一个字符至少 n 次。
- 用法:
a{2,}匹配aa、aaa等。
{n,m}- 名称:范围匹配
- 解释:匹配前一个字符至少 n 次,至多 m 次。
- 用法:
a{2,4}匹配aa、aaa、aaaa。
断言
x(?=y)- 名称:正向先行断言
- 解释:匹配
x,仅当x后面跟着y。 - 用法:
\d(?=px)匹配3px中的3。
x(?!y)- 名称:负向先行断言
- 解释:匹配
x,仅当x后面不跟着y。 - 用法:
\d(?!px)匹配3em中的3¹²³。
for of 和 for in ? 哪个用于枚举,哪个用于迭代? 可迭代有哪些?可枚举数据有哪些?
for...in枚举 (遍历对象的可枚举属性(包括对象原型链上的属性))
for (let key in object) {
console.log(key, object[key]);
}
for of迭代 (用于遍历可迭代对象(如数组、字符串、Map、Set、NodeList 等),但不能直接用于对象)
for (let value of iterable) {
console.log(value);
}
尖头函数优缺点? 哪些场景不适用
优点:
- 代码简单
- 调用的时候确定 this 作用域
缺点:
- 对象方法不能用
const obj = {
value: 42,
getValue: function(){
return this.value // `this` 指向obj作用域
}
};
- 构造函数不能用
const MyFunc = () => {};
const instance = new MyFunc(); // 报错:MyFunc is not a constructor
内存泄漏 检测方法?哪些关键帧?初次渲染对应哪个关键帧?
可以在chorme 浏览器 performance memory
关键帧 核心性能指标
| 关键帧 | 定义 | 健康值 |
|---|---|---|
| FP (First Paint) | 首次像素渲染 | <1s |
| FCP (First Contentful Paint) | 首次内容渲染 | <1.8s |
| LCP (Largest Contentful Paint) | 最大内容渲染 | <2.5s |
| TTI (Time to Interactive) | 可交互时间 | < 3.5s |
| CLS (Cumulative Layout Shift) | 布局稳定性 | <0.1s |
DCL: (DOMContentLoaded). 表示 HTML 文档加载完成事件。当初始 HTML 文档完全加载并解析之后触发,无需等待样式、图片、子 frame 结束。作为明显的对比,load 事件是当个页面完全被加载时才触发。
FP(First Paint)首屏绘制,页面刚开始渲染的时间。
FCP(First Contentful Paint)首屏内容绘制,首次绘制任何文本,图像,非空白canvas 或 SVG 的时间点。
FMP(First Meaningful Paint)首屏有意义的内容绘制,这个“有意义”没有权威的规定,本质上是通过一种算法来猜测某个时间点可能是 FMP。有的理解为是最大元素绘制的时间,即同LCP(Largest Contentful Paint )。
L(Onload)页面所有资源加载完成事件。
LCP(Largest Contentful Paint )最大内容绘制,页面上尺寸最大的元素绘制时间。
