0%

推荐系统架构

推荐和搜索的核心任务是从海量物品中找到用户感兴趣的内容。

系统架构

一般推荐系统架构分为离线层(Offline)、近线层(Nearline)和在线层(Online)。

  • 离线层:不实用实时数据,不提供实时响应。
    • 数据处理、数据存储。
    • 特征工程、离线特征计算。
    • 离线模型训练。
    • 优势与不足:数据量级最大,需要存储海量数据、大规模特征工程以及多机分布式模型训练。当前主流通过HDFS收集业务数据,通过Hive等大数据工具进行数据提取、筛选以及处理。主流框架是Spark。可以进行批量处理与计算而没有响应时间的要求。
  • 近线层:使用实时数据,不保证实时响应。
    • 不严格要求实时性,但需要弥补离线层与在线层之间的不足。适合处理对时延较为敏感的任务。
      • 特征的事实更新计算:离线层数据与实时数据分布不一致,通过近线层调节。
      • 获取实时训练数据:用户较短时间内产生的数据特征可以通过近线层直接输入模型。
      • 模型实时训练:通过在线学习的方法更新推送模型。
    • 主流大数据框架:Flink,Storm。
    • 架构技术栈
  • 在线层:使用实时数据,保证实时在线服务。
    • 面向用户的一层,对响应时延有要求,需要立即响应推荐结果。
      • 模型在线服务:包括了快速召回和排序。
      • 在线特征快速处理拼接:根据传入的用户ID和场景,快速读取特征和处理。
      • AB实验或分流:根据不同用户采用不一样的模型,比如冷启动用户和正常服务模型。
      • 运筹优化和业务干预:比如对特殊商家的流量扶持、对特定内容的限流。

算法架构

推荐系统会细分为四个阶段:召回、粗排、精排、重排.

  • 召回:从推荐池中选取大量items送个后续排序模块。
    • 由于面对的候选集庞大且需要在线输出,所以召回模块需要轻量快速低延迟。不需要十分准确,但不可遗漏。
    • 考虑内容:
      • 用户层面:用户需求、兴趣与场景多元化。
      • 系统层面:增强鲁棒性。兜底。
      • 多样性内容分发:召回目标多元。
      • 可解释性。
  • 粗排:对召回结果进行初步筛选送给精排。兼顾准确性和低延迟。
    • 考虑内容:
      • 根据精排模型的重要特征来做候选集的截断。
      • 根据与user相关性进行召回结果截断。
      • 性能保证。
  • 精排:大部分算法研究的层面。对粗排的候选集进行打分和排序。
  • 重排:解决精排结果可能同质化和冗余的问题。以及根据运营策略等去影响精排结果。是对精排结果的微调。

技术栈

画像层

推荐系统的物料库,绘制用户、商品画像等。通过tag的方式进行统计。

  • 内容特征提取与标签分类
    • 文本理解:RNN,TextCNN,FastText,Bert等。
    • 关键词标签:TF-IDF,Bert,LSTM-CRF。
    • 内容理解:TSN,RetinaFace,PSENet等。
    • 知识图谱:KGAT,RippleNet。

召回/粗排

经典模型召回:FM,双塔DSSM,Multi-View DNN等。 序列模型召回:CBOW,Skip-Gram,GRU,Bert等。 用户序列拆分:对用户的多维兴趣进行拆分,刻画更细致的方向。 知识图谱:对推荐结果的可解释性。 图模型:通过图模型来表示系统。

精排

