当前位置:   article > 正文

前端开发必看1000道大厂面试题(一)_参数 "item" 隐式具有 "any" 类型,但可以从用法中推断出更好的类型。

参数 "item" 隐式具有 "any" 类型,但可以从用法中推断出更好的类型。

在这里插入图片描述
本文内含前端开发常见大厂面试题,包含笔试题及问答题,持续更新中,建议收藏慢慢看


1. 说说gulp和webpack的区别

开放式题目

Gulp强调的是前端开发的工作流程。我们可以通过配置一系列的task,定义task处理的事务(例如文件压缩合并、雪碧图、启动server、版本控制等),然后定义执行顺序,来让Gulp执行这些task,从而构建项目的整个前端开发流程。通俗一点来说,“Gulp就像是一个产品的流水线,整个产品从无到有,都要受流水线的控制,在流水线上我们可以对产品进行管理。”

Webpack是一个前端模块化方案,更侧重模块打包。我们可以把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。 Webpack就是需要通过其配置文件(Webpack.config.js)中 entry 配置的一个入口文件(JS文件),然后在解析过程中,发现其他的模块,如scss等文件,再调用配置的loader或者插件对相关文件进行解析处理。

虽然Gulp 和 Webpack都是前端自动化构建工具,但看2者的定位就知道不是对等的。Gulp严格上讲,模块化不是他强调的东西,旨在规范前端开发流程。Webpack更明显的强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能。

2. 小程序路由跳转

  1. 通过组件navigator跳转,设置url属性指定跳转的路径,设置open-type属性指定跳转的类型(可选),open-type的属性有 redirect, switchTab, navigateBack
// redirect 对应 API 中的 wx.redirect 方法
<navigator url="/page/redirect/redirect?title=redirect" open-type="redirect">在当前页打开</navigator>

// navigator 组件默认的 open-type 为 navigate 
<navigator url="/page/navigate/navigate?title=navigate">跳转到新页面</navigator>

// switchTab 对应 API 中的 wx.switchTab 方法
<navigator url="/page/index/index" open-type="switchTab">切换 Tab</navigator>

// reLanch 对应 API 中的 wx.reLanch 方法
<navigator url="/page/redirect/redirect?title=redirect" open-type="redirect">//关闭所有页面,打开到应用内的某个页面

// navigateBack 对应 API 中的 wx.navigateBack 方法
<navigator url="/page/index/index" open-type="navigateBack">关闭当前页面,返回上一级页面或多级页面</navigator>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  1. 通过api跳转,wx.navigateTo() , wx.navigateBack(), wx.redirectTo() , wx.switchTab(), wx.reLanch()
wx.navigateTo({
  url: 'page/home/home?user_id=1'  // 页面 A
})
wx.navigateTo({
  url: 'page/detail/detail?product_id=2'  // 页面 B
})
// 跳转到页面 A
wx.navigateBack({
  delta: 2  //返回指定页面
})

// 关闭当前页面,跳转到应用内的某个页面。
wx.redirectTo({
url: 'page/home/home?user_id=111'
})

// 跳转到tabBar页面(在app.json中注册过的tabBar页面),同时关闭其他非tabBar页面。
wx.switchTab({
url: 'page/index/index'
})

// 关闭所有页面,打开到应用内的某个页面。
wx.reLanch({
url: 'page/home/home?user_id=111'
})
  • 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

3. 阐述一下http1.0与http2.0的区别,及http和https区别

1、HTTP1.0和HTTP1.1的一些区别

缓存处理,HTTP1.0中主要使用Last-Modified,Expires 来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略:ETag,Cache-Control…

带宽优化及网络连接的使用,HTTP1.1支持断点续传,即返回码是206(Partial Content)
错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除…

Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)

长连接,HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点

2、HTTP2.0和HTTP1.X相比的新特性

新的二进制格式(Binary Format),HTTP1.x的解析是基于文本,基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合,基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮

header压缩,HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小

服务端推送(server push),例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了

4. 谈谈宏任务与微任务的理解,举一个宏任务与微任务的api

要理解宏任务(macrotask)与微任务(microtask),就必须了解javascript中的事件循环机制(event loop)以及js代码的运行方式

要了解js代码的运行方式,得先搞懂以下几个概念:

JS是单线程执行

单线程指的是JS引擎线程

宿主环境

JS运行的环境,一般为浏览器或者Node

执行栈

是一个存储函数调用的栈结构,遵循先进后出的原则

JS引擎常驻于内存中,等待宿主将JS代码或函数传递给它执行,如何传递,这就是**事件循环(event loop)**所做的事情:当js执行栈空闲时,事件循环机制会从任务队列中提取第一个任务进入到执行栈执行,优先提取微任务(microtask),待微任务队列清空后,再提取宏任务(macrotask),并不断重复该过程

在实际应用中,宏任务(macrotask)与微任务(microtask)的API分别如下:

宏任务
setTimeout/setInterval
ajax
setImmediate (Node 独有)
requestAnimationFrame (浏览器独有)
I/O
UI rendering (浏览器独有)
微任务
process.nextTick (Node 独有)
Promise
Object.observe
MutationObserver

5. 如何在TS中对函数的返回值进行类型约束

ts中函数参数的类型定义

函数的参数可能是一个,也可能是多个,有可能是一个变量,一个对象,一个函数,一个数组等等。

1.函数的参数为单个或多个单一变量的类型定义

function fntA(one, two, three) {
    // 参数 "two" 隐式具有 "any" 类型,但可以从用法中推断出更好的类型。
    return one + two + three
}
const aResult = fntA(1, '3', true)
  • 1
  • 2
  • 3
  • 4
  • 5

修改后:

function fntA(one: number, two: string, three: boolean) {
    return one + two + three
}
const aResult1 = fntA(1, '3', true)
// 如果函数的参数为单个或者多个变量的时候,只需要为这些参数进行静态类型下的基础类型定义就行
  • 1
  • 2
  • 3
  • 4
  • 5

2. 函数的参数为数组的类型定义

function fntB(arr) {
    //参数 "arr" 隐式具有 "any" 类型,但可以从用法中推断出更好的类型。
    return arr[0]
}
const bResult = fntB([1, 3, 5])
  • 1
  • 2
  • 3
  • 4
  • 5

修改后:

function fntB(arr: number[]) {
    return arr[0]
}
const bResult1 = fntB([1, 3, 5])
// 如果参数是数组时,只需要为这些变量进行对象类型下的数组类型定义
  • 1
  • 2
  • 3
  • 4
  • 5

3.函数的参数为对象的类型定义

function fntC({ one, two }) {
    return one + two
}
const cResult = fntC({ one: 6, two: 10 })
  • 1
  • 2
  • 3
  • 4

修改后:

function fntC({ one, two }: { one: number, two: number }) {
    return one + two
}
const cResult1 = fntC({ one: 6, two: 10 })
// 如果参数是对象,只需要为这些变量进行对象类型下的对象类型定义
  • 1
  • 2
  • 3
  • 4
  • 5

4.函数的参数为函数的类型定义

function fntD(callback) {
    //参数 "callback" 隐式具有 "any" 类型,但可以从用法中推断出更好的类型
    callback(true)
}
function callback(bl: boolean): boolean {
    console.log(bl)
    return bl
}
const dResult = fntD(callback)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

修改后:

function fntD(callback: (bl: boolean) => boolean) {
    callback(true)
}
function callback(bl: boolean): boolean {
    console.log(bl)
    return bl
}
const dResult = fntD(callback)
// 如果参数是函数,只需要为参数进行对象类型下的函数类型定义即可
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

