介绍一个比较难的项目
start 法则
虚拟列表和动态虚拟列表
为什么定时器不准确
有什么其他方法可以降低时间这个不准确的问题
js
// performance.now 比 Date.now 更精确
let startTime = performance.now();
// count表示定时器被调用的次数,次数需要是全局变量
let count = 0;
function myTimeout() {
let runTime = performance.now();
// 先增加次数
++count;
// diffTime 就是已经延后的时间
let diffTime = runTime - (startTime + count * 1000);
// 既然已经延后了,就需要时间间隔,减去已经拖延的时间,提前开始
setTimeout(myTimeout, 1000 - diffTime);
}
let t = setTimeout(myTimeout, 1000);
// performance.now 比 Date.now 更精确
let startTime = performance.now();
// count表示定时器被调用的次数,次数需要是全局变量
let count = 0;
function myTimeout() {
let runTime = performance.now();
// 先增加次数
++count;
// diffTime 就是已经延后的时间
let diffTime = runTime - (startTime + count * 1000);
// 既然已经延后了,就需要时间间隔,减去已经拖延的时间,提前开始
setTimeout(myTimeout, 1000 - diffTime);
}
let t = setTimeout(myTimeout, 1000);
SSE 是什么协议
websocket 是什么协议
0.1+0.2 问题
0.1 在二进制中的近似表示可能是 0.000110011001100...,但在计算机的浮点数表示中,它可能被截断或舍入为 0.00011001100110,这就导致了 0.1 + 0.2 在计算机中可能不等于 0.3,而是略微有所偏差。
说一下 promise
- 拥有三种状态为 pending fulfilled rejected
- 实例化一个 promise 会得到一个状态为 pending 的实例对象
- 该对象可以访问 promise 原型上的 then 方法
- 当该对象中的状态没有变更为 fulfilled,then 接收到的回调是不触发的
promise 的 allsettle、race、any 是干什么的
Promise.any()静态方法接受一个可迭代的 Promise 作为输入,并返回一个 Promise。
当输入的任何一个 Promise 满足时,返回的 Promise 即为满足,并返回第一个满足值。
当输入的所有 Promise 都拒绝时(包括传入一个空的可迭代对象),返回的 Promise 即为拒绝,并返回一个 AggregateError 包含拒绝原因数组的 。
怎么知道用户长时间没操作
监听鼠标和键盘事件
console.log 怎么去除,什么插件,原理是什么
- 重写一下 console.log 方法
js
const isDebug = true; // 控制是否屏蔽全局console.log 日志;isDebug设为false即可屏蔽
console.log = (function (oldLogFunc) {
return function () {
if (isDebug) {
oldLogFunc.apply(this, arguments);
}
};
})(console.log);
const isDebug = true; // 控制是否屏蔽全局console.log 日志;isDebug设为false即可屏蔽
console.log = (function (oldLogFunc) {
return function () {
if (isDebug) {
oldLogFunc.apply(this, arguments);
}
};
})(console.log);
- 使用包 uglifyjs-webpack-plugin
js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
// 省略...
mode: 'production',
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
// 删除注释
output: {
comments: false
},
compress: {
drop_console: true, // 删除所有调式带有console的
drop_debugger: true,
pure_funcs: ['console.log'] // 删除console.log
}
}
})
]
}
};
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
// 省略...
mode: 'production',
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
// 删除注释
output: {
comments: false
},
compress: {
drop_console: true, // 删除所有调式带有console的
drop_debugger: true,
pure_funcs: ['console.log'] // 删除console.log
}
}
})
]
}
};
- 手写 Loader 删除 console
js
// clearConsole.js
const reg = /(console.log\()(.*)(\))/g;
module.exports = function (source) {
source = source.replace(reg, '');
return source;
};
// clearConsole.js
const reg = /(console.log\()(.*)(\))/g;
module.exports = function (source) {
source = source.replace(reg, '');
return source;
};
js
// Vue.config.js
module.exports = {
// 省略...
configureWebpack: {
module: {
rules: [
{
test: /\.vue$/,
exclude: /node_modules/,
loader: path.resolve(__dirname, './clearConsole.js')
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: path.resolve(__dirname, './clearConsole.js')
}
]
}
}
};
// Vue.config.js
module.exports = {
// 省略...
configureWebpack: {
module: {
rules: [
{
test: /\.vue$/,
exclude: /node_modules/,
loader: path.resolve(__dirname, './clearConsole.js')
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: path.resolve(__dirname, './clearConsole.js')
}
]
}
}
};
pdf 预览是怎么做的
浏览器可以直接展示 pdf 吗
可与
浏览器渲染的过程,html
- 解析阶段:构建 DOM 树与 CSSOM 树
- 渲染树合成:合并 DOM 与 CSSOM
- 布局(Layout/Reflow):计算几何信息
- 分层与绘制(Paint)
- 合成与显示(Composite)
讲一下重流重绘,并且他们在上面的哪个阶段出发
重流和重绘是浏览器渲染流水线的核心环节:重流作用于布局阶段(Layout),重绘作用于绘制阶段(Paint)。
理解两者的触发条件和性能影响,可针对性优化关键渲染路径(Critical Rendering Path),显著提升页面性能
XSS 和 CSRF
讲讲 CSP 有哪些
白名单控制:通过HTTP头部(如Content-Security-Policy)或HTML meta标签定义资源加载规则,限制脚本、样式等资源的来源。
关键指令:
- script-src:控制JavaScript来源(如'self'仅允许同源加载)
- style-src:限制CSS来源
- report-uri:收集策略违规报告(用于调试)。
高级特性:
- Nonce/哈希值:允许特定内联脚本执行(如
<script nonce="随机值">
)。 - 仅报告模式:通过Content-Security-Policy-Report-Only测试策略而不阻断资源。
典型场景:
- 电商网站限制支付页面仅加载可信CDN资源。
- Vue项目配置default-src 'self'; script-src https://trusted-cdn.com防止恶意脚本注入。
箭头函数和普通函数的区别
算法题
reduce 实现 map 方法
js
Array.prototype.mapByReduce = function (callback) {
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
return this.reduce((accumulator, currentValue, index, array) => {
accumulator.push(callback(currentValue, index, array));
return accumulator;
}, []);
};
// 示例
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.mapByReduce((num) => num * 2);
console.log(doubledNumbers); // 输出: [2, 4, 6, 8, 10]
const strings = ['apple', 'banana', 'orange'];
const uppercasedStrings = strings.mapByReduce((str) => str.toUpperCase());
console.log(uppercasedStrings); // 输出: ["APPLE", "BANANA", "ORANGE"]
Array.prototype.mapByReduce = function (callback) {
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
return this.reduce((accumulator, currentValue, index, array) => {
accumulator.push(callback(currentValue, index, array));
return accumulator;
}, []);
};
// 示例
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.mapByReduce((num) => num * 2);
console.log(doubledNumbers); // 输出: [2, 4, 6, 8, 10]
const strings = ['apple', 'banana', 'orange'];
const uppercasedStrings = strings.mapByReduce((str) => str.toUpperCase());
console.log(uppercasedStrings); // 输出: ["APPLE", "BANANA", "ORANGE"]
版本比较,比如 compare(1.3.0-beta2.0, 1.3.0-apla1.0),返回 1,-1,0, dev < alpha < beta < rc
js
// 版本号比较函数
// 比较两个版本号字符串,返回1表示version1大于version2,0表示相等,-1表示version1小于version2
function compare(version1, version2) {
// 解析版本号为数组
const v1Parts = parseVersion(version1);
const v2Parts = parseVersion(version2);
// 逐部分比较
const len = Math.max(v1Parts.length, v2Parts.length);
for (let i = 0; i < len; i++) {
const v1Part = i < v1Parts.length ? v1Parts[i] : 0;
const v2Part = i < v2Parts.length ? v2Parts[i] : 0;
if (v1Part > v2Part) return 1;
if (v1Part < v2Part) return -1;
}
return 0;
}
// 解析版本号字符串为数组
function parseVersion(version) {
// 分割版本号和后缀
const [versionPart, suffixPart] = version.split('-');
const versionNumbers = versionPart.split('.').map(Number);
if (!suffixPart) {
// 没有后缀,视为正式发布版本,优先级最高
return versionNumbers;
}
// 处理后缀
const suffixPriority = getSuffixPriority(suffixPart);
return versionNumbers.concat(suffixPriority);
}
// 获取后缀的优先级数值
function getSuffixPriority(suffix) {
// 定义后缀优先级映射
const priorityMap = {
dev: 0,
alpha: 1,
beta: 2,
rc: 3
};
// 查找后缀中的优先级
for (const [key, value] of Object.entries(priorityMap)) {
if (suffix.startsWith(key)) {
// 如果后缀有额外版本号,如beta1.2,则解析为数组
const extra = suffix
.slice(key.length)
.split('.')
.filter(Boolean)
.map(Number);
return [value, ...extra];
}
}
// 如果没有匹配的后缀,视为最低优先级
return [-1];
}
// 测试函数
console.log(compare('1.3.2', '1.2.3')); // 1
console.log(compare('1.3.0', '1.3.0-beta1.2')); // 1
console.log(compare('1.3.0-beta1.2', '1.3.0')); // -1
console.log(compare('1.3.0-beta1.2', '1.3.0-alpha2.1')); // 1
console.log(compare('1.3.0-beta1.2', '1.3.0-beta1.2')); // 0
// 版本号比较函数
// 比较两个版本号字符串,返回1表示version1大于version2,0表示相等,-1表示version1小于version2
function compare(version1, version2) {
// 解析版本号为数组
const v1Parts = parseVersion(version1);
const v2Parts = parseVersion(version2);
// 逐部分比较
const len = Math.max(v1Parts.length, v2Parts.length);
for (let i = 0; i < len; i++) {
const v1Part = i < v1Parts.length ? v1Parts[i] : 0;
const v2Part = i < v2Parts.length ? v2Parts[i] : 0;
if (v1Part > v2Part) return 1;
if (v1Part < v2Part) return -1;
}
return 0;
}
// 解析版本号字符串为数组
function parseVersion(version) {
// 分割版本号和后缀
const [versionPart, suffixPart] = version.split('-');
const versionNumbers = versionPart.split('.').map(Number);
if (!suffixPart) {
// 没有后缀,视为正式发布版本,优先级最高
return versionNumbers;
}
// 处理后缀
const suffixPriority = getSuffixPriority(suffixPart);
return versionNumbers.concat(suffixPriority);
}
// 获取后缀的优先级数值
function getSuffixPriority(suffix) {
// 定义后缀优先级映射
const priorityMap = {
dev: 0,
alpha: 1,
beta: 2,
rc: 3
};
// 查找后缀中的优先级
for (const [key, value] of Object.entries(priorityMap)) {
if (suffix.startsWith(key)) {
// 如果后缀有额外版本号,如beta1.2,则解析为数组
const extra = suffix
.slice(key.length)
.split('.')
.filter(Boolean)
.map(Number);
return [value, ...extra];
}
}
// 如果没有匹配的后缀,视为最低优先级
return [-1];
}
// 测试函数
console.log(compare('1.3.2', '1.2.3')); // 1
console.log(compare('1.3.0', '1.3.0-beta1.2')); // 1
console.log(compare('1.3.0-beta1.2', '1.3.0')); // -1
console.log(compare('1.3.0-beta1.2', '1.3.0-alpha2.1')); // 1
console.log(compare('1.3.0-beta1.2', '1.3.0-beta1.2')); // 0