研究最多的方向,主要通过各种深度学习方法进行打分排序。

  1. SFT Loss计算方法
    • 交叉熵损失 CE Loss
      • 假设输入prompt为x,目标输出序列为 y = (y1, y2, ..., yT),模型预测条件概率为 pθ(yt|x, y < t)
      • 则SFT Loss定义为: SFTθ = −∑t = 1Tlog pθ(yt|x, y < t)
    • 模型在生成每一个token时会预测一个概率分布,而希望它尽可能把正确token的概率提高。其损失就是“模型预测分布和参考答案之间的差距”。
    • 每一步预测都会对计算交叉熵取平均,最后对整个结果取平均。
    • 交叉熵:用来度量两个概率分布差异的函数,熵越小越接近真实分布。
    • 真实分布p(x),预测分布q(x),交叉熵定义为 H(p, q) = −∑p(xi)log q(xi).
  2. Transformer与注意力机制
    • Attention类别:Self Attention,Cross Attention,MHA,Flash Attention等。
    • Transformer结构:
      • Encoder模块:由N层堆叠,每一层主要包含:
        • 多头自注意力:捕捉输入序列中不同位置之间的依赖关系。
          • Attention(Q, K, V) = softmax(QKT/sqrtdk)V
          • 其中 Q, K, V 均由输入 X 投影得到:Q = XWQ, K = XWK, V = XWV.
        • 前向传播:对于每个token进行线性变换,提高表达能力.
          • FFN(x) = ReLU(xW1 + b1)W2 + b2
        • 残差连接和Layer Norm:保证梯度稳定,提高训练效果。
          • X = LayerNorm(X + MultiHead(X, X, X)), Y = LayerNorm(X + FFN(X))
      • Decoder模块:同样由N层堆叠
        • Masked多头自注意力:防止token看到未来信息,保证自回归生成。
          • QKT + M,通过mask矩阵屏蔽token之后的所有位置。
        • Encoder-Decoder多头注意力:让Decoder关注Encoder计算出的特征。
          • 公式相同,但是Q由decoder产生,K和V由Encoder提供。
        • 前馈网络:增强表达。
        • 残差连接:稳定训练,提高梯度传播能力。
        • Decoder的最终输出会通过一个线性变换和softmax层得到最后的预测分布。
    • 使用多头自注意力机制的优势:
      • MHA可以让模型从不同的表示子空间中学习不同的特征,增强模型的表达能力;提高模型的鲁棒性,减少单个注意力头可能带来的信息丢失;允许不同的注意力头关注输入序列的不同部分,使模型能够捕捉更丰富的上下文信息;增强模型的表达能力,避免单一注意力模式可能导致的局部最优解。
    • 为什么Q和K需要使用不同的权重矩阵:如果Q和K使用相同的权重矩阵,则 QKT 变成了一个对称矩阵,这样会导致注意力分数的计算缺乏灵活性,无法区分不同的输入关系。
    • 计算注意力使用点积计算的优势:
      • 计算效率更高,矩阵运算可以使用并行加速;避免了额外的可训练参数,而假发注意力需要额外的权重矩阵和非线性变换;也适用于高维向量。
    • 为什么对 QKT 进行scaled处理:
      • dk 是K的维度,如不不进行缩放,当 dk 较大时 QKT 的数值会变得过大,导致softmax的梯度消失。scaled后可以使得分布更加平稳,提高训练稳定性。
    • 为什么在进行多头注意力时需要先concat再降维:
      • 如果不降维会导致每个头的计算量变得很大,因此一般将d维的输入向量映射到一个较低维的子空间,使得每个头的计算量减少;如果直接相加那么所有注意力头的输出都会累驾到同一个维度上;而多头的优势在于可以关注不同的特征,直接相加会让这些特征混合,进而无法区分不同的注意力模式,降低了表达力。
    • Mask如何实现:对于需要padding的位置设置负无穷,需要的位置设置0.
    • 残差连接的作用:
      • 缓解梯度消失:梯度可以残差路径传播,使得深层网络训练更加稳定。
      • 加速收敛:通过跳跃连接使得梯度流动更加顺畅,减少训练时间。
      • 防止信息丢失:输入信息可以沿着残差路径流向更深层网络。
      • 提高泛化能力:残差+LayerNorm可以防止过拟合。
    • 为什么用LayerNorm而不是BatchNorm:
      • BatchNorm依赖于mini-batch统计量,而NLP任务序列长度不固定,BatchNorm可能表现不稳定,而LayerNorm不受影响。BatchNorm也依赖于较大的batchsize来获得稳定的均值和方差,而Transformer的batchsize通常较小。此外,transformer中每个token的计算是独立的,LayerNorm可以为每个token进行归一化,而batchnorm需要整个batch的信息。

  1. Echarts
    • 是一个基于 JavaScript 的开源数据可视化工具库,用来绘制交互式、可定制的图表。
    • 常用的组件:
      • title标题组件:show,text,link等属性。
      • toolbox工具栏组件:导出图片、数据视图、切换、缩放
      • tooltip组件:trigger组件
      • markPoint标注点
      • markLine图标标线
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        var chart = echarts.init(document.getElementById('main'));

        // 2. 配置项
        var option = {
        title: { text: 'ECharts 示例' },
        tooltip: {},
        legend: { data:['销量'] },
        xAxis: { data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"] },
        yAxis: {},
        series: [{
        name: '销量',
        type: 'bar', // 柱状图
        data: [5, 20, 36, 10, 10, 20]
        }]
        };

        // 3. 使用配置项生成图表
        chart.setOption(option);
  2. Webpack
    • webpack 是一个 前端资源打包工具 (module bundler),本质作用就是:把你写的各种资源(JS、CSS、图片、字体等),当作模块来处理,经过编译打包生成浏览器可用的优化文件。
    • 在前端项目中如果用到ES6+、Vue/React组件化、TypeScript、各种资源(图片、字体等)需要用到webpack把它们转换成浏览器能认识的纯JS/CSS/HTML文件
    • 打包过程:
      • 从入口文件开始,构建依赖关系图。
      • 遇到 import 或 require,就递归分析依赖。
      • 通过 Loader 转换代码。
      • 把所有模块打包成一个或多个 bundle。
      • 插件做额外优化(压缩、抽离 CSS、生成 HTML)。
    • Module、Chunk与Bundle的关系:
      • Module:项目中所有的源码文件都叫模块。可以是 JS、CSS、图片、字体等,webpack把它们都视作“模块”。
      • Chunk:webpack在打包过程中,按照依赖关系,把多个module合并成一个“代码块”。
      • Bundle:chunk经过webpack处理、loader转换、插件优化后,最终生成的实际输出文件。
    • Bibel转译器
      • 将高版本语言(ES6+等)转译成兼容性更好的旧版本JS,是一个JS编译器。
      • 原理:
        • 解析(Parsing):源代码转译为抽象语法树。
        • 转换(Transform):遍历AST,调用各种插件修改节点。
        • 生成(Generating):把修改后的 AST 再转成代码字符串。
    • Webpack打包和不打包区别:
      • 运行效率:若不打包,写的代码较多需要发送多次http请求,不易维护。
      • 对技术的支持:对于高版本语法可以通过webpack的bable来转化,使得低版本浏览器可以运行。
  3. HTTP协议版本对比
    • HTTP/1.0:每次请求都新建连接,效率最低。
    • HTTP/1.1:支持长连接,但有队头阻塞。
    • HTTP/2:二进制、多路复用、头部压缩,但仍受 TCP 队头阻塞限制。
    • HTTP/3:基于 QUIC(UDP),真正解决队头阻塞,握手更快,更适合移动互联网。
    • HTTP Versions
  4. HTTP与HTTPS
    • HTTPS (HTTP Secure) = HTTP + SSL/TLS。有身份验证。
    • 对比:
      • 端口:HTTP用80,HTTPS用443.
      • 安全性:HTTP明文传输,HTTPS加密传输。
      • 速度:HTTP相对较快。
      • 成本:HTTPS需要SSL/TLS证书。
  5. OSI七层网络模型
    1. 应用层:面向用户,有HTTP,FTP,SMTP,DNS等协议。
    2. 表示层:负责数据加密、压缩、表示等,把应用层数据转换为标准格式。如SSL/TLS,JPEG/MP3等。
    3. 会话层:负责建立、终止和管理会话。
    4. 传输层:提供端到端数据传输,保证数据可靠性。使用TCP/UDP。
    5. 网络层:负责路由寻址(IP),使用IP/ICMP/BGP等算法和协议。
    6. 链路层:在同一局域网内传输数据帧,解决错误检测,错误重传等。通过Ethernet,MAC地址等寻址。在交换机层面沟通。
    7. 物理层:比特流传输。

高频手撕代码考察

  1. 手写Promise.all()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    //接受一个数据(或可迭代对象)
    //等所有 Promise 成功,返回一个新 Promise,结果是 按顺序 的结果数组。
    //如果其中有一个失败(reject),立即返回 reject。
    function promiseAll(promises) {
    return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
    return reject(new TypeError("args not an array"));
    }

    let results = [];
    let completed = 0;

    promises.forEach((p, index) => {
    Promise.resolve(p).then(value => {
    results[index] = value;
    completed++;
    if (completed === promises.length) {
    resolve(results);
    }
    }) catch (error => {
    reject(error);
    });
    });

    if (promises.length === 0) {
    resolve([]);
    }
    });
    }

    const p1 = Promise.resolve(1);
    const p2 = new Promise(res => setTimeout(() => res(2), 1000));
    const p3 = 3;

    promiseAll([p1, p2, p3])
    .then(results => console.log("success:", results))
    .catch(error => console.error("failed:", error));

  2. 手撕防抖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function debounce(fn, delay) {
    let timer = null;

    return function(...args) {
    const context = this;
    if(timer) {
    clearTimeout(timer);
    }

    timer = setTimeout(() => {
    fn.apply(context, args);
    }, delay);
    };
    }

    function onResize() {
    console.log("resize 事件触发:", new Date().toLocaleTimeString());
    }
    // 500ms 防抖
    window.addEventListener("resize", debounce(onResize, 500));

  • 在防抖函数里:
    • context = this; 保存了外部调用时的 this(比如某个按钮 DOM 对象)。
    • args = arguments(用扩展语法 …args 收集)保存了调用时传进来的参数。
  • 当定时器触发时,如果直接写 fn():
    • this 会丢失(定时器里的 this 默认是 window 或 undefined)。
    • 参数也没法自动带过去。
  • 所以要用 fn.apply(context, args):
    • 保证 this 正确 → 就是调用 debounce 包裹函数时的 this。
    • 保证参数不丢失 → args 是数组,apply 会把它展开传给 fn。
  1. 手撕new函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function myNew(Constructor, ...args) {
    // create null obj, let its __proto__ pointed to constructor's prototype
    const obj = Object.create(Constructor.prototype);

    // execute constructor, bind this to obj
    const result = Constructor.apply(obj, args);

    return (result !== null && (typeof result === 'object' || typeof result === 'function'))
    ? result
    : obj;
    }

    function Person(name, age) {
    this.name = name;
    this.age = age;
    }
    Person.prototype.sayHi() = function() {
    console.log("Hi, I'm " + this.name);
    };

    const p1 = myNew(Person, 'Alice', 30);
    p1.sayHi();

  2. 手撕LRU,时间复杂度O(1)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class LRUCache {
    constructor(capacity) {
    this.capacity = capacity;
    this.map = new Map(); //有序表
    }

    get(key) {
    if (!this.map.has(key)) return -1;
    const value = this.map.get(key);
    this.map.delete(key);
    this.map.set(key, value);//放到队尾
    return value;
    }

    put(key, value) {
    if (this.map.has(key)) {
    this.map.delete(key);
    } else if (this.map.size >= this.capacity) {
    const firstKey = this.map.keys().next().value;
    this.map.delete(firsetKey);//找到并删除队首
    }
    this.map.set(key, value);
    }
    }