ts中函数返回值的类型定义

当函数有返回值时,根据返回值的类型在相应的函数位置进行静态类型定义即可

返回数字:

function getTotal2(one: number, two: number): number {
    return one + two;
}
const total2 = getTotal(1, 2);
// 返回值为数字类型
  • 1
  • 2
  • 3
  • 4
  • 5

返回布尔值

function getTotal2(one: number, two: number): boolean {
    return Boolean(one + two);
}
const total2 = getTotal(1, 2);
// 返回值为布尔类型
  • 1
  • 2
  • 3
  • 4
  • 5

返回字符串

function getTotal2(one: string, two: string): string{
    return Bone + two;
}
const total2 = getTotal('1', '2');
// 返回值为字符串
  • 1
  • 2
  • 3
  • 4
  • 5

返回对象

function getObj(name: string, age: number): { name: string, age: number } {
    return {name,age}
}
getObj('小红',16)
// 返回值为对象
  • 1
  • 2
  • 3
  • 4
  • 5

返回数组

function getArr(arr: number[]) :number[]{
    let newArr = [...arr]
    return newArr
}
getArr([1,2,3,4])
// 返回值为数组
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

函数返回值为underfinde,仅仅时为了在内部实现某个功能,我们就可以给他一个类型注解void,代表没有任何返回值,

function sayName() {
    console.log('hello,world')
}
  • 1
  • 2
  • 3

修改后:

function sayName1(): void {
    console.log('无返回值')
}
  • 1
  • 2
  • 3

当函数没有返回值时

// 因为总是抛出异常,所以 error 将不会有返回值
// never 类型表示永远不会有值的一种类型
function error(message: string): never {
    throw new Error(message);
}
  • 1
  • 2
  • 3
  • 4
  • 5

6. 平时工作中有是否有接触linux系统?说说常用到linux命令?

1、常用的目录操作命令

创建目录 mkdir <目录名称>
删除目录 rm <目录名称>
定位目录 cd <目录名称>
查看目录文件 ls ll
修改目录名 mv <目录名称> <新目录名称>
拷贝目录 cp <目录名称> <新目录名称>

2、常用的文件操作命令

创建文件 touch <文件名称> vi <文件名称>
删除文件 rm <文件名称>
修改文件名 mv <文件名称> <新文件名称>
拷贝文件 cp <文件名称> <新文件名称>

3、常用的文件内容操作命令

查看文件 cat <文件名称> head <文件名称> tail <文件名称>
编辑文件内容 vi <文件名称>
查找文件内容 grep ‘关键字’ <文件名称>

7. 常见的 HTTP Method 有哪些? GET/POST 区别?

1、常见的HTTP方法

GET:获取资源
POST:传输资源
PUT:更新资源
DELETE:删除资源
HEAD:获得报文首部

2、GET/POST的区别

GET在浏览器回退时是无害的,而POST会再次提交请求
GET请求会被浏览器主动缓存,而POST不会,除非手动设置
GET请求参数会被完整保留在浏览器的历史记录里,而POST中的参数不会被保留
GET请求在URL中传送的参数是有长度限制的,而POST没有限制
GET参数通过URL传递,POST放在Request body中
GET请求只能进行 url 编码,而POST支持多种编码方式
GET产生的URL地址可以被收藏,而POST不可以
对参数的数据类型,GET只接受ASCII字符,而POST没有限制
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息

8. vue打包内存过大,怎么使用webpack来进行优化

开放式题目

打包优化的目的

1、优化项目启动速度,和性能

2、必要的清理数据

3、性能优化的主要方向

cdn加载

-压缩js

减少项目在首次加载的时长(首屏加载优化)

4、目前的解决方向

cdn加载不比多说,就是改为引入外部js路径

首屏加载优化方面主要其实就两点

第一:
尽可能的减少首次加载的文件体积,和进行分布加载

第二:
首屏加载最好的解决方案就是ssr(服务端渲染),还利于seo
但是一般情况下没太多人选择ssr,因为只要不需要seo,ssr更多的是增加了项目开销和技术难度的。

1、路由懒加载

在 Webpack 中,我们可以使用动态 import语法来定义代码分块点 (split point): import(’./Fee.vue’) // 返回 Promise如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析语法。
结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。

const Fee = () => import(‘./Fee.vue’)
在路由配置中什么都不需要改变,只需要像往常一样使用 Foo:

const router = new VueRouter({
  routes: [
    { path: '/fee', component: Fee }
  ]
})
  • 1
  • 2
  • 3
  • 4
  • 5

2、服务器和webpack打包同时配置Gzip

Gzip是GNU zip的缩写,顾名思义是一种压缩技术。它将浏览器请求的文件先在服务器端进行压缩,然后传递给浏览器,浏览器解压之后再进行页面的解析工作。在服务端开启Gzip支持后,我们前端需要提供资源压缩包,通过Compression-Webpack-Plugin插件build提供压缩

需要后端配置,这里提供nginx方式:

