2022年前端面试题整理,持续更新中

前端面试题整理

已同步到掘金,掘金地址: https://juejin.cn/post/7075332630417244173

个人整理了很多网上常见的面试题,希望也能通过这来复习
内容有点多,可能CSDN上预览效果不好,想要markdown文档的可以私信我,推荐使用Typora

比较好的面试题

2021 年我的前端面试准备
2021年前端面试必读文章【超三百篇文章/赠复习导图】

CSS、HTML

你是怎么理解 HTML 语义化

HTML 语义化简单来说就是用正确的标签来做正确的事。

比如表示段落用 p 标签、表示标题用 h1-h6 标签、表示文章就用 article 等。

DOCTYPE 的作用

Doctype作用?严格模式与混杂模式如何区分?它们有何差异?

  1. 声明位于文档中的最前面,处于 标签之前。告知浏览器以何种模式来渲染文档。

  2. 严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。

  3. 在混杂模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的行为以防止站 点无法工作。

  4. DOCTYPE 不存在或格式不正确会导致文档以混杂模式呈现。复制代码 你知道多少种 Doctype 文档类型? 该标签可声明三种 DTD 类型,分别表示严格版本、过渡版本以及基于框架的 HTML 文档。 HTML 4.01 规定了三种文档类型:Strict、Transitional 以及 Frameset。 XHTML 1.0 规定了三种 XML 文档类型:Strict、Transitional 以及 Frameset。 Standards (标准)模式(也就是严格呈现模式)用于呈现遵循最新标准的网页,而 Quirks (包容)模式(也就是松散呈现模式或者兼容模式)用于呈现为传统浏览器而设计的网页。

行内元素、块级元素、 空元素有那些?

  • 行内元素 (不能设置宽高,设置宽高无效) a,span,i,em,strong,label
  • 行内块元素:img, input
  • 块元素: div, p, h1-h6, ul,li,ol,dl,table…
  • 知名的空元素 br, hr,img, input,link,meta

可以通过 display 修改 inline-block, block, inline

注意

只有文字才能组成段落,因此 p 标签里面不能放块级元素,特别是 p 标签不能放 div。同理还有这些标签h1,h2,h3,h4,h5,h6,dt ,他们都是文字类块级标签,里面不能放其他块级元素。

html5 新特性

HTML5新特性

  1. 语义化标签, header, footer, nav, aside,article,section
  2. 增强型表单
  3. 视频 video 和音频 audio
  4. Canvas 绘图
  5. SVG绘图
  6. 地理定位
  7. 拖放 API
  8. WebWorker
  9. WebStorage( 本地离线存储 localStorage、sessionStorage )
  10. WebSocket

css3 新特性

CSS3有哪些新特性?CSS3新特性详解

1、圆角效果;2、图形化边界;3、块阴影与文字阴影;4、使用 RGBA 实现透明效果;5、渐变效果;6、使用“@Font-Face”实现定制字体;7、多背景图;8、文字或图像的变形处理;9、多栏布局;10、媒体查询等。