HTML5-CSS3高频问题

  1. 语义化
    • 在HTML页面结构中所用的标签都有意义。
    • 头部用head,主体用body,底部用foot。
    • 如何判断是否语义化:去掉CSS内容,页面结构显示内容较为正常。
    • 为什么语义化:让HTML结构更加清晰明了,方便团队合作利于开发,可以让浏览器更好的去解析,优化用户体验。
  2. H5C3的新特性
    • HTML5的新特性:语义化标签;音频视频;画布canvas;数据存储localStorage,sessionStorage;表单控件email,url,search等;拖拽释放API等
    • CSS3新特性:选择器:属性选择器、伪类选择器、伪元素选择器;媒体查询;文字阴影;边框;盒子模型boxing-size;渐变;过度;自定义动画;背景属性;2D与3D等。
  3. rem如何适配
    • rem是相对根元素的font-size属性来计算大小,通常做移动端适配。
  4. 移动端兼容问题
    • 当样式设置overflow:scroll/auto时IOS上滑动会卡顿。-webkit-overflow-scrolling: touch
    • 安卓环境下placeholder文字设置行高时会偏上。input由placeholder时不要设置行高。
    • 移动端字体小于12px时显示异常。先整体放大一倍,再用transform缩小。

JavaScript高频问题

  1. JS的组成部分
    • ECMA Script:JS的核心内容,基础语法。如var, for, 数据类型等。
    • 文档对象模型(DOM):DOM将HTML页面规划为元素构成的文档。
    • 浏览器对象模型(BOM):对浏览器窗口进行访问和操作(如提供浏览器特性,获取浏览器API)。
  2. JS内置对象
    • String, Boolean, Number, Array, Object, Function, Math, Date, RegExp
    • 常用内置对象:
      • Math: abs(), sqrt(), max(), min(), ...
      • Date: new Date(), getYear(), getHour(), ...
      • String: concat(), length, slice(), split(), ...
  3. 操作数组的方法
    • push(), pop(), sort(), shift(), unshift(), reverse(), concat(), join(), map(), filter, every(), some(), reduce(), isArray(), findIndex(), splice()
    • 哪些方法会改变原数组:push(), pop(), shift(), unshift(), sort(), reverse(), splice()
  4. 检测数据类型
    • typeof:判断基础数据类型,无法判断引用数据类型(class)。typeof 12345
    • instanceof():只能判断引用数据类型,无法判断基本数据类型。[] instanceof Array
    • constructor:几乎可以判断基本数据类型和引用数据类型。('abc').constructor === String
    • Object.prototype.toString.call():解决以上方式的问题。Object.prototype.toString.call(2/'abc'/true/[]/{})
  5. 闭包特性
    • 函数嵌套函数,内部函数被外部函数返回并保存下来时会产生闭包。
      1
      2
      3
      4
      5
      6
      7
      function fn(param) {
      return function() {
      console.log(param);
      }
      }
      var foo = fn('abcd');
      foo()
      输出abcd
    • 特性:
      • 可以复用变量且该变量不会污染全局(变量始终保存在内存中,不会被垃圾回收机制回收)。
      • 缺点:闭包较多时对内存占用较多,导致页面性能下降。只有在IE浏览器中会导致内存泄露。
    • 使用场景:防抖、节流、需避免全局污染时。
  6. 前端内存泄漏
    • JS中已分配内存空间的对象为能及时释放清除,会导致长期占用内存的现象,导致内存资源可用性变差,最终导致运行效率降低,甚至内存泄漏。
    • JS的垃圾回收机制:自动释放长时间不用的内存资源。
    • 导致内存泄漏的原因:为声明直接赋值的变量、未清空的定时器、过度闭包、引用元素未被清除等。
  7. 事件委托(事件代理)
    • 利用事件冒泡机制,将子元素事件(event)绑定到父元素上。
    • 若子元素阻止事件冒泡(event.stop()),则委托不成立。
    • addEventListener('click', func_name, false)默认false,false控制事件冒泡,true为事件捕获。
    • 好处:提高性能,减少事件绑定,减少内存占用。
  8. 基本数据类型与引用数据类型
    • 基本数据类型:String, Number, Boolean, undefined, null等保存在中,直接保存数据。
    • 引用数据类型(复杂数据类型):Object, Function, Array等保存在中,类似于C语言的指针类型(或C++引用),保存引用数据类型的地址。加入声明两个引用类型变量引用同一个地址时,改变其中一个另一个也改变。
  9. 原型链
    • 原型就是一个普通对象,用于构造函数的实例,共享属性和方法;所有实例中引用的原型都是同一个对象。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function Person() {
      this.say = function() {
      console.log('Hello');
      }
      }
      var p1 = new Person();
      var p2 = new Person();
      p1.say(); //p1和p2的say方法相互独立,需要存两个实例。
      p2.say();
    • 使用prototype可以把方法挂在原型上,不同实例共享同一内存。
      1
      2
      3
      4
      5
      Person.prototype.look = function() {
      console.log('Look');
      }
      p1.look();//p1和p2的look方法指向同一内存。
      p2.look();
    • __proto__可以理解为指针,实例对象中的属性指向构造函数的原型(prototype)。
    • 参照C++虚函数表机制。
    • JS中若把属性定义在Prototype上,只读时内存共享,一旦需要写入则创建拷贝(遮蔽)。
    • 原型链:一个实例对象在调用属性、方法时一次按照实例本身(Person)、构造函数原型(Person.prototype)、原型的原型(Object.prototype)上去查找。
  10. new操作符
    1. 创建空对象。
    2. 把空对象和构造函数通过原型链连接。
    3. 把构造函数的this绑定到新的空对象。
    4. 根据构造函数返回的类型判断,若为值则返回对象值,若为引用则返回引用。
  11. JS的继承机制
    • 原型链继承:方便简洁,容易理解;对象实例继承所有属性和方法,无法像父类构造函数传参。Child.prototype = new Parent()
    • 借用构造函数继承:在子类构造函数内部调用父类构造函数;使用apply()call()方法将父对象构造函数绑定在子对象上。在子类中写Parent.call(this, param)。解决了原型链实现继承无法向父类传参的问题;方法都在构造函数中定义,无法实现函数复用,且父类原型中定义的方法对子类不可见,所有类型只能使用构造函数模式。
    • 组合式继承:解决了原型链继承和构造函数继承的缺点;然而无论什么情况下都会调用两次父类构造函数(一次是创建子类原型时,另一次在子类构造函数内部)。
    • ES6的class类继承:Class通过extends关键字实现继承,实质是先创造出父类this对象在调用子类构造函数修改this。子类构造方法中必须调用super方法且只有在调用了super后才能使用this。简单易懂;对于不支持ES6的浏览器无法适用。
  12. JS的设计原理
    • JS引擎:编译器。
    • 运行上下文:浏览器中可调用的API(外部接口)。
    • 调用栈:函数调用顺序的数据结构。用于同步执行、递归、错误堆栈等场景。(JS是单线程,只有一个主线程)
    • 事件循环:由于JS是单线程,为防止卡死引入了异步机制。事件循环为调用栈为空时,从任务队列里取出一个回调,压入调用栈执行,重复这个过程。把耗时任务交给“外部系统 (浏览器内核的 Web APIs 或 Node 的 libuv)”,等结果好了,再通过 事件循环 (Event Loop) 安排执行回调函数。
    • 回调:是一个被当作参数传递的函数,在合适的时候被调用。在异步编程里,回调通常是“异步任务完成后要执行的代码”。
  13. JS中this的指向
    • 全局对象中this的指向:指向window。
    • 全局作用域或普通函数中的this:指向window。
    • this永远指向最后调用它的对象。(非箭头函数)
    • new关键词会改变this的指向。
    • applycall以及bind都可以改变this的指向。(非箭头函数)
    • 箭头函数中的this在定义时已被确定,箭头函数无this,看外层是否有函数,有就是外层函数的this,没有就是window。
    • 匿名函数中this永远指向window。(匿名函数的执行环境具有全局性)。
  14. Script标签中asyncdefer
    • 没有async与defer时:浏览器会立刻加载并执行指定的脚本。
    • async:加载和渲染后面元素的过程将与Script的加载和执行并行执行(异步)。
    • defer:加载Script与加载渲染元素异步完层,执行则在所有元素解析完成之后。
  15. setTimeout的最小执行时间
    • setTimeout:4ms
    • setInterval: 10ms
    • 由HTML5规定(浏览器可能会影响)
  16. ES6和ES5的区别
    • ES5: ECMA Script 5(ECMAScript2009);
    • ES6: ECMA Script 6(ECMAScript2015);
  17. ES6的新特性
    • 新增块级作用域(let,const)
      • 不能在同一个作用域内重复声明。
      • 不存在变量提升。
      • 存在暂时性死区问题
    • 新增定义类(class)
    • 新增一种数据类型(symbol):代表独一无二的值,即使内容一致。
    • 新增结构赋值:从数据或者对象中取值赋给变量。
    • 新增函数参数默认值
    • 新增数组API
    • 新增对象和数组的扩展运算符
    • 新增Promise
      • 表示未来才会完成的值,用于解决回调地狱的问题。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        const p = new Promise((resolve, reject) => {
        setTimeout(() => {
        resolve("任务完成"); // 或 reject("出错了")
        }, 1000);
        });

        p.then(result => {
        console.log(result);
        }).catch(error => {
        console.error(error);
        });
      • 自身有all,reject,resolve,rece这些方法。原型上有then,catch方法。
      • 把异步操作序列话,按预期返回结果。
      • 三种状态:pending(初始),fulfilled(操作成功),rejected(操作失败)。一旦状态转变就会凝固。
      • async与await:同步代码做异步操作,必须搭配使用。是Promise的语法糖。在函数面前添加async会自动返回一个Promise。遇到await暂停执行,等待Promise完成再继续。
    • 新增模块化(import,export)
    • 新增set和map数据结构
    • 新增generator
    • 新增箭头函数
      • 不能作为构造函数使用,不能用new。
      • 箭头函数没有arguments。
      • 不能使用call,apply,bind去改变this的执行
      • 无prototype。
  18. call, apply, bind的区别
    • 都是改变this指向和函数调用。
    • call与apply类似,只是传参方法不同。
    • call传参数列表。
    • apply传一个数组。
    • bind传参后不会立即执行,会返回一个改变了this指向的函数,这个函数可传参,通过bind()()来调用。
    • call方法性能比apply好一些,bind无法构造函数。
  19. 递归的问题
    • 递归:函数内部调用函数本身(递归函数必须有退出条件,否则会导致栈溢出)。
  20. 深拷贝的实现
    • 完全拷贝一个新的对象,在堆中重新开辟新的空间。拷贝的对象被修改后,原对象不受影响。
    • 主要针对引用数据类型。
    • 扩展运算符:只能针对第一层深拷贝,对于多层对象只是浅拷贝。
      1
      2
      3
      4
      5
      let obj1 = {
      name: "jack",
      age: 23
      };
      let obj2 = {...obj};
    • JSON.parse(JSON.stringify()):原对象中若有函数则不会被拷贝过来。
    • 利用递归函数:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      function copy(origin, isDeepCopy=true) {
      let result = {};
      if (origin instanceof Array) {
      result = [];
      }
      for (let key in origin) {
      let v = origin[key];
      result[key] = (!!isDeepCopy && typeof v === 'object' && v !== null) ? copy(v, isDeepCopy) : v;
      }
      return result;
      }
  21. 事件循环
    • JS是一个单线程脚本语言。
    • 主线程:唯一线程上任务。
    • 执行栈:任务栈,存储上下文与执行顺序。
    • 任务队列:任务调度。
    • 宏任务/微任务:异步任务,微任务优先级更高。
    • 主线程先执行同步任务,再执行任务队列里的微任务或宏任务。执行完再去查询任务队列,或者执行主任务。
  22. AJAX
    • 创建交互式网页应用的开发技术,再不重新加载整个页面的情况下与服务器交换数据并更新部分内容。
    • 通过XMLHttpRequest对象向服务器发送异步请求,返回数据后通过JS操作DOM的形式更新页面。
    • 过程:
      • 创建XMLHttpRequest对象。
      • 通过XMH对象里的open()方法与服务器建立连接。
      • 构建请求所需的数据,并通过XMH的send()方法发送至服务器。
      • 通过XMH对象的onReadyStateChange事件监听服务器与本地的通信状态。
      • 接受并处理服务器响应的结果。
      • 将处理结果更新到HTML页面中。
  23. GETPOST的区别
    • GET一般获取数据,POST一般提交数据。
    • GET参数在URL中,安全性较差。
    • POST参数在body中,数据长度无限制。
    • GET请求刷新服务器或退回无影响,POST请求退回时重新提交数据。
    • GET请求会被缓存,POST不会。
    • GET会被保存在浏览器的历史记录中,POST不会。
    • GET只能进行url编码,POST支持多种数据结构。
  24. Promise的内部原理与优缺点
    • Promise对象封装了一个异步操作且可以获取成功或失败的结果。
    • Promise主要解决回调地狱的问题,若之前异步任务较多且之间包含依赖关系就只能用回调函数解决,这样会导致代码可读性差、可维护性差形成了回调地狱。
    • Promise的三种状态:Pending,Fulfilled,Rejected。只能从Pending变为后者其中之一且不可逆。
    • Promise无法被取消,一旦创建立即执行,无法中断。
    • 如果不设置回调,Promise内部抛出的错误无法反馈到外面。
    • 原理:
      • 构造一个Promise实例需要传递一个函数作为参数,该函数有两个形参切都是函数类型,一个是resolve另一个是reject。
      • Promise上还有then方法,用来指定状态发生改变时的确定操作,resolve执行第一个函数,reject执行第二个函数。
  25. Promise与async、await的区别
    • 都是异步处理请求方式。
    • Promise是ES6,async和await是ES7.
    • async与await都是基于Promise实现的,都是非阻塞的。
    • 优缺点:
      • Promise是一个返回对象需要用then和catch来捕获,且书写方式是链式的容易造成代码重叠不好维护。而async和await通过try-catch来捕获异常。
      • async await最大的优点是让代码看起来像同步一样,只要遇到await就会立刻返回结果然后再执行后面的操作。而Promise.then()的方式会出现请求还没返回就执行了后面操作的情况。
  26. 浏览器的存储方式
    • cookies:H5标准前的本地存储方式;兼容性好,请求头自带cookie;存储量小容易造成资源浪费,使用麻烦(需要封装)。
    • localStorage:H5加入的标准方式,以key-value的形式存储。操作方便、永久存储、兼容性好;保存值的类型可能被限定,浏览器隐私模式下不可读取,不能被爬虫。
    • sessionStorage:类似于localStorage;当前页面关闭后会被立即清理,会话级别的存储方式。
    • indexedDB:H5标准存储方式,key-value存储,可以快速读取,适合web场景。
  27. token的存储位置
    • token:验证身份的令牌,一般是用户通过账号密码登录后,服务端把这些凭证通过加密等一系列操作后得到的字符串。
    • 若存在localStorage里,后期每次请求接口都需要把它当作一个字段传给后台,容易被XSS攻击。
    • 若存在cookie中会自动发送,不能跨域,会有CSRF攻击。
    • 一般存在localStorage/sessionStorage中,具体看需求。
  28. token的登录流程
    1. 客户端用账号密码请求登录。
    2. 服务端收到请求后验证账号密码。
    3. 验证成功后,服务端签发一个token发送给客户端。
    4. 客户端收到token保存后。
    5. 后续客户端每次向服务端发送资源请求时,都需要携带这个token。
    6. 服务端收到请求后验证token。成功则返回资源。
  29. 页面渲染的过程
    • DNS解析-建立TCP连接-发送HTTP请求-服务端处理请求-渲染页面-断开TCP
    • 渲染页面:
      • 获取HTML和CSS资源,把HTML解析成DOM树。
      • 解析CSS为CSSOM。
      • 把DOM和CSSOM合并为渲染树。
      • 把渲染树每个节点渲染到页面上(绘制)。
  30. DOM树与渲染树
    • DOM树是HTML标签的一一对应,包括head和隐藏元素。
    • 渲染树不包含head和隐藏元素。
  31. 精灵图与base64
    • 精灵图:多张图拼接后压缩,前端若要请求多张图片可以只请求一次。
    • base64:单张图片二进制信息转译为字符串(字符串大小可能比原图大)。
  32. svg格式
    • 基于XML语法格式的图像格式,可缩放矢量图。
    • svg可直接插入页面中,用JS或css对其进行操作。<svg></svg>
    • 也可以作为图像文件被引入。
    • 可以转为base64引入。
  33. JWT
    • JSON Web Token通过JSON形式在web应用中的令牌,可以在各方之间安全的把信息作为JSON对象传输。
    • 用作信息传输、授权。
    • 如果用session来验证会导致服务端负载过大,不利于负载均衡。
    • JWT认证流程:
      • 前端把账号密码发送给后端接口。
      • 后端核对信息,把用户id等信息作为JWT负载,把它和head分别进行base64编码后拼接的签名,形成一个JWT Token。
      • 前端每次请求时都会把JWT放在请求头的Authorization位置。
      • 后端检查是否存在,如果存在就验证JWT的有效性(签名是否正确,token是否过期等)。
      • 验证后后端使用JWT中包含的用户信息进行其他的操作,并返回对应结果。
    • JWT简洁、可跨语言使用。
  34. npm的底层环境
    • node package manager,node的包管理和分发工具,已经成为分发node的标准。是JS的运行环境。
    • 组成:网站、注册表(每一个包基本信息的数据库),命令行工具。
  35. HTTP协议规定包含的响应头和请求头
    • 请求头信息:
      • Accept:浏览器告诉服务器所支持的数据类型。
      • Host:浏览器告诉服务器像访问的主机。
      • Referer:浏览器告诉服务器我从哪里来(防盗链)。
      • User-Agent:浏览器类型、版本。
      • Date:浏览器告诉服务器我是什么时候访问的。
      • Connection:连接方式。
      • Cookie
      • X-Request-With:请求方式。
    • 响应头信息:
      • Location:告诉浏览器你要去找谁。
      • Server:告诉浏览器服务器的类型。
      • Content-Type:告诉浏览器返回的数据类型。
      • Refresh:控制定时刷新。
  36. 浏览器的缓存策略
    • 强缓存(本地缓存):不发起请求,直接使用缓存中的内容,浏览器把JS,CSS,image等内容存到内存中,下次访问时直接从内存中取。
    • 弱缓存(协商缓存):需要向后台发请求,通过判断来决定是否使用协商缓存,如果请求内容没有变化,则返回304,浏览器使用缓存里的内容。
    • 强缓存的触发:HTTP1.0用时间戳响应标头;HTTP1.1用Cache-Control响应标头。
    • 弱缓存的触发:HTTP1.0用if-modified-since响应头last-modified;HTTP1.1用if-none-match响应头Etag。
  37. 同源策略
    • 浏览器的基本(核心)安全策略,URL中协议、域名以及端口号完全相同即为同源。不一样则产生跨域。
    • 三个允许跨域加载资源的标签:img,link,script。
    • 跨域是可以正常发送请求的,后端也会返回结果,只不过该结果被浏览器拦截。
    • 解决跨域的方式:JSONP(服务端允许),CORS(服务端和客户端同时配置),websocket,反向代理(中转站)。
  38. 防抖和节流
    • 都是应对页面中频繁触发事件的优化方案。
    • 防抖:触发事件后,在一段时间内只执行最后一次。如果在这段时间内事件又被触发,就重新计时。
      • 频繁和服务端交互
      • 输入框自动保存时间
    • 节流:触发事件后,在固定时间间隔内只执行一次。不管事件触发多少次,都会按照时间间隔来执行。
      • 页面滚动监听 → 每 200ms 计算一次位置。
      • 鼠标拖拽 → 每 100ms 更新一次位置。
  39. JSON
    • JSON是纯字符串形式数据,适合在网络中传输,不提供任何方法。
    • JSON数据存储在.json文件中,或者以字符串形式在数据库、cookie中。
    • JS提供了JSON.parse(), JSON.stringify()方法。
    • 使用场景:定义接口、序列化、生成token、配置文件。
  40. 数据请求失败的做法
    • 在渲染数据的地方给默认值。
    • if判断,没有数据可以隐藏。
  41. 无感登录
    • 无感刷新token,token失效时自动更新。
    • 在响应拦截器中拦截,token过期后调用刷新token的接口。(常用)
    • 后端返回过期的时间,前端判断token的过期时间,主动调用刷新token的接口。
    • 写定时器,定时刷新token。
    • 流程:
      • 登录成功后保存token和refresh_token。
      • 响应拦截器中对401状态码引入刷新token的api方法调用。
      • 替换保存本地新的token。
      • 把错误对象里的token替换。
      • 再次发送未完成请求。
      • 如果refresh_token也过期,清除所有token返回登录页面。
  42. 大文件上传
    • 分片上传:把需要上传的文件按一定规则分割成大小相同的数据块。
      • 初始化分片上传任务,返回本次上传的唯一标识。
      • 按照一定规则把各个数据块上传。
      • 发送完成后服务端判断数据完整性,若完整则合并。
    • 断点续传:
      • 服务端返回从哪里开始/浏览器自己处理。
  43. varletconst的区别
    • 作用域:
      • var在函数内声明则只作用于函数内部,其他情况都是全局作用域。
      • let/const是在花括号内有效。
    • 变量提升:
      • var声明会被提升(hoist),初始化为undefined。声明之前访问为undefined。
      • let/const也会被提升,但是会有TDZ(Temporary Dead Zone),在声明之前访问会报错。
    • 重复声明:
      • var可以重复声明同名变量。
      • let/const在同一作用域内不允许重复声明。
    • 是否可以重新赋值:
      • var/let:可以重新赋值。
      • const:声明后必须立刻赋初值,且不能重复赋值。
    • 对象/数据的const特殊性:const只是保证变量绑定(引用)不可辨,但对象/数组的内容可以被改变。

