function fntC({ one, two }) {
return one + two
}
const cResult = fntC({ one: 6, two: 10 })
修改后:
function fntC({ one, two }: { one: number, two: number }) {
return one + two
}
const cResult1 = fntC({ one: 6, two: 10 })
// 如果参数是对象,只需要为这些变量进行对象类型下的对象类型定义
4.函数的参数为函数的类型定义
function fntD(callback) {
//参数 "callback" 隐式具有 "any" 类型,但可以从用法中推断出更好的类型
callback(true)
}
function callback(bl: boolean): boolean {
console.log(bl)
return bl
}
const dResult = fntD(callback)
修改后:
function fntD(callback: (bl: boolean) => boolean) {
callback(true)
}
function callback(bl: boolean): boolean {
console.log(bl)
return bl
}
const dResult = fntD(callback)
// 如果参数是函数,只需要为参数进行对象类型下的函数类型定义即可
ts中函数返回值的类型定义
当函数有返回值时,根据返回值的类型在相应的函数位置进行静态类型定义即可
返回数字:
function getTotal2(one: number, two: number): number {
return one + two;
}
const total2 = getTotal(1, 2);
// 返回值为数字类型
返回布尔值
function getTotal2(one: number, two: number): boolean {
return Boolean(one + two);
}
const total2 = getTotal(1, 2);
// 返回值为布尔类型
返回字符串
function getTotal2(one: string, two: string): string{
return Bone + two;
}
const total2 = getTotal('1', '2');
// 返回值为字符串
返回对象
function getObj(name: string, age: number): { name: string, age: number } {
return {name,age}
}
getObj('小红',16)
// 返回值为对象
返回数组
function getArr(arr: number[]) :number[]{
let newArr = [...arr]
return newArr
}
getArr([1,2,3,4])
// 返回值为数组
函数返回值为underfinde,仅仅时为了在内部实现某个功能,我们就可以给他一个类型注解void,代表没有任何返回值,
function sayName() {
console.log('hello,world')
}
修改后:
function sayName1(): void {
console.log('无返回值')
}
当函数没有返回值时
// 因为总是抛出异常,所以 error 将不会有返回值
// never 类型表示永远不会有值的一种类型
function error(message: string): never {
throw new Error(message);
}
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 }
]
})
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; # 压缩文件类型
}
// 安装插件
$ cnpm i --save-dev compression-webpack-plugin
// 在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
}))
}
}
}
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('@', '')}`
}
}
}
}
};
}
}
}
// 至此,你会发现原先的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'
}
}
}
}
// 在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>
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'
// }
}
9. git经常用哪些指令
产生代码库
新建一个git代码库
git init
下载远程项目和它的整个代码历史
git clone 远程仓库地址
配置
显示配置
git config --list [--global]
编辑配置
git config -e [--global]
设置用户信息
git config [--global] user.name "名"
git config [--global] user.email "邮箱地址"
暂存区文件操作
增加文件到暂存区
# 1.添加当前目录的所有文件到暂存区
git add .
# 2.添加指定目录到暂存区,包括子目录
git add [dir]
# 3.添加指定文件到暂存区
git add [file1] [file2] ...
在暂存区中删除文件
# 删除工作区文件,并且将这次删除放入暂存区
git rm [file1] [file2] ...
# 停止追踪指定文件,但该文件会保留在工作区
git rm --cached [file]
重命名暂存区文件
# 改名文件,并且将这个改名放入暂存区
git mv [file-original] [file-renamed]
代码提交
# 提交暂存区到仓库区
git commit -m [message]
分支操作
# 列出所有本地分支
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]
信息查看
# 显示有变更的文件
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
同步操作
# 增加一个新的远程仓库,并命名
git remote add [shortname] [url]
# 取回远程仓库的变化,并与本地分支合并
git pull [remote] [branch]
# 上传本地指定分支到远程仓库
git push [remote] [branch]
# 强行推送当前分支到远程仓库,即使有冲突
git push [remote] --force
# 推送所有分支到远程仓库
git push [remote] --all
撤销操作
# 恢复暂存区的指定文件到工作区
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]
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
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')
最终输出
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
解析:
在说返回结果之前,先说一下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')
})
整体分析:先执行同步任务 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文件为例,实现步骤如下:
- 下载node-xlsx
npm install node-xlsx
- 编写接口
以下代码使用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);
// 设置表头
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);
});
})
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);
};
}
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>
);
}
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')
}
}, [])
43. 在类组件定义事件时this指向的问题
一般来说我们都是直接使用箭头函数然后后面跟上一个事件,也可以使用呢call bind apply直接改变事件
这里要注意箭头函数的定义事件的性能问题,call bind apply的坑
44. useEffect中过期闭包的表现
在React中,过期闭包问题是指因为闭包的生命周期长于其引用的变量的生命周期而导致的问题。
在React组件的render函数中,如果使用了闭包引用组件的state或props,当state或props发生变化时,闭包将不会自动更新引用的变量。这就可能导致闭包引用了错误的值,从而导致组件的不正确行为。
45. 什么是useMemo
给大家的福利
零基础入门
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
同时每个成长路线对应的板块都有配套的视频提供:
因篇幅有限,仅展示部分资料
网络安全面试题
绿盟护网行动
还有大家最喜欢的黑客技术
网络安全源码合集+工具包
所有资料共282G,朋友们如果有需要全套《网络安全入门+黑客进阶学习资源包》,可以扫描下方二维码领取(如遇扫码问题,可以在评论区留言领取哦)~
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!