1、颜色:新增RGBA、HSLA模式
2、文字阴影:(text-shadow)
3、边框:圆角(border-radius)边框阴影:box-shadow
4、盒子模型:box-sizing
5、背景:background-size,background-origin background-clip(削弱)
6、渐变:linear-gradient(线性渐变):
eg: background-image: linear-gradient(100deg, #237b9f, #f2febd);
radial-gradient (径向渐变)
7、过渡:transition可实现动画
8、自定义动画: animate@keyfrom
9、媒体查询:多栏布局@media screen and (width:800px)
10、border-image
11、2D转换:transform:translate(x,y) rotate(x,y)旋转 skew(x,y)倾斜 scale(x,y)缩放
12、3D转换
13、字体图标:Font-Face
14、弹性布局:flex

css 选择器

id 选择器( #myid)

类选择器(.myclassname)

标签选择器(div, h1, p)相邻选择器(h1 + p)

子选择器(ul > li)后代选择器(li a)

属性选择器(a[rel = “external”])

伪类选择器(a: hover, li:nth-child)

通配符选择器( * )

        !Important > 行内式 > id > 类/伪类/属性 > 标签选择器  >  全局
    (对应权重:无穷大∞ > 1000> 100 >  10        >  1   >      0)

盒模型

一个盒子,会用 content,padding,border,margin 四部分,

标准的盒模型的宽高指的是 content 部分

ie 的盒模型的宽高包括了 content+padding+border

我们可以通过 box-sizing 修改盒模型,box-sizing border-box content-box

margin 合并

在垂直方向上的两个盒子,他们的 margin 会发生合并(会取最大的值),比如上边盒子设置margin-bottom:20px,下边盒子设置margin-top:30px;,那么两个盒子间的间距只有30px,不会是50px

解决 margin 合并,我们可以给其中一个盒子套上一个父盒子,给父盒子设置 BFC

margin 塌陷

效果: 一个父盒子中有一个子盒子,我们给子盒子设置margin-top:xxpx结果发现会带着父盒子一起移动(就效果和父盒子设置margin-top:xxpx的效果一样)

解决: 1、给父盒子设置 border,例如设置border:1px solid red; 2、给父盒子设置 BFC

BFC

块级格式化上下文 (block format context)

BFC 的布局规则 *

  • 内部的 Box 会在垂直方向,一个接一个地放置。
  • Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠。
  • 每个盒子(块盒与行盒)的 margin box 的左边,与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
  • BFC 的区域不会与 float box 重叠。
  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  • 计算 BFC 的高度时,浮动元素也参与计算。

触发 BFC 的条件 *

  • 根元素 html
  • float 的值不是 none。
  • position 的值 absoute、fixed
  • display 的值是 inline-block、table-cell、flex、table-caption 或者 inline-flex
  • overflow 的值不是 visible

解决什么问题

  1. 可以用来解决两栏布局BFC 的区域不会与 float box 重叠

    .left { float: flet; }
    .right { overflow: hidden; }
    
  2. 解决margin塌陷和margin合并问题

  3. 解决浮动元素无法撑起父元素

flex

设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效

什么是rem、px、em区别

rem是一个相对单位,rem的是相对于html元素的字体大小没有继承性

em是一个相对单位,是相对于父元素字体大小有继承性

px是一个“绝对单位”,就是css中定义的像素,利用px设置字体大小及元素的宽高等,比较稳定和精确。

响应式布局

响应式布局有哪些实现方式?什么是响应式设计?响应式设计的基本原理是什么?

1.百分比布局,但是无法对字体,边框等比例缩放

2.弹性盒子布局 display:flex

3.rem布局,1rem=html的font-size值的大小

  1. css3媒体查询 @media screen and(max-width: 750px){}

5.vw+vh

6.使用一些框架(bootstrap,vant)

什么是响应式设计:响应式网站设计是一个网站能够兼容多个终端,智能地根据不同设备环境进行相对应的布局

响应式设计的基本原理:基本原理是通过媒体查询检测不同的设备屏幕尺寸设置不同的css样式 页面头部必须有meta声明的

布局

  • 两栏布局,左边定宽,右边自适应
  • 三栏布局、圣杯布局、双飞翼布局

水平垂直居中

方法一:给父元素设置成弹性盒子,子元素横向居中,纵向居中

方法二:父相子绝后,子部分向上移动本身宽度和高度的一半,也可以用transfrom:translate(-50%,-50%)(最常用方法)

方法三:父相子绝,子元素所有定位为0,margin设置auto自适应

DOM 事件机制/模型

DOM0 级模型、IE 事件模型、DOM2 级事件模型

就比如用户触发一个点击事件,有一个触发的过程

事件捕获-阶段(从上大小,从外到内)—>处于目标事件-阶段---->事件冒泡-阶段(从下到上,从内到外)

window.addEventListener(
  "click",
  function (event) {
    event = event || window.event /*ie*/;

    const target = event.target || event.srcElement; /*ie*/ // 拿到事件目标
    
    // 阻止冒泡
    // event.stopPropagation()
    // event.cancelBubble=true; // ie
    
    // 阻止默认事件
    // event.preventDefault();
    // event.returnValue=false; // ie
  },
  /* 是否使用捕获,默认是fasle, */ fasle
);

2022年前端面试题整理,持续更新中_第1张图片

事件委托

简介:事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是

在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的

触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。

举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用

事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。

好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事

件触发机制

JavaScript

js 数据类型

8 中, ES6出的 Symbol BigInt

Number String Boolean undefined null Object Symbol BigInt

js 的基本数据类型和复杂数据类型的区别(在堆和栈中,赋值时的不同,一个拷贝值一个拷贝地址)

基本类型和引用类型在内存上存储的区别

2022年前端面试题整理,持续更新中_第2张图片

null 与 undefined 的异同

相同点:

  • Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null

不同点:

  • null 转换成数字是 0, undefined 转换数字是NaN

  • undefined 代表的含义是未定义, null 代表的含义是空对象。

  • typeof null 返回’object’,typeof undefined 返回’undefined’

  • null == undefined; // true
    null === undefined; // false
    复制代码;
    

- 其实 null 不是对象,虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

**说说 JavaScript 中判断数据类型的几种方法**

**typeof**

- `typeof`一般用来判断基本数据类型,**除了判断 null 会输出"object",其它都是正确的**
- `typeof`判断引用数据类型时,**除了判断函数会输出"function",其它都是输出"object"**

**instanceof**

> Instanceof 可以准确的判断引用数据类型,它的原理是检测构造函数的`prototype`属性是否在某个实例对象的原型链上, 不能判断基本数据类型

​```js
// instanceof 的实现
function instanceofOper(left, right) {
  const prototype = right.prototype;
  while (left) {
    if ((left = left.__proto__) === prototype) {
      return true;
    }
  }
  return false;
}
// let obj  = {}
// Object.getPrototypeOf(obj) === obj.__proto__ ==> true

// 实现 instanceof 2
function myInstanceof(left, right) {
  // 这里先用typeof来判断基础数据类型,如果是,直接返回false
  if (typeof left !== "object" || left === null) return false;
  // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
  let proto = Object.getPrototypeOf(left);
  while (true) {
    if (proto === null) return false;
    if (proto === right.prototype) return true; //找到相同原型对象,返回true
    proto = Object.getPrototypeof(proto);
  }
}

Object.prototype.toString.call() 返回 [object Xxxx] 都能判断

深拷贝和浅拷贝

let obj = { b: "xxx" };
let arr = [{ a: "ss" }, obj, 333];

// 赋值
let arr2 = arr;
// 浅拷贝-只拷贝了一层,深层次的引用还是存在
// Object.assign(), ...扩展运算符,slice等
let arr2 = arr.slice();
let arr2 = [...arr];
obj.b = "222"; // arr2[1].b => 222
// arr[2] = 4444 ==> arr2[2] ===> 333

// 深拷贝
// 最简单的
let arr2 = JSON.parse(JSON.stringify(arr));
// undefined 丢失,如果里面有 函数,还有循环引用就会报错
// 自己封装,要递归处理

实现深拷贝-简单版

export function deepClone(obj, map = new Map()) {
  if (!obj && typeof obj !== 'object') return obj
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)

  if (map.get(obj)) { //  如果有循环引用、就返回这个对象
    return map.get(obj)
  }
 
  const cloneObj = obj.constructor() // 数组的就是[],对象就是{}

  map.set(obj, cloneObj) // 缓存对象,用于循环引用的情况

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], map)
    }
  }

  return cloneObj
}

模块化

  • commonjs

    // 由nodejs实现
    const fs = require("fs");
    module.exports = {};
    
  • ESM

    // 由es6实现
    import $ from "jquery";
    export default $;
    
  • AMD(异步加载模块)

    // 由RequireJS实现
    define(["juqery", "vue"], function ($, Vue) {
      // 依赖必须一开始就写好
      $("#app");
      new Vue({});
    });
    
  • CMD

    // 由SeaJS 实现
    define(function(require, exports, module) {
        var a = require('./a')
        a.doSomething()
        // ....
        var b = require('./b') // 依赖可以就近书写
        b.doSomething()
       // ...
    })
    
  • UMD (通用加载模块)

    (function (global, factory) {
      typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
      typeof define === 'function' && define.amd ? define(factory) :
      (global = global || self, global.Vue = factory());
    }(this, function () { 'use strict';
    

AMD 和 CMD 的区别有哪些

https://blog.csdn.net/qq_38912819/article/details/80597101

  1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)
  2. CMD 推崇依赖就近,AMD 推崇依赖前置

CommonJS 与 ES6 Module 的差异

总结:CommonJS 是值拷贝,ES6是值的引用

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

  • CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
  • ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

  • 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
  • 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。

CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

JS 延迟加载的方式

JavaScript 会阻塞 DOM 的解析,因此也就会阻塞 DOM 的加载。所以有时候我们希望延迟 JS 的加载来提高页面的加载速度。

  • 把 JS 放在页面的最底部
  • script 标签的 defer 属性:脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
  • Async 是在外部 JS 加载完成后,浏览器空闲时,Load 事件触发前执行,标记为 async 的脚本并不保证按照指定他们的先后顺序执行,该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
  • 动态创建 script 标签,监听 dom 加载完毕再引入 js 文件

call、apply 、bind

call,apply, bind 都是改变 this 指向,bind 不会立即执行,会返回的是一个绑定 this 的新函数

面试官问:能否模拟实现 JS 的 call 和 apply 方法

obj.call(this指向, 参数1, 参数2)ss
obj.apply(this指向, [参数1, 参数2])

function fn(age) {
    console.log(this, age)
}
const obj = {name:''}
const result = fn.bind(obj) // bind会返回一个新的函数
result(20)
// 实现一个 apply
Function.prototype.myApply = function (context) {
  context = context || window;
  const key = Symbol();
  context[key] = this;
  var res = context[key](...arguments[1]);
  delete context[key];
  return res;
};

防抖

debounce 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

function debounce(func, wait, immediate) {
  let timeout;
  return function () {
    const context = this;
    const args = [...arguments];
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait)
      if (callNow) func.apply(context, args)
    }
    else {
      timeout = setTimeout(() => {
        func.apply(context, args)
      }, wait);
    }
  }
}

节流

就是指连续触发事件但是在 n 秒中只执行一次函数

function throttle(fn, wait) {
  let pre = 0;
  return function (...args) {
    let now = Date.now();
    if (now - pre >= wait) {
      fn.apply(this, args);
      pre = now;
    }
  };
}

闭包

闭包是指有权访问另一个函数作用域中的变量的函数 ——《JavaScript 高级程序设计》

当函数可以记住并访问所在的词法作用域时,就产生了闭包,

即使函数是在当前词法作用域之外执行 ——《你不知道的 JavaScript》

  • 闭包用途:
    1. 能够访问函数定义时所在的词法作用域(阻止其被回收)
    2. 私有化变量
    3. 模拟块级作用域
    4. 创建模块
  • 闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏

原型、原型链(高频)

原型: 对象中固有的__proto__属性,该属性指向对象的prototype原型属性。

原型链: 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是Object.prototype所以这就是我们新建的对象为什么能够使用toString()等方法的原因。

特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

this 指向、new 关键字

this对象是是执行上下文中的一个属性,它指向最后一次调用这个方法的对象,在全局函数中,this等于window,而当函数被作为某个对象调用时,this 等于那个对象。 在实际开发中,this的指向可以通过四种调用模式来判断。

  1. 函数调用,当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象。
  2. 方法调用,如果一个函数作为一个对象的方法来调用时,this指向这个对象。
  3. 构造函数调用,this指向这个用new新创建的对象。
  4. 第四种是 apply 、 call 和 bind调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,`` bind方法通过传入一个对象,返回一个this绑定了传入对象的新函数。这个函数的this指向除了使用new `时会被改变,其他情况下都不会改变。

new

面试官问:能否模拟实现 JS 的 new 操作符

  1. 首先创建了一个新的空对象
  2. 设置原型,将对象的原型设置为函数的prototype对象。
  3. 让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)
  4. 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
// new 操作符的实现
function newOperator(ctor) {
  if (typeof ctor !== "function") {
    throw "newOperator function the first param must be a function";
  }
  newOperator.target = ctor;
  var newObj = Object.create(ctor.prototype);
  var argsArr = [].slice.call(arguments, 1);
  var ctorReturnResult = ctor.apply(newObj, argsArr);
  var isObject =
    typeof ctorReturnResult === "object" && ctorReturnResult !== null;
  var isFunction = typeof ctorReturnResult === "function";
  if (isObject || isFunction) {
    return ctorReturnResult;
  }
  return newObj;
}

作用域、作用域链、变量提升

作用域负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。(全局作用域、函数作用域、块级作用域)。 作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链

继承(含 es6)、多种继承方式

function Animal(name) {
  // 属性
  this.name = name || "Animal";
  // 实例方法
  this.sleep = function () {
    console.log(this.name + "正在睡觉!");
  };
}
// 原型方法
Animal.prototype.eat = function (food) {
  console.log(this.name + "正在吃:" + food);
};

(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。

// 原型链继承
function Cat() {}
Cat.prototype = new Animal("小黄"); // 缺点 无法实现多继承 来自原型对象的所有属性被所有实例共享
Cat.prototype.name = "cat";

(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。

// 借用构造函数继承
function Cat() {
  Animal.call(this, "小黄");
  // 缺点 只能继承父类实例的属性和方法,不能继承原型上的属性和方法。
}

(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。

function object(o){
	function F(){}
	F.prototype = o;
	return new F();
}

(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。

function createAnother(original){
	var clone = object(original);//通过调用object函数创建一个新对象
	clone.sayHi = function(){//以某种方式来增强这个对象
		alert("hi");
	};
	return clone;//返回这个对象
}

(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。

function extend(subClass,superClass){
	var prototype = object(superClass.prototype);//创建对象
	prototype.constructor = subClass;//增强对象
	subClass.prototype = prototype;//指定对象
}

类型转换

大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:

-、*、/、% :一律转换成数值后计算

+:

  • 数字 + 字符串 = 字符串, 运算顺序是从左到右

  • 数字 + 对象, 优先调用对象的 valueOf -> toString

  • 数字 + boolean/null -> 数字

  • 数字 + undefined -> NaN

  • [1].toString() === ‘1’

  • {}.toString() === ‘[object object]’

  • NaN !== NaN 、+undefined 为 NaN

Object.is()与比较操作符=====的区别?

  • ==会先进行类型转换再比较
  • ===比较时不会进行类型转换,类型不同则直接返回 false
  • Object.is()===基础上特别处理了NaN,-0,+0,保证-0 与+0 不相等,但 NaN 与 NaN 相等

==操作符的强制类型转换规则

  • 字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
  • 其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
  • null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
  • 对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。
  • 如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
  • 如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false。

2022年前端面试题整理,持续更新中_第3张图片

ES6

  1. 新增 Symbol 类型 表示独一无二的值,用来定义独一无二的对象属性名;
  2. const/let 都是用来声明变量,不可重复声明,具有块级作用域。存在暂时性死区,不存在变量提升。(const 一般用于声明常量);
  3. 变量的解构赋值(包含数组、对象、字符串、数字及布尔值,函数参数),剩余运算符(…rest);
  4. 模板字符串(${data});
  5. ...扩展运算符(数组、对象);;
  6. 箭头函数;
  7. Set 和 Map 数据结构;
  8. Proxy/Reflect;
  9. Promise;
  10. async 函数;
  11. Class;
  12. Module 语法(import/export)。

let/const

const声明一个只读的常量。一旦声明,常量的值就不能改变 https://es6.ruanyifeng.com/#docs/let

var 在全局作用域中声明的变量会变成全局变量

let、const 和 var 的区别

  • 不允许重复声明

  • 不存在变量提升

    // var 的情况
    console.log(foo); // 输出undefined
    var foo = 2;
    
    // let 的情况
    console.log(bar); // 报错ReferenceError
    let bar = 2;
    
  • 暂时性死区(不能在未声明之前使用)

    注意暂时性死区和不存在变量提升不是同一个东西

    只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

    var tmp = 123; // 声明了 tmp
    
    if (true) {
      tmp = "abc"; // ReferenceError
      let tmp;
    }
    
  • 块级作用域:用 let 和 const 声明的变量,在这个块中会形成块级作用域

    es5 只有函数作用域和全局作用域

    IIFE 立即执行函数表达式

    // IIFE 写法
    (function () {
      var tmp = ...;
      ...
    }());
    
    // 块级作用域写法
    {
      let tmp = ...;
      ...
    }
    
    // 函数声明
    function a() {}
    // 函数表达式
    const b = function () {};
    

ES6 的一些叫法

  • reset 参数

    function add(...values) {
      let sum = 0;
    
      for (var val of values) {
        sum += val;
      }
    
      return sum;
    }
    
    add(2, 5, 3); // 10
    
  • 扩展运算符

    console.log(...[1, 2, 3]);
    // 1 2 3
    
    const b = { ...{ a: "2", b: "3" } };
    
  • ?. 可选链运算符

    左侧的对象是否为nullundefined。如果是的,就不再往下运算,而是返回undefined

    a?.b;
    // 等同于
    a == null ? undefined : a.b;
    // 注意 undefined == null ==> true
    
  • ?? Null 判断运算符

https://es6.ruanyifeng.com/#docs/operator#Null-%E5%88%A4%E6%96%AD%E8%BF%90%E7%AE%97%E7%AC%A6

const headerText = response.settings.headerText ?? "Hello, world!";
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;

但左侧的为 undefined或者null是就返回右边的,否则就直接返回左边的

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

  1. 箭头函数没有thisthis是继承于当前的上下文,不能通过call,apply,bind去改变 this
  2. 箭头函数没有自己的 arguments 对象,但是可以访问外围函数的 arguments对象
  3. 不能通过new 关键字调用(不能作为构造函数),同样也没有 new.target 和原型

vue

vue2 是通过Object.defineProperty来实现响应式的,所以就会有一些缺陷

  1. 当修改一个对象的某个键值属性时,当这个键值没有在这个对象中,vue 不能做响应式处理
  2. 但直接修改数组的某一项(arr[index]='xxx')vue 不能做响应式处理

可用下面的解决响应式

  1. Vue.set ==> this.$set(对象\数组, key 值、index, value)

  2. 修改数组length, 调用数据的 splice 方法

vue 生命周期

beforeCreate 实例化之前这里能拿到this,但是还不能拿到data里面的数据
created  实例化之后
beforeMount()
mounted() $el
beforeUpdate
updated

beforeDestroy 清除定时/移除监听事件
destroyed

// 被keep-alive 包裹的
// keep-alive 标签 include exclude max
activated() {},
deactivated() {},

// 父子
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。

// 离开页面:实例销毁 --> DOM卸载
parent  beforeDestroy
child   beforeDestroy
child   destroyed
parent  destroyed

动态组件

// component 动态组件,通过is设置要显示的组件
<component is="UserInfo" >

递归组件

就是给组件设置name,之后就可以在当前组件去递归使用组件

Vue 组件间的传值的几种方式

// Vue组件间的传值的几种方式
1. props/emit
2. $attrs/$listeners // $attrs 除了父级作用域 props、class、style 之外的属性
// $listeners 父组件里面的所有的监听方法
3. $refs/$parent/$children/$root/
4. vuex
5. 事件总线,通过new Vue去实现 / mitt <==> vue3
6. provide/inject
    // 父组件
    props: {},
    provide() {
        name: this.name,
        user: this.user
    }
    // 子组件
    props: {},
    inject: ['user']
7. 本地存储、全局变量

watch、mixins、组件顺序、组件配置

export default {
  name: "MyComponentName",
  mixins: [tableMixin],
  components: {},
  inject: ["xxx"],
  // props: ['value', 'visible'],
  props: {
    id: String,
    type: {
      // required: true,
      type: String,
      default: "warning",
      validator(val) {
        return ["primary", "warning", "danger", "success", "info"].includes(
          val
        );
      },
    },
    list: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      name: "张三",
      user: { name: "张三", age: 18 },
      loading: true,

      // vue2
      obj: {
        name: "李四~",
      },
      // vue2 会进行深度合并
      // obj  {"name":"李四~","age":19}

      // vue3 { name: "李四~" }
    };
  },
  // provide 不支持响应式,想支持响应式的话我们要传对象
  provide() {
    return {
      userName: this.name,
      user: this.user,
    };
  },
  computed: {
    // fullName() {
    //   return 'xxxxx'
    // }
    fullName: {
      get() {
        return this.$store.state.userName;
        // return '李四'
      },
      set(val) {
        this.$store.commit("SET_NAME", val);
      },
    },
  },

  watch: {
    // name(value) {
    //   this.handlerName()
    // }
    // name: {
    //   immediate: true,
    //   deep: true, //
    //   handler(val, oldValue) {
    //     this.handlerName()
    //   },
    // },
    // this.obj.name = 'xxxx' 这样不会执行
    // this.obj = {name: 'xxx'} 这样才会执行
    // obj(value) {
    //   console.log(' value: ', value)
    // }
    //  和上面等价
    // obj: {
    //   handler(value) {
    //     console.log(" value: ", value)
    //   },
    // },
    // this.obj.name = 'xxxx' 这样去修改也能监听
    // obj: {
    //   deep: true, // 深度监听
    //   immediate: true, // 第一次就用执行这个方法,可以理解为在 created 的时候会执行 handler
    //   handler(value) {
    //     console.log(" value: ", value)
    //   },
    // },
    //
    // obj: {
    //   deep: true, // 深度监听
    //   immediate: true, // 第一次就用执行这个方法,可以理解为在 created 的时候会执行 handler
    //   handler: 'handlerName',
    // },
    // ==》
    // obj: 'handlerName'
    // '$route.path': {},
    // 'obj.a' : {}
  },

  beforeCreate() {
    console.log("this", this);
  },
  mounted() {
    // this.handlerName()
    this.fullName = "xxxx";

    //  this.fullName '李四'
  },

  methods: {
    handlerName() {
      this.obj.name = "xxxx";
    },
  },
};

指令

常用指令

  • v-show dispaly none 的切换

  • v-if/v-else

  • v-html

  • v-text

  • v-for (vue2 v-forv-if优先级高,vu3v-if优先级比v-for高 )

  • v-cloak [v-cloak] {dispaly:none}

  • v-once 静态内容

  • v-bind=> : v-on => @

    
    <Child v-bind="$attrs" v-on="$listeners">Child>
    
  • v-model

    <el-input v-model="keyword">el-input>
    
    <el-input :value="keyword" @input="keyword = $event">el-input>
    
Vue.directive("指令名", {
  // 生命周期
  // 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  bind(el, binding, vnode, oldVnode) {
    //
    // binging.value 拿到指令值
    // binding.modifiers 修饰符对象
  },
  // 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
  inserted() {},
  update() {},
  componentUpdated() {},
  // 只调用一次,指令与元素解绑时调用
  unbind() {},
});

// 默认绑定 bind update 的生命周期
Vue.directive("指令名", function (el, binding, vnode, oldVnode) {});

修饰符

  • .lazy、.number、.trim、.enter、.prevent、.self

  • .sync

    <Dialog  :visible.sync="visible">Child>
    
    <Dialog  :visible="visible" @update:visible="visible = $event">Child>
    

scoped

加了 scoped 就只作用于当前组件

<style scoped>style>

渲染规则

.a .b {}  ==> .a  .b[data-v-xx]{}
.a /deep/ .b {} ==> .a[data-v-xxx] .b {}
.a >>> .b {} ==> .a[data-v-xxx] .b {}
.a ::v-deep .b {} ==> .a[data-v-xxx] .b {}

vue-router

// 全局路由守卫
router.beforeEach((to, from, next) => {})
router.afterEach((to, from) => {})

new VueRouter({
    mode: 'hash', // hash | history | abstract
    // 滚动位置
    scrollBehavior(to, from, savedPosition) {
       if (savedPosition) return savedPosition
       return { y: 0 }
	},
    routes: [
        {
            path: '/',
            // 路由独享守卫
            beforeEnter(to, from, next) {}
        }
    ]
})

// 组件内的路由
beforeRouteEnter(to, from, next) {}
beforeRouteUpdate(to, from, next) {}
beforeRouteLeave(to, from, next) {}

// 跳转
this.$router.push({name: '', path: '', query: {}})
// 路由信息
this.$route.query this.$route.params

vuex

state getters mutations actions modules

// state
this.$store.state.userInfo;
// getters
this.$store.getters.userInfo;

// mutations
this.$store.commit("SET_USER_INFO", "传递数据");

// actions
this.$store.dispatch("logout").then((res) => {});

// -----------------------------------
// modules > user
// namespaced: true,

// state 拿 name
this.$store.state.user.avatar;
// getters
this.$store.getters.user.avatar;

// mutations
this.$store.commit("user/SET_TOKEN", "传递数据");

// actions
this.$store.dispatch("user/login").then((res) => {});

// -----------------------------------
// modules > user
// namespaced: false,

// state 拿 name
this.$store.state.user.avatar;
// getters
this.$store.getters.user.avatar;

// mutations
this.$store.commit("SET_TOKEN", "传递数据");

// actions
this.$store.dispatch("login").then((res) => {});

辅助函数

mapState, mapGetters, mapMutations, mapActions;

vue3

Vue3 的 8 种和 Vue2 的 12 种组件通信,值得收藏

聊一聊 Vue3 的 9 个知识点

Vue3 有哪些变化

  • 新增了三个组件:Fragment 支持多个根节点、Suspense 可以在组件渲染之前的等待时间显示指定内容、Teleport 可以让子组件能够在视觉上跳出父组件(如父组件 overflow:hidden)
  • 新增指令 v-memo,可以缓存 html 模板,比如 v-for 列表不会变化的就缓存,简单说就是用内存换时间
  • 支持 Tree-Shaking,会在打包时去除一些无用代码,没有用到的模块,使得代码打包体积更小
  • 新增 Composition API 可以更好的逻辑复用和代码组织,同一功能的代码不至于像以前一样太分散,虽然 Vue2 中可以用 minxin 来实现复用代码,但也存在问题,比如方法或属性名会冲突,代码来源也不清楚等
  • Proxy 代替 Object.defineProperty 重构了响应式系统,可以监听到数组下标变化,及对象新增属性,因为监听的不是对象属性,而是对象本身,还可拦截 apply、has 等 13 种方法
  • 重构了虚拟 DOM,在编译时会将事件缓存、将 slot 编译为 lazy 函数、保存静态节点直接复用(静态提升)、以及添加静态标记、Diff 算法使用 最长递增子序列 优化了对比流程,使得虚拟 DOM 生成速度提升 200%
  • 支持在 里使用 v-bind,给 CSS 绑定 JS 变量(color: v-bind(str))
  • setup 代替了 beforeCreate 和 created 这两个生命周期
  • 新增了开发环境的两个钩子函数,在组件更新时 onRenderTracked 会跟踪组件里所有变量和方法的变化、每次触发渲染时 onRenderTriggered 会返回发生变化的新旧值,可以让我们进行有针对性调试
  • 毕竟 Vue3 是用 TS 写的,所以对 TS 的支持度更好
  • Vue3 不兼容 IE11

vue3 生命周期

选项式 API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated

基本代码

main.js

// main.js
import { createApp } from "vue";
import App from "./App.vue";

import HelloWorld from "./components/HelloWorld.vue";
const app = createApp(App);

// 全局组件
app.component("HelloWorld", HelloWorld);

// 全局属性
// vue2.0 Vue.prototype.$http
app.config.globalProperties.$http = () => {
  console.log("http ==");
};

app.mount("#app");

App.vue


<template>
  <div>
    
    
    

    

    

    

    
    <Child
      title="父组件传递的title"
      :modelValue="num"
      @update:modelValue="num = $event"
      @change="onChildChange"
      v-model:visible="visible"
      ref="childRef"
    >Child>
    
  div>
template>

<script>
  import Child from "./Child-setup.vue";
  import { reactive, ref } from "@vue/reactivity";
  import { onMounted, provide } from "@vue/runtime-core";
  export default {
    components: { Child },
    // data() {
    //   return {
    //     msg: '哈哈哈',
    //   }
    // },
    setup() {
      const msg = ref("哈哈哈2"); // => reactive({value: 哈哈哈2 })
      const obj = ref({ x: "xx" });
      console.log(" obj.value: ", obj.value);
      const user = reactive({ name: "张三", age: 18 });
      const count = ref(0);

      provide("count", count);
      provide("http", () => {
        console.log("$http >>>");
      });

      const add = () => {
        count.value++;
      };

      const reduce = () => {
        count.value--;
      };

      const num = ref(1);
      const visible = ref(false);

      // this.$refs.childRef x
      // refs
      // 1. 用字符串
      const childRef = ref(null);
      onMounted(() => {
        console.log(" childRef.value: ", childRef.value);
      });

      let divRef;
      const setDivRef = (el) => {
        console.log(" el: ", el);
        divRef = el;
      };

      return {
        msg,
        user,
        count,
        add,
        reduce,
        num,
        visible,
        childRef,
        setDivRef,
      };
    },

    methods: {
      onChildChange() {},
    },
  };
script>

<style>
  #app {
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
  }
style>

Child-composition (组合式 api)

<template>
  
  <div>
    <button @click="triggerEvent">触发事件button>

    <div>num2:{{ num2 }}div>
    <div>count:{{ count }}div>

    modelValue:{{ modelValue }}
    <button @click="add">+button>
    <hr />
    visible:{{ visible }}
    <button @click="updateVisible">更新visiblebutton>

    
    <teleport to="body">
      <div v-if="visible">对话框div>
    teleport>
  div>
template>

<script>
  import {
    computed,
    inject,
    onActivated,
    onBeforeMount,
    onBeforeUnmount,
    onBeforeUpdate,
    onDeactivated,
    onMounted,
    onUnmounted,
    onUpdated,
    watch,
    watchEffect,
  } from "@vue/runtime-core";
  export default {
    props: {
      title: String,
      modelValue: Number,
      visible: Boolean,
    },
    // computed: {
    //   num2() {
    //     return this.modelValue * 2
    //   }
    // },
    emits: ["change", "update:modelValue", "update:visible"],
    // 发生在 beforeCreate
    // attrs 除了 class style,props 之外的属性
    //

    // watch: {
    //   title: {
    //     deep: true, // 深度简单

    //   }
    // },
    // 组合式API(composition),  选项式API(options)
    setup(props, { emit, attrs, slots }) {
      console.log(" attrs: ", attrs);
      console.log(" props: ", props);

      // computed
      const num2 = computed(() => props.modelValue * 2);
      // const num2 = computed({
      //   get: () => props.modelValue * 2,
      //   set: (val) => {
      //     ssss
      //   }
      // })

      //
      const count = inject("count");
      console.log(" count: ", count);

      // watch
      // this.$watch()
      const unwatch = watch(
        () => props.modelValue,
        (newVal, oldValue) => {
          console.log(" newVal: ", newVal);
          if (newVal >= 10) {
            // 取消监听
            unwatch();
          }
        },
        {
          deep: true,
          // immediate: true
        }
      );

      // 自动收集依赖,所以会初始化的时候就执行一次
      watchEffect(() => {
        console.log(" props.modelValue: ", props.modelValue);
      });

      // hooks
      onBeforeMount(() => {});
      onMounted(() => {
        console.log("哈哈哈");
      });
      onBeforeUpdate(() => {});
      onUpdated(() => {});
      onBeforeUnmount(() => {});
      onUnmounted(() => {});

      // keep-alive
      onActivated(() => {});
      onDeactivated(() => {});

      // methods
      const triggerEvent = () => {
        emit("change", "传递的数据");
      };

      const add = () => {
        emit("update:modelValue", props.modelValue + 1);
      };
      const updateVisible = () => {
        console.log(" props.visible: ", props.visible);
        emit("update:visible", !props.visible);
      };

      return {
        triggerEvent,
        add,
        updateVisible,
        num2,
        count,
      };
    },
    // beforeCreate() {
    //   console.log('beforeCreate')
    // },
    // created() {
    //   console.log('created')
    // },

    // beforeDestroy beforeUnmount
    // destroyed unmounted
  };
script>

Child-setup

<template>
  <div>
    <p>title: {{ title }}p>
    <p>num2: {{ num2 }}p>
    <p>count: {{ count }}p>

    <div>
      modelValue:{{ modelValue }}
      <button @click="add">+button>
    div>

    <button @click="triggerEvent">触发事件button>

    
    
    <input type="text" :value="inputValue" @input="onInputUpdate" />

    
    <Foo>Foo>
  div>
template>


<script setup>
  import {
    computed,
    getCurrentInstance,
    inject,
    ref,
    useAttrs,
    useSlots,
  } from "@vue/runtime-core";
  import Foo from "./foo.vue";

  // props
  const props = defineProps({
    title: String,
    modelValue: Number,
  });
  // computed
  const num2 = computed(() => props.modelValue * 2);
  const count = inject("count");

  // emit
  const emit = defineEmits(["change", "update:modelValue", "update:visible"]);
  const triggerEvent = () => {
    emit("change", "传递的数据");
  };
  const add = () => {
    emit("update:modelValue", props.modelValue + 1);
  };

  // 向父组件暴露自己的属性和方法
  defineExpose({
    num2,
    test() {
      console.log("888");
    },
  });

  const attrs = useAttrs();
  console.log(" attrs: ", attrs);
  const solts = useSlots();

  const ctx = getCurrentInstance();

  const http = ctx.appContext.config.globalProperties.$http;
  http("xxx");

  const $http = inject("http");
  $http();

  // $ref: ref(false)

  const inputValue = ref("");

  const onInputUpdate = (event) => {
    console.log(" event: ", event);
    inputValue.value = event.target.value;
  };
script>

项目相关

git 常用命令

https://shfshanyue.github.io/cheat-sheets/git

Webpack 一些核心概念:

【万字】透过分析 webpack 面试题,构建 webpack5.x 知识体系

  • Entry:入口,指示 Webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。

  • Output:输出结果,告诉 Webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。

  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。

  • Loader:模块代码转换器,让 webpack 能够去处理除了 JS、JSON 之外的其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

  • Plugin:扩展插件。在 webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 api 改变输出结果。常见的有:打包优化,资源管理,注入环境变量。

  • Mode:模式,告知 webpack 使用相应模式的内置优化


  • hash: 每次构建的生成唯一的一个 hash,且所有的文件 hash 串是一样的
  • chunkhash: 每个入口文件都是一个 chunk,每个 chunk 是由入口文件与其依赖所构成,异步加载的文件也被视为是一个 chunk, chunkhash是由每次编译模块,根据模块及其依赖模块构成 chunk 生成对应的 chunkhash, 这也就表明了每个 chunk 的 chunkhash 值都不一样, 也就是说每个 chunk 都是独立开来的,互不影响,每个 chunk 的更新不会影响其他 chunk 的编译构建

  • contenthash:由文件内容决定,文件变化 contenthash 才会变化,一般配合 mini-css-extract-plugin插件提取出 css

    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const HTMLWebpackPlugin = require("html-webpack-plugin");
    
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              {
                // 把 style-loader替换掉,不要使用 style-loader了
                loader: MiniCssExtractPlugin.loader,
                options: {
                  outputPath: "css/",
                },
              },
              "css-loader",
            ],
          },
        ],
      },
      plugins: [
        // ....
        new MiniCssExtractPlugin({
          filename: "css/[name].[contenthash].css",
        }),
      ],
    };
    