CSS高频问题

  1. CSS盒模型
    • 在HTML页面中的所有元素都可以看作一个盒子。
    • 盒子的组成:内容content,内边距padding,边框border,外边距margin。
    • 盒模型的类型:
      • 标准盒模型:margin + border + padding + content。
      • IE盒模型:margin + content(border + padding)。
    • 控制盒模型的模式:box-sizing : content-box (default,标准盒模型), border-box(IE盒模型)。
  2. CSS选择器优先级
  • CSS的特性:继承性、层叠性、优先级。
  • 优先级:写CSS样式时给同一个元素添加多个样式,此时样式权重高的则优先显示。
  • 选择器:!important > 行内样式 > id > 类/伪类/属性 > 标签 > 全局选择器。
  1. 如何隐藏元素
    • display: none; (元素直接消失,不占据空间)
    • opacity: 0; (透明度为0,占据空间)
    • visibility: hidden; (元素消失,占据空间)
    • position: absolute;(移除页面)
    • clip-path (剪切元素)
  2. pxrem的区别
    • px是像素,显示器上呈现画面的像素,大小统一的绝对单位。
    • rem是相对单位,相对于HTML根节点的font-size的值。
      • 若设定HTML根结点font-size为62.5%, 则1rem = 10px
      • 16px * 62.5% = 10px。(默认状态下1rem = 16px
  3. 重绘与重排的区别
    • 重排(回流):布局引擎根据所有样式计算盒模型在页面中的位置和大小;对DOM的大小、位置等属性进行修改后需要重新计算几何属性,就叫重排。
    • 重绘:计算完成盒模型的位置、大小等属性之后,浏览器会根据每个盒模型的特性在页面上绘制;对DOM的样式进行修改后,浏览器不需要重新计算几何属性直接绘制该元素新样式,就叫重绘。
    • 浏览器的渲染机制浏览器渲染机制
  4. 让元素水平垂直的方式
    • 定位+margin:子元素设置top: 0; bottom: 0; left: 0; right: 0; margin: auto;
    • 定位+transform:子元素设置top: 50%; left: 50%; transform: translate(-50%, -50%);
    • flex布局:父元素设置display: flex; justify-content: center; align-items: center;
    • table布局:父元素设置display: table-cell; vertical-align: middle;,子元素设置margin: 0 auto;
    • grid布局:父元素设置display: grid; justify-items: center;
  5. 属性继承
    • 子元素可以继承父元素的样式:
      • 字体类属性:font…
      • 文本类属性:text-align, line-height…
      • 元素可见性:visibility: hidden…
      • 表格布局:border-spacing…
      • 列表属性:list-style…
      • 页面样式:page…
      • 声音样式
  6. 预处理器
    • CSS弊端:样式重复使用,不利于扩展维护。
    • 预处理:增加变量、函数、混入等强大功能。类似于宏定义,通过编译器替换变量、函数等。
      • SASS与LESS
      • @global-color: #EEE; box { color: @global-color; }

燈塔只是信標, 帶不走過客的錨。 歲月不再豐饒, 只有海水無情, 看著新大陸的孤島。

寒刀劈水掣天洪,殘花依舊綉春風。 乖看河東新鞍馬,幾時颯沓長安宮。

附黃巢《不第后賦菊》 待到秋來八九月,我花開后百花殺。 冲天香陣透長安,滿城盡帶黃金甲。

煤山樹下縊漢帝,山海門開迎遼皇。 新都依舊高宗夢,四鎮偏安棄江東。 北伐秋夢望中原,騰蛟斷鱗南天開。 北馬渡江諸王薨,三峽雁泣遍夔東。 閩海不睬粵王詔,滇池遙哭金陵師。 天子堂下驚風雨,忠黨拜權膝下臣。 九州披髮擧義幟,三朝壯士恨蒙塵。 廿載春秋會之事,天下豈止一武穆。