2021前端面试题,持续更新

1、项目如何在用户高峰期发布
为什么很多公司升级系统,选择在晚上上线?
答:美名其曰,晚上上线,对用户影响最小。

为什么会对用户产生影响?
答:系统升级往往需要重启,重启的过程中,正在访问的用户会访问失败。

web-server升级能否不影响正在处理的请求?

答:可以,需要nginx和web-server配合。
1)给nginx发指令,将ip1上的流量切走
2)nginx不会将新流量放给ip1,旧流量会很快处理完成
3)旧流量完成后,升级web-server
此时,ip1上的web-server处于没有流量的状况,可以随意进行升级等操作
4)给nginx发指令,将流量切回ip1
5)流量切回ip1,单节点上线成功
2、vue中虚拟DOM和diff算法

虚拟DOM

  • 什么是虚拟DOM
能够局部替换HTML tag
能够声明式的书写 html
能够在 javascript 中书写 html
能够小粒度的复用我们的这些 "html"
  • 虚拟DOM的目的和好处
组件的高度抽象化
减少页面重绘和重排,节省页面性能
可以更好的实现SSR(服务器端渲染),同构渲染等
框架跨平台

Diff算法

  • diff对比的原则
1、逐层对比
第一层不一样,直接替换;第一层一样,进行第二层对比……

2、从两边向中间对比
新旧元素的子节点首尾两两对比,有相同的,则放到新元素对应的位置;不相同就向中间移动,再次对比
  • 对比新旧元素分四大类
第一类,第一层(最外层)都不一样,则直接替换
第二类,外层元素一样,旧元素没有子节点,新元素有子节点,则更新子节点
第三类,旧元素有子节点,新元素没有子节点,则删除旧的子节点
第四类,新旧元素都有子节点,按照对比原则2,首尾两两对比,向中间移动

diff算法只是对比的手段,本质的落脚点是新元素,说白了,就是根据新元素的子节点,按照一定的规则去旧元素的子节点中,查找有无相同的,有无复用的。

3、vue封装组件的原则和技巧

基本原则:高内聚,低耦合。

需要注意的点

  1. 属性
  2. 事件
  3. 插槽

单向数据流是Vue组件一个非常明显的特征,不应该在子组件中直接修改props的值

  • 如果传递的prop仅仅用作展示,不涉及修改,则在模板中直接使用即可
  • 如果需要对prop的值进行转化然后展示,则应该使用computed计算属性
  • 如果prop的值用作初始化,应该定义一个子组件的data属性并将prop作为其初始值
4、 redex原理

基本思想: 保证数据的单向流动,同时便于控制、使用、测试。

核心概念:

  • action

    只是描述要发生的事件,并不改变state

  • reducer

    根据action的描述,改变state

  • dispatch

    将要发生的事件,分发到reducer,触发改变。store.dispatch(action)

  • store

    用来保存state;
    提供 getState() 方法获取 state;

    提供 dispatch(action) 方法更新 state;

    通过 subscribe(listener) 注册监听器;

    通过 subscribe(listener) 返回的函数注销监听器。

特性

  • 唯一数据源:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
  • 保持状态只读:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
  • 数据改变只能通过纯函数:这里的纯函数就是 Reducer 它函数中包含两个参数 state、action,大致的意思就是通过 action 去改变 state,一般来说是返回一个新的 state
  • 预见性:所有的用户的行为都是提前定义好的
  • 统一管理 state:所有的状态都在一个store中分配管理
5、 数据劫持+发布者-订阅者模式

数据劫持

  • 数据劫持就是修改内容后,差值表达式能够知道修改并做出变化。
  • vue实现数据劫持是通过遍历所有data对象中的所有属性,并对每一个属性使用Object.defineProperty劫持,当属性值发生变化的时候,监听并执行渲染视图的操作。

发布者-订阅者模式

  • 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
  • 实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的更新函数,从而更新视图。
  • 实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并初始化模板数据以及初始化相应的订阅器。

Observer 监听所有被劫持数据的集合