提升 webpack 打包速度

一套骚操作下来,webpack 项目打包速度飞升 、体积骤减 ↓

玩转 webpack,使你的打包速度提升 90%

带你深度解锁 Webpack 系列(优化篇)

学习 Webpack5 之路(优化篇)- 近 7k 字

  • 速度分析,可以使用 speed-measure-webpack-plugin

  • 提升基础环境,nodejs 版本,webpack 版本

  • CDN 分包 html-webpack-externals-plugin, externals

  • 多进程、多实例构建 thread-loader happypack(不再维护)

  • 多进程并行构建打包uglifyjs-webpack-plugin terser-webpack-plugin

  • 缓存: webpack5 内置了cache模块 、babel-loadercacheDirectory 标志、cache-loader, HardSourceWebpackPlugin

    module.exports = {
        // webpack5内置缓存
        cache: {
          type: 'filesystem', // 使用文件缓存
        },
    }
    
  • 构建缩小范围 include,exclude

  • 加快文件查找速度resolve.alias,resolve.extensions, module.noParse

  • DllPlugin

  • babel配置的优化

webpack 常用 loader,plugin

loader

  • babel-loaderes6 转换成 es5 , ts-loadervue-loader
  • eslint-loader 配置 enforce: 'pre' 这个loader最先执行
  • css-loaderstyle-loaderpostcss-loaderless-loadersass-loader
  • file-loader 把文件转换成路径引入, url-loader(比file-loader多了小于多少的能转换成 base64)
  • image-loader
  • svg-sprite-loader 处理 svg
  • thread-loader 开启多进程 ,会在一个单独的 worker 池(worker pool)中运行
  • cache-loader 缓存一些性能开销比较大的 loader 的处理结果

