面试知识点(自用)

150 阅读12分钟

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() 结构类似数组
  1. 数组去重
  2. 并集交集差集
  • weakSet()
  1. 成员只能为对象;
  2. 弱引用,无法遍历,垃圾回收机制不考虑该对象的应用
  • map():结构类似对象
  1. 键可以是任何类型的值 ;
  2. 同一个对象的引用是键,相同的键会覆盖;

5. 箭头函数

  • 与普通函数的区别:
  1. 没有自己的this,this指向外部
  2. this定义时就确定了,不可用call,bind改变
  3. 不可作为构造函数

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. 构造函数与原型链的继承问题:1.原型中的引用值会在实例间共享;2.构造函数外在原型上定义的方法,不能重用。
  2. class解决了以上问题

7. proxy

9. Symbol()

10. 模块化 import export 1.解决命名冲突。2.提高复用性 3.提高代码可读性

11. 数组方法扩展 find() findIndex() keys() values() includes()

react

1.setstate是同步还是异步。

setState其实并不是真的异步,只是看起来像是异步执行的,它是通过isBatchingUpdates来判断当前执行是同步还是异步的,如果isBatchingUpdatestrue,则按异步执行,反之就是同步执行。要改变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.虚拟列表结解决监控因子记录过多的情况。

  1. 列表项高度 itemSize = 100

  2. 可视区可显示数量 viewcount = viewport / itemSize

  3. 可视区最后一个元素标号 endIndex = startIndex + viewcount

当用户滚动时,逻辑处理如下:

  1. 获取可视区滚动距离 scrollTop;

  2. 根据 scrollTop 和 itemSize 计算出 startIndex 和 endIndex;

// 获取startIndex 
const getStartIndex = (scrollTop) => { 
    return Math.floor(scrollTop / itemSize); // 这里可以思考下,为什么要用Math.floor 
};

3. 根据 startIndex 和 itemSize 计算出 startOffset;

  1. 只显示startIndex 和 endIndex之间的元素;

  2. 设置 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';
  1. 需要在 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,当打包的modeproduction时,自动开启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

7.