面试题

514 阅读24分钟

打包工具

常见打包工具:webpack,rollup,parcel,esbuild,代码写好后,对代码进行一系列操作,

vue-cli,create-react-app,umi是基于webpack的上层封装,通过简单配置就能快速创建项目,将精力放在业务上面。

vite在开发环境依赖于esbuild进行预构建,生产环境依赖rollup进行打包,充分利用现代浏览器特性,http2,esmodule是站在众多巨人肩膀上的产物。

webpack

静态资源打包工具,将js打包后在浏览器中使用。js模块化,每个文件就是一个模块

entry

文件入口,默认是src/index.js,可以传入数组(打包为一个文件),对象(分别打包,名字为属性名)

output

输出资源,filename,path,clean

loader

文件转换功能,webpack只理解js,其他类型文件需要借助loader,在modules的rules中以数组形式进行配置。

  • babel-loader es6转义为es5
  • image-loader 加载并压缩图片资源
  • awesome-typescript-loader 将ts转为js
  • sass-loader 将scss、sass转换为css
  • css-loader 加载css文件
  • style-loader css样式加载
  • eslint-loader eslint检查js代码

plugin

在webpack构建的生命周期节点添加功能,通过plugins以数组形式进行配置。

  • html-webpack-plugin 单页面应用添加html
  • terser-webpack-plugin 压缩代码 tree-shaking
  • compression-webpack-plugin 生产环境使用gzip压缩js和css

module chunk bundle

module是源代码,一个文件就是一个module

chunk是webpack打包中生成的,从entry 一个入口就是一个chunk

bundle是生成的最终打包文件

构建流程

1.初始化参数

解析webpack配置参数,合并shell语句和webpack配置文件的参数,形成最后配置结果。

2.开始编译

使用参数初始化compiler对象,

注册配置的插件,插件监听webpack构建生命周期的事件节点,做出相应反应,

执行对象的run方法开始执行编译。

3.确定入口

从配置的entry作为入口,开始解析文件构建AST语法树,找出依赖,递归下去。

4.编译模块

递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,

再找出该模块依赖的模块,再执行此步骤,直到所有入口依赖的文件都经过了本步骤的处理。

5.完成模块编译

完成以上后得到了每个模块的最终内容和他们之间的依赖关系。

6.输出资源

根据入口与模块之间的依赖关系,组装成一个个包含多个模块的chunk,再把chunk转换成一个单独的文件加入到输出列表,这步是可以修改内容的最后机会。

7.输出完成

确定输出内容后根据陪住确定输出路径和文件名,将文件内容写到文件系统。

文件指纹

文件打包后的后缀,作用是版本管理,和浏览器缓存区分。

Hash:项目相关,项目修改hash就变化

ContentHash:内容相关,内容改变contenthash改变

Chunkhash:不同entry构建不同的chunk,各个之间不影响

js使用chunkhash

css使用contenthash

图片等静态资源使用hash

babel原理

babel将代码转义为目标代码,对目标环境不支持的api进行pollyfill。

流程:解析,转换,生成

Tree-Shanking

未使用的模块或方法不加入打包

sideEffects为false可以让打包工具放心的tree-shaking,一般可以保留css文件

自调用函数不会被tree-shaking,可以加上pure,表明可以tree-shakng

optimization(优化):usedExports 清除死代码得益于该选项,如果没有使用的函数,进行标记unused harmony expotrs

使用esm规范编写代码,将babel-loader 中模块导入导出转义关闭

webpack性能优化

  • 代码压缩

    • js压缩:默认生产模式下支持代码压缩,内部使用terser-webpack-plugin
    • css压缩:去除无用空格,css-minimizer-webpack-plugin
    • html压缩:html-webpack-plugin 通过配置minify属性优化html
  • 图片压缩:image-loader

  • tree-shaking(消除无用代码)

    • usedExports,标记某些函数是否被使用,通过terser进行优化,没被使用会告诉terser优化时,删除这段代码。
    • sideEffects:跳过整个模块/文件,在package.json中配置,如果设置为false,就是告诉webpack可以安全的删除未用到的exports,如果需要保留,通过数组形式传入。
  • 缩小打包域:排除webpack不需要解析的模块,使用loader在尽可能少的模块中。借助include和exclude,规定loader使用范围。

  • 提取公共代码:配置common-chunk-plugin将多个页面公共代码抽离成为单独的文件。

vite

vite为什么比webpack快

1.运行原理:(webpack真实打包,vite冷启动)

webpack通过配置文件,分析出项目中所有模块的依赖关系,最后打包,交给浏览器渲染,项目越大,启动时间越长。

vite借由esmodule特性(浏览器遇到内部的import引用,发起http请求,加载对应模块),首先用esbuild进行预构建,将所有模块转为es module,不需要对项目进行编译打包,在浏览器加载模块式,拦截http请求,根据请求按需编译,返回给浏览器。

2.构建方式

webpack基于node运行,js单线程。

vite是使用esbuild完成,esbuild用go写的,充分利用多核cpu优势,快

3.http2,支持多个并发请求,vite借助这一优势,可以同时加载多个模块。

4.热更新

webpack修改文件后重新进行打包,虽然现在有缓存机制,无法从根本解决问题。

vite监听到文件变更后,通过websocket告诉浏览器,重新发起新的请求,只对该模块进行编译,替换。基于esmodule特性,利用浏览器缓存策略,针对源码做了协商缓存,对第三方库做了强缓存。

5.生产环境vite使用rollup进行打包,rollup比webpack小巧,也更快。

package.json文件

对项目的描述,安装包时根据文件安装

name: 项目名
version:版本号
​
description:描述
keywords:关键词
author:作者
contributors:贡献者
repository:仓库地址
​
dependencies:依赖
devDependencies:开发依赖
^当前最大版本的最新版 ~当前版本的小版本最新版
​
script:脚本配置
config:项目配置 process.env.npm_package_config_baseUrl
​
main:入口文件
license:开源协议 ISC MIT
​
sideEffects:说明文件是否有副作用,帮助webpack等打包工具进行tree-shaking 设置为false说明直接tree-shaking,也可以使用数组形式传入文件,排除哪些文件
browserslist:支持哪些浏览器版本

npm yarn pnpm

npm3之前

①可能有的包有重复引用,重复安装②依赖树比较深,路径比较长,会出现问题

npm3之后

将依赖拉平,扁平化依赖树,①幽灵依赖,没有安装的包也可以访问到②扁平化算法复杂③有些包不能被拉平④依赖重复安装

yarn基本上与npm3相同

pnpm使用软链接和硬链接

pnpm将包存储在一个位置,全局安装一次,使用链接引用

团队成员包安装工具冲突:packageManager 在package.json指定安装包工具和其版本

网络安全

常见网络攻击与防御方式

xss攻击

跨站脚本攻击,网页中插入js代码,打开页面时js执行,达到攻击的目的。

反射型(非持久)

浏览器-》服务器-》浏览器

存储型(持久)

浏览器-》服务器-》服务器存储,浏览器端取数据会发生xss攻击

DOM型

在浏览器操作dom进行攻击

预防:过滤标签,事件,转义等

第三方工具 npm xss

现代框架会屏蔽xss攻击,除非手动插入 v-html dangerouslySetInnerHTML

csrf攻击

跨站请求伪造:盗用身份,发送请求

引诱用户访问他的网站,他的网站收到带有cookie的请求。

限制跨域请求,短信验证码双重验证

点击劫持

点击劫持:将恶意代码隐藏在看似正常的内容下,引导用户点击、

通过iframe嵌入恶意网站,引导用户点击,操作

git常用命令


git init
git remote add origin
git clone
git pull
git push
​
git status
git log
git reset 代码回退
​
git add
git commit
​
git stash  、pop
​
git branch
git checkout
git merge

css

css动画

keyframes和animation


//定义动画各个状态
@keyframes name {
    from {}
    50% {}
    to {}
}

div:hover {
  animation-name: rainbow; //keyframes名字
  animation-duration: 1s;//持续时间
  animation-timing-function: linear;//运动状态 匀速
  animation-delay: 1s;//几秒后开始执行
  animation-fill-mode:forwards;//最后停止执行时状态
  animation-direction: normal;//运动方向
  animation-iteration-count: 3;//执行次数
}

transition

状态只有开始和结束,但是animation可以有多个状态

transition需要借助触发方式,animation可以自动触发

transition-propperty//过渡属性

transition-duration//持续时间