plugin

  • html-webpack-plugin 将生成的css,js自动注入到html文件中,能对html文件压缩

  • copy-webpack-plugin 拷贝某个目录

  • clean-webpack-plugin 清空某个目录

  • webpack.HotModuleReplacementPlugin 热重载

  • webpack.DefinePlugin 定义全局变量

  • mini-css-extract-plugin 提取 CSS 到独立 bundle 文件。

  • optimize-css-assets-webpack-plugin 压缩css

  • purgecss-webpack-plugin 会单独提取 CSS 并清除用不到的 CSS

  • uglifyjs-webpack-plugin ❌(不推荐) 压缩js、多进程 parallel: true

  • terser-webpack-plugin 压缩js, 可开启多进程压缩、推荐使用

    module.exports = {
      optimization: {
        minimize: true,
        minimizer: [
          new TerserPlugin({
            parallel: true, // 多进程压缩
          }),
        ],
      },
    };
    
  • Happypack ❌(不再维护) 可开启多进程

  • HardSourceWebpackPlugin 缓存

  • speed-measure-webpack-plugin 打包构建速度分析、查看编译速度

  • webpack-bundle-analyzer打包体积分析

  • compression-webpack-plugin gzip 压缩

前端性能优化

前端性能优化 24 条建议(2020)

  1. 减少 http 请求
  2. 使用 http2
  3. 静态资源使用 CDN
  4. 将 CSS 放在文件头部,JavaScript 文件放在底部
  5. 使用字体图标 iconfont 代替图片图标
  6. 设置缓存,强缓存,协商缓存
  7. 压缩文件,css(MiniCssExtractPlugin),js(UglifyPlugin),html(html-webpack-plugin)文件压缩,清除无用的代码,tree-shaking(需要 es6 的 import 才支持),gzip 压缩(compression-webpack-plugin)
  8. splitChunks 分包配置,optimization.splitChunks 是基于 SplitChunksPlugin 插件实现的
  9. 图片优化、图片压缩
  10. webpack 按需加载代码,hashcontenthash
  11. 减少重排重绘
  12. 降低 css 选择器的复杂性

