- HTML 语义化
在 H5 中增加了许多语义化标签,例如aside
、nav
、header
、main
、section
、footer
这些语义化标签优点:
- 代码结构更加清晰,增加了代码可读性
- 更好的
SEO
- 原生HTML怎么实现复用
- 使用
iframe
和object
等标签,导入相应的html
- 使用
JS
插入 - 使用
Web Components
进行原生组件化开发
script
中defer
和async
的区别
相同点:立即下载,但不阻塞,外部文件引用
区别:
defer
: 延迟脚本。在整个页面解析完毕后执行,不阻塞HTML
解析,H5
规范中要求顺序执行,先于DOMContentLoaded
事件,但现实中不保证执行顺序
async
: 异步脚本。有可能阻塞HTML
解析,一定在load
事件前执行,但可能在DOMContentLoaded
事件触发之前或之后执行(注意先DOMContentLoaded
再load
)
#浏览器
- 从浏览器地址栏输入
url
到请求返回发生了什么?
URL 的组成:协议、主机、端口、路径、查询参数、锚点
- 域名解析成 IP 地址(使用 DNS 服务器)
- TCP 连接(TCP 三次握手)
在发送之前,根据请求头的expires
和cache-control
判断是否过期(强缓存),如果没有过期,则直接获取缓存
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
在返回 HTTP 报文时分为两种情况:
一:如果请求头的If-Modified-Since
和If-None-Match
判断命中了协商缓存,那么会直接从缓存中取,标志是响应码一般为 304
二:如果没有命中协商缓存,则后台返回内容
- 浏览器解析渲染页面
- 断开连接(
TCP
四次挥手)
GET
与POST
的区别
GET
请求只能进行URL
编码,而POST
支持多种编码方式GET
请求在URL
中传输的参数是有长度限制的,而POST
没有- 对参数的数据类型,
GET
只接受ASCII
字符,而POST
没有限制 GET
比POST
更不安全GET
参数通过URL
传递,POST
放在Request body
中
http
和https
的区别
HTTP
是明文传输,数据都是未加密的,安全性较差,HTTPS
数据传输过程是加密的,安全性较好HTTP
的响应速度快于HTTPS
,是因为TLS
在加密的过程中需要握手导致时间会慢于http
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):也叫回流,是更新元素的几何属性后重现触发布局的过程
常见的方式:
- 元素尺寸变化
- DOM 元素添加或删除
- 元素位置变化
- 浏览器窗口尺寸变化
- 元素内容发生变化
- 当获取元素的尺寸时也会发生重排现象
重绘(repaint):当知道几何尺寸变化后,浏览器进入图层绘制阶段,渲染出屏幕的实际尺寸
一般发生在元素颜色、透明度等发生改变,不会影响界面布局
合成:直接跳过布局和图层绘制的阶段,在非主线程上执行更新,如 CSS 的transform
属性
- 对 BFC 的理解
块级格式化上下文(Block Formatting context)
它是 CSS 视觉渲染的一部分,用来决定块级盒的布局及浮动相互影响范围的一个区域
触发 BFC 的方式:
- position 不为 relative 和 static
- display 值为 table-cell、table-caption、inline-block、flex
- overflow 的值为 auto、scroll 或 hidden
- float 的值不为 none
- html 根元素
影响
- 清除浮动的影响,避免父元素塌陷
- 子元素与外部元素不发生
margin
合并
- 实现两栏布局
flex
grid
float
- 实现垂直居中的多种实现方式
- flex 布局
align-items: center;
+justify-content: center;
- postion:
absolute
布局,left
、top
、margin
- postion:
absolute
布局,left
、top
、transform
- grid 布局
- 一行文本
text-align
、line-height
- flex 布局问题
flex 属性是flex-grow
、flex-shrink
、flex-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
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
space-evenly
:元素与元素之间、元素与父元素容器之间的间隔相等space-around
:元素与元素之间的间隔是元素与父元素之间的间隔两倍关系
- line-height
- 百分比:
font-size
*line-height
- 具体数值:
line-height
- CSS3硬件加速
触发硬件加速的几个属性:transform
、opacity
、filter
、will-change
底层是通过GPU
进行加速
#JS
- js 数据类型
7 种简单数据类型:null
、undefined
、boolean
、string
、number
、Symbol
、BigInt
其中BigInt
用于大数计算
复杂数据类型:object
- 判断数据类型
typeof
instanceof
Object.prototype.toString.call(xxx)
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;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
问题
Function
、Date
没办法深拷贝
要实现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
Number.EPSILON
判断- 转成字符串进行相加操作然后判断
- call、apply、bind 实现
都是基本实现,没有深究细节
- 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;
};
2
3
4
5
6
7
8
9
10
- 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;
};
2
3
4
5
6
7
8
9
10
- 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));
};
};
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;
}
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;
};
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;
});
};
}
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);
}
};
}
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)
}
}
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;
}
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
// 骚操作,数字
function flat(arr): number[] {
return arr
.toString()
.split(',')
.map((item) => +item);
}
2
3
4
5
6
7
- 尾调用优化
只保留内层函数的调用帧,注意需要符合条件,即不再用到外层函数的内部变量
- 异步
- 宏任务:DOM 渲染后触发
- 微任务:DOM 渲染前触发
- webpack 和 rollup 的区别
模块打包器的基本特征:
找出所有的依赖,然后打包
webpack
- 代码拆分,可以按需加载
- 支持第三方导入,图片导入,CSS 预处理等
- Tree shaking 需要做额外工作
Tree Shaking原理
满足条件:使用ESM规范编写模块代码,启动代码优化功能
- 先标记出模块导出值中哪些没有被用过
- 使用Terser删掉没有被用到的导出语句
webpack loader 和 plugin 实现原理
loader在加载文件的时候执行
plugin在打包的某个时间点做一些操作
rollup
- Rollup 不支持模块的热更新
- 所有代码放在同一个地方,一次性加载,不产生额外代码,无代码拆分
- 仅支持 JS
- 通过 ES6 模块来进行 tree-shaking(比 webpack 好)
vite
- vite 最终打包需要依赖 rollup
- 通过浏览器的 ES 模块处理模块
- 模块的区别
CommonJS: 是由 Node 应用模块组成,require
、module.exports
,特征是同步加载,输出是值的拷贝
AMD:define
、require
,RequiredJS 为代表,特征是依赖前置,提前执行
CMD:define
、use
,SeaJS 为代表,依赖就近,延迟执行
UMD: 是集结了 CommonJS、CMD、AMD 规范于一身,通过判断条件来让代码运行相关模块
ES6:import
、export default
,特征是编译时输出,输出是值的引用
- MVC 与 MVVM 的区别
MVC: Model-View-Controller
MVVM: View-ViewModel<->Model
- lottie 的原理
根据 JSON 文件的相应属性去读取文件的内容,根据相应数据结构渲染出相应动画
- webpack 相关
功能:模块打包(开发时任意划分文件模块)、编译兼容(polyfill 功能)、能力拓展(plugin 功能)
打包原理:
- 读取
webpack
配置参数 - 启动
webpack
,创建compiler
对象并开始解析项目 - 从入口文件(entry)开始解析,找到其导入的依赖模块,递归遍历分析,形成依赖关系树(AST 语法树)
- 对不同类型的依赖模块文件使用对应的
loader
进行编译,最终转为 JS 文件 - 过程中 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
- 200-成功
- 301-永久重定向
- 302-临时重定向
- 304-资源未被修改
- 403-无权限,关于授权方面的,服务器完成认证过程,但是拒绝资源
- 404-资源未找到
- 500-服务器错误
- 502-网关错误(与 504 的区别是是否与后端连接,502 是建立连接了)
- 504-网关超时
DNS 查询过程
DNS查询有两种方式:递归和迭代
客户端使用的DNS服务器一般是递归服务器,而DNS根域名服务器一般采用迭代查询
#Vue2 与 TS 开发的痛点
- template 校验
- 不能很好的支持TS
#vue
- vue的生命周期
vue2的生命周期都是成对出现的
// 组件的生命周期
beforeCreate & created
beforeMount && mounted
beforeUpdate & updated
beforeDestroy & destroyed
// keep-alive组件的生命周期
activated & deactivated
2
3
4
5
6
7
8
vue3的生命周期相对于vue2来说有所改变
// 销毁的钩子
beforeUnmount & unmounted
2
在setup中的生命周期去除了beforeCreate
和created
,同时相应的生命周期需要加on
,例如
obBeforeMount & onMounted
v-model
做了什么处理:
`text`和`textarea`使用`value`属性和`input`事件
`checkbox`和`radio`使用`checked`属性和`change`事件
`select`使用`value`属性和`change`事件
2
3
computed
和methods
的区别
computed
是基于响应式依赖进行缓存,如果没有缓存,则其他依赖该计算属性的值会多次触发A的gettermethods
当触发重新渲染时,调用方法总会再次执行函数
nextTick
使用的原因:数据的变化不是马上触发watcher,而是会放到一个队列中,然后批量执行,因此数据的变化到DOM的重新渲染是一个异步过程,发生在下一个tick中
nextTick的原理:通过常见的异步函数去执行回调函数
为什么优先使用Promise:因为Promise是微任务,它的执行速度快于宏任务,宏任务执行的速度相对较慢
虚拟DOM的好处:更细粒度的更新DOM,同时抽象渲染过程,实现跨平台能力
Vue的插件机制:通过触发install
函数同时传入Vue
实现挂载功能
Vue2和Vue3响应式实现上的区别:defineProperty
和proxy
proxy的好处:
- 实现深度监听
- 检测到属性的增删
- 实现对数组的监听
keep-alive的实现原理,缓存算法
- 首次渲染的时候设置缓存
- 设置vnode.data.keepAlive为true
自定义render
函数并且利用了插槽,keep-alive缓存了vnode
LRU缓存策略:Least rencently used算法,根据数据的历史访问记录来进行淘汰数据
没有完全遵循MVVM模型,因为MVVM要求不能手动操作视图更新,但是Vue中有一个ref属性
#Vue3
watch
与watchEffect
的区别
watch
-显式指定依赖源,依赖更新时执行回调函数watchEffect
-自动搜集依赖源,依赖源更新时重新执行自身
- Vue3 移除了 .native 事件修饰符,我们怎么去写原生事件?
直接在组件上定义,同时在组件内部emits
选项中不定义该事件
#Vue-Router
两种模式对应事件:hashchange
、popstate
和history.pushState
Vue router 底层原理,不同模式的区别,history 路由刷新页面 404 的原因和解决方法
因为history是一个路径,刷新的时候会重新请求这个路径,如果后台没有这个get请求,那么会返回404,因此需要后端配合配置,即如果没有发现请求路径则返回index.html文件
#Vuex
- Vuex 中的变量打包之后存在哪里?和全局作用域中的变量有什么区别
#伪类与伪元素
伪类是依靠现有元素,伪元素是依靠自己新创建的元素
伪类:hover
、active
伪元素:before
、after
- websocket的安全问题
- 使用
wss://
进行传输 - 针对
origin
头部进行验证 - 输入校验
- 设置单IP可建立连接的最大连接数
#Node
设置跨域的头:Access-Control-Allow-Origin
,可以指定请求域
设置请求头:Access-Control-Allow-Headers
设置请求方法:Access-Control-Allow-Methods
,CORS默认GET
、POST
、HEAD
#算法
红包问题
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
}
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的区别
- forin只遍历可枚举属性(包括原型链上的属性)
- forin遍历对象键值,forof调用的是对象的
Symbol.iterator
属性
#TS 相关
is
有什么作用
在 TypeScript 中,is
关键字是用于用户定义的类型保护函数。类型保护函数是一个返回结果为布尔值的表达式,当这个值为true
时,TypeScript
编译器会根据定义的类型更改变量的类型。
类型保护函数的定义通常遵循以下格式:
function is<Type>(value: any): value is Type {
// ... 类型检查逻辑 ...
}
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);
}
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
类型。