observerObject主要做的就是使用Object.defineProperty去监听传入的属性,如果target是一个对象的话,就递归执行observer,确保data中所有的对象中的所以属性都能够被监听到。当我们set的时候,去执行renderView(执行视图渲染相关逻辑)。

Object.defineProperty的一些问题

  1. 递归遍历所有的对象的属性,这样如果我们数据层级比较深的话,是一件很耗费性能的事情
  2. 只能应用在对象上,不能用于数组
  3. 只能够监听定义时的属性,不能监听新加的属性,这也就是为什么在vue中要使用Vue.set的原因,删除也是同理

vue2.x使用的为Object.defineProperty,vue3使用的为proxy

6、前后端分离

原因

不同的工程,同步开发,提高工作效率;前端只需要关注页面的样式和动态数据的解析渲染,后端只需要关注具体业务逻辑。

优点

  1. 彻底解放前端。前端不再需要向后台提供模板或是后台在前端HTML中嵌入后台代码。
  2. 提高工作效率,分工更加明确,开发更加灵活。
  3. 局部性能提升。通过前端路由配置,可以实现页面按需加载,无需加载页面的时候一次性加载网站所有资源。
  4. 降低维护成本。快速定位问题所在,重构性及可维护性增强。
  5. 实现高内聚低耦合,减少后端(应用)服务器的并发/负载压力。
  6. 即使后端服务暂时超时或者宕机了,前端页面也会正常访问,但无法提供数据。
  7. 可以使后台能更好的追求高并发、高可用、高性能,使前端能更好的追求页面表现、速度流畅、兼容性、用户体验等。
7、gulp/grunt 与 webpack的区别是什么?

grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。

webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。

8、有哪些常见的Loader?他们是解决什么问题的?
  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
  • url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试
  • image-loader:加载并且压缩图片文件
  • babel-loader:把 ES6 转换成 ES5
  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
  • eslint-loader:通过 ESLint 检查 JavaScript 代码
9、webpack的构建流程是什么?

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  3. 确定入口:根据配置中的 entry 找出所有的入口文件;
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

10、webpack的优化

webpack优化分为优化开发体验和优化输出质量两部分

  1. 缩小文件搜索范围

    • 在配置 Loader 时通过 include 去缩小命中范围
    • 优化 resolve.modules 配置,指明存放第三方模块的绝对路径,以减少寻找
    • resolve.alias 配置路径别名
    • 优化 resolve.extensions 配置
      • 后缀尝试列表要尽可能的小,不要把项目中不可能存在的情况写到后缀尝试列表中。
      • 频率出现最高的文件后缀要优先放在最前面,以做到尽快的退出寻找过程。
      • 在源码中写导入语句时,要尽可能的带上后缀,从而可以避免寻找过程。例如在你确定的情况下把 require('./data') 写成 require('./data.json')
  2. 使用 DllPlugin

    由于动态链接库中大多数包含的是常用的第三方模块,例如 react、react-dom,只要不升级这些模块的版本,动态链接库就不用重新编译。

  3. 使用 ParallelUglifyPlugin

    ParallelUglifyPlugin 会开启多个子进程,把对多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过 UglifyJS去压缩代码,但是变成了并行执行。 所以 ParallelUglifyPlugin 能更快的完成对多个文件的压缩工作

11、移动端常见的布局
  1. 列表式布局
  2. 陈列式布局
  3. 九宫格式布局
  4. 选项卡式布局
  5. 轮播图式布局
  6. 伸展式布局
  7. 抽屉式布局
  8. 弹出框式布局
  9. 横向拓展式布局
  10. 多面板式布局
12、前端缓存

前端缓存,分两种,HTTP缓存和浏览器缓存

HTTP缓存,主要存在于服务器请求传输时需要记录的一些参数,在服务器代码上设置。

浏览器缓存,主要是由前端JS代码主动存储的某些参数。

缓存是前端项目性能优化中简单高效的一种方式。优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。

浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中。

缓存又分强制缓存和协商缓存