babel

不容错过的 Babel7 知识

核心库 @babel/core

Polyfill 垫片

CLI 命令行工具 @babel/cli

插件

预设:包含了很多插件的一个组合,@babel/preset-env @babel/preset-react @babel/preset-typescript

polyfill

Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如IteratorGeneratorSetMapProxyReflectSymbolPromise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。

举例来说,ES6 在Array对象上新增了Array.from方法。Babel 就不会转码这个方法。如果想让这个方法运行,可以使用core-jsregenerator-runtime(后者提供 generator 函数的转码),为当前环境提供一个垫片。

@babel/plugin-transform-runtime

Babel 会使用很小的辅助函数来实现类似 _createClass 等公共方法。默认情况下,它将被添加(inject)到需要它的每个文件中。

如果你有 10 个文件中都使用了这个 class,是不是意味着 _classCallCheck_defineProperties_createClass 这些方法被 inject 了 10 次。这显然会导致包体积增大,最关键的是,我们并不需要它 inject 多次。

@babel/plugin-transform-runtime 是一个可以重复使用 Babel 注入的帮助程序,以节省代码大小的插件。

npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
//.babelrc
{
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage", // 配置 polyfill 动态导入
                "corejs": 3 // core-js@3
            }
        ]
    ],
    "plugins": [
        [
            "@babel/plugin-transform-runtime"
        ]
    ]
}