transition-delay//延迟时间

transition-timing-function//速度效果


#box {
        margin-left: 0;
        width: 100px;
        height: 100px;
        background-color: yellowgreen;
        transition-property: margin-left;
        transition-duration: 10s;
      }
#box:hover {
    margin-left: 1000px;
}

transform

不会引起浏览器重绘回流

平移


transform:translateX(100px)

旋转


transform:rotateX()//rotateY()//rotateZ()

缩放


transform:scale(1)//缩放比例

倾斜


transform:skew(0deg,10deg)

css和js实现div右移1000px动画

animation


     @keyframes move {
        from {
          transform: translateX(0);
        }
        to {
          transform: translateX(1000px);
        }
      }
​
      #box {
        margin-bottom: 20px;
        width: 100px;
        height: 100px;
        background-color: yellowgreen;
        animation-name: move;
        animation-duration: 10s;
        animation-timing-function: linear;
        animation-fill-mode: backwards;
      }

transition设置marginleft

js

动态设置margin-left,当值大于等于end时,结束


<body>
    <div id="box"></div>
    <input type="button" value="点击" id="btn" />
    <script>
      let btn = document.getElementById('btn')
      let box = document.getElementById('box')
      let begin = 0,
        end = 1000,
        step = 1
      btn.addEventListener('click', () => {
        let timer = setInterval(() => {
          begin += step
          if (begin >= end) clearInterval(timer)
          box.style.marginLeft = begin + 'px'
        }, 0)
      })
    </script>
  </body>

flex布局

flex-direction //确定主轴方向

主轴方向元素不会被拉伸,但是可以缩小,交叉轴不设置长度会被拉伸填充

flex-wrap:换行 wrap 、no-wrap

flex-flow为direction和wrap的简写

flex-grow,flex-shrink,flex-basis

flex-grow瓜分剩余空间

flex-shrink压缩空间(flex不换行,溢出时进行压缩)

flex-basis 主轴方向初始大小(属性优先级max-width/min-width>flex-basis>width)

flex为以上的简写

align-items:stretch、flex-start、flex-end、center

justify-content:stretch、flex-start、flex-end、center、space-around、space-between

文本截断,js实现

css

单行文本截断


      .box {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }

多行文本截断


      .box {
        overflow: hidden;
        text-overflow: ellipsis;
        display: -webkit-box;
        -webkit-line-clamp: 3;
        -webkit-box-orient: vertical;
        word-break: break-all;
      }

js

画一条0.5px的线

1.transform:scaleY(0.5),trnasform-origin:50% 100%

2.svg

bfc

块级格式化上下文

盒子内部元素不会影响到外部

触发bfc

html根标签

float除none

position absolute,fixed

display inline-block,table-cells,flex

overflow除了visible以外

同一个bfc下两个盒子发生margin塌陷

清除浮动

parent宽高不定,设置scale固定宽高比为4:3

1.aspect-ratio:4/3(兼容性问题)


      .parent {
        aspect-ratio: 4/3;
        background-color: rebeccapurple;
      }

2.使用padding-bottom撑开外部盒子


    <style>
      .parent {
        width: 20vw;
      }
      .child {
        width: 100%;
        padding-bottom: 75%;
        background-color: sandybrown;
      }
    </style>
  </head>
  <body>
    <div class="parent">
      <div class="child"></div>
    </div>
  </body>

css垂直居中

1.felx


      .box {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 500px;
        background-color: yellowgreen;
      }
      .cube {
        width: 200px;
        height: 200px;
        background-color: salmon;
      }

2.定位+translate


      .box {
        position: relative;
        height: 500px;
        background-color: yellowgreen;
      }
      .cube {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 200px;
        height: 200px;
        background-color: salmon;
      }

3.grid布局


      .box {
        display: grid;
        justify-content: center;
        align-items: center;
        height: 500px;
        background-color: yellowgreen;
      }
      .cube {
        width: 200px;
        height: 200px;
        background-color: salmon;
      }

4.定位+margin:auto


      .box {
        position: relative;
        height: 500px;
        background-color: yellowgreen;
      }
      .cube {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        top: 0;
        margin: auto;
        width: 200px;
        height: 200px;
        background-color: salmon;
      }

伪元素伪类

按照功能,可划分为以下几类:

  • 动态伪类::visited:focus:hover
  • 状态伪类::disabled:empty:requiredchecked
  • 结构伪类::first-child:nth-child()
  • 其他伪类::target:lang:not()

伪元素

  • ::before

  • ::after

  • ::placeholder

    before和after必须设置content属性

position

position:static、relative、absolute、fixed、sticky

static不能使用top left。。。

relative可以使用top,left。。。

absolute相对于上一级偏移,脱离文档流,子绝父相

fixed相对于viewport,固定

sticky 动态固定

盒模型

标准盒模型

怪异盒模型

响应式布局方案

圣杯布局

如何提高动画渲染性能

sass和scss

sass和scss是用于编写css不同的语法形式,scss是sass的一个语言版本,scss更接近css,sass通过缩进区分层级,scss通过{}区分层级。

变量声明


$bg:orange;
​
background-color:$bg;
​
//变量默认值$size : 20px !default; //如果导入文件中存在变量,则使用该变量,不存在使用默认值

嵌套语法


.parent{
​
    .child {
        & p {
            //父选择器标识符&
        }
    }
​
}
​

导入文件

@import导入scss文件 :多次导入会导致多次加载scss文件

@use 导入文件:避免重复加载 可以创建命名空间

嵌套导入

.blue-theme{

​ @import 'blue-theme'

}

混合器

@mixin button {

​ ...

}

可以使用@imoprt导入,也可以使用@include 使用

@extend .error 继承样式

js

js模块化

优点

复用性

可维护性

commonjs模块化规范

Node是是commonjs在服务器端的实现,browserify是commonjs在浏览器端的实现,webpack具备对commonjs的支持和转换。

使用:

exports:实际为module.exports简写,exports.XXX为在导出对象中加属性。

module.exports:module.exports = {}

require:引入模块导出的对象。

AMD异步模块定义,浏览器模块化规范

CMD:通用模块定义,浏览器模块化规范

ES Module:ES提出的官方模块化规范

import

export

自动采用严格模式

在html中使用esm type=‘module’

map,set,对象,数组

map

map可以使用任意值作为key,对象只能使用字符串或symbol类型作为key,map有很多方法,get,set,delete,has,size等

set

set 无序,不重复,有很多方法,has,add,delete,size等

weakmap

weakmap的键必须是对象,如果key没有用的话,可以被垃圾回收。

weakset

weakset,只能存储对象,值为弱引用,可以被垃圾回收,

weakmap和weakset没有size属性,其内容可能随时被垃圾回收

es6新特性

  • 默认参数
  • 模版字符串
  • 解构(数组,对象)
  • 展开运算符
  • 箭头函数
  • promise
  • let const
  • es6模块化 导入导出
  • array,object,string等新增方法
  • symbol
  • map set

数组新增方法

  • 增删 push pop shift unshift splice
  • 连接 concat
  • 切割 slice
  • 顺序 sort reverse
  • 遍历 forEach filter map every some
  • 累加器 reduce
  • 查找 indexOf lastIndexOf find findIndex includes
  • Array.of(5) 声明数组
  • Array.from 伪数组转为数组
  • fill 填充
  • Array.isArray()

object新增方法

  • Object.assign 对象合并(浅拷贝,同名属性替换)对象添加属性 方法 ,克隆对象,合并对象 ,为属性设置默认值
  • Object.getOwnPropertyDescriptors()返回对象属性的描述
  • Object.getPrototypeOf Object.setPrototypeOf
  • Object.keys Object.values

string新增方法

  • includes starsWith endsWith
  • repeat
  • padStart padEnd 补全长度
  • trim() trimStart() trimEnd()
  • replace replaceAll
  • at

如果const定义对象不想被修改怎么办

Object.defineProperty(obj,prop,config)//config中的writeable设置为false

Object.freeze(obj)//对象被冻结,不能修改

堆和栈

栈:系统自动分配系统,自动释放,固定大小的内存空间,遵循先进后出

堆:系统动态分配,也不自动释放,内存大小不固定,key-value存储

基本数据类型存储在栈中,引用数据类型存储在堆中

ts

ts是js的超集,需要编译成js在浏览器运行

优势:代码质量更好,更健壮,ide自动联想

劣势:开发增加工作量

联合类型

type和interface

type

