Skip to content

介绍一个比较难的项目

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 怎么去除,什么插件,原理是什么

  1. 重写一下 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);
  1. 使用包 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
          }
        }
      })
    ]
  }
};
  1. 手写 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

  1. ​​解析阶段:构建 DOM 树与 CSSOM 树​
  2. ​渲染树合成:合并 DOM 与 CSSOM​
  3. 布局(Layout/Reflow):计算几何信息​​
  4. 分层与绘制(Paint)​
  5. 合成与显示(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测试策略而不阻断资源。

典型场景​​:

箭头函数和普通函数的区别

算法题

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

Released under the MIT License.