13、Vue代码优化
  1. 代码层面
    • 路由懒加载
    • 代码模块化
    • for循环设置key值
    • 更加了解vue的生命周期
    • 可以使用keep-live
    • 节流防抖
    • 图片的懒加载
    • 无状态的组件标记为函数式组件
    • 合理利用计算属性的依赖缓存
  2. 打包层面
    • 合理使用CDN方式引入外部资源
    • 不把css打包在一起
    • 不生成.map文件
    • 减少图片使用
    • 按需引入

14、new操作符都做了那些事情

  1. 创建了一个对象。
  2. 设置原型链
  3. 绑定this值
  4. 判定返回值并返回对象
15、节流防抖

防抖

function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) 
        }
        timer = setTimeout(fn,delay) // 简化写法
    }
}
// 然后是旧代码
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll = debounce(showTop,1000) // 为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置

节流

function throttle(fn,delay){
    let valid = true
    return function() {
       if(!valid){
           //休息时间 暂不接客
           return false 
       }
       // 工作时间,执行函数并且在间隔期内把状态位设为无效
        valid = false
        setTimeout(() => {
            fn()
            valid = true;
        }, delay)
    }
}
/* 请注意,节流函数并不止上面这种实现方案,
   例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。
   也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样
    */

// 以下照旧
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll = throttle(showTop,1000) 
15、链式调用的原理

1.对于第一个设置属性的方法而言,新建一个对象的时候,正常的调用其方法,如果这个方法返回的是this,意味着将这个设置过属性的对象返回

2.那么对于第二个方法而言,是一个设置方法返回的是已经设置过属性的对象,而这个返回的对象也是有对应的属性设置方法的,这样一来,就相当于对一个创建的对象调用其属性设置方法,并且将这个对象返回

3.以此类推,上一个方法返回对象,是下一个调用方法的执行对象,依次执行下去,就成了链式调用方法

16、vue-router两种模式的区别
  1. hash模式

    /#/后面 hash值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。每次 hash值的变化,会触发hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化.

    HashHistory有两个方法:HashHistory.push()将新路由添加到浏览器访问历史的栈顶 和HashHistory.replace()替换掉当前栈顶的路由

  2. history模式

    因为HTML5标准发布,多了两个 API,pushState() 和 replaceState()。
    (1)可以改变 url 地址且不会发送请求,

    (2)不仅可以读取历史记录栈,还可以对浏览器历史记录栈进行修改。

    除此之外,还有popState().当浏览器跳转到新的状态时,将触发popState事件.

  • 区别
    • 前面的hashchange,你只能改变#后面的url片段。而pushState设置的新URL可以是与当前URL同源的任意URL。
    • history模式则会将URL修改得就和正常请求后端的URL一样,如后端没有配置对应/user/id的路由处理,则会返回404错误。(怕刷新页面,因为刷新页面后对应的path就丢失了)
17、route的区别
  1. route.query或者 this.$route.params 接收)
  2. router.push方法;返回上一个history也是使用$router.go方法
18、this指向问题

下面网址详细解释了各种情况
https://blog.csdn.net/foreverwy/article/details/78150563

19、原型链

由于原型链涉及构造函数、函数Function、引用类型Object及特定的两个属性prototype和proto,因此在谈原型链前先搞清楚他们之间的关系。

  1. 函数必然有prototype和proto两个属性,所有的函数(包括自定义函数)都是Function实例的对象;
  2. 对象必然有proto属性,但不一定有prototype;实例的对象通过proto属性连接到构造函数的prototype属性上。而原型链就是从这两者的关系开始一层一层往下找的关系;
  3. 都通过proto属性指向了Function.prototype,而Function.prototype也想相当于一个对象,他的构造器就是Function,所以可以得出结论,Function是所有实例对象的自定义构造函数;
  4. Function.prototype通过proto属性找到了Object.prototype,该对象的proto再往下找就是null了,所以不难得出结论,Function其实是Object的实例对象;
  5. 由始至终引用类型Object只有向外指的箭头,而没有指入的箭头,原因就是“万物皆对象”,任何对象都是属于object的实例,所以最终proto都会指向Object的prototype中,再通过proto往下就为null;

属性共享和独立的控制,当对象实例需要独立的属性,所有做法的本质都是在对象实例里面创建属性。

你可能感兴趣的:(2021前端面试题,持续更新)