表示基本类型 对象类型 联合类型 元组类型let tom: [string, number] = ['Tom', 25];

对象可以通过&(交叉类型)继承

interface

一般用于描述对象

interface可以继承 extends

implements:同时重写属性和方法

extends:继承

Omit删除

Pick挑选

Partial可选

required必选

判断对象是一个空对象

  • JSON.stringify(obj)==='{}'
  • for in 遍历
  • Object.getOwnPropertyNames(obj).length===0
  • Object.keys(obj).length===0

Object.create和new


Object.create =  function (o) {
    let F = function () {};
    F.prototype = o;
    let newObj=new F();
    return newObj;
};

不同

Object.create将现有对象作为原型创建新对象

new 利用构造函数生成新的实例

new会保留构造函数的属性,create不会,create只继承原型的方法

onload和DOMContentLoaded

onload 页面加载完成后调用的方法(dom,图片,样式加载完成)是dom事件

domcontentload dom加载完成(不等图片,样式等内容)是h5事件

RequestAnimationFrame

告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

为什么区分宏任务和微任务

为了插队,宏任务执行完之后,会清空微任务队列,后加的微任务可以插队

浏览器

浏览器缓存

访问网站时,会加载各种资源,html css js 图片等,浏览器会将一些不经常改变的资源保存在本地,下次访问时,直接从本地加载资源。

强缓存

如果资源没过期,则使用缓存,过期了请求服务器,一般用于js css 图片等资源

浏览器根据response header判断是否是强缓存

http 1.1控制缓存字段

Cache-control:控制网页缓存的字段

public:客户端,服务器都可以缓存

privite:只有客户端可以缓存

no-cache:客户端缓存,是否缓存需要经过协商缓存验证 协商缓存标识

no-store:不使用缓存

max-age:缓存保质期 相对时间

http 1.0控制网页缓存字段

expires:值为时间戳,服务器返回该资源到期的时间。

缺点:根据本地时间做判断,本地时间可以修改,不准确。

memory cache与disk cache都属于强缓存,主要区别在存储位置和读取速度上。

memory cache 内存 快 当tab关闭后数据不存在,资源释放,再次打开变为diskcache

disk cache 硬盘 关闭后依然存在,打开后仍然是disk cache

js 和 图片存在内存中,css存在硬盘中

协商缓存

浏览器携带缓存标识向服务器发送请求,服务器根据缓存标识决定该资源是否过期,一般用于html资源,验证版本更新

cache-control的值为no-cache或max-age=0

协商缓存标识

last-modified:文件在服务器最后被修改时间

验证流程:

访问页面时,服务器响应头返回last-modified

再次请求时,请求头加上 if-modified-since携带last-modified值

服务器根据值判断,与当前最后修改时间做比较,如果服务器的最后修改时间大于返回的时间,则返回新资源,返回200,反之,返回304,标识资源未更新,使用缓存。

etag:当前资源文件的唯一标识,如果文件内容发生改变,则该值就会改变。

验证流程:

访问页面时,响应头返回etag

再次请求,请求头加上if-none-match带上etag值

服务器根据etag值与服务器etag比较,如果发生改变返回新资源200,相同,返回304,继续使用缓存。

etag优先级优于last-modified

文件周期性更改,内容未变,修改时间变了,etag检查的颗粒度是秒级的。

浏览器兼容性问题

h5兼容

html5shiv:用于解决IE9以下版本浏览器对HTML5新增标签不识别,并导致CSS不起作用的问题。

css兼容问题

Respond.js 是一个快速、轻量的 polyfill,用于为 IE6-8 以及其它不支持 CSS3 Media Queries 的浏览器提供媒体查询的 min-widthmax-width特性,实现响应式网页设计(Responsive Web Design)。

  • 清除浏览器默认样式(不同浏览器padding margin不同 设置默认样式)Normalize.css ,CSS Reset
  • css兼容前缀(-o- opera -ms- IE -moz- firefox -webkit- chrome)
  • postcss 利用caniuse获取数据,为样式自动添加前缀

js兼容

addeventlistenner

getattribute

babel进行语法降级

同源策略和跨域解决方案

浏览器同源策略:协议域名端口号相同

为什么有同源策略:没有同源策略的话,网站可以获取另一个网站的登录信息,会有安全隐患

同源策略限制:dom操作限制,浏览器存储访问限制,网络请求限制。

解决方案

jsonp:利用script的src不受同源策略限制,只支持get方法,调用客户端函数,传回参数为数据,获取值

cors:access-control-allow-origin:开启跨域资源共享

反向代理服务器:配置代理服务器

sessionStorage,localstorage,cookie

cookie

document.cookie='a=b'

document.cookie='a=1;expires=time'

浏览器关闭失效,或者expires过期失效 4kb 保存在客户端,每次请求都会带上

localstorage

永久有效,可以手动清除, 5M 保存在客户端,不与浏览器交互,适合存储持久化数据,页面偏好配置等,sessionStorage适合保存一次性临时数据。

sessionstorage

仅在当前会话有效,关闭页面后清除 5M

内存泄露

什么是内存泄漏

当数据不再使用时,仍然存在就是内存泄漏

哪些情况会引起内存泄漏

以外的全局变量

遗忘的定时器

事件监听未移除

闭包

遗忘的dom引用

垃圾回收机制

标记清除法和引用计数法

计网

加密算法

对称加密:两把钥匙相同,可以加密解密

非对称加密:公钥和私钥,私钥解密,公钥加密

http与https

http 明文传输 不安全

https http+ssl协议,进行加密

区别:

https是需要申请证书,需要费用

http明文,https加密

http端口80,https端口443

http是无状态连接,https是由https+ssl构成,身份认证的网络协议,安全,可靠。

http1和http2

http0.9

http/0.9 get请求,响应html

http1.0(短链接)

http/1.0 任何格式都可以请求,(图片,视频,二进制文件),post head patch,有头部信息

缺点:短连接,重新请求重新连接,下一个请求只有在前一个请求响应到达才能发送

http1.1(长连接 管道传输)

http/1.1 建立tcp连接 中间进行一系列请求响应,最后断开

长连接,管道传输(同一个tcp连接可以同时发送多个请求,服务器按照顺序相应)

缺点:队头阻塞(可以通过减少http请求避免,同时开多个tcp连接,一个域名最多有6个tcp连接)明文传输 不安全 巨大头部开销 不支持服务端消息推送

http2(二进制传输 多路复用 头部压缩 服务器推送)

只有一个tcp连接

特点:

1.二进制协议

http1XX使用文本传输,http使用二进制传输,可以对传输内容分割为帧,并行传输,收到后按照顺序拼凑。

2.多路复用

一个tcp连接,复用 可以承载任意数量的双向数据流

3.头压缩

维护静态表格,很多header高度相似,每次传输数据会浪费很多资源,使用头压缩,使用表格索引索引来表示

4.服务器推送

服务器推送消息

缺点,解决了应用层的对头阻塞,没有解决传输层队头阻塞,仍然基于tcp协议

http3

http3基于quic协议,quic基于udp,quic是在udp基础上,重写了tcp功能,没有3次握手,四次挥手,没有对头阻塞

udp怎么确保可靠性?

http数据包中添加packet header

packetheader实现可靠连接,通过其中的number实现报文重传

怎么建立连接?

使用DH密钥交换算法

客户端发请求,服务器返回server config,通过server config获取密钥,数据通过密钥加密,服务器收到后再生成密钥进行解密。

204、304、404、504

  • 204 No Content。它的含义与“200 OK”基本相同,但响应头后没有 body 数据
  • 304 Not Modified 它用于 If-Modified-Since 等条件请求,表示资源未修改,用于缓存控制。它不具有通常的跳转含义,但可以理解成“重定向已到缓存的文件”(即“缓存重定向”)。
  • 404 Not found。表示服务器无法根据客户端的请求找到资源。
  • 504 Gateway Timeout。是一种HTTP协议的服务器端错误状态代码,表示扮演网关或者代理的服务器无法在规定的时间内获得想要的响应。

tcp三次握手,四次挥手

tcp提供可靠的 面向连接的 基于字节的传输层连接协议 是全双工模式

tcp首部

序号 seq 确认传输字节序号

确认好 对上一次序号做出确认号

标志位 SYN同步标志位用于建立连接 ACK确认标志位 对已接受数据确认 FIN 完成标志位 表示即将关闭连接

nginx正向代理和反向代理

正向代理

代理客户端

