前端知识总结

2/19/2022
  • HTML 语义化

在 H5 中增加了许多语义化标签,例如asidenavheadermainsectionfooter

这些语义化标签优点:

  1. 代码结构更加清晰,增加了代码可读性
  2. 更好的SEO
  • 原生HTML怎么实现复用
  1. 使用iframeobject等标签,导入相应的html
  2. 使用JS插入
  3. 使用Web Components进行原生组件化开发

  • scriptdeferasync的区别

相同点:立即下载,但不阻塞,外部文件引用

区别:

defer: 延迟脚本。在整个页面解析完毕后执行,不阻塞HTML解析,H5规范中要求顺序执行,先于DOMContentLoaded事件,但现实中不保证执行顺序

async: 异步脚本。有可能阻塞HTML解析,一定在load事件前执行,但可能在DOMContentLoaded事件触发之前或之后执行(注意先DOMContentLoadedload


#浏览器

  • 从浏览器地址栏输入url到请求返回发生了什么?

URL 的组成:协议、主机、端口、路径、查询参数、锚点

  1. 域名解析成 IP 地址(使用 DNS 服务器)
  2. TCP 连接(TCP 三次握手)

在发送之前,根据请求头的expirescache-control判断是否过期(强缓存),如果没有过期,则直接获取缓存

  1. 发送 HTTP 请求
  2. 服务器处理请求并返回 HTTP 报文

在返回 HTTP 报文时分为两种情况:

一:如果请求头的If-Modified-SinceIf-None-Match判断命中了协商缓存,那么会直接从缓存中取,标志是响应码一般为 304

二:如果没有命中协商缓存,则后台返回内容

  1. 浏览器解析渲染页面
  2. 断开连接(TCP四次挥手)

  • GETPOST的区别
  1. GET请求只能进行URL编码,而POST支持多种编码方式
  2. GET请求在URL中传输的参数是有长度限制的,而POST没有
  3. 对参数的数据类型,GET只接受ASCII字符,而POST没有限制
  4. GETPOST更不安全
  5. GET参数通过URL传递,POST放在Request body

  • httphttps的区别
  1. HTTP是明文传输,数据都是未加密的,安全性较差,HTTPS数据传输过程是加密的,安全性较好
  2. HTTP的响应速度快于HTTPS,是因为TLS在加密的过程中需要握手导致时间会慢于http
  3. HTTPS是基于HTTP的基础上加上SSL/TLS的协议,更耗费服务器资源

SSL/TLS加密使用的是对称加密,使用了非对称加密来加密对称密钥

#CSS

  • 盒模型

两种:标准盒模型以及 IE(替代)盒模型

CSS 中 box-sizing 表现为 content-box 和 border-box。

在标准盒模型中设置 width 作用于 content,实际容器宽度是 width + padding + border,而在 IE 盒模型中设置的宽度就是盒子的实际宽度


  • CSS 选择器和优先级

选择器: id、class、attr(属性)、name(标签名)、_
优先级:!important > style > id > class > attr > name > *

可以假设!important 权重为无穷大、style=""为1000、id为100、class为10、元素选择器1、通配符0


  • 重排和重绘

重排(reflow):也叫回流,是更新元素的几何属性后重现触发布局的过程

常见的方式:

  1. 元素尺寸变化
  2. DOM 元素添加或删除
  3. 元素位置变化
  4. 浏览器窗口尺寸变化
  5. 元素内容发生变化
  6. 当获取元素的尺寸时也会发生重排现象

重绘(repaint):当知道几何尺寸变化后,浏览器进入图层绘制阶段,渲染出屏幕的实际尺寸

一般发生在元素颜色、透明度等发生改变,不会影响界面布局

合成:直接跳过布局和图层绘制的阶段,在非主线程上执行更新,如 CSS 的transform属性


  • 对 BFC 的理解

块级格式化上下文(Block Formatting context)

它是 CSS 视觉渲染的一部分,用来决定块级盒的布局及浮动相互影响范围的一个区域

触发 BFC 的方式:

  1. position 不为 relative 和 static
  2. display 值为 table-cell、table-caption、inline-block、flex
  3. overflow 的值为 auto、scroll 或 hidden
  4. float 的值不为 none
  5. html 根元素

影响

  1. 清除浮动的影响,避免父元素塌陷
  2. 子元素与外部元素不发生margin合并

  • 实现两栏布局
  1. flex
  2. grid
  3. float

  • 实现垂直居中的多种实现方式
  1. flex 布局align-items: center;+justify-content: center;
  2. postion:absolute布局,lefttopmargin
  3. postion:absolute布局,lefttoptransform
  4. grid 布局
  5. 一行文本text-alignline-height
  • flex 布局问题

flex 属性是flex-growflex-shrinkflex-basis的缩写

  • flex-grow:定义项目的放大比例,默认为 0,如果存在剩余空间,也不放大

  • flex-shrink: 定义项目的缩小比例,默认为 1,如果空间不足,项目将等比例缩小

  • flex-basis: 设置单个项目的宽度,功能类似于width,默认值为auto

  • flex: none:0 0 auto

  • flex: 1:1 1 0%

  • flex: auto:1 1 auto

  • flex: 0:0 1 0%

与flex布局相关的属性:

.box {
  // 父元素
  display: flex;
  flex-direction: row; // row-reverse | column | column-reverse
  flex-wrap: nowrap;
  align-items: center; // flex-start | flex-end | stretch
  justify-content: center; // stretch | flex-start | flex-end | center | space-around | sapce-between | space-evenly
  flex-flow: row column;

  // 子元素
  // 三个可以合并为flex
  flex-grow: 0;
  flex-shrink: 1;
  flex-basis: auto;

  order: 2;
  align-self: center; // same as align-items
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • space-evenly:元素与元素之间、元素与父元素容器之间的间隔相等
  • space-around:元素与元素之间的间隔是元素与父元素之间的间隔两倍关系

  • line-height
  1. 百分比:font-size*line-height
  2. 具体数值:line-height
  • CSS3硬件加速

触发硬件加速的几个属性:transformopacityfilterwill-change

底层是通过GPU进行加速

#JS

  • js 数据类型

7 种简单数据类型:nullundefinedbooleanstringnumberSymbolBigInt

其中BigInt用于大数计算

复杂数据类型:object

  • 判断数据类型
  1. typeof
  2. instanceof
  3. Object.prototype.toString.call(xxx)
  4. Reflect.toString.call(xxx)
  • 手写深拷贝
const deepCopy = (data: unknown, map = new Map()) => {
  if (typeof data !== 'object' || data === null) {
    return data;
  }

  if (map.has(data)) {
    return map.get(data);
  }

  let value = Array.isArray(data) ? [] : {};
  map.set(data, value);

  for (const key in data) {
    if (data.hasOwnProperty(key)) {
      value[key] = deepCopy(data[key], map);
    }
  }

  return value;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

问题

  1. FunctionDate没办法深拷贝
    要实现Date的深拷贝,可以利用instanceOf判断,然后使用new Date的形式拷贝
    如果是实现函数的深拷贝,可以使用new Function的形式拷贝
  • 0.1 + 0.2 !== 0.3

使用了 IEEE-754 标准,该标准存储为双精度浮点数类型,浮点数 64 位,第一位表示符号(sign),0 代表正数,1 代表负数;接着有 11 位代表阶码(exponent);接着 52 位代表尾数(mantissa)

公式:(-1)^sign * 2^(exponent) * 1.mantissa

阶码:Number.parseInt(1111111111111111, 2) - 1023 = 1024

尾数只能存 52 位,因此在舍弃第 53 尾数时根据规则为舍0补1

Number.MAX_SAFE_INTEGER = Math.pow(53) - 1,由于 2^53 转二进制为100000000000000000000000000000000000000000000000000000(53个0),只保留 52 个 0,2^53 + 1 位100000000000000000000000000000000000000000000000000001,舍弃末尾的 1,变成 52 位,因此安全整数为2^53 - 1

原因:使用了 IEEE-754 标准,它的尾数最多 52 位,在 0.1 转成二进制后会存在循环,超过 52 位后自动舍弃,0.2 同样如此,因此它们在相加的过程中由于位数舍弃,导致相加之后并不等于 0.3

如何判断 0.1 + 0.2 = 0.3

  1. Number.EPSILON判断
  2. 转成字符串进行相加操作然后判断
  • call、apply、bind 实现

都是基本实现,没有深究细节

  1. call
Function.prototype.call2 = function(context = globalThis, ...args) {
  if (typeof this !== 'function') {
    throw new Error(`xxx is not a function`);
  }

  context.fn = this;
  let result = context.fn(...args);
  delete context.fn;
  return result;
};
1
2
3
4
5
6
7
8
9
10
  1. apply
Function.prototype.apply2 = function(context = globalThis, args) {
  if (typeof this !== 'function') {
    throw new Error(`xxx is not a function`);
  }

  context.fn = this;
  let result = context.fn(...args);
  delete context.fn;
  return result;
};
1
2
3
4
5
6
7
8
9
10
  1. bind
Function.prototype.bind2 = function(context = globalThis, ...bindArgs) {
  if (typeof this !== 'function') {
    throw new Error(`xxx is not a function`);
  }

  const self = this;
  return function(...args) {
    return self.apply(context, bindArgs.concat(args));
  };
};

// 更简单
Function.prototype.bind2 = function(context = globalThis, ...bindArgs) {
  if (typeof this !== 'function') {
    throw new Error(`xxx is not a function`);
  }

  return (...args) => {
    return this.apply(context, bindArgs.concat(args));
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • new 实现
function new2(context, ...args) {
  let obj = Object.create(null);
  obj.__proto__ = context.prototype;
  const res = context.apply(obj, args);
  return typeof res === 'object' && res !== null ? res : obj;
}
1
2
3
4
5
6
  • reduce 实现
Array.prototype.reduce2 = function(fn, target) {
  let start = 0;
  let value = target;

  // 查看第二个参数是否存在,如果不存在则从第0项开始
  if (typeof target === 'undefined') {
    value = this[0];
    start = 1;
  }

  for (let i = start; i < this.length; i++) {
    value = fn(value, this[i], i, this);
  }
  return value;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

  • 防抖实现

一般实现简单的就可以了

function debounce(func, wait) {
  let timer = null;
  return function(...args) {
    if (timer) {
      clearTimeout(timer);
    }

    timer = setTimeout(() => {
      func.apply(this, args);
      timer = null;
    });
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13

复杂一点的

function debounce(func: Function, wait: number, immediate = false) {
  let timer = null;

  return function(...args: unknown[]) {
    if (timer) {
      clearTimeout(timer);
    }

    if (!immediate) {
      timer = setTimeout(() => {
        // 注意,这里需要绑定this
        func.call(this, ...args);
      }, wait);

      return;
    }

    // 只执行一次
    let callNow = !timer;
    timer = setTimeout(() => {
      timer = null;
    }, wait);

    if (callNow) {
      func(...args);
    }
  };
}
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

  • 节流
function throttle(fn, wait) {
    let timer = null
    return function(...args: unknown[]) {
        if (timer) {
            return
        }

        timer = setTimeout(() => {
            fn.apply(this, args)
            timer = null
        }, wait)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

  • instanceof 实现
function instanceOf(obj, proto): boolean {
  while (obj) {
    if (obj.__proto__ === proto.prototype) {
      return true;
    }

    obj = obj.__proto__;
  }

  return false;
}
1
2
3
4
5
6
7
8
9
10
11
  • flat 实现
// 简单实现
function flat(arr): number[] {
  let newArr: number[] = [];
  for (const item of arr) {
    if (Array.isArray(item)) {
      newArr.push(...flat(item));
      continue;
    }

    newArr.push(item);
  }

  return newArr;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 骚操作,数字
function flat(arr): number[] {
  return arr
    .toString()
    .split(',')
    .map((item) => +item);
}
1
2
3
4
5
6
7

  • 尾调用优化

只保留内层函数的调用帧,注意需要符合条件,即不再用到外层函数的内部变量


  • 异步
  1. 宏任务:DOM 渲染后触发
  2. 微任务:DOM 渲染前触发
  • webpack 和 rollup 的区别

模块打包器的基本特征:
找出所有的依赖,然后打包

webpack

  1. 代码拆分,可以按需加载
  2. 支持第三方导入,图片导入,CSS 预处理等
  3. Tree shaking 需要做额外工作

Tree Shaking原理

满足条件:使用ESM规范编写模块代码,启动代码优化功能

  1. 先标记出模块导出值中哪些没有被用过
  2. 使用Terser删掉没有被用到的导出语句

webpack loader 和 plugin 实现原理

loader在加载文件的时候执行

plugin在打包的某个时间点做一些操作


rollup

  1. Rollup 不支持模块的热更新
  2. 所有代码放在同一个地方,一次性加载,不产生额外代码,无代码拆分
  3. 仅支持 JS
  4. 通过 ES6 模块来进行 tree-shaking(比 webpack 好)

vite

  1. vite 最终打包需要依赖 rollup
  2. 通过浏览器的 ES 模块处理模块

  • 模块的区别

CommonJS: 是由 Node 应用模块组成,requiremodule.exports,特征是同步加载,输出是值的拷贝
AMD:definerequire,RequiredJS 为代表,特征是依赖前置,提前执行
CMD:defineuse,SeaJS 为代表,依赖就近,延迟执行
UMD: 是集结了 CommonJS、CMD、AMD 规范于一身,通过判断条件来让代码运行相关模块
ES6:importexport default,特征是编译时输出,输出是值的引用


  • MVC 与 MVVM 的区别

MVC: Model-View-Controller
MVVM: View-ViewModel<->Model

  • lottie 的原理

根据 JSON 文件的相应属性去读取文件的内容,根据相应数据结构渲染出相应动画


  • webpack 相关

功能:模块打包(开发时任意划分文件模块)、编译兼容(polyfill 功能)、能力拓展(plugin 功能)

打包原理:

  1. 读取webpack配置参数
  2. 启动webpack,创建compiler对象并开始解析项目
  3. 从入口文件(entry)开始解析,找到其导入的依赖模块,递归遍历分析,形成依赖关系树(AST 语法树)
  4. 对不同类型的依赖模块文件使用对应的loader进行编译,最终转为 JS 文件
  5. 过程中 webpack 对外抛出 hooks,plugin 通过监听事件节点,达到干预输出结果的目的

compiler: 全局单例,负责打包构建

compilation: 构建的上下文对象,包含构建信息

__webpack_modules__: 编译后的 JS 内容,key-value 形式
__webpack_module_cache: 模块缓存,缓存后如果命中直接返回
__webpack_require__: 依赖引入函数

SourceMap: 方便调试代码,使打包后的文件能够映射到编译前的源码

cheap: 不包含列信息,也不包含 loader 的 source map
module: loader 输出的 source map 被采用,能够查看 loader 处理前的原始代码
inline: 经过 base64 编码嵌入文件中
eval: 使用 eval 包裹模块代码,提高 rebuild 速度
hidden: bundle 内不包含 source map 的引用地址

各个配置项代表的含义:

eval: 每个模块通过 eval 执行,源文件通过 eval 包裹
source-map: eval 消失,变成一个函数,多了一个 map 文件
eval-source-map: 单个文件,把 sourceMapURL 转成 base64 编码
eval-cheap-source-map?: 相比eval-source-map少了 loader 生成的 source map
eval-cheap-module-source-map?: 相比eval-cheap-source-map多了 loader 的 source map

浏览器通过 sourceURL 源码与混淆代码之间的映射

Loader 功能:负责文件转换,如果是非 JS 类型文件,则需要转换成 JS 才能继续打包任务,根据匹配规则链式调用每一个 loader,前一个 loader 返回的内容作为下一个 loader 的入参,需遵循单一职责

plugin 功能:负责功能拓展,在相应的 hooks 时调用

webpack 热更新原理

构建 bundle 时加入 HRM runtime 的 js 和 websocket 相关代码,文件修改触发 webpack 重新构建,服务器向浏览器(通过 websocket)发送更新消息,浏览器通过 jsonp 拉取更新模块内容,jsonp 回调触发热替换逻辑,实现代码的热更新

  • HTTP
  1. 200-成功
  2. 301-永久重定向
  3. 302-临时重定向
  4. 304-资源未被修改
  5. 403-无权限,关于授权方面的,服务器完成认证过程,但是拒绝资源
  6. 404-资源未找到
  7. 500-服务器错误
  8. 502-网关错误(与 504 的区别是是否与后端连接,502 是建立连接了)
  9. 504-网关超时

DNS 查询过程

DNS查询有两种方式:递归迭代

客户端使用的DNS服务器一般是递归服务器,而DNS根域名服务器一般采用迭代查询

#Vue2 与 TS 开发的痛点

  1. template 校验
  2. 不能很好的支持TS

#vue

  • vue的生命周期

vue2的生命周期都是成对出现的

// 组件的生命周期
beforeCreate & created
beforeMount && mounted
beforeUpdate & updated
beforeDestroy & destroyed

// keep-alive组件的生命周期
activated & deactivated
1
2
3
4
5
6
7
8

vue3的生命周期相对于vue2来说有所改变

// 销毁的钩子
beforeUnmount & unmounted
1
2

在setup中的生命周期去除了beforeCreatecreated,同时相应的生命周期需要加on,例如

obBeforeMount & onMounted
1

v-model做了什么处理:

`text``textarea`使用`value`属性和`input`事件
`checkbox``radio`使用`checked`属性和`change`事件
`select`使用`value`属性和`change`事件
1
2
3

computedmethods的区别

  • computed是基于响应式依赖进行缓存,如果没有缓存,则其他依赖该计算属性的值会多次触发A的getter
  • methods当触发重新渲染时,调用方法总会再次执行函数

nextTick

使用的原因:数据的变化不是马上触发watcher,而是会放到一个队列中,然后批量执行,因此数据的变化到DOM的重新渲染是一个异步过程,发生在下一个tick中
nextTick的原理:通过常见的异步函数去执行回调函数
为什么优先使用Promise:因为Promise是微任务,它的执行速度快于宏任务,宏任务执行的速度相对较慢

虚拟DOM的好处:更细粒度的更新DOM,同时抽象渲染过程,实现跨平台能力
Vue的插件机制:通过触发install函数同时传入Vue实现挂载功能
Vue2和Vue3响应式实现上的区别:definePropertyproxy

proxy的好处:

  1. 实现深度监听
  2. 检测到属性的增删
  3. 实现对数组的监听

keep-alive的实现原理,缓存算法

  1. 首次渲染的时候设置缓存
  2. 设置vnode.data.keepAlive为true

自定义render函数并且利用了插槽,keep-alive缓存了vnode

LRU缓存策略:Least rencently used算法,根据数据的历史访问记录来进行淘汰数据

没有完全遵循MVVM模型,因为MVVM要求不能手动操作视图更新,但是Vue中有一个ref属性

#Vue3

watchwatchEffect的区别

watch-显式指定依赖源,依赖更新时执行回调函数
watchEffect-自动搜集依赖源,依赖源更新时重新执行自身

  • Vue3 移除了 .native 事件修饰符,我们怎么去写原生事件?

直接在组件上定义,同时在组件内部emits选项中不定义该事件

#Vue-Router

两种模式对应事件:hashchangepopstatehistory.pushState

Vue router 底层原理,不同模式的区别,history 路由刷新页面 404 的原因和解决方法

因为history是一个路径,刷新的时候会重新请求这个路径,如果后台没有这个get请求,那么会返回404,因此需要后端配合配置,即如果没有发现请求路径则返回index.html文件

#Vuex

  • Vuex 中的变量打包之后存在哪里?和全局作用域中的变量有什么区别

#伪类与伪元素

伪类是依靠现有元素,伪元素是依靠自己新创建的元素

伪类:hoveractive
伪元素:beforeafter

  • websocket的安全问题
  1. 使用wss://进行传输
  2. 针对origin头部进行验证
  3. 输入校验
  4. 设置单IP可建立连接的最大连接数

#Node

设置跨域的头:Access-Control-Allow-Origin,可以指定请求域

设置请求头:Access-Control-Allow-Headers

设置请求方法:Access-Control-Allow-Methods,CORS默认GETPOSTHEAD

#算法

红包问题

function redPacket(amount: number, number: number) {
    let rndSum = 0
    const rndArr: number[] = []
    for (let i = 0; i < number; i++) {
        let rnd = Math.random();
        rndSum += rnd;
        rndArr.push(rnd)
    }

    const result: number[] = []
    let sum = 0
    rndArr.forEach((num, index) => {
        let val = +(num * amount / rndSum).toFixed(2)
        result.push(val)
        if (index < number - 1) {
            sum += val
        }
    });

    result[number-1] = +(amount - sum).toFixed(2)
    return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • forof

for of循环可以使用的范围:数组、Set、Map、类数组对象、Generator对象、字符串

与forin的区别

  1. forin只遍历可枚举属性(包括原型链上的属性)
  2. forin遍历对象键值,forof调用的是对象的Symbol.iterator属性

#TS 相关

  • is有什么作用

在 TypeScript 中,is关键字是用于用户定义的类型保护函数。类型保护函数是一个返回结果为布尔值的表达式,当这个值为true时,TypeScript编译器会根据定义的类型更改变量的类型。

类型保护函数的定义通常遵循以下格式:

function is<Type>(value: any): value is Type {
  // ... 类型检查逻辑 ...
}
1
2
3

这里有一个简单的例子:

interface Cat {
  type: "cat";
  numberOfLives: number;
}

interface Dog {
  type: "dog";
  isGoodBoy: boolean;
}

type Animal = Cat | Dog;

function isCat(animal: Animal): animal is Cat {
  return animal.type === "cat";
}

const myAnimal: Animal = { type: "cat", numberOfLives: 9 };

if (isCat(myAnimal)) {
  // TypeScript 知道 `myAnimal` 是一个 `Cat` 类型
  console.log(myAnimal.numberOfLives); // 9
} else {
  // TypeScript 知道 `myAnimal` 是一个 `Dog` 类型
  console.log(myAnimal.isGoodBoy);
}
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

这个例子里,类型保护函数isCat作为一个判断输入值是否为Cat类型的谓词。当isCat函数返回true时,TypeScript 会自动将myAnimal的类型缩小为Cat,使得numberOfLives属性可用。反之,TypeScript

会自动将myAnimal类型缩小为Dog类型。

#参考来源

Last Updated:5/25/2024, 2:23:06 AM