浏览器\编译原理\底层

垃圾回收机制

  • 标记清除: 进入环境、离开环境
  • 引用计数(不常用):值被引用的次数, 当引用次数为零时会被清除(缺陷,相互引用的会有问题)

缓存机制

强缓存

如果命中强缓存–就不用像服务器去请求

  1. Expires 设置时间,过期时间 expires: Tue, 15 Oct 2019 13:30:54 GMT

    通过本地时间和 expires 比较是否过期,如果过期了就去服务器请求,没有过期的话就直接使用本地的

    缺点:本地时间可能会更改, 导致缓存出错

  2. Cache-Control HTTP1.1 中新增的

    • max-age 最大缓存多少毫秒,列如 Cache-Control: max-age=2592000

    • no-store (每次都要请求,就连协商缓存都不走)表示不进行缓存,缓存中不得存储任何关于客户端请求和服务端响应的内容。每次 由客户端发起的请求都会下载完整的响应内容。Cache-Control: no-store

    • no-cache(默认值)表示不缓存过期的资源,缓存会向源服务器进行有效期确认后处理资源,也许称 为 do-notserve-from-cache-without-revalidation 更合适。浏览器默认开启的是 no-cache,其 实这里也可理解为开启协商缓存

    • public 和 private

      public 与 private 是针对资源是否能够被代理服务缓存而存在的一组对立概念

      当我们为资源设置了 pubile,那么它既可以被浏览器缓存也可被代理服务器缓存。设置为

      private 的时候,则该资源只能被浏览器缓存,其中默认值是 private。

    • max-age 和 s-maxage

      s-maxage 只适用于供多用户使用的公共服务器上(如 CND cache),并只对 public 缓存有效