国内无法访问google,香港服务器可以访问,每次请求香港的服务器,香港服务器收到请求后去访问,google服务器将响应结果返回给香港服务器,香港服务器将结果返回用户。

服务器只清楚请求来自哪个代理服务器,而不清楚来自哪个具体客户端,正向代理模式屏蔽或隐藏了真实客户端信息。

正向代理对于服务器不清楚客户端信息。

代理一般分为三种:

  • 透明代理:代理服务器暴露客户端真实信息
  • 匿名代理:隐瞒客户端信息,但是声明自己是代理服务器
  • 高匿名代理:隐藏客户端信息,也不声明自己是代理服务器

作用:

  1. 科学上网
  2. 加速访问(加速器)
  3. 缓存数据(代理服务器缓存数据)
  4. 隐藏访问者
  5. 授权访问

反向代理

代理服务端

客户端不清楚是哪台服务器响应

作用:

保护服务器:访问代理服务器来请求数据,保护服务器

负载均衡:分发给不同的服务器进行处理

跨域解决

框架通识

vue和react比较

vue2vue3react
设计思想渐进式框架,vue-router,vuex可以渐进式引入,mvvm模式,数据驱动视图和数据双向绑定函数式编程,mvc
编写语法模板语法,<template>,<script>,<style>使用数据通过mustache语法插入jsx
构建工具全局安装vue-cli脚手架,vue create创建项目,可以选择模板create-react-app没有选择模板
数据流双向绑定:v-modle语法糖value和input单向数据流,数据不可变,使用setState修改,setState是异步的,在第二个参数获取最新内容
指令vue有很多内部指令 v-if v-for v-module v-once v-prereact没有指令
生命周期breforecreate created
beforemount mounted
beforeupdate updated
beforedestroy destroyed
activited deavtivited
setup
onbeforemount onmounted
onbeforeupdate onupdated
onbeforeunmount onunmounted
onactivited ondeactivited
类组件
初始化阶段
constructor:组件状态初始化 render渲染函数 componnetDidMount:组件挂载dom
更新阶段
shouldComponetUpdate:判断组件是否需要重新渲染 render componentDidUpdate:组件更新后触发
卸载阶段
componnetWillUnmount:组件卸载前执行
函数组件
通过useEffect模拟来达到生命周期效果
useEffect的第二个参数为【】相当于componnetDidMount
useEffect第二个参数不存在,则监听所有属性
第二个属性传入数组,数组中属性变化则执行
useEffect return函数相当于componnetWillUnmount
状态管理工具vuex
state:存储数据
getter:通过state派生新数据
mutation:修改state
action:异步操作,通过mutation修改state
pinia
去掉mutation
redux
创建store
创建reducer,接收state和action返回新的state
通过dispatch修改状态
组件通信props,emits,inject,provide,eventbusdefineProps和defineEmits,父组件调用要defineExposeprops传递之后直接通过参数获取,context跨级通信
变量创建data中return对象ref,reactiveuseState ,state和setState

vue和react技术选型

1.团队成员的技术栈

2.业务适用性,react和vue3适用于大型项目,vue2适用于小型项目

spa和mpa

spampa
组成一个外壳页面和多个页面片段组成多个完整页面构成
资源共用(css,js)共用,只需在外壳部分加载不共用,每个页面都需要加载
刷新方式页面局部刷新或更改整页刷新
用户体验用户体验良好用户体验比较差
数据传递容易依赖 url传参、或者cookie 、localStorage等
seo不利于SEO有利于seo

css module原理

css模块化,有作用的概念,避免类名重复等问题

1.局部作用域

css规则是全局的,产生局部作用域是使用一个独一无二的类名


import React from 'react';
import style from './App.css';
​
export default () => {
  return (
    <h1 className={style.title}>
      Hello World
    </h1>
  );
};
​
//构建工具会把style.title编译成哈希字符串
<h1 class="_3zyde4l1yATCOkgn-DBWEL">
  Hello World
</h1>
​
//css文件同时也会被编译
._3zyde4l1yATCOkgn-DBWEL {
  color: red;
}

2.全局作用域


//被:global()包裹不会编译成为哈希字符串
:global(.title) {
  color: green;
}

3.定制hash字符串格式,通过webpack配置


module: {
  loaders: [
    // ...
    {
      test: /.css$/,
      loader: "style-loader!css-loader?modules&localIdentName=[path][name]---[local]---[hash:base64:5]"
    },
  ]
}

4.继承


.font-red {
  color: red;
}
​
.App-header {
  composes: font-red;//继承font-red类名
}

5.变量 需要安装postcss


@value blue: #0c77f8;
@value red: #ff0000;
@value green: #aaf200;
​
@value colors: "./colors.css";
@value blue, red, green from colors;
​
.title {
  color: red;
  background-color: blue;
}
​

说一下虚拟DOM?为什么要使用虚拟DOM?

频繁操作真实dom会引起重绘和回流

虚拟dom将多次修改的diff保存在js对象中,最终一次性修改

真实dom

优点:易用

缺点:性能差

虚拟dom

优点:性能好

缺点:首次渲染大量dom,多了一层虚拟dom计算

react和vue diff算法区别

vue2双端diff算法

vue3快速diff算法,最长递增子序列

react从左到右比较,

区别:

1.如果节点的key值与元素类型相同,属性值不同,react会认为是同类型节点,只是修改节点属性 ,vue会删除重建。

2.vue采用双端diff,react采用从左到右

组件通信方式

vue

props emit provide、inject

react

props emit context(provider,consumer)

vue

vue-router原理

前端路由是url与组件的映射,当url变化,组件切换

hash改变hash不会引起页面刷新

通过hashchange监听url变化


      let container = document.getElementById('container')
      window.addEventListener('hashchange', () => {
        container.innerHTML = location.hash
      })
      window.addEventListener('DOMContentLoaded', () => {
        if (!location.hash) location.hash = '#/home'
        container.innerHTML = location.hash
      })

history提供了replaceState和pushState,修改url不会引起页面刷新,浏览器前进和后退会触发popstate事件


      let container = document.getElementById('container')
      window.addEventListener('popstate', () => {
        container.innerHTML = location.pathname
      })
      window.addEventListener('DOMContentLoaded', () => {
        container.innerHTML = location.pathname
        let link = document.querySelectorAll('a[href]')
        link.forEach((a) =>
          a.addEventListener('click', (e) => {
            e.preventDefault()
            history.pushState(null, '', a.getAttribute('href'))
            container.innerHTML = location.pathname
          })
        )
      })

nextTick原理

vue异步更新dom,将dom更新缓存在一个队列中,批量执行,为了保证每个组件不管发生多少次变化,都执行一次更新。有利于性能优化,利用事件循环机制,在dom更新后执行回调

使用场景:

created中获取dom

响应式数据变化后获取dom的状态,如列表高度等

原理:

  • callbacks为存储回调的队列,pending是保证函数执行一次的flag当pending为false时,执行timerFunction,将pending变为true
  • timerFunction是选用哪种异步形式来执行flushCallbacks,顺序是promise,mutationobserver,setImmediate,setTimeout
  • flushCallbacks清空队列,将pending设为false,复制callbacks数组,清空callbacks队列,执行复制后的数组

      const callbacks = []
      let pending = false
      function nextTick(cb) {
        callbacks.push(cb)
        if (!pending) {
          pending = true
          timerFunc()
        }
      }
​
      // 优先级:Promise---> MutationObserver---> setImmediate---> setTimeout
      // 当在同一轮事件循环中多次调用 nextTick 时 ,timerFunc 只会执行一次
//mutationObserver在dom节点全部变动完成后执行,是异步触发
​
      const p = Promise.resolve()
      let timerFunc = () => {
        // 用 promise.then 把 flushCallbacks 函数包裹成一个异步微任务
        p.then(flushCallbacks)
      }
​
      // 如果多次调用 nextTick,会依次执行上面的方法,将 nextTick 的回调放在 callbacks 数组中
      // 最后通过 flushCallbacks 函数遍历 callbacks 数组的拷贝并执行其中的回调
      function flushCallbacks() {
        pending = false
        const copies = callbacks.slice(0) // 拷贝一份 callbacks,nextTick中可能还会有nextTick,避免循环所以清空
        callbacks.length = 0 // 清空 callbacks
        for (let i = 0; i < copies.length; i++) {
          // 遍历执行传入的回调
          copies[i]()
        }
      }

响应式原理

观察者模式+数据劫持

