JS
- instanceof 和 typeof 的实现原理 1 typeOf null // Object 可使用Object.prototype.toString.call 判断
fn instanceof Function // true
// 能在实例的 原型链 中找到该构造函数的prototype属性所指向的原型对象,就返回true
es6
1. let const
- 和var的区别
2. Promise
- 三种状态
- 链式调用
- .then()的2个参数
3. 解构赋值
- 字符串
- 数组
- 对象
4. 数据结构 set() weakSet() map() weakMap()
- set() 结构类似数组
- 数组去重
- 并集交集差集
- weakSet()
- 成员只能为对象;
- 弱引用,无法遍历,垃圾回收机制不考虑该对象的应用
- map():结构类似对象
- 键可以是任何类型的值 ;
- 同一个对象的引用是键,相同的键会覆盖;
5. 箭头函数
- 与普通函数的区别:
- 没有自己的this,this指向外部
- this定义时就确定了,不可用call,bind改变
- 不可作为构造函数
6. class类
- 是构造函数语法糖。底层还是构造函数
// 构造函数 模式
functon A (x){
this.x = x
}
// 在原型链上挂载原型方法
A.prototype.showX = function (){
return this.x
}
// 生成对象实例
let a = new A(1)
// 调用原型方法
a.showX() // 1
// class 模式
class A {
// 构造函数,相当于之前的函数A
constructor(x){
this.x = x
}
// 相当于挂载在原型链上的原型方法
showX () {
return this.x
}
}
// 生成对象实例
let a = new A(1)
// 调用原型方法
a.showX() // 1
- constructor是类的构造函数,new的时候自动调用该方法
- extends 继承
- 构造函数与原型链的继承问题:1.原型中的引用值会在实例间共享;2.构造函数外在原型上定义的方法,不能重用。
- class解决了以上问题
7. proxy
9. Symbol()
10. 模块化 import export 1.解决命名冲突。2.提高复用性 3.提高代码可读性
11. 数组方法扩展 find() findIndex() keys() values() includes()
react
1.setstate是同步还是异步。
setState其实并不是真的异步,只是看起来像是异步执行的,它是通过isBatchingUpdates来判断当前执行是同步还是异步的,如果isBatchingUpdates为true,则按异步执行,反之就是同步执行。要改变isBatchingUpdates,只需要打破React的合成事件,在js的原生事件中setTimeout执行setState即可,在 react18 里面,如果用 createRoot 的 api,就不会有这种问题了。
unstable_batchedUpdates强制合成事件,批量更新。
2.setstate和useefect实现原理
VUE
vue的编译流程:
- 1.parse:通过parse函数,把模板编译成AST对象;
- 2.transform:通过transform函数,把AST转化成javaScript AST;
- 3.generate:通过generate,把javaScript AST转化成渲染函数(render)
项目中遇到什么问题?如何解决?
项目中难点及解决方案
1.虚拟列表结解决监控因子记录过多的情况。
-
列表项高度
itemSize = 100 -
可视区可显示数量
viewcount = viewport / itemSize -
可视区最后一个元素标号
endIndex = startIndex + viewcount
当用户滚动时,逻辑处理如下:
-
获取可视区滚动距离
scrollTop; -
根据 scrollTop 和 itemSize 计算出 startIndex 和 endIndex;
// 获取startIndex
const getStartIndex = (scrollTop) => {
return Math.floor(scrollTop / itemSize); // 这里可以思考下,为什么要用Math.floor
};
3. 根据 startIndex 和 itemSize 计算出 startOffset;
-
只显示
startIndex和endIndex之间的元素; -
设置
list-area的偏移量为startOffset;
其中第2步是比较关键的(后面也会多次提到),其实计算出了startIndex 也就计算出了endIndex 和 startOffset;
2.公司资质大文件上传
- 1.文件
FIle对象是Blob对象的子类,Blob对象包含一个重要的方法slice通过这个方法,我们就可以对二进制文件进行拆分 - 2.结合
Promise.race和异步函数实现,多个请求同时并发的数量,防止浏览器内存溢出 - 3.在单个请求失败后,触发
catch的方法的时候,讲当前请求放到失败列表中,在本轮请求完成后,重复对失败请求做处理
3.使用乾坤后,整个项目进行权限包的划分
- 1.权限的设计是由前端进行设计的,公司有一个项目管理系统,可以在里面进行项目菜单和权限的设计, 其他端主要都是设计成管理员权限和普通权限进行划分,企业端比较复杂。
- 2.通过用户的登录和付费情况进行划分为 游客身份,登录注册未认证身份,登录注册已认证身份,已认证的还分为是否购买服务,只有购买服务的才展示对应的服务。
- 3.服务分为基础服务和付费服务,登录注册已认证的客户,展示基础服务,付费的客户通过动态路由的形式展示后端接口返回的对应服务权限。
乾坤微应用中出现的问题
1.样式隔离问题。
这个问题我估计用到相同框架并进行嵌套使用的需要看下,这个问题必须解决。不然影响太多了。由于你可能将你的微应用嵌入到某些页面下进行使用。据这段时间发现的就有 tab 样式上发生莫名其妙的margin问题。不知名多了一个marginlefet 值这个就是相同框架下layout嵌套导致的。
说下解决的方案吧,其实之前的思路有两种。
- 一种是通过在主应用上进行加 prefixCls 进行解决
- 一种是通过启动乾坤的沙箱模式即(将 settings={{sandbox:{strictStyleIsolation:true}}} 时表示开启严格的样式隔离模式。这种模式下 qiankun 会为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响。)
<MicroApp name='rd' className={styles.oneRdContainer} settings={{sandbox:{strictStyleIsolation:true}}} />
这边的话用的是第一种方式。第二种我这边感觉有点复杂,会造成原先的react事件不好使需要解决 shadow dom 的问题
下面具体说一下第一种方案。(以umi 启动的项目为例)
首先需要创建一个文件或者在defaultSettings 中加上以下代码,有了下面代码可以继续下面的步骤了
export const prefix = 'one';
- 需要在 config.ts 下面配置上(修改 antd css 前缀)
import {prefix} from '../defaultSettings';
export default defineConfig({
theme:{prefix},
});
2.更改 dom 上的 class 这块操作需要再 入口去做
import { ConfigProvider } from 'antd';
import { prefix } from '../../../config/defaultSettings';
export default () => (
<ConfigProvider direction={`${prefix}`}>
<App />
</ConfigProvider>
);
搞定上面两步可以覆盖掉 antd 80% 的场景,但是需要注意的是有些场景用不了。antd 虽然针对这种也有解决的措施,但是对于开发者来说不友好。可以看下antd 例举
wepack中做的一些优化
打包时间优化
1. 使用热更新替换自动刷新。
我们在日常开发中,由于每次改完代码都要刷新页面,但是如果项目体积过大,特别慢。可以使用HotModuleReplacementPlugin插件,开启热更新代替自动刷新。
//引入webpack
const webpack = require('webpack');
//使用webpack提供的热更新插件
plugins: [
new webpack.HotModuleReplacementPlugin()
],
//最后需要在我们的devserver中配置
devServer: {
+ hot: true
},
2、启用多进程打包(项目较大再使用)
happypack
happypack本身就是对node开启了一个多进程打包,用法有点麻烦
//首先引入happypack
const HappyPack = require('happypack')
//然后在lorder中使用即可,以babel-lorder为例
{
test: /.js$/,
// 对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
include: srcPath,
// exclude: /node_modules/
}
//需要在plugins中使用HappyPack
// happyPack 开启多进程打包
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// 处理 .js 文件,用法和 Loader 配置中一样,也能开启缓存
loaders: ['babel-loader?cacheDirectory']
}),
当然由于webpack4中官方文档的极力推荐thread-loader,并且HappyPack将不再被维护,所以当我们使用多进程打包时首选thread-loader
thread-loader
hread-loader 使用起来也非常简单,只要把 thread-loader 放置在其他 loader 之前即可,这样一来,按照官方的解释之后的 loader 就会在一个单独的 worker 池(worker pool)中运行,并且还支持之定义配置,方便性能优化
{
test: /.js$/,
exclude: /node_modules/,
// 创建一个 js worker 池
use: [
//直接在loader之前使用
'thread-loader',
'babel-loader'
]
},
//自定义配置行
use[
{
loader: "thread-loader",
// loaders with equal options will share worker pools
// 设置同样option的loaders会共享
options: {
// worker的数量,默认是cpu核心数
workers: 2,
// 一个worker并行的job数量,默认为20
workerParallelJobs: 50,
// 添加额外的node js 参数
workerNodeArgs: ['--max-old-space-size=1024'],
// 允许重新生成一个dead work pool
// 这个过程会降低整体编译速度
// 开发环境应该设置为false
poolRespawn: false,
//空闲多少秒后,干掉work 进程
// 默认是500ms
// 当处于监听模式下,可以设置为无限大,让worker一直存在
poolTimeout: 2000,
// pool 分配给workder的job数量
// 默认是200
// 设置的越低效率会更低,但是job分布会更均匀
poolParallelJobs: 50,
}
}
'babel-loader'
]
ParallelUglifyPlugin
我们知道压缩 JS 需要先将代码解析成 AST 语法树,然后需要根据复杂的规则去分析和处理 AST,最后将 AST 还原成 JS,这个过程涉及到大量计算,因此比较耗时,那么我们使用ParallelUglifyPlugin这个插件就能开启多进程压缩JS使用方式也非常简单
//引入插件
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
plugins: [
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
})
]
复制代码
3、使用include或者exclude配置,来避免重复打包
在我们的日常开发中,我们引入的一些插件,类库,都是被打包过了的,那么我们用babel去做编译的时候,就需要配置一下,给已经编译过的语法剔除掉,这样就能减少打包时间,在此,科普一下,我们在使用插件的时候webpack是怎么做的,比如我们在项目中去引入jq插件
import $ from 'jquery'
复制代码
首先当我们取用esmodel去引用jquery的时候npm搜索会先从当前目录的node_modules中找,找不到。就往上一级目录找node_modules。一直往上找到当前磁盘。如果都没有。才报错。当找到node_modules包之后他回去找package.json文件,在文件中确入口文件,然后找到入口文件去加载,当加载后发现他其实已经是一个已经编译过的文件了
确定入口
如上图,这是一个编译过的es5的代码,因为你可以看到熟悉的原型对象,那我我们应该怎么使用include,或者exclude,比如我们配置webpack的时候我们在babel-loader中去配置:
{
test: /.js$/,
//使用include来指定编译文件夹
include: path.resolve(__dirname, '../src'),
//使用exclude排除指定文件夹
exclude: /node_modules/
use: [{
loader: 'babel-loader'
}]
}
4.cache-loader
cache-loader缓存资源,提高二次构建的速度,使用方法是将cache-loader放在比较费时间的loader之前,比如babel-loader
由于启动项目和打包项目都需要加速,所以配置在
webpack.base.js
npm i cache-loader -D
// webpack.base.js
{
test: /.js$/,
use: [
'cache-loader',
'thread-loader',
'babel-loader'
],
},
5. 构建区分环境
区分环境去构建是非常重要的,我们要明确知道,开发环境时我们需要哪些配置,不需要哪些配置;而最终打包生产环境时又需要哪些配置,不需要哪些配置:
开发环境:去除代码压缩、gzip、体积分析等优化的配置,大大提高构建速度生产环境:需要代码压缩、gzip、体积分析等优化的配置,大大降低最终项目打包体积
打包体积优化
主要是打包后项目整体体积的优化,有利于项目上线后的页面加载速度提升
本项目已经是webpack最新版本
0.合理配置打包文件hash
利用浏览器缓存,当文件没被修改hash不变,浏览器只需要下载hash文件名不同的文件(修改过的文件),没修改的文件直接利用浏览器缓存。
| 占位符 | 解释 |
|---|---|
| ext | 文件后缀名 |
| name | 文件名 |
| path | 文件相对路径 |
| folder | 文件所在文件夹 |
| hash | 每次构建生成的唯一 hash 值 |
| chunkhash | 根据 chunk 生成 hash 值 |
| contenthash | 根据文件内容生成hash 值 |
因为js我们在生产环境里会把一些公共库和程序入口文件区分开,单独打包构建,采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响,可以继续使用浏览器缓存,所以js适合使用chunkhash。
css和图片资源媒体资源一般都是单独存在的,可以采用contenthash,只有文件本身变化后会生成新hash值。
1.CSS代码压缩
CSS代码压缩使用css-minimizer-webpack-plugin,效果包括压缩、去重
代码的压缩比较耗时间,所以只用在打包项目时,所以只需要在
webpack.prod.js中配置
npm i css-minimizer-webpack-plugin -D
复制代码
// webpack.prod.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
optimization: {
minimizer: [
new CssMinimizerPlugin(), // 去重压缩css
],
}
复制代码
2.JS代码压缩
JS代码压缩使用terser-webpack-plugin,实现打包后JS代码的压缩
代码的压缩比较耗时间,所以只用在打包项目时,所以只需要在
webpack.prod.js中配置
npm i terser-webpack-plugin -D
复制代码
// webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin')
optimization: {
minimizer: [
new CssMinimizerPlugin(), // 去重压缩css
new TerserPlugin({ // 压缩JS代码
terserOptions: {
compress: {
drop_console: true, // 去除console
},
},
}), // 压缩JavaScript
],
}
复制代码
3.tree-shaking
tree-shaking简单说作用就是:只打包用到的代码,没用到的代码不打包,而webpack5默认开启tree-shaking,当打包的mode为production时,自动开启tree-shaking进行优化
module.exports = {
mode: 'production'
}
复制代码
4.source-map类型
source-map的作用是:方便你报错的时候能定位到错误代码的位置。它的体积不容小觑,所以对于不同环境设置不同的类型是很有必要的。
- 开发环境
开发环境的时候我们需要能精准定位错误代码的位置
// webpack.dev.js
module.exports = {
mode: 'development',
devtool: 'eval-cheap-module-source-map'
}
复制代码
- 生产环境
生产环境,我们想开启source-map,但是又不想体积太大,那么可以换一种类型
// webpack.prod.js
module.exports = {
mode: 'production',
devtool: 'nosources-source-map'
}
复制代码
5.打包体积分析
使用webpack-bundle-analyzer可以审查打包后的体积分布,进而进行相应的体积优化
只需要打包时看体积,所以只需在
webpack.prod.js中配置
npm i webpack-bundle-analyzer -D
复制代码
// webpack.prod.js
const {
BundleAnalyzerPlugin
} = require('webpack-bundle-analyzer')
plugins: [
new BundleAnalyzerPlugin(),
]
复制代码
用户体验优化
模块懒加载,小图片转base64, 合理配置hash,gzip
问题
1.$nextTick
nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法。
2.$set
了解 Vue 响应式原理的同学都知道在两种情况下修改 Vue 是不会触发视图更新的。
1、在实例创建之后添加新的属性到实例上(给响应式对象新增属性)
2、直接更改数组下标来修改数组的值。
Vue.set 或者说是 $set 原理如下
因为响应式数据 我们给对象和数组本身新增了ob属性,代表的是 Observer 实例。当给对象新增不存在的属性,首先会把新的属性进行响应式跟踪 然后会触发对象 ob 的dep收集到的 watcher 去更新,当修改数组索引时我们调用数组本身的 splice 方法去更新数组。
3.bfc 块级格式化上下文。
BFC是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。- 怎样触发BFC,
overflow: hidden,display: inline-block,position: absolute,position: fixed,display: table-cell,display: flex - 造成问题: 1.使用Float脱离文档流,高度塌陷,(给父元素触发bfc,设置以上属性可解决),2 外边距重叠(包一个div可解决)
4.区分数组和对象
- Array.isArray,Object.prototype.toString.call, instanceof
5.ref和reactive区别
-
reactive将引用类型值变为响应式,使用Proxy实现 -
ref可将基本类型和引用类型都变成响应式,Object.defineProperty()的get()和set()的,但是当传入的值为引用类型时实际上内部还是使用reactive方法进行的处理 -
ref经修改实现方式后性能更高,推荐使用
ref一把梭
6.如何优化SPA应用的首屏加载
- 1.将公用的JS库通过script标签外部引入,减小 app.bundel 的大小,让浏览器并行下载资源文
件,提高下载速度;
- 2.在配置 路由时,页面和组件使用懒加载的方式引入,进一步缩小 app.bundel 的体积,在调用某
个组件时再加载对应的js文件;
- 3.加一个首屏loading图,提升用户体验;
- 4.使用预渲染插件prerender-spa-plugin生成对特定路由静态的html文件
- 5.懒加载使用react.lazy配合import + Suspense进行懒加载,时webpack打包时进行分包,使得首屏加载时只加载需要用到的js