协商缓存

需要向服务器请求,如果没有过期,服务器会返回 304,

  1. ETag 和 If-None-Match 唯一标识
  • 服务器响应 ETag 值,浏览器携带的是 If-None-Match(携带的是上一次响应的 ETag),服务拿到这 If-None-Match 值后判断过期–> 没有过期 304,并且返回 ETag


    二者的值都是服务器为每份资源分配的唯一标识字符串。

    • 浏览器请求资源,服务器会在响应报文头中加入 ETag 字段。资源更新的时候,服务端的

    ETag 值也随之更新

    • 浏览器再次请求资源,会在请求报文头中添加 If-None-Match 字段,它的值就是上次响应

    报文中的 ETag 值,服务器会对比 ETag 和 If-None-Match 的值是否一致。如果不一致,服务

    器则接受请求,返回更新后的资源,状态码返回 200;如果一致,表明资源未更新,则返回

    状态码 304,可继续使用本地缓存,值得注意的是此时响应头会加上 ETag 字段,即使它没

    有变化


  • Last-Modified 和 If-Modified-Since 时间戳
    缺点: 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说 1s 内修改了 N 次),

    If-Modified-Since 可查到的是秒级,这种修改无法判断

预编译

四部曲

  1. 创建AO对象
  2. 找形参和变量声明,将变量和形参名作为AO的属性名,值为undefined
  3. 将实参值和形参值相统一
  4. 在函数体里面找到函数声明,值赋予函数体
// 预编译
function foo(test/* 形参 */) {
    console.log(' test: ', test) // function(){}
    var test = 2
    var str = 'bs'
    console.log(' test: ', test) // 2
    // 函数声明
    function test() {}
	// 函数表达式
    str = function() {}
    console.log(' test: ', test) // 2
}

// 预编译 四部曲
// 1. 创建AO对象
// 2. 找形参和变量声明,将变量和形参名作为AO的属性名,值为undefined
// 3. 将实参值和形参值相统一
// 4. 在函数体里面找到函数声明,值赋予函数体

// AO {
//   test: undefined
//   str: undefined
// }
// AO {
//   test: 1
//   str: undefined
// }
// AO {
//   test: 1
//   str: function() {}
// }


foo(1/*实参*/)

function foo (a, b, c) {
    console.log(a)
    console.log(b)
    var a = '222'
    function a() {}
    var b = function() {}
    console.log(a)
    console.log(b)

    console.log(' a, b, c: ', a, b, c)
}
// AO {
//   a : '222',
//   b : function() {},
//   c : 3
// }
foo(1, 2, 3)

var a = 22
// let a = 22
// window.a ==> 22

全局

  1. 创建 GO 对象==window
  2. 变量声明,将变量作为 GO 的属性名,值为undefined
  3. 找到函数声明,值赋予函数体

event-loop(事件循环)

一次弄懂Event Loop(彻底解决此类面试问题)

JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.thenMutationObserver,宏任务的话就是setImmediate setTimeout setInterval

MacroTask(宏任务)*

  • script全部代码、setTimeoutsetIntervalsetImmediate(浏览器暂时不支持,只有 IE10 支持,具体可见MDN)、I/OUI Rendering

MicroTask(微任务)

  • Process.nextTick(Node独有)Promise.thenObject.observe(废弃)MutationObserver

浏览器中

执行完一个宏任务,会执行所有的微任务

console.log("script start");

setTimeout(function () {
  console.log("setTimeout");
}, 0);

new Promise((resolve) => {
  console.log("promise1");
  resolve();
}).then(function () {
  console.log("promise2");
});
console.log("script end");

执行结果

script start
promise1
script end
promise2
setTimeout

nodejs 中

在 11 之前的版本,会在每个阶段之后执行所有的微任务

在 11 版本及之后,会每执行完一个宏任务,就会清空所用的微任务(和浏览器保存一致)

new Promise((resolve) => {
  console.log("new Promise 1");
  resolve();
}).then(() => {
  console.log("new Promise then");
});