vue2 Object.defineProperty

vue3 Proxy

vue中为什么只能一个结点

vue2中只有一个根节点,因为dom树是一个单个根节点的树形结构,只能有一个节点

vue3中可以有多个节点,因为引入了fragment的概念,如果有多个节点,创建一个fragment将多个节点作为它的children

vuex为什么设计action和mutation

为了让devtool追踪到数据变化,mutation状态变更后devtool会变化,如果mutation异步操作的话,无法进行很好的状态追踪

vue2vue3数据绑定区别

v-model

Object.defineProperty//无法检测属性的添加删除,不能检测修改数组长度改变和通过索引修改值

Proxy

keep-alive原理

keep-alive是内置组件,将不活动的组件保存起来,不直接销毁,是一个抽象组件,不会渲染为真实dom

有include和exclude属性,存储缓存的组件,有两个钩子activited,deactivited

原理

  • ceated时创建缓存对象和keys数组,缓存对象用来缓存组件,keys用来保存缓存过的组件的key

  • mounted实施对include和exclude的监听,当变化时对缓存也进行改变

  • destroyed实施清除缓存,遍历缓存对象,调用pruneCatchEntry函数,pruneCatch如果传入的参数的缓存存在,则调用缓存组件实例的destroy方法,keys中去掉这个key,catch对象去掉这个key,value

  • render函数获取组件,根据插槽中传入的第一个组件,查看这个组件是否有配置

    • 如果有配置,获取此组件的组件名,查看是否在include和exclude中,如果组件不在include或者在exclude中,说明组件不需要缓存,则直接返回组件。
    • 下面说明需要缓存组件,获取组件的key,如果在catch对象中含有这个key的话,说明缓存过这个组件,将vnode的instance赋值为缓存的组件实例,调整缓存顺序。(将当前的组件顺序调到最后)如果没有缓存过,则将组件进行缓存,查看缓存长度是否超过max,超过删除第一个。
    • 最后返回组件

created(){
    this.catch = Object.create(null)
    this.keys = []
}
​
mounted(){
    //实时对include和exclude监听
    this.$watch('include',val=>{
        pruneCache(this,name=>matches(val,name))
    })
    this.$watch('exclude',val=>{
        pruneCatch(this,name=>!matches(val,name))
    })
}
​
/* destroyed钩子中销毁所有cache中的组件实例 */
destroyed () {
    for (const key in this.cache) {
        pruneCacheEntry(this.catch,key,this.keys)
    }
}    

function pruneCatchEntry(catch,key,keys,current?){
    const catched = catch[key]
    if(catched&&(!current||catched.tag!==current.tag)) {
        catched.componentInstance.$destroy()//调用销毁函数
    }
    cache[key] = null
    remove(keys,key)
}

  // src/core/components/keep-alive.js
  render () {
    const slot = this.$slots.default
    const vnode = getFirstComponentChild(slot) // 找到第一个子组件对象
    const componentOptions = vnode && vnode.componentOptions
    if (componentOptions) { // 存在组件参数
      // check pattern
      const name = getComponentName(componentOptions) // 组件名
      const { include, exclude } = this
      if ( // 条件匹配
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }
​
      const { cache, keys } = this
      const key = vnode.key == null // 定义组件的缓存key
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
      if (cache[key]) { // 已经缓存过该组件
        vnode.componentInstance = cache[key].componentInstance
        remove(keys, key)
        keys.push(key) // 调整key排序
      } else {
        cache[key] = vnode // 缓存组件对象
        keys.push(key)
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) { // 超过缓存数限制,将第一个删除
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }
​
      vnode.data.keepAlive = true // 渲染和执行被包裹组件的钩子函数需要用到
    }
    return vnode || (slot && slot[0])
  }
​

vue优化方案

1.v-show和v-if的正确使用,频繁切换使用v-show

2.computed和watch正确使用,computed依赖的值改变,会重新计算,watch当观察的数据改变,执行回调

3.v-for添加key,diff算法使用,并且避免同时使用v-if

4.组件销毁时清除定时器,清除监听事件

5.图片资源懒加载

6.路由懒加载

7.长列表优化

8.服务端渲染

computed和watch区别

computed根据值计算出新值,watch监听数据变化后执行操作

computed可以写成get和set方法,会缓存值,不支持异步,初次执行

watch监听数据可以监听简单数据类型,开启深度监听要使用deep,支持异步,可以监听data,computed,props变化,不支持缓存,初次不执行,有两个参数,新值和旧值

data为什么是函数,不是对象

防止多个组件实例共用一个对象,产生数据污染

watch能监听computed吗

可以

vue2和vue3比较

template

vue3多个根节点 fragment

teleport内置组件

script:

compositionAPI

更好的ts支持

生命周期变化

其他:

数据劫持 object.defineproperty转为proxy

更好的treeshaking

diff算法优化

proxy为什么可以数据劫持

react

setState是同步还是异步的

react状态更新是异步的,第一个参数是对象或函数,第二个是回调函数

setState为什么是异步的?

可以提升性能,如果是同步的,每次修改都要重新渲染,可以修改后批量更新。

当调用setState时,将setState对象合并到组件的当前状态,触发调和过程,根据生成的新旧dom进行diff算法对比,根据对比差异进行最小化重渲染。

对同一个值多次setState,会进行覆盖,取最后一次结果

不同值setState,会在更新时进行合并批量更新

相当于调用Object.assign

setTimeout中的setState是同步的

如果新状态不依赖于原状态使用对象

如果新状态依赖于原状态使用函数(preState参数拿到同步最新的值)

如果在setState后获取最新状态,在第二个回调函数中获取

setState返回一样的引用,render会执行吗

什么场景会触发重新渲染(re-render)

state props setState(即使值一样)都会重新渲染

pureComponent

fiber的实现原理

fiber的时间调度通过哪两个原生api实现的(requestAnimationFrame和requestIdleCallback???)

React合成事件是如何实现的

使用Redux时需要注意的点

如果Redux没返回新的数据会怎样

Redux是如何派发数据的? connect原理?

useEffect的使用方法?useEffect的return会在什么时候执行?useEffect原理是什么?

useEffect可以模拟生命周期

如果不传第二个参数,每次重新渲染都执行

第二个参数为空数组 相当于componentDidMount和componentWillUnmount

第二个参数数组中有值, 每次值变化,执行

useMemo和useCallback的区别,它们的实现原理是什么?

都是优化性能的钩子

useMemo缓存计算结果,当依赖项重新变化才会重新计算

useCallback缓存组件,当依赖项重新变化重新创建函数

useEffect、useMemo、useCallback是如何做依赖收集的

React Hooks有什么优势和劣势

优点:

代码复用

缺点:

effect只包含componentDidMount componentDidUpdate,componentWillUnmount

状态不同步

Hooks的实现原理

context的实现原理是什么?如何做依赖收集?

React的生命周期(React15、React16)

PureComponent和Component的区别

在类组件中,如果状态发生变化,便会触发组件的重新渲染,并且子组件也全部重新渲染,有时候重新渲染是没必要的

shouldComponentUpdate是react生命周期函数只有,每次render函数执行前调用,如果shouldComponentUpdate返回true表示执行render,返回false不渲染。组件首次渲染调用forceUpdate方法,不会调用shouldComponentUpdate,,改组件执行时机是state发生变化时触发重新渲染,自行生命会覆盖默认行为,需要自己判断state变化,决定是否重新渲染。接收参数nextProps nextState

PureComponent内置shouldcomponentUpdate逻辑,对props和state变化前后进行浅对比,如果没有变化跳过重新渲染。在纯组件跳过渲染,纯组件的子组件也会跳过渲染,需要保证子组件也是纯组件。

React.memo是类似PureComponent的高阶组件,用于函数组件,效果是一致的

React.memo

是一个用于优化性能的高阶组件,可以避免在父组件重新渲染时,渲染子组件,如果传入的值没变,不进行渲染

如果在map循环中没有设置key值,那么从 A B C D 四个节点变成 B C D三个节点,它会以什么样的方式变化

不加key重新删除再次创建

React dom绑定事件和原生事件有什么区别

react事件绑定到document上面 事件对象参数是合成的

原生绑定到dom上

为什么绑定到domcument上?

如果绑定到dom上,在页面响应时会受到影响,导致页面很慢,避免dom事件滥用,同时屏蔽不同浏览器事件的差异,实现中间层syntheticEvent,利用事件冒泡到document,react才会把事件交给对应函数处理。