http:{ 
      gzip on; #开启或关闭gzip on off
      gzip_disable "msie6"; #不使用gzip IE6
      gzip_min_length 100k; #gzip压缩最小文件大小,超出进行压缩(自行调节)
      gzip_buffers 4 16k; #buffer 不用修改
      gzip_comp_level 8; #压缩级别:1-10,数字越大压缩的越好,时间也越长
      gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; #  压缩文件类型 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

// 安装插件

  $ cnpm i --save-dev compression-webpack-plugin
  • 1

// 在vue-config.js 中加入

  const CompressionWebpackPlugin = require('compression-webpack-plugin');
  const productionGzipExtensions = [
    "js",
    "css",
    "svg",
    "woff",
    "ttf",
    "json",
    "html"
  ];
  const isProduction = process.env.NODE_ENV === 'production';
  .....
  module.exports = {
  ....
   // 配置webpack
   configureWebpack: config => {
    if (isProduction) {
     // 开启gzip压缩
     config.plugins.push(new CompressionWebpackPlugin({
      algorithm: 'gzip',
      test: /\.js$|\.html$|\.json$|\.css/,
      threshold: 10240,
      minRatio: 0.8
     }))
    }
   }
  }
  • 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

3、优化打包chunk-vendor.js文件体积过大

当我们运行项目并且打包的时候,会发现chunk-vendors.js这个文件非常大,那是因为webpack将所有的依赖全都压缩到了这个文件里面,这时我们可以将其拆分,将所有的依赖都打包成单独的js。

  // 在vue-config.js 中加入

  .....
  module.exports = {
  ....
   // 配置webpack
   configureWebpack: config => {
    if (isProduction) {
      // 开启分离js
      config.optimization = {
        runtimeChunk: 'single',
        splitChunks: {
          chunks: 'all',
          maxInitialRequests: Infinity,
          minSize: 20000,
          cacheGroups: {
            vendor: {
              test: /[\\/]node_modules[\\/]/,
              name (module) {
                // get the name. E.g. node_modules/packageName/not/this/part.js
                // or node_modules/packageName
                const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
                // npm package names are URL-safe, but some servers don't like @ symbols
                return `npm.${packageName.replace('@', '')}`
              }
            }
          }
        }
      };
    }
   }
  }
  • 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
  • 29
  • 30
  • 31
  • 32

// 至此,你会发现原先的vender文件没有了,同时多了好几个依赖的js文件

4、启用CDN加速

用Gzip已把文件的大小减少了三分之二了,但这个还是得不到满足。那我们就把那些不太可能改动的代码或者库分离出来,继续减小单个chunk-vendors,然后通过CDN加载进行加速加载资源。

  // 修改vue.config.js 分离不常用代码库
  // 如果不配置webpack也可直接在index.html引入

  module.exports = {
   configureWebpack: config => {
    if (isProduction) {
     config.externals = {
      'vue': 'Vue',
      'vue-router': 'VueRouter',
      'moment': 'moment'
     }
    }
   }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

// 在public文件夹的index.html 加载

  <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.runtime.min.js"></script>
  <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
  • 1
  • 2
  • 3

5、完整vue.config.js代码

  const path = require('path')

  // 在vue-config.js 中加入
  // 开启gzip压缩
  const CompressionWebpackPlugin = require('compression-webpack-plugin');
  // 判断开发环境
  const isProduction = process.env.NODE_ENV === 'production';

  const resolve = dir => {
    return path.join(__dirname, dir)
  }

  // 项目部署基础
  // 默认情况下,我们假设你的应用将被部署在域的根目录下,
  // 例如:https://www.my-app.com/
  // 默认:'/'
  // 如果您的应用程序部署在子路径中,则需要在这指定子路径
  // 例如:https://www.foobar.com/my-app/
  // 需要将它改为'/my-app/'
  // iview-admin线上演示打包路径: https://file.iviewui.com/admin-dist/
  const BASE_URL = process.env.NODE_ENV === 'production'
    ? '/'
    : '/'

  module.exports = {
    //webpack配置
    configureWebpack:config => {
      // 开启gzip压缩
      if (isProduction) {
        config.plugins.push(new CompressionWebpackPlugin({
          algorithm: 'gzip',
          test: /\.js$|\.html$|\.json$|\.css/,
          threshold: 10240,
          minRatio: 0.8
        }));
        // 开启分离js
        config.optimization = {
          runtimeChunk: 'single',
          splitChunks: {
            chunks: 'all',
            maxInitialRequests: Infinity,
            minSize: 20000,
            cacheGroups: {
              vendor: {
                test: /[\\/]node_modules[\\/]/,
                name (module) {
                  // get the name. E.g. node_modules/packageName/not/this/part.js
                  // or node_modules/packageName
                  const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
                  // npm package names are URL-safe, but some servers don't like @ symbols
                  return `npm.${packageName.replace('@', '')}`
                }
              }
            }
          }
        };
        // 取消webpack警告的性能提示
        config.performance = {
          hints:'warning',
              //入口起点的最大体积
              maxEntrypointSize: 50000000,
              //生成文件的最大体积
              maxAssetSize: 30000000,
              //只给出 js 文件的性能提示
              assetFilter: function(assetFilename) {
            return assetFilename.endsWith('.js');
          }
        }
      }
    },
    // Project deployment base
    // By default we assume your app will be deployed at the root of a domain,
    // e.g. https://www.my-app.com/
    // If your app is deployed at a sub-path, you will need to specify that
    // sub-path here. For example, if your app is deployed at
    // https://www.foobar.com/my-app/
    // then change this to '/my-app/'
    publicPath: BASE_URL,
    // tweak internal webpack configuration.
    // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
    devServer: {
      host: 'localhost',
      port: 8080, // 端口号
      hotOnly: false,
      https: false, // https:{type:Boolean}
      open: true, //配置自动启动浏览器
      proxy:null // 配置跨域处理,只有一个代理

    },
    // 如果你不需要使用eslint,把lintOnSave设为false即可
    lintOnSave: true,
    css:{
      loaderOptions:{
        less:{
          javascriptEnabled:true
        }
      },
      extract: true,// 是否使用css分离插件 ExtractTextPlugin
      sourceMap: false,// 开启 CSS source maps
      modules: false// 启用 CSS modules for all css / pre-processor files.
    },
    chainWebpack: config => {
      config.resolve.alias
        .set('@', resolve('src')) // key,value自行定义,比如.set('@@', resolve('src/components'))
        .set('@c', resolve('src/components'))
    },
    // 打包时不生成.map文件
    productionSourceMap: false
    // 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串
    // devServer: {
    //   proxy: 'localhost:3000'
    // }
  }
  • 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
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113

9. git经常用哪些指令

产生代码库

新建一个git代码库

git init
  • 1

下载远程项目和它的整个代码历史

git clone 远程仓库地址
  • 1

配置

显示配置

git config --list [--global]
  • 1

编辑配置

git config -e [--global]
  • 1

设置用户信息

git config [--global] user.name "名"
git config [--global] user.email "邮箱地址"
  • 1
  • 2

暂存区文件操作

增加文件到暂存区

# 1.添加当前目录的所有文件到暂存区
git add .
# 2.添加指定目录到暂存区,包括子目录
git add [dir]
# 3.添加指定文件到暂存区
git add [file1] [file2] ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在暂存区中删除文件

# 删除工作区文件,并且将这次删除放入暂存区
git rm [file1] [file2] ...
# 停止追踪指定文件,但该文件会保留在工作区
git rm --cached [file]
  • 1
  • 2
  • 3
  • 4

重命名暂存区文件

# 改名文件,并且将这个改名放入暂存区
git mv [file-original] [file-renamed]
  • 1
  • 2

代码提交

# 提交暂存区到仓库区
git commit -m [message]
  • 1
  • 2

分支操作

# 列出所有本地分支
git branch

# 列出所有远程分支
git branch -r

# 列出所有本地分支和远程分支
git branch -a

# 新建一个分支,但依然停留在当前分支
git branch [branch-name]

# 新建一个分支,并切换到该分支
git checkout -b [branch]

# 新建一个分支,指向指定commit
git branch [branch] [commit]

# 新建一个分支,与指定的远程分支建立追踪关系
git branch --track [branch] [remote-branch]

# 切换到指定分支,并更新工作区
git checkout [branch-name]

# 切换到上一个分支
git checkout -

# 建立追踪关系,在现有分支与指定的远程分支之间
git branch --set-upstream [branch] [remote-branch]

# 合并指定分支到当前分支
git merge [branch]

# 选择一个commit,合并进当前分支
git cherry-pick [commit]

# 删除分支
git branch -d [branch-name]

# 删除远程分支
git push origin --delete [branch-name]
git branch -dr [remote/branch]
  • 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
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

信息查看

# 显示有变更的文件
git status

# 显示当前分支的版本历史
git log

# 显示commit历史,以及每次commit发生变更的文件
git log --stat

# 搜索提交历史,根据关键词
git log -S [keyword]

# 显示某个commit之后的所有变动,每个commit占据一行
git log [tag] HEAD --pretty=format:%s

# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
git log [tag] HEAD --grep feature

# 显示过去5次提交
git log -5 --pretty --oneline
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

同步操作

# 增加一个新的远程仓库,并命名
git remote add [shortname] [url]

# 取回远程仓库的变化,并与本地分支合并
git pull [remote] [branch]

# 上传本地指定分支到远程仓库
git push [remote] [branch]

# 强行推送当前分支到远程仓库,即使有冲突
git push [remote] --force

# 推送所有分支到远程仓库
git push [remote] --all
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

撤销操作

# 恢复暂存区的指定文件到工作区
git checkout [file]

# 恢复某个commit的指定文件到暂存区和工作区
git checkout [commit] [file]

# 恢复暂存区的所有文件到工作区
git checkout .

# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
git reset [file]

# 重置暂存区与工作区,与上一次commit保持一致
git reset --hard

# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
git reset [commit]

# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
git reset --hard [commit]

# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
git reset --keep [commit]

# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
git revert [commit]
  • 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

10. 协商缓存和强缓存

浏览器缓存主要分为强强缓存(也称本地缓存)和协商缓存(也称弱缓存)。浏览器在第一次请求发生后,再次发送请求时:

  • 浏览器请求某一资源时,会先获取该资源缓存的header信息,然后根据header中的Cache-Control和Expires来判断是否过期。若没过期则直接从缓存中获取资源信息,包括缓存的header的信息,所以此次请求不会与服务器进行通信。这里判断是否过期,则是强缓存相关。后面会讲Cache-Control和Expires相关。
  • 如果显示已过期,浏览器会向服务器端发送请求,这个请求会携带第一次请求返回的有关缓存的header字段信息,比如客户端会通过If-None-Match头将先前服务器端发送过来的Etag发送给服务器,服务会对比这个客户端发过来的Etag是否与服务器的相同,若相同,就将If-None-Match的值设为false,返回状态304,客户端继续使用本地缓存,不解析服务器端发回来的数据,若不相同就将If-None-Match的值设为true,返回状态为200,客户端重新机械服务器端返回的数据;客户端还会通过If-Modified-Since头将先前服务器端发过来的最后修改时间戳发送给服务器,服务器端通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回最新的内容,如果是最新的,则返回304,客户端继续使用本地缓存。

强缓存

强缓存是利用http头中的Expires和Cache-Control两个字段来控制的,用来表示资源的缓存时间。强缓存中,普通刷新会忽略它,但不会清除它,需要强制刷新。浏览器强制刷新,请求会带上Cache-Control:no-cache和Pragma:no-cache

Expires

Expires是http1.0的规范,它的值是一个绝对时间的GMT格式的时间字符串。如我现在这个网页的Expires值是:expires:Fri, 14 Apr 2017 10:47:02 GMT。这个时间代表这这个资源的失效时间,只要发送请求时间是在Expires之前,那么本地缓存始终有效,则在缓存中读取数据。所以这种方式有一个明显的缺点,由于失效的时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。如果同时出现Cache-Control:max-age和Expires,那么max-age优先级更高。如我主页的response headers部分如下:

cache-control:max-age=691200
expires:Fri, 14 Apr 2017 10:47:02 GMT
  • 1
  • 2

Cache-Control

Cache-Control是在http1.1中出现的,主要是利用该字段的max-age值来进行判断,它是一个相对时间,例如Cache-Control:max-age=3600,代表着资源的有效期是3600秒。cache-control除了该字段外,还有下面几个比较常用的设置值:

  • no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。

  • no-store:直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。

  • public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。

  • private:只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。

  • Cache-Control与Expires可以在服务端配置同时启用,同时启用的时候Cache-Control优先级高。

协商缓存

协商缓存就是由服务器来确定缓存资源是否可用,所以客户端与服务器端要通过某种标识来进行通信,从而让服务器判断请求资源是否可以缓存访问。

普通刷新会启用弱缓存,忽略强缓存。只有在地址栏或收藏夹输入网址、通过链接引用资源等情况下,浏览器才会启用强缓存, 这也是为什么有时候我们更新一张图片、一个js文件,页面内容依然是旧的,但是直接浏览器访问那个图片或文件,看到的内容却是新的。

这个主要涉及到两组header字段:Etag和If-None-Match、Last-Modified和If-Modified-Since。上面以及说得很清楚这两组怎么使用啦~复习一下:

Etag和If-None-Match

Etag/If-None-Match返回的是一个校验码。ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。服务器根据浏览器上送的If-None-Match值来判断是否命中缓存。

与Last-Modified不一样的是,当服务器返回304 Not Modified的响应时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag跟之前的没有变化。

Last-Modify/If-Modify-Since

浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间,例如Last-Modify: Thu,31 Dec 2037 23:59:59 GMT。

当浏览器再次请求该资源时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存。

如果命中缓存,则返回304,并且不会返回资源内容,并且不会返回Last-Modify。

为什么要有Etag

你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

  • 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
  • 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
  • 某些服务器不能精确的得到文件的最后修改时间。

Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

11. 对Event loop的了解?

Javascript是单线程的,那么各个任务进程是按照什么样的规范来执行的呢?这就涉及到Event Loop的概念了,EventLoop是在html5规范中明确定义的;

何为eventloop,javascript中的一种运行机制,用来解决浏览器单线程的问题

Event Loop是一个程序结构,用于等待和发送消息和事件。同步任务、异步任务、微任务、宏任务

javascript单线程任务从时间上分为同步任务和异步任务,而异步任务又分为宏任务(macroTask)和微任务(microTask)

宏任务:主代码块、setTimeOut、setInterval、script、I/O操作、UI渲染

微任务:promise、async/await(返回的也是一个promise)、process.nextTick

在执行代码前,任务队列为空,所以会优先执行主代码块,再执行主代码块过程中,遇到同步任务则立即将任务放到调用栈执行任务,遇到宏任务则将任务放入宏任务队列中,遇到微任务则将任务放到微任务队列中。

主线程任务执行完之后,先检查微任务队列是否有任务,有的话则将按照先入先出的顺序将先放进来的微任务放入调用栈中执行,并将该任务从微任务队列中移除,执行完该任务后继续查看微任务队列中是否还有任务,有的话继续执行微任务,微任务队列null时,查看宏任务队列,有的话则按先进先出的顺序执行,将任务放入调用栈并将该任务从宏任务队列中移除,该任务执行完之后继续查看微任务队列,有的话则执行微任务队列,没有则继续执行宏任务队列中的任务。

需要注意的是:每次调用栈执行完一个宏任务之后,都会去查看微任务队列,如果microtask queue不为空,则会执行微任务队列中的任务,直到微任务队列为空再返回去查看执行宏任务队列中的任务

console.log('script start')

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end') 
}
async1()

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')
  • 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

最终输出

script start
VM70:8 async2 end
VM70:17 Promise
VM70:27 script end
VM70:5 async1 end
VM70:21 promise1
VM70:24 promise2
undefined
VM70:13 setTimeout
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

解析:

在说返回结果之前,先说一下async/await,async 返回的是一个promise,而await则等待一个promise对象执行,await之后的代码则相当于promise.then()

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end') 
}

