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封装组件的原则和技巧
基本原则:高内聚,低耦合。
需要注意的点
- 属性
- 事件
- 插槽
单向数据流是Vue组件一个非常明显的特征,不应该在子组件中直接修改props的值
- 如果传递的prop仅仅用作展示,不涉及修改,则在模板中直接使用即可
- 如果需要对prop的值进行转化然后展示,则应该使用computed计算属性
- 如果prop的值用作初始化,应该定义一个子组件的data属性并将prop作为其初始值
4、 redex原理
基本思想: 保证数据的单向流动,同时便于控制、使用、测试。
核心概念:
action
只是描述要发生的事件,并不改变statereducer
根据action的描述,改变statedispatch
将要发生的事件,分发到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的一些问题
- 递归遍历所有的对象的属性,这样如果我们数据层级比较深的话,是一件很耗费性能的事情
- 只能应用在对象上,不能用于数组
- 只能够监听定义时的属性,不能监听新加的属性,这也就是为什么在vue中要使用Vue.set的原因,删除也是同理
vue2.x使用的为Object.defineProperty,vue3使用的为proxy
6、前后端分离
原因
不同的工程,同步开发,提高工作效率;前端只需要关注页面的样式和动态数据的解析渲染,后端只需要关注具体业务逻辑。
优点
- 彻底解放前端。前端不再需要向后台提供模板或是后台在前端HTML中嵌入后台代码。
- 提高工作效率,分工更加明确,开发更加灵活。
- 局部性能提升。通过前端路由配置,可以实现页面按需加载,无需加载页面的时候一次性加载网站所有资源。
- 降低维护成本。快速定位问题所在,重构性及可维护性增强。
- 实现高内聚低耦合,减少后端(应用)服务器的并发/负载压力。
- 即使后端服务暂时超时或者宕机了,前端页面也会正常访问,但无法提供数据。
- 可以使后台能更好的追求高并发、高可用、高性能,使前端能更好的追求页面表现、速度流畅、兼容性、用户体验等。
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 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
- 确定入口:根据配置中的 entry 找出所有的入口文件;
- 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
- 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
10、webpack的优化
webpack优化分为优化开发体验和优化输出质量两部分
-
缩小文件搜索范围
- 在配置 Loader 时通过 include 去缩小命中范围
- 优化 resolve.modules 配置,指明存放第三方模块的绝对路径,以减少寻找
- resolve.alias 配置路径别名
- 优化 resolve.extensions 配置
- 后缀尝试列表要尽可能的小,不要把项目中不可能存在的情况写到后缀尝试列表中。
- 频率出现最高的文件后缀要优先放在最前面,以做到尽快的退出寻找过程。
- 在源码中写导入语句时,要尽可能的带上后缀,从而可以避免寻找过程。例如在你确定的情况下把 require('./data') 写成 require('./data.json')
-
使用 DllPlugin
由于动态链接库中大多数包含的是常用的第三方模块,例如 react、react-dom,只要不升级这些模块的版本,动态链接库就不用重新编译。
-
使用 ParallelUglifyPlugin
ParallelUglifyPlugin 会开启多个子进程,把对多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过 UglifyJS去压缩代码,但是变成了并行执行。 所以 ParallelUglifyPlugin 能更快的完成对多个文件的压缩工作
11、移动端常见的布局
- 列表式布局
- 陈列式布局
- 九宫格式布局
- 选项卡式布局
- 轮播图式布局
- 伸展式布局
- 抽屉式布局
- 弹出框式布局
- 横向拓展式布局
- 多面板式布局
12、前端缓存
前端缓存,分两种,HTTP缓存和浏览器缓存
HTTP缓存,主要存在于服务器请求传输时需要记录的一些参数,在服务器代码上设置。
浏览器缓存,主要是由前端JS代码主动存储的某些参数。
缓存是前端项目性能优化中简单高效的一种方式。优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。
浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中。
缓存又分强制缓存和协商缓存
13、Vue代码优化
- 代码层面
- 路由懒加载
- 代码模块化
- for循环设置key值
- 更加了解vue的生命周期
- 可以使用keep-live
- 节流防抖
- 图片的懒加载
- 无状态的组件标记为函数式组件
- 合理利用计算属性的依赖缓存
- 打包层面
- 合理使用CDN方式引入外部资源
- 不把css打包在一起
- 不生成.map文件
- 减少图片使用
- 按需引入
14、new操作符都做了那些事情
- 创建了一个对象。
- 设置原型链
- 绑定this值
- 判定返回值并返回对象
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两种模式的区别
-
hash模式
/#/后面 hash值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。每次 hash值的变化,会触发hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化.
HashHistory有两个方法:HashHistory.push()将新路由添加到浏览器访问历史的栈顶 和HashHistory.replace()替换掉当前栈顶的路由
-
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的区别
- route.query或者 this.$route.params 接收)
- router.push方法;返回上一个history也是使用$router.go方法
18、this指向问题
下面网址详细解释了各种情况
https://blog.csdn.net/foreverwy/article/details/78150563
19、原型链
由于原型链涉及构造函数、函数Function、引用类型Object及特定的两个属性prototype和proto,因此在谈原型链前先搞清楚他们之间的关系。
- 函数必然有prototype和proto两个属性,所有的函数(包括自定义函数)都是Function实例的对象;
- 对象必然有proto属性,但不一定有prototype;实例的对象通过proto属性连接到构造函数的prototype属性上。而原型链就是从这两者的关系开始一层一层往下找的关系;
- 都通过proto属性指向了Function.prototype,而Function.prototype也想相当于一个对象,他的构造器就是Function,所以可以得出结论,Function是所有实例对象的自定义构造函数;
- Function.prototype通过proto属性找到了Object.prototype,该对象的proto再往下找就是null了,所以不难得出结论,Function其实是Object的实例对象;
- 由始至终引用类型Object只有向外指的箭头,而没有指入的箭头,原因就是“万物皆对象”,任何对象都是属于object的实例,所以最终proto都会指向Object的prototype中,再通过proto往下就为null;
属性共享和独立的控制,当对象实例需要独立的属性,所有做法的本质都是在对象实例里面创建属性。