类组件和纯函数组件的区别

类组件有状态 有生命周期钩子函数 this

函数组件无状态使用hooks 可以使用hooks, 没有生命周期 ,没有this ,没有state

类组件this问题,props会更新

函数组件props通过参数传递进来

小程序

小程序在安卓和ios区别

taro

taro将代码转为不同平台所需要的代码,实现跨平台的开发,taro内置了不同平台的编译器和构建工具,转为不同平台可以使用的代码,

提供了一套统一的api接口,使用这些api,taro在编译过程中转换为适用于各个平台的api

组件也是转为不同平台的组件

基于webpack编译

taro如何实现跨端

通过运行时框架,组件,Api抹平多端差异

不同平台有一些无法消除的差异

process.env.TARO_ENV 根据不同平台做判断,编译后只保留对应平台代码

vue的template中支持条件编译特性 指定平台的代码 指定平台剔除的代码

统一接口的多端文件

通过环境变量虽然可以解决大部分问题,但是充斥逻辑判断影响代码可维护性,可以通过统一接口的多端文件,通过文件名+端类型命名形式,不同端文件代码对外保持接口统一,使用import引入,taro编译是根据当前平台类型,将加载文件变更为带有对应端类型文件名的文件

taro如何实现跨框架

taro优化方案

taro2和taro3

taro2(重编译,轻运行)

编译时,通过Babel将react组件转换为小程序原生语法,运行时处理生命周期,事件,setDate等,运行时和react无关

jsx转为小程序template比较困难,因为jsx很灵活,taro2使用穷举方式进行转换

缺点:

与react强绑定,jsx适配工作量大,很多前端生态无法直接复用

不支持sourcemap

taro3

taro3的runtime,自行实现一套bom,dom api,让前端框架可以直接运行在小程序环境中

taro3与react适配

taro3与vue适配

优点:

跨框架,新特性无缝支持(本质将react/vue)运行在小程序上

缺点:

运行时性能不如静态模板编译

初始化渲染性能低

小程序体积压缩

开放题

antd grid实现

row和col,row为一行,col为row的直接元素,row中常用gutter为例,col中常用span为例

  • 创建row组件,props接收gutter,children,创建row的context,将gutter传入,创建类名为row的div,children作为后代放入
  • 创建col组件,props接收span,children,接收row的context,包裹类名为col的div,将gutter/2作为col的paddingleft和paddingright,将传入的span作为col-${span}类名,设置对应类名的width

export default function RowComponents(props) {
  const {gutter, children} = props
  return (
    <RowContext.Provider value={gutter}>
      <div className="row">{children}</div>
    </RowContext.Provider>
  )
}

export default function ColComponents(props) {
  const {span, children} = props
  return (
    <RowContext.Consumer>
      {(gutter) => {
        return (
          <div
            className={`col col-${span}`}
            style={{paddingLeft: gutter / 2, paddingRight: gutter / 2}}>
            {children}
          </div>
        )
      }}
    </RowContext.Consumer>
  )
}

劫持所有的a标签,点击时不发生跳转,而是弹出提示框提示即将跳转到某个网址,点击确认则跳转,点击取消则无操作

  • 利用冒泡,监听documentElement点击事件
  • 判断e.target.nodeName toLowerCase之后是否为a,
  • e.preventDefault阻止默认行为
  • alert标签的href 点击确认后使用window.location.href替换为点击的href

promise并行串行执行


function fn1 () {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('1执行')
      resolve(1)
    }, 1000)
  })
}
function fn2 () {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('2执行')
      resolve(2)
    }, 2000)
  })
}
function fn3 () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('3执行')
      reject(3)
    }, 200)
  })
}
​
let arr = [fn1, fn2, fn3]

并行

Promise.all


Promise.all(arr.map(fn => fn())).then(res => {
  console.log('success')
}).catch((res) => {
  console.log('fail')
})

arr.forEach遍历执行


function exeFn () {
  arr.forEach(async fn => {
    let res = []
    try {
      res.push(await fn())
    } catch (error) {
      console.log('fail')
    }
    if (res.length === arr.length) console.log('success')
  })
}
exeFn()

串行

for遍历执行


async function fn () {
  let res = []
  try {
    for (let i = 0; i < arr.length; i++) {
      res.push(await arr[i]())
    }
  } catch (error) {
    console.log('fail')
  }
  if (res.length === arr.length) console.log('success')
}
​
fn()

实现图片懒加载


      const intersectionObserver = new IntersectionObserver(
        (entries) => {
          entries.forEach((item) => {
            if (item.isIntersecting) {
              item.target.src = item.target.getAttribute('data-lazysrc')
              intersectionObserver.unobserve(item.target)
            }
          })
        },
        {
          threshold: 1,
        }
      )
      // 开始监听
      let imgs = document.getElementsByTagName('img')
      for (let img of imgs) {
        intersectionObserver.observe(img)
      }
​

长列表优化方案和虚拟列表

1.触底加载更多

监听滚动时间,当scrolltop+可视区域高度>=scrollHeight时触底,加载后面的数据

缺点:增加新元素导致重绘回流,只对首次有效,对性能不好。

2.虚拟列表

只渲染可视区域的元素,非可视区域不渲染,动态更新可视区域元素,根据scrollTop计算出开始的index,根据可是区域高度计算出end index,进行数据展示。

微前端

概念:是一种软件架构概念,将前端应用程序拆分为更小相互独立的部分,各个部分可以独立开发,测试,部署。每个部分可以由不同团队开发维护。提高程序的可维护性和可拓展性。

优点:技术栈无关,各自独立,服用已有功能模块

与iframe对比

iframe微前端
时间成本新项目相差不大,旧项目低新项目相差不大,旧项目高
消息通信通过postMessage通信直接通信
加载速度比微前端慢一些比iframe快一些
交互体验js,css,dom隔离,交互受到限制,弹窗只在iframe区域,不在整个项目展示没有dom隔离限制,与正常项目无差别
url同步问题url不同步,重新刷新不会保存url没有问题

前端监控

错误上报

错误捕获

trycatch捕获局部错误,语法错误和异步错误无法捕获

onerror捕获常规运行时错误,语法错误和promise错误和资源错误无法捕获

window.addEventListener('error',()=》())可以捕获资源加载错误

unhandledrejection捕获promise错误

vue中的错误不会被onerror捕获,抛给config文件中的errorhandler

发送错误

Navigator.sendBeacon 一般在visibilitychange/pagehide调用发送数据,

用户切换带其他程序,不关闭页面,可能不会触发beforeunload和unload事件

用户行为上报

无痕埋点

简单,全局监听事件,通过自定义属性作为埋点属性来进行上报,如果目标元素动态生成,将埋点属性放到父元素中

代码埋点

开发成本高,业务代码与埋点代码耦合

vue指令埋点

自定义指令v-trace


        <button v-trace.click="123" 
          :trace-params=" JSON.stringify({
            exposureAction: 's_pictures',
            clickAction: 'c_pictures',
            detail: {
              value: '测试',
            },
          })" 
          trace-key="123"
          >
            123
        </button>
        <button v-trace.expose="798"           
            :trace-params=" JSON.stringify({
            exposureAction: 's_pictures',
            detail: {
              value: 'expose',
            },
          })" 
          trace-key="55">
          55
        </button>
​
const vTrace = (el,binding)=> {
    const {modifiers} = binding
    if(modifiers.click) {
      click.add({el})
    }else if(modifiers.expose) {
      expose.add({el})
    }
}

export default class Click {
  add (entry) {
    const traceVal = entry.el.attributes['trace-params'].value
    const traceKey = entry.el.attributes["trace-key"].value
    entry.el.addEventListener("click", function () {
      console.log("上报点击埋点", JSON.parse(traceVal))
      console.log("埋点key", traceKey)
    })
  }
}

export default class Exposure {
  constructor() {
    this._observer = null
    this.init()
  }
​
  init () {
    const self = this
​
    // 实例化监听
    this._observer = new IntersectionObserver(
      function (entries) {
        entries.forEach((entry) => {
          // 出现在视窗内
          if (entry.isIntersecting) {
            const traceKey = entry.target.attributes["trace-key"].value
            const traceVal = entry.target.attributes["trace-params"].value
            const { exposureAction, detail } = JSON.parse(traceVal)
            const data = {
              action: exposureAction,
              detail,
            }
            self._observer.unobserve(entry.target)
            self.track(traceKey, data)
          }
        })
      },
      {
        threshold: 0.5, // 元素出现面积,0 - 1,这里当元素出现一半以上则进行曝光
      }
    )
  }
​
  /**
   * 元素添加监听
   *
   * @param {*} entry
   * @memberof Exposure
   */
  add (entry) {
    this._observer && this._observer.observe(entry.el)
  }
​
  /**
   * 埋点上报
   *
   * @memberof Exposure
   */
  track (traceKey, data) {
    console.log("曝光埋点", traceKey, data)
  }
}