setTimeout(() => {
  console.log("timer1");
  new Promise((resolve) => {
    console.log("timer1 new Promise");
    resolve();
  }).then(() => {
    console.log("timer1 new Promise then");
  });
  Promise.resolve().then(() => {
    console.log("timer1 Promise then");
  });
});

setTimeout(() => {
  console.log("timer2");
  Promise.resolve().then(() => {
    console.log("timer2 Promise then");
  });
});

console.log("start end");

在 node11 版本之前(不包含 11)

new Promise 1
start end
new Promise then
timer1
timer1 new Promise
timer2
timer1 new Promise then
timer1 Promise then
timer2 Promise then

在 node11 版本及之后

new Promise 1
start end
new Promise then
timer1
timer1 new Promise
timer1 new Promise then
timer1 Promise then
timer2
timer2 Promise then

2022年前端面试题整理,持续更新中_第4张图片

NodeEvent loop一共分为6个阶段,每个细节具体如下:

  1. timers: 执行setTimeoutsetInterval中到期的callback
  2. pending callback: 上一轮循环中少数的callback会放在这一阶段执行。
  3. idle, prepare: 仅在内部使用。
  4. poll: 最重要的阶段,执行pending callback,在适当的情况下回阻塞在这个阶段。
  5. check: 执行setImmediate(setImmediate()是将事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行setImmediate指定的回调函数)的callback
  6. close callbacks: 执行close事件的callback,例如socket.on('close'[,fn])或者http.server.on('close, fn)

网络

TCP

面试官,不要再问我三次握手和四次挥手

三次握手

2022年前端面试题整理,持续更新中_第5张图片

为什么需要三次握手,两次不可以吗

为了防止失效的连接请求又传送到主机,因而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请
求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于 TCP 的客
户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条
报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。
此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失
效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不
必要的错误和资源的浪费。

如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了
那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器
收不到确认,就知道客户端并没有请求连接。

四次挥手

2022年前端面试题整理,持续更新中_第6张图片

挥手为什么需要四次?

因为当服务端收到客户端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当服务端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉客户端,“你发的 FIN 报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四次挥手。

2MSL等待状态

TIME_WAIT 状态也成为2MSL等待状态。每个具体 TCP 实现必须选择一个报文段最大生存时间 MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为 TCP 报文段以 IP 数据报在网络内传输,而 IP 数据报则有限制其生存时间的 TTL 字段。

对一个具体实现所给定的 MSL 值,处理的原则是:当 TCP 执行一个主动关闭,并发回最后一个 ACK,该连接必须在 TIME_WAIT 状态停留的时间为 2 倍的 MSL。这样可让 TCP 再次发送最后的 ACK 以防这个 ACK 丢失(另一端超时并重发最后的 FIN)。

这种 2MSL 等待的另一个结果是这个 TCP 连接在 2MSL 等待期间,定义这个连接的插口(客户的 IP 地址和端口号,服务器的 IP 地址和端口号)不能再被使用。这个连接只能在 2MSL 结束后才能再被使用。

HTTP 版本

https(http + ssl/tls)

http: 最广泛网络协议,BS 模型,浏览器高效。

https: 安全版,通过 SSL 加密,加密传输,身份认证,密钥

1 https 相对于 http 加入了 ssl 层, 加密传输, 身份认证;

2 需要到 ca 申请收费的证书;

3 安全但是耗时多,缓存不是很好;

4 注意兼容 http 和 https;

5 连接方式不同, 端口号也不同, http 是 80, https 是 443

  • 明文: 普通的文本

  • 密钥:把明文加密的那个钥匙

  • 密文: 把明文加密

    明文+密钥==>密文==>密钥==解密=>明文

  • 对称加密 解密的 key(密钥)和解密的 key 是同一个 3 + 1

  • 非对称加密 私钥和公钥

2022年前端面试题整理,持续更新中_第7张图片

手写

10个常见的前端手写功能,你全都会吗

event bus 事件总线 | 发布订阅模式

// event bus

class EventBus {
    constructor()  {
        this.events = {}
    }
    on(name, callback) {
        const { events } = this
        if (!events[name]) {
            events[name] = []
        }
        events[name].push(callback)
    }
    emit(name, ...args) {
        const handlers = this.events[name]
        handlers && handlers.forEach(fn => {
            fn.apply(this, args)
        })
    }
    off(name, callback) {
        const { events } = this
        if (!events[name]) return
        events[name] = events[name].filter(fn => fn !== callback)
    }
    once(name, callback) {
        const handler = function() {
            callback.apply(this, arguments)
            this.off(name, handler)
        }
        this.on(name, handler)
    }
    clear() {
        this.events = {}
    }
}


数据偏平化

// 数据偏平化
function flatter(arr) {
    return arr.reduce((prev, curr) => {
        return Array.isArray(curr) ? 
            [...prev, ...flatter(curr)]
           : [...prev, curr]
    }, [])
}

手写 new

// 手写 new
function myNew(ctr, ...args) {
    const obj = Object.create(ctr.prototype)
    myNew.target = ctr
    const result = ctr.apply(obj, args)
    if (result && (typeof result === 'function' || typeof result === 'function')) {
        return result
    }
    return obj
}

call、bind

Function.prototype.myCall = function(context, ...args) {
    context = context || window
    const fn = Symbol()
    context[fn] = this
    const result = context[fn](...args)
    delete context[fn]
    return result
}



//bind实现要复杂一点  因为他考虑的情况比较多 还要涉及到参数合并(类似函数柯里化)

Function.prototype.myBind = function (context, ...args) {
  if (!context || context === null) {
    context = window;
  }
  // 创造唯一的key值  作为我们构造的context内部方法名
  let fn = Symbol();
  context[fn] = this;
  let _this = this;
  //  bind情况要复杂一点
  const result = function (...innerArgs) {
    // 第一种情况 :若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符使用,则不绑定传入的 this,而是将 this 指向实例化出来的对象
    // 此时由于new操作符作用  this指向result实例对象  而result又继承自传入的_this 根据原型链知识可得出以下结论
    // this.__proto__ === result.prototype   //this instanceof result =>true
    // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
    if (this instanceof _this === true) {
      // 此时this指向指向result的实例  这时候不需要改变this指向
      this[fn] = _this;
      this[fn](...[...args, ...innerArgs]); //这里使用es6的方法让bind支持参数合并
    } else {
      // 如果只是作为普通函数调用  那就很简单了 直接改变this指向为传入的context
      context[fn](...[...args, ...innerArgs]);
    }
  };
  // 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
  // 实现继承的方式: 使用Object.create
  result.prototype = Object.create(this.prototype);
  return result;
};

异步控制并发数

function limitRequest(requests, limit = 3) {
  requests = requests.slice()
  return new Promise((resolve, reject) => {
    let count = 0
    const len = requests.length
    while (limit > 0) {
      start()
      limit--
    }

    function start() {
      const promiseFn = requests.shift()

      promiseFn?.().finally(() => {
        count++ // 一定要通过 count 判断、不能通过 requests.length 判断是否为空,这样不对的
        if (count === len) {
          // 最后一个
          resolve()
        } else {
          start()
        }
      })
    }
  })
}
// 测试
const arr = []
for (let value of '12345') {
  arr.push(() => fetch(`https://www.baidu.com/s?ie=UTF-8&wd=${value}`))
}
limitRequest(arr)

你可能感兴趣的:(面试,前端,html5,javascript,vue.js,css3)