//等价于
new Promise(resolve => 
resolve(console.log('async2 end'))
).then(res => {
console.log('async1 end')
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

整体分析:先执行同步任务 script start -> async2 end -> await之后的console被放入微任务队列中 -> setTimeOut被放入宏任务队列中 ->promise -> promise.then被放入微任务队列中 -> script end -> 同步任务执行完毕,查看微任务队列 --> async1 end -> promise1 -> promise2 --> 微任务队列执行完毕,执行宏任务队列打印setTimeout

12. node.js如何导出页面数据形成报表

node.js如何导出页面数据形成报表

生成报表并下载是作为web应用中的一个传统功能,在实际项目中有广范的应用,实现原理也很简单,以NodeJS导出数据的方法为例,就是拿到页面数据对应id,然后根据id查询数据库,拿到所需数据后格式化为特点格式的数据,最后导出文件。

在nodejs中,也提供了很多的第三方库来实现这一功能,以node-xlsx导出excel文件为例,实现步骤如下:

  1. 下载node-xlsx
    npm install node-xlsx
  • 1
  1. 编写接口

以下代码使用express编写,调用接口实现下载功能

    const fs = require('fs');
    const path = require('path');
    const xlsx = require('node-xlsx');
    const express = require('express');
    const router = express.Router();
    const mongo = require('../db');

    router.post('/export', async (req, res) => {
        const { ids } = req.body;
        const query = {
            _id: { $in: ids }
        }
        // 查询数据库获取数据
        let result = await mongo.find(colName, query);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
     // 设置表头
     const keys = Object.keys(Object.assign({}, ...result));
     rows[0] = keys;
 
     // 设置表格数据
     const rows = []
     result.map(item => {
         const values = []
         keys.forEach((key, idx) => {
             if (item[key] === undefined) {
                 values[idx] = null;
             } else {
                 values[idx] = item[key];
             }
         })
         rows.push(values);
     });
 
     let data = xlsx.build([{ name: "商品列表", data: rows }]);
     const downloadPath = path.join(__dirname, '../../public/download');
     const filePath = `${downloadPath}/goodslist.xlsx`;
     fs.writeFileSync(filePath, data);
     res.download(filePath, `商品列表.xlsx`, (err) => {
         console.log('download err', err);
     });
 })
  • 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

13. 说说你对nodejs的了解

我们从以下几方面来看nodejs.

什么是nodejs?

Node.js 是一个开源与跨平台的 JavaScript 运行时环境, 在浏览器外运行 V8 JavaScript 引擎(Google Chrome 的内核),利用事件驱动、非阻塞和异步输入输出模型等技术提高性能.

可以理解为 Node.js 就是一个服务器端的、非阻塞式I/O的、事件驱动的JavaScript运行环境

非阻塞异步

Nodejs采用了非阻塞型I/O机制,在做I/O操作的时候不会造成任何的阻塞,当完成之后,以时间的形式通知执行操作

例如在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率

事件驱动

事件驱动就是当进来一个新的请求的时,请求将会被压入一个事件队列中,然后通过一个循环来检测队列中的事件状态变化,如果检测到有状态变化的事件,那么就执行该事件对应的处理代码,一般都是回调函数

优缺点

优点:

处理高并发场景性能更佳
适合I/O密集型应用,指的是应用在运行极限时,CPU占用率仍然比较低,大部分时间是在做 I/O硬盘内存读写操作

因为Nodejs是单线程

缺点:

不适合CPU密集型应用
只支持单核CPU,不能充分利用CPU
可靠性低,一旦代码某个环节崩溃,整个系统都崩溃

应用场景

借助Nodejs的特点和弊端,其应用场景分类如下:

善于I/O,不善于计算。因为Nodejs是一个单线程,如果计算(同步)太多,则会阻塞这个线程
大量并发的I/O,应用程序内部并不需要进行非常复杂的处理
与 websocket 配合,开发长连接的实时交互应用程序

具体场景可以表现为如下:

第一大类:用户表单收集系统、后台管理系统、实时交互系统、考试系统、联网软件、高并发量的web应用程>序
第二大类:基于web、canvas等多人联网游戏
第三大类:基于web的多人实时聊天客户端、聊天室、图文直播
第四大类:单页面浏览器应用程序
第五大类:操作数据库、为前端和移动端提供基于json的API

14. useRef与createRef的区别

1、useRef是针对函数组件的,如果是类组件则使用React.createRef()。

2、React.createRef()也可以在函数组件中使用。useRef只能在react hooks中使用

3、createRef每次都会返回个新的引用;而useRef不会随着组件的更新而重新创建

15.createSelector的使用场景

createSelector函数主要用于优化React应用程序中的性能,特别是在具有大量数据的情况下。它的主要用途是创建输出选择器函数,该函数将redux store中的多个状态组合并到单个值中,并将该值缓存以提高性能

1、过滤和排序数据

通过createSelector函数,可以根据多个条件从Redux store中选择数据,并使用JavaScript函数对其进行过滤、排序等处理。

2、转换数据格式

通过createSelector函数,可以将Redux store中的原始数据转换为更易于处理的格式,如图表数据,饼状图数据等。

3、避免不必要的渲染

使用createSelector函数可以避免不必要的渲染。当createSelector函数的输入参数未更改时,将从缓存中返回结果。只有当输入参数更改时,createSelector函数才会重新计算其输出,并在React组件中触发渲染。

4、避免重复计算

在Redux store中包含大量数据时,使用createSelector函数可以避免不必要的计算。例如,可以通过创建一个选择器函数,该函数选择一个对象数组并返回其长度来避免在每次计算数组长度时进行重复的大量计算

16. 代码分割(路由懒加载)

React Router 在使用时,会把所有路由相关的组件都打包进同一个 JavaScript 文件中,这会导致整个应用的体积变得很大,加载时间变长。为了解决这个问题,我们可以使用代码分割(code splitting)技术,将不同的路由组件分别打包成不同的 JavaScript 文件,实现按需加载。

17. redux-thunk的工作原理

源码解读

function thunkMiddleware({ dispatch, getState }) {
  return next => action => {
    // 如果 action 是一个函数,那么调用它,并传递 dispatch 和 getState 函数作为参数
    if (typeof action === 'function') {
      return action(dispatch, getState);
    }
    // 否则,调用下一个中间件
    return next(action);
  };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

18. 什么是Concurrent React

并发可以理解为同时执行大量任务的能力。并发并不是一个特性。相反,它是一个幕后功能,它允许 React 同时(并发地)准备许多 UI 实例。

并发涉及同时执行多个状态更新,这可以说是 React 18 中最重要的特性。除了并发之外,React 18 还引入了两个新的钩子,称为 useTransition() 和 useDeferredValue() 钩子。

它们都有助于降低状态更新的优先级

19. useDeferredValue vs useTransition

1、相同点

useDeferredValue 本质上和内部实现与 useTransition 一样都是把任务标记成了过渡更新任务。

2、不同点

useTransition 是把 startTransition 内部的更新任务变成了过渡任务transtion,而 useDeferredValue 是把原值通过过渡任务得到新的值,这个值作为延时状态。 也就是说一个是处理一段逻辑,另一个是生产一个新的状态。

useDeferredValue 还有一个不同点就是这个任务,本质上在 useEffect 内部执行,而 useEffect 内部逻辑是异步执行的 ,所以它一定程度上更滞后于 useTransition。可以理解成useDeferredValue = useEffect + transtion

20. redux中如何使用中间件

applyMiddleware是 Redux 的一个高阶函数,用于向 Redux Store 应用中间件。

中间件是一个函数,它可以在 dispatch 操作执行前后,对 action 进行拦截、处理、修改等操作。例如:日志记录、错误捕获、异步请求、数据缓存等等。

使用applyMiddleware,你可以实现额外的功能,并且可以在不修改原始代码的情况下对其进行扩展。

21. context的使用场景

1、主题色切换。

2、多国语言切换。也就是国际化

3、祖孙组件之间的传值

22. flux架构

Flux 是一种由 Facebook 提出的前端应用程序架构,旨在解决传统的 MVC(Model-View-Controller)架构在复杂应用场景下出现的一系列问题。Flux 基于单向数据流的思想,将应用程序拆分为四个主要组件:Action、Dispatcher、Store 和 View。

23. 介绍一下Redux Toolkit(RTK)

简化Redux的配置

Redux Toolkit提供了一个createSlice函数,可以用来快速创建Redux的action和reducer,不需要手动编写大量的模板代码。

封装常用的Redux函数

Redux Toolkit提供了一些封装过的Redux函数,如createAsyncThunk、createEntityAdapter等,这些函数可以帮助开发者更加容易地处理异步操作、管理实体数据等常见任务。

整合常用的中间件

Redux Toolkit默认集成了常用的中间件,如redux-thunk、redux-logger等,使得开发者可以更3加便捷地使用这些中间件,而不需要手动配置。

提供默认的Redux store配置

Redux Toolkit提供了一个configureStore函数,可以用来快速创建一个Redux store,并且默认配置了许多常用的中间件和插件,减少了开发者的配置工作量。

介绍一下redux-toolkit中的configureStore

configureStore是Redux Toolkit中的一个工厂函数,用于创建Redux store。它的目的是简化Redux store的设置,并提供许多默认设置和内置的中间件,使其更易于使用。

24. useEffect如何写在依赖

useEffect如何写在依赖

import React, { useState, useEffect } from 'react';
export default function hook() {

  const [num, setNum] = useState(1)

  /**
   * 第一个参数是回调函数
   * 第二个参数是依赖项
   * 每次num变化时都会变化
   * 
   * 注意初始化的时候,也会调用一次
   */
  useEffect(() => {
    console.log("每次num,改变我才会触发")
    
    return () => {
      /**
       * 这是卸载的回调可以执行某些操作
       * 如果不想执行,则可以什么都不写
       */
      console.log("卸载当前监听")
    }
  }, [num])

  useEffect(() => {
    console.log("每次render页面我就会触发")
    return () => {
      console.log("卸载当前监听")
    }
  })

  return (
    <div>
      <button onClick={() => setNum(num + 1)}>+1</button>
      <div>你好,react hook{num}</div>
    </div>
  );
}
  • 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
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

25. ReactDOM.createPorta

Portal 提供了一种将子节点渲染到存在于父组件以外 DOM 节点的方案。在 CSS 中,我们可以使用 position: fixed 等定位方式,让元素从视觉上脱离父元素。在 React 中,Portal 直接改变了组件的挂载方式,不再是挂载到上层父节点上,而是可以让用户指定一个挂载节点。一般用于模态对话框,工具提示、悬浮卡、加载动画等

26. 除jsx外,react还可以使用那些方式编写UI

当你不想在构建环境中配置有关 JSX 编译时,不在 React 中使用 JSX 会更加方便。每个 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成

26. 在项目中封装过哪些组件+如何封装一个弹框组件

使用画一个弹框的UI,使用固定定位,定到屏幕中央,然后确定接收的参数,如标题,弹框内容,按钮名等,暴露出两个按钮的点击事件。最后使用ReactDOM.createPortal创建这个组件

27. CSS module的实现原理

CSS Modules 通过对 CSS 类名重命名,保证每个类名的唯一性,从而避免样式冲突的问题。也就是说:所有类名都具有“局部作用域”,只在当前组件内部生效。在 React 脚手架中:文件名、类名、hash(随机)三部分,只需要指定类名即可 BEM

28. 合成事件的优势

1、进行浏览器兼容,实现更好的跨平台

React 采用的是顶层事件代理机制,能够保证冒泡一致性,可以跨浏览器执行。React 提供的合成事件用来抹平不同浏览器事件对象之间的差异,将不同平台事件模拟合成事件。

2、避免垃圾回收

事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象。即 React 事件对象不会被释放掉,而是存放进一个数组中,当事件触发,就从这个数组中弹出,避免频繁地去创建和销毁(垃圾回收)。

进阶理解(挑战高薪)

3、方便事件统一管理和事务机制

29.react中样式污染产生的原因

React最终编译打包后都在一个html页面中,如果在两个组件中取一样类名分别引用在自身,那么后者会覆盖前者。默认情况下,只要导入了组件,不管组件有没有显示在页面中,组件的样式就会生效。也就是说并没有自己的局部作用域

30. 当ref值是一个函数,什么时候会被调用,以及参数会有什么表现

React 会在组件挂载时,调用 ref 回调函数并传入 DOM元素(或React实例),当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 Refs 一定是最新的。

31. React.memo的使用场景

1、展示型组件

如果你有一个仅仅用于展示数据的组件,且数据不需要频繁更新,那么可以使用 React.memo 避免不必要的重新渲染。

2、性能瓶颈

如果某个组件是你应用中性能瓶颈的主要原因,那么可以使用 React.memo 优化它的性能。

3、模拟 PureComponent

如果你想在函数组件中模拟类组件的 PureComponent 行为,那么可以使用 React.memo

32. react17后删除了那些生命周期?为什么?

react 打算在17版本推出新的 Async Rendering,提出一种可被打断的生命周期,而可以被打断的阶段正是实际 dom 挂载之前的虚拟 dom 构建阶段,也就是要被去掉的三个生命周期。本身这三个生命周期所表达的含义是没有问题的,但 react 官方认为我们(开发者)也许在这三个函数中编写了有副作用的代码,所以要替换掉这三个生命周期,因为这三个生命周期可能在一次 render 中被反复调用多次

33. setState同步还是异步的

分版本来讲,在 react17 中,setState 是批量执行的,因为执行前会设置 executionContext。但如果在 setTimeout、事件监听器等函数里,就不会设置 executionContext 了,这时候 setState 会同步执行。可以在外面包一层 batchUpdates 函数,手动设置下 excutionContext 来切换成异步批量执行。

在react 18里都是异步的

34.什么是fiber

Fiber 是一个基于优先级策略和帧间回调的循环任务调度算法的架构方案。随着应用变得越来越庞大,整个更新渲染的过程开始变得吃力,大量的组件渲染会导致主进程长时间被占用,导致一些动画或高频操作出现卡顿和掉帧的情况。而关键点,便是 同步阻塞。在之前的调度算法中,React 需要实例化每个类组件,生成一棵组件树,使用 同步递归 的方式进行遍历渲染,而这个过程最大的问题就是无法 暂停和恢复。

35. 什么context

上下文 (Context) 是 React 中一个重要的特性,它允许您在组件树中共享数据,而不必通过每个级别显式地传递参数。这是一种将数据传递到树中任意深度的组件的方法,无论其祖先组件是否知道该数据。

36. useLayoutEffect和useEffect的区别

你可以把useLayoutEffect等同于componentDidMount、componentDidUpdate,因为他们调用阶段是相同的。而useEffect是在componentDidMount、componentDidUpdate调用之后才会触发的。

也就是说,当组件所有DOM都渲染完成后,同步调用useLayoutEffect,然后再调用useEffect。

useLayoutEffect永远要比useEffect先触发完成

37. 为什么map的时候要加key

key是react用来追踪哪些列表的元素被修改,被添加或者是被删除的辅助标示。在开发过程中我们需要保证某个元素的key在其同级元素中具有唯一性。

在react的diff算法中react会借助元素的key来判断该元素是最新创建的还是被移动而来的,从而减少不必要的元素渲染。除此之外,react还要根据key来判断元素与本地状态的关联关系。

38. 为什么index不能当做key

当我们用index做下标的时候,点击删除列表中的每一项的下标都会发生变化,如果用下标当做key就会触发dom重新渲染

react组件通讯

父传子
在父组件里的子组件标签上定义属性,在子组件里使用props接收

子传父
父组件给子组件传入一个方法,子组件接收这个方法,在对应的事件里触发接收到的方法,并且可以传参。子传父本质上来说就是通过观察者去触发了一个回调函数。

39. useDeferredValue和useTransition的作用

useDeferredValue 和useTransition这两个钩子可以让我们延迟渲染不紧急的部分,类似于防抖但没有固定的延迟时间,延迟的渲染会在紧急的部分先出现在浏览器屏幕以后才开始,并且可中断不会阻塞用户输入。

简单理解就是如果说某些渲染比较消耗性能,比如存在实时计算和反馈,我们可以使用这个Hook降低其计算的优先级,使得避免整个应用变得卡顿。较多的场景可能就在于用户反馈输入上。比如百度的输入框,用户在输入的时候,页面会发生变化,但是输入框输入并不卡顿

40. 介绍一下useImperativeHandle

useImperativeHandle可以让父组件获取并执行子组件内某些自定义函数(方法)。本质上其实是子组件将自己内部的函数(方法)通过useImperativeHandle添加到父组件中useRef定义的对象中。

注意:
1、useRef创建引用变量
2、React.forwardRef将引用变量传递给子组件
3、useImperativeHandle将子组件内定义的函数作为属性,添加到父组件中的ref对象上。

因此,如果想使用useImperativeHandle,那么还要结合useRef、React.forwardRef一起使用。

41. 为什么 React 需要 Immutable Data

调用setState时,React 会以 shallowMerge(浅层合并) 的方式将我们传入的对象与旧的 state 进行合并。shallowMerge 只会合并新旧 state 对象中第一层的内容,如果 state 中对象的引用未变,那么 React 认为这个对象前后没有发生变化。所以如果我们以 mutable 的方式更改了 state 中的某个对象, React 会认为该对象并没有更新,那么相对应的 UI 就不会被重渲染。而以 Immutable 的方式更新 state 就不会出现这个问题

42. hooks模仿componentWillUnmount

  useEffect(() => {
   
    return () => {
      console.log('componentWillUnmount')
    }
  }, [])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

43. 在类组件定义事件时this指向的问题

一般来说我们都是直接使用箭头函数然后后面跟上一个事件,也可以使用呢call bind apply直接改变事件

这里要注意箭头函数的定义事件的性能问题,call bind apply的坑

44. useEffect中过期闭包的表现

在React中,过期闭包问题是指因为闭包的生命周期长于其引用的变量的生命周期而导致的问题。

在React组件的render函数中,如果使用了闭包引用组件的state或props,当state或props发生变化时,闭包将不会自动更新引用的变量。这就可能导致闭包引用了错误的值,从而导致组件的不正确行为。

45. 什么是useMemo

它可以帮助你避免在不必要的情况下重新渲染组件。它通过对比当前状态和前一个状态,决定是否重新计算或记忆一个值。

46. useState的实现原理

React 16.8.0 正式增加了 Hooks ,它为函数组件引入了 state 的能力,换句话说就是使函数组件拥有了 Class 组件的功能。

React.useState() 返回的第二个参数 setState 用于更新 state ,并且会触发新的渲染。同时,在后续新的渲染中 React.useState() 返回的第一个 state 值始终是最新的。

为了保证 memoizedState 的顺序与 React.useState() 正确对应,我们需要保证 Hooks 在最顶层调用,也就是不能在循环、条件或嵌套函数中调用。

React.useState() 通过 Object.is() 来判断 memoizedState 是否需要更新

47. 什么是redux

Redux 是一种 JavaScript 库,用于管理应用的全局状态。它的目的是帮助开发者管理和同步应用中的数据状态,以实现组件间的数据共享和通信。

Redux 遵循了一种单向数据流的架构模式,将整个应用的状态数据存储在一个全局的状态树(即 store)中,并通过明确的操作,比如 dispatch 一个 action,来修改数据状态。这样可以有效地降低数据状态的耦合度,使得代码更加可维护和可读。

Redux 还支持中间件(middleware)和插件(plugins),允许开发者扩展其功能,以适应不同的业务需求。它也支持热加载(hot reloading),可以在不重启应用的情况下更新代码。

总的来说,Redux 是一个用于简化应用状态管理的工具,广泛应用在 React 和其他前端框架中。

48.为什么会出现ref

在react典型的数据流中,props传递是父子组件交互的唯一方式;通过传递一个新的props值来使子组件重新re-render,从而达到父子组件通信。当然,就像react官网所描述的一样,在react典型的数据流之外,某些情况下(例如和第三方的dom库整合,或者某个dom元素focus等)为了修改子组件我们可能需要另一种方式,这就是ref方式。

49.什么场景下需要使用useReducer

1、当多个 state 需要一起更新时

2、当state 更新逻辑较复杂

3、当下一个 state 依赖于之前的 state,即 编写 setState(prevState => newState)时

包括但不限于以上三种

50. useReducer()相对于 useState() 的优势

1、useReducer 相对于 useState 可以更好的描述“如何更新状态”。 比如:useReducer 能够读取相关的状态、同时更新多个状态。

2、组件负责发出 action,reducer 负责更新状态的模式, 使得代码逻辑更加清晰。代码行为更加可以预测(比useEffect 的更新时机更加稳定)

3、通过传递 dispatch ,可以减少状态值的传递。 useReducer 总是返回相同的 dispatch 函数,这是彻底解耦的标志:状态更新逻辑可以任意变化,而发起 action 的渠道始终不变。

51. useCallback 和 useMemo的区别

1、目的不同

useMemo 是用于缓存计算结果,useCallback 是用于缓存函数引用。

2、使用方法不同

useMemo 用于缓存计算结果,并在其依赖项发生变化时进行重新计算;而 useCallback 只是在依赖项发生变化时重新生成一个新的回调函数。

3、返回值不同

useMemo 返回缓存的计算结果,useCallback 返回一个缓存的回调函数

4、总结

总的来说,useMemo 适用于需要缓存计算结果的场景,useCallback 适用于缓存回调函数的场景。

52.什么是合成事件

React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。它根据 W3C 规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。

53. 什么是useCallback(这里可以和React.memo关联,和过期闭包关联)

在 React 中,当组件重新渲染时,它会重新执行所有的函数,因此在频繁更新的组件中使用多个函数会导致不必要的性能开销。useCallback 可以解决这个问题,它接受两个参数:一个回调函数和一个依赖项列表。当依赖项列表中的任意一项改变时,useCallback 会重新定义回调函数,否则它会返回一个缓存的函数引用,从而避免不必要的函数重新定义

54. 什么是不可变数据

在编程领域,Immutable Data 是指一种一旦创建就不能更改的数据结构。它的理念是:在赋值时,产生一个与原对象完全一样的新对象,指向不同的内存地址,互不影响

55. 什么是render props

Render props 是一种在 React 中实现代码复用的方法。它允许你在一个组件中定义逻辑,并在另一个组件中渲染这个逻辑。这个技术通过一个特殊的 props,称为 “render props”,来实现复用。

56. 状态提升

在React框架中,当多个组件需要反映相同的变化数据,这时建议将共享状态提升到最近的共同父组件中去。一般我们将该操作称为状态提升。由此我们可以很清楚的明白React状态提升主要就是用来处理父组件和子组件的数据传递的;他可以让我们的数据流动的形式是自顶向下单向流动的,所有组件的数据都是来自于他们的父辈组件,也都是由父辈组件来统一存储和修改,再传入子组件当中。

57. hooks模仿componentDidMount

useEffect(() => {
    /**
     * 当它是一个空数组时,回调只会被触发一次,类似于 componentDidMount
     */
    console.log("componentDidmount")
  }, [])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

58.hooks模仿shouldComponentUpdate

import React, { useState, useEffect, useContext } from 'react';


const MyComponent =  React.memo((props) => {
  /* 使用 props 渲染 */
  return (
    <div>{props.num}</div>
  )
  /**
   * prevProps 上次的值
   * nextProps 最新的值
   * 
   * 如果传来的值是偶数的话则不更新组件
   */
}, (prevProps, nextProps) => {
  console.log(nextProps, nextProps.num % 2)
  return nextProps.num % 2 === 0
})

export default function hook() {

  const [num, setNum] = useState(1)

  useEffect(() => {
    /**
     * 当它是一个空数组时,回调只会被触发一次,类似于 componentDidMount
     */
    console.log("componentDidmount")
  }, [])

  return (
    <div>
      <button onClick={() => setNum(num + 1)}>+1</button>
      <MyComponent num={num}></MyComponent>
    </div>
  )
}
  • 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
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

58. 如何封装封装actions模块

bindActionCreators 是 Redux 中的一个函数,用于将多个 action creators 绑定到 dispatch 函数上,使得可以在 Redux 应用中轻松调用这些 action creators。

59. 什么是CSS IN JS

CSS IN JS是使用 JavaScript 编写 CSS 的统称,用来解决 CSS 样式冲突、覆盖等问题。

CSS IN JS 的具体实现有 50 多种,比如:React常用(CSS Modules、styled-components)、 Vue常用(

60.useTransition或者useDeferredValue和防抖的区别

1、节流防抖本质是 setTimeout ,只不过控制了执行的频率,原理就是让 render 次数减少了。而 transitions 和它相比,并没有减少渲染的次数。

2、节流和防抖需要有效掌握延迟时间,如果时间过长,那么给人一种渲染滞后的感觉,如果时间过短,那么就类似于 setTimeout(fn,0) 还会造成前面的问题。而 startTransition 就不需要考虑这么多

61. 高阶组件的使用场景

1、数据获取:高阶组件可以在组件挂载时自动获取数据,并将数据通过 props 传递给被包装组件。

2、权限控制:高阶组件可以检查用户是否有访问该组件的权限,从而决定是否渲染该组件。

3、代码重用:高阶组件可以通过封装一些常见的逻辑,来提高代码的复用性。

4、状态管理:高阶组件可以管理一些状态,并通过 props 将状态传递给被包装组件,如redux里的connect。

5、表单处理:高阶组件可以处理表单的数据,包括数据验证、数据提交等,antd里面的Form。

6、设计模式:高阶组件可以作为设计模式的一部分,例如实现观察者模式、策略模式等

62. context祖孙传值

1、父组件

import React from "react";
import LearnContext2 from "./LearnContext2";

const MyContext = React.createContext();

export default class LearnContext extends React.Component {
  state = {
    num: 1,
  };

  /**
   * 接收所有层级的子组件返回的消息
   * @param {} msg
   */
  getChildData = (msg) => {
    console.log(msg);
  };

  render() {
    return (
      <div>
        <div>父级组件</div>
        <MyContext.Provider
          /**
           * 给所有的子级组件传一个num的值
           * 并且传一个方法 让他们也可以和父级组件通讯
           */
          value={{ num: this.state.num, getChildData: this.getChildData }}
        >
          <LearnContext2></LearnContext2>
        </MyContext.Provider>
      </div>
    );
  }
}
  • 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
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

2、子组件

import React from "react";
import LearnContext3 from "./learn-context3";

export default class LearnContext2 extends React.Component {
  render() {
    return (
      <div>
        <div>子组件</div>
        <LearnContext3></LearnContext3>
      </div>
    );
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3、孙组件

使用 Consumer 接收传下来的context

import React from "react";
import { MyContext } from "./learn-context";

class LearnContext3 extends React.Component {
  
  render() {
    return (
      <MyContext.Consumer>
        {/* 
          直接使用Consumer
          这种方式获取参数
          但是这种方式 不方便在render意外的地方使用传下来的参数
        */}
        {(context) => (
          <div>
            <div>孙组件</div>
            <div>爷爷——传下来的{context.num}</div>
            <button onClick={() => context.onChildHandle('哈喽爷爷')}>给爷爷问声好</button>
          </div>
        )}
      </MyContext.Consumer>
    );
  }
}

export default LearnContext3;
  • 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

使用contextType 接收context

import React from "react";
import { MyContext } from "./learn-context";

class LearnContext4 extends React.Component {

  // 可以直接在类里面使用static 声明静态属性
  static contextType = MyContext

  render () {
    return (
      <div>
        <div>孙组件</div>
        {/* 
          当我们把context挂载到当前组件的contextType的时候
          就可以直接使用this.context拿到相关的值
        */}
        <div>爷爷——传下来的{this.context.num}</div>
        <button onClick={() => this.context.onChildHandle('哈喽爷爷')}>给爷爷问声好</button>
      </div>
    )
  }
}

// 也可以直接在这上面进行赋值
LearnContext4.contextType = MyContext

export default LearnContext4
  • 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

63. 项目中如何使用不可变数据

一般来说,如果对象不是特别复杂可以直接使用结构赋值的方式,如果对象十分巨大那么可以使用immer这个开源库解决不可变数据的问题

immer的实现原理:原始对象先做了一层 Proxy 代理,得到 draftState 传递给 function。function(带副作用) 直接更改 draftState,最后 produce 返回新的对象

64. 如何在类组件中使用最新版的路由v6

编写一个高阶组件

import React from "react";
import { useNavigate } from "react-router-dom";

export default function WithRouter(WarpComponent) {
  const navigate = useNavigate();
  return <WarpComponent {...this.props} navigate={navigate}></WarpComponent>;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

65. 如何分割state或者说分割reducer

使用combineReducers可以对redux进行模块化管理,在 Redux 中,你可以使用多个 Reducer 来处理不同的数据,然后使用 combineReducers 函数将它们合并起来。

66. createSlice解决了什么问题

createSlice 解决了传统 Redux 应用中需要手动编写 reducer 和 action creators 的问题,简化了 Redux 应用程序的开发流程,提高了代码的可读性和可维护性。

在传统的 Redux 应用程序中,我们需要手动编写 reducer 函数来处理每个 action type,然后将它们组合成一个大的 rootReducer 函数。这样的代码结构往往比较冗长和繁琐,同时也容易出错。而 createSlice 的出现,让我们可以通过提供一个包含 reducer 和 action creators 的对象来自动生成相应的 reducer 函数和 action creators,从而避免了手动编写 reducer 的过程,使得开发者更加专注于业务逻辑的实现。

67. Redux Toolkit中如何编写异步代码

使用createAsyncThunk, createAsyncThunk 是一个由 Redux Toolkit 提供的函数,用于创建处理异步操作的 thunk action creator。使用 createAsyncThunk 可以简化 Redux 中处理异步操作的流程,使代码更加清晰、简洁。

68. 什么是切片

在 Redux 中,一个 state tree 是由多个 reducer 组成的,每个 reducer 负责管理一个 state 的一部分,这个概念称为“切片”(slice)。使用切片的好处在于,每个 reducer 只需要关心自己管理的部分 state,而不需要关心整个 state tree 的结构。这样可以使 reducer 的代码更加清晰和易于维护。此外,切片还可以让我们更加方便地组织 action creators 和 selectors,从而使代码结构更加清晰

69. 介绍一下createSlice

createSlice是Redux Toolkit中提供的一个用于快速创建Redux reducer的函数。它基于Redux reducer的标准结构,使得创建和更新Redux state更加方便、简洁。使用createSlice函数创建的slice包含了一个Redux reducer、一组action creators和对应的action types。

70. useInsertionEffect出现的原因

这个钩子与其他钩子略有不同,因为它的唯一目的是对CSS-in-JS库很重要,这些库在运行中生成新的规则并在文档中插入


持续更新ing

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/243244
推荐阅读
  

闽ICP备14008679号