pv,uv

路由后置守卫监听

前端性能优化

网络请求:

减少请求次数:图片懒加载,浏览器缓存

资源压缩:图片视频 css js压缩减少资源体积 treeshaking

首屏加载

骨架屏 loading效果 ssr 资源懒加载

渲染过程优化

减少dom操作 css transform绘制效率好

计算逻辑优化

计算结果缓存 避免内存泄漏

页面通信

单点登录

sso,在同一个账号平台下的多个应用系统中,用户只需登录一次,就可以访问所有仙湖信任的应用系统。多个应用系统共享登陆状态。

sso需要一个独立的认证中心,只有认证中心能登录,其他系统不提供登录入口

用户访问系统(未登录)

用户访问系统-》系统验证未登录-》sso认证中心验证未登录-》返回登录页面 用户进行登录-》用户登录提交后 sso认证中心验证 创建全局会话 创建令牌-》跳转到访问系统-》访问系统向sso认证中心校验令牌是否有效-》有效后注册系统1,返回令牌有效 系统返回资源

登陆后

访问系统-》验证未登录 跳转到sso认证中心=》sso验证已登录 携带令牌跳转系统=》系统 到sso认证中心验证令牌是否有效=》 sso验证有效 注册系统2 =》返回令牌有效 系统返回资源

注销

子系统中注销请求=》系统携带令牌 到sso认证中心请求注销=》sso验证令牌有效 销毁全局会话 取出注册系统 sso认证中心向各个系统发出销毁局部会话请求, 最后返回登陆页面

设计模式

创建型

单例模式


class SingleInstance {
  static Instance
  static getInstance () {
    if (!SingleInstance.Instance) {
      SingleInstance.Instance = new SingleInstance()
    }
    return SingleInstance.Instance
  }
}

原型模式


Object.create(obj)
以obj为原型创建对象,创建结果的__proto__指向obj

工厂模式


class Product {
  constructor(brand, price) {
    this.brand = brand
    this.price = price
  }
}
​
class ProductA extends Product {
  constructor(brand, price) {
    super('小米', 500)
  }
}
class ProductB extends Product {
  constructor(brand, price) {
    super('华为', 200)
  }
}
​
class Factory {
  getProduct (type) {
    switch (type) {
      case 'A':
        return new ProductA()
      case 'B':
        return new ProductB()
      default:
        break
    }
  }
}
​
let factory = new Factory()
let a = factory.getProduct('A')
let b = factory.getProduct('B')
console.log(a, b)

结构型

装饰器模式

不改动原有的状态,添加新的功能·

代理模式

es6的proxy

图片预加载

享元模式

当需要大量相似对象时,使用享元模式可以减少内存占用,提升性能。

行为型

观察者模式


class Subject {
  constructor() {
    this.observers = []
  }
  add (observe) {
    this.observers.push(observe)
  }
  delete (observe) {
    this.observers = this.observers.filter((item) => item !== observe)
  }
​
  notify () {
    this.observers.forEach(item => item.update())
  }
​
}
​
class Observer {
  constructor(name) {
    this.name = name
  }
  update () {
    console.log(`我是${this.name},接收到通知了`)
  }
}
​
let subject = new Subject()
let obs1 = new Observer('A')
let obs2 = new Observer('B')
subject.add(obs1)
subject.add(obs2)
​
subject.notify()

策略模式

定义一系列算法,封装起来,可以方便使用


let obj = {
  add: function (a, b) {
    return a + b
  },
  sub: function (a, b) {
    return a - b
  },
}
​
function fn (type, a, b) {
  return obj[type](a, b)
}
​
console.log(fn('add', 1, 3))

const strategies = {
  isNotEmpty: function (value, errMsg) {
    if (value === '') return errMsg
  },
  minLength: function (value, length, errMsg) {
    if (value.length < length) return errMsg
  },
  isMobile: function (value, errMsg) {
    if (!/^1(3|5|8)[0-9]{9}$/.test(value)) return errMsg
  }
}
​
class Validator {
  constructor() {
    this.cache = []
  }
  add (dom, rule, errMsg) {
    const arr = rule.split(':')
    this.cache.push(() => {
      const s = arr.shift()
      arr.unshift(dom.value)
      arr.push(errMsg)
      return strategies[s].apply(dom, arr)
    })
  }
  start () {
    for (let i = 0; i < this.cache.length; i++) {
      const msg = this.cache[i]()
      if (msg) return msg
    }
  }
}
​
function validate () {
  const validator = new Validator()
  validator.add(form.userName, 'isNotEmpty', '用户名不能为空')
  const errMsg = validator.start()
  if(errMsg) {
    alert(errMsg)
    return false
  }
}

发布订阅模式


class PubSub {
  constructor() {
    this.obj = {
    }
  }
  subscribe (type, cb) {
    if (!this.obj[type] || this.obj[type].length === 0) {
      this.obj[type] = [cb]
    } else {
      this.obj[type].push(cb)
    }
  }
  unsubscribe (type, cb) {
    if (!cb) {
      this.obj[type].length = 0
    } else {
      this.obj[type] = this.obj[type].filter(item => item !== cb)
    }
  }
  publish (type, data) {
    if (!this.obj[type] || this.obj[type].length === 0) return
    this.obj[type].forEach((cb) => {
      cb(data)
    })
  }
}
​
let pubsub = new PubSub()
function fn1 (data) {
  console.log('fn1收到数据data', data)
}
function fn2 (data) {
  console.log('fn2收到数据data', data)
}
pubsub.subscribe('A', fn1)
pubsub.subscribe('A', fn2)
pubsub.subscribe('B', fn2)
pubsub.publish('B', 5)

使用hash路由,刷新后自动滚到上次位置

监听页面滚动记录滚动位置,重新进入页面重新定位

实现多级菜单,菜单层级不定

1.封装menu组件


<Menu :data="data"></Menu>
const data =  [
  {
    id: 1,
    father_id: 0,
    status: 1,
    name: '生命科学竞赛',
    _child: [
      {
        id: 2,
        father_id: 1,
        status: 1,
        name: '野外实习类',
        _child: [
          { id: 3, father_id: 2, status: 1, name: '植物学' },
          { id: 4, father_id: 2, status: 1, name: '动物学' },
          { id: 5, father_id: 2, status: 1, name: '微生物学' },
          { id: 6, father_id: 2, status: 1, name: '生态学' }
        ]
      },
      {
        id: 7,
        father_id: 1,
        status: 1,
        name: '科学研究类',
        _child: [
          { id: 8, father_id: 7, status: 1, name: '植物学与植物生理学' },
          { id: 9, father_id: 7, status: 1, name: '动物学与动物生理学' },
          { id: 10, father_id: 7, status: 1, name: '微生物学' },
          { id: 11, father_id: 7, status: 1, name: '生态学' },
          {
            id: 21,
            father_id: 7,
            status: 1,
            name: '农学',
            _child: [
              { id: 22, father_id: 21, status: 1, name: '植物生产类' },
              { id: 23, father_id: 21, status: 1, name: '动物生产类' },
              { id: 24, father_id: 21, status: 1, name: '动物医学类' }
            ]
          },
          {
            id: 41,
            father_id: 7,
            status: 1,
            name: '药学'
          },
          { id: 55, father_id: 7, status: 1, name: '其他' }
        ]
      },
      { id: 71, father_id: 1, status: 1, name: '添加' }
    ]
  },
  {
    id: 56,
    father_id: 0,
    status: 1,
    name: '考研相关',
    _child: [
      { id: 57, father_id: 56, status: 1, name: '政治' },
      { id: 58, father_id: 56, status: 1, name: '外国语' }
    ]
  },
  {
    id: 65,
    father_id: 0,
    status: 1,
    name: '找工作',
    _child: [
      { id: 66, father_id: 65, status: 1, name: '招聘会' },
      { id: 67, father_id: 65, status: 1, name: '简历' }
    ]
  },
  {
    id: 70,
    father_id: 0,
    status: 1,
    name: '其他',
    _child: [
      {
        id: 72,
        father_id: 70,
        status: 1,
        name: '新增的根级12311111'
      }
    ]
  },
  {
    id: 80,
    father_id: 0,
    status: 1,
    name: '最后',
  }
]

  <el-menu>
    <template v-for="menu in data" :key="menu.id">
        <SubMenu :data="menu"></SubMenu>
    </template>
  </el-menu>
//遍历传入数据,使用submenu展示

2.submenu组件封装


  <el-sub-menu :index=data.id v-if="data._child&&data._child.length>0">
    <template #title >
      <el-icon><message /></el-icon>{{ data.name }}
    </template>
    <SubMemu v-for="item in data._child" :key="item.id" :data="item"></SubMemu>
  </el-sub-menu>
  <template v-else>
    <el-menu-item :index="data.id">
      {{ data.name }}
    </el-menu-item>
  </template>

根据是否有children展示,如果有children用submenu展示,递归调用自己,如果不是,直接用menuitem展示

如何监控排查内存泄露问题

使用浏览器开发工具 memory 和 performance monitor 如果js heap size不断增加可能就有内存泄漏风险 ,使用memory可以定位到泄漏位置

什么是内存泄漏

当数据不再使用时,仍然存在就是内存泄漏

哪些情况会引起内存泄漏

以外的全局变量

遗忘的定时器

事件监听未移除

闭包

遗忘的dom引用

模拟实现sleep函数

settimeout实现


function sleep (cb, time) {
  setTimeout(() => {
    cb()
  }, time)
}

promise+settimeout


let sleep = new Promise((resolve) => {
  setTimeout(() => {
    resolve()
  }, 2000)
})
​
sleep.then(() => {
  console.log(123)
})

使用var实现const和let


function myLet (key, value) {
  return Object.defineProperty(global, key, {
    configurable: false,
    enumerable: false,
    set (newValue) {
      value = newValue
    },
    get () {
      return value
    }
  })
}

function myConst (key, value) {
  if (arguments.length === 1) throw new Error('Missing initializer in const declaration')
  return Object.defineProperty(global, key, {
    configurable: false,
    enumerable: false,
    set (newValue) {
      throw new Error('不能重复赋值')
    },
    get () {
      return value
    }
  })
}

实现splice方法

// 0 返回空数组 原数组不变

// 1 返回当前index到末尾 原数组0到当前index前

// 2 返回从index删除的元素 原数组为删除后的剩余元素

// 2+返回从index删除的元素 原数组为删除元素和添加的元素

A页面跳转到B页面,在b页面做的操作传输给A页面

A页面跳转到B页面,返回A页面时,怎么保持上一次的状态

  1. 使用keep-alive
  2. 使用vuex保存页面数据,返回后展示

Sentry如何实现错误检控

搭建项目会使用哪些技术方案

如何做技术选型

编写一个函数,传入一个promise和n,如果n秒内没有返回结果,直接reject

ssr

服务端渲染

优点:

1.首屏加载更快

2.更好的seo

缺点

1.开发限制,有些代码只能在某些生命周期中使用,一些外部库要特殊处理才能使用

2.服务端负载

ssg(静态站点生成)如果说每个页面的数据对每个用户都相同,可以只渲染一次,预渲染页面被服务器托管。

nuxt

生命周期

服务端:

nuxtServerInit 在store中,将服务器端数据传给客户端

middleware

validate 路由发生变化做参数校验

asyncData(return对象放在data中) fetch (在breforecreate和created之后执行)

render

服务端&&客户端

beforeCreate created

客户端

vue其他生命周期

没有keep-alive不支持activited和deactivited,在服务端生命周期只能通过context访问服务端上下文环境,this指向undefined,不存在window对象

浅拷贝和深拷贝

浅拷贝

Object

object.assign()和展开运算符

Array

slice和concat方法

深拷贝

JSON.parse(JSON.stringify())//拷贝function正则等对象有问题

手写递归


function deepClone (data) {
  if (typeof data !== 'object' || data === null) return data
  if (Array.isArray(data)) {
    let arr = []
    data.forEach(item => {
      arr.push(deepClone(item))
    })
    return arr
  } else {
    let obj = {}
    for (let key in data) {
      obj[key] = deepClone(data[key])
    }
    return obj
  }
}

如何提高团队开发效率

准备工作:技术栈选择,排期(分析项目中要用到的技术)

开发:公共组件抽离 代码风格统一

eslint:代码合适检查工具,配合vscode的eslint插件提示并修复代码格式错误,也可以通过运行eslint --fix检查并修复代码格式,配置文件后没有告诉开发者,可以通过安装eslint插件和script添加lint ‘eslint .‘运行lint会在控制台打印不符合代码风格的错误信息

手动修复比较麻烦

通过eslint插件,可以使用format on save

手动添加lint ’eslint . --fix‘ 手动运行 代码自动格式化

prettier:代码格式化

测试

单元测试:缩短反馈周期,降低bug修复成本

开发时间长,修复bug时间短(之前提前发现)

playwright 前端自动化测试框架 可以测试组件运行 写一些测试case 运行

jest 前端自动化测试框架 测试代码 testMatch配置测试文件

Vitest 前端自动化测试框架

部署

Devops

gitlab ci/cd是内置在gitlab中的工具,通过持续的方法来进行软件开发

ci continuous integration 持续集成

cd continuous delivery 持续交付

cd continuous deployment 持续部署

持续继承原理是,每次将代码推送到仓库时,都会运行一系列脚本来打包,测试,验证代码更改,然后将其合并到主分支中

持续交付和部署可以在每次推送到仓库默认分支的同时将应用程序部署到生产环境

可以在开发早期发现bug,保证部署到生产环境的代码都符合代码标准

通过.gitlab-ci.yml配置 代码运行状态也会展示 成功 失败 错误 运行中

配置runner运行配置的workflow

项目亮点

unocss,tailwind

tailwind

优点:

使用apply将内置工具模式提取到自定义css类中

vscode插件 自动提示

缺点

过度使用类

unocss

可定制

通过配置文件自定义css工具类

即时性

无解析,无扫描,比tailwind快

属性化

属性化增加代码可读性


<button
  bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  text="sm white"
  font="mono light"
  p="y-2 x-4"
  border="2 rounded blue-200"
>
  Button
</button>

拖拽组件

标签设置draggable为true

执行顺序dragstart-》drag-》dragend

dragenter->dragover->dragleave->drop

组件封装

初始化包 名字 description

npm官网上传

组件文档,组件库

vue

编写插件,要有install方法 install传入app和config参数,install方法中可以写组件

app.component(‘name’,component)

react

dumi 组件开发静态站点框架

数据双向绑定 v-model

props emit使用传入数据和事件

npm发布包 login publish

表单联动

聊天websocket

应用层协议 基于tcp要进行三次握手四次挥手 全双工通信 持续连接

ws 80端口

wss443端口

没有同源限制

主要方法:close send

主要事件:open close error message

心跳检测:保证链接的持续性和稳定性,如果过网络出现问题不会触发websocket任何事件,直到发送数据才会发现,定期发送ping,检测连接问题,一旦出现异常重新连接

ts

微前端 qiankun

iframe问题:

url不同步,刷新后iframe状态丢失,dom不共享,通信要通过postmessage

qiankun基于single-spa

优点:

技术栈无关

独立开发独立部署

增量升级:一些老项目重构,可以使用微前端做增量升级

通信:

localstorage

路由

props和函数传参

官方提供initGlobalState(state) 初始化action,通过porsp传给子应用

vuex pinia通过props传给子应用

spline

spline tool工具

taro

taro2 只支持react 编译时 将jsx编译为小程序原生语法,jsx比较灵活 使用穷举法

虚拟列表

webview和iframe

webview移动应用中展示网页

iframe在网页中嵌入另一个网页

umi webpack vite

umi 对react进行打包

webpack很多脚手架基于webpack打包 umi

vite冷启动热更新

前端登录

普通登录

cookie:浏览器请求自动携带,容易引发csrf攻击

authorization:解决没有cookie场景,避免csrf攻击

token存储:localstorage

refresh token:token过期

请求拦截:token注入

响应拦截:401 refresh或登录页面

路由拦截:页面是否需要token

sso

同一个主域名

在cookie中同一个主域名可以获取到cookie存储

主域名不同

需要独立的认证服务,称为sso

第三方登录

OAuth,第三方登录