axios的实现原理
// 拦截器构造函数 function InterceptorManager() { this.handlers = []; } InterceptorManager.prototype.use = function use(fulfilled, rejected) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected }); return this.handlers.length - 1; }; // 请求拦截和响应拦截均是拦截器的实例化 function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } // 用数组存储执行链 var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); // 将请求拦截成对推入执行链的头部 // 知道这个原理以后,我们就知道在设置多个请求拦截时,会按照设置时的顺序,倒序处理 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); // 将响应拦截成对推入执行链的尾部,执行时按照设置时的顺序,正序处理 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); // 依次成对执行,并用新的promise代替旧的promise,最后返回最新的promise while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; // 取消请求 function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } // 在 CancelToken 上定义一个 pending 状态的 promise ,将 resolve 回调赋值给外部变量 resolvePromise var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; // 立即执行 传入的 executor函数,将真实的 cancel 方法通过参数传递出去。 // 一旦调用就执行 resolvePromise 即前面的 promise 的 resolve,就更改promise的状态为 resolve。 // 那么xhr中定义的 CancelToken.promise.then方法就会执行, 从而xhr内部会取消请求 executor(function cancel(message) { // 判断请求是否已经取消过,避免多次执行 if (token.reason) { return; } token.reason = new Cancel(message); resolvePromise(token.reason); }); } CancelToken.source = function source() { // source 方法就是返回了一个 CancelToken 实例,与直接使用 new CancelToken 是一样的操作 var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); // 返回创建的 CancelToken 实例以及取消方法 return { token: token, cancel: cancel }; };
package.json版本命名的几种方式
~和^
npm install --save xxx, 会优先考虑使用 ^而不是~
以版本号x.y.z为例
x:主版本号, 当你做了不兼容的API修改
y:次版本号, 当你做了向下兼容的功能性问题
z:修订号, 当你做了向下兼容的问题修复
~x.y.z, 会更新到y最新的版本,
^x.y.z, 会更新到x的最新版本
Tips: 通常会使用package-lock.json来解决发布版本不一致问题
浏览器的重绘与回流
重绘不一定需要重排(比如颜色的改变)
重排(回流)必然导致重绘(比如改变网页位置)
重排(Reflow)(回流):
当渲染树的一部分必须更新并且节点的尺寸发生了变化,浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。
重绘(Repaint):
是在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。比如改变某个元素的背景色、文字颜色、边框颜色等等
减少重排次数和缩小重排影响范围解决方式:
1. 将多次改变样式属性的操作,合成一次操作。
2. 将需要多次重排的元素,嗯,position属性。设为absolute。或fixed。使其脱离文档流,这样,他的变化就不会影响到其他的元素。
3. 在内存中多次操作节点。完成后再添加到文档中去。
4. 如果对一个元素进行复杂的操作,可以将display属性设为none嗯使其隐藏。操作完后再显示。
5. 在需要经常获取那些引起浏览器重排的属性值时,要缓存到变量。
用css隐藏dom的几种方式
方式一:display:none
.hide { display:none; }
将元素设置为display:none后,元素在页面上将彻底消失,元素本来占有的空间就会被其他元素占有,也就是说它会导致浏览器的重排和重绘。
方式二:visibility:hidden
.hidden{ visibility:hidden }
元素在页面消失后,其占据的空间依旧会保留着,所以它只会导致浏览器重绘而不会重排。
方式三:opacity:0
.transparent { opacity:0; }
设置透明度为0后,元素只是隐身了,它依旧存在页面中。
方式四:设置height,width等盒模型属性为0
display: flex几个常用的属性
display:flex;/*flex块级,inline-flex:行内快*/
6个属性
flex-direction
justify-content:space-around; /** *center:水平居中, *flex-start:靠左; *flex-end:靠右; *space-between:两边的向两边靠,中间等分; *space-around:完美的平均分配 **/
align-items:
align-items:stretch; /** *center:垂直居中、 *flex-start:至顶、 *flex-end:至底、 *space-between、 *space-around **/
flex-direction:
flex-direction: row; /** *column从上向下的排列, *column-reverse、 *row:从左到右, *row-reverse:从右向左 **/
flex-wrap:
flex-wrap:wrap; /** *wrap多行显示(父容器不够显示的时候,从上到下)、 *nowrap(当容器不够宽的时候,子元素会平分父容器的宽或者高)、 *wrap-reverse:从下向上 **/
/*flex-flow是flex-direction、flex-wrap的缩写*/
vue父子组件的生命周期过程
加载渲染过程:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。
子组件更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程:父beforeUpdate->父updated
销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
vue的双向绑定
vue2.x版本的数据双向绑定主要通过Object.defineProperty()方法来进行数据劫持以及发布者-订阅模式来实现的。
概括描述:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
vue3.x采用数据劫持结合发布者-订阅者模式的方式,通过new Proxy()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
小结:Vue 3.0与Vue 2.0的区别仅是数据劫持的方式由Object.defineProperty更改为Proxy代理,其他代码不变。
虚拟dom性能好在哪里
优势:1、虚拟 DOM 不会立马进行排版与重绘操作2、虚拟 DOM 进行频繁修改,然后一次性比较并修改真实 DOM 中需要改的部分,最后在真实 DOM 中进行排版与重绘,减少过多DOM节点排版与重绘损耗3、虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部
缺点:
1、无法进行极致优化:虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。2、首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
父子组件的通信
1、Prop(常用)
2、$emit (组件封装用的较多)
3、.sync语法糖 (较少)
4、$attrs 和 $listeners (组件封装用的较多)
5、provide 和 inject (高阶组件/组件库用的较多)
6、其他方式通信(EventBus、Vuex、$parent、$root、broadcast / dispatch【vue1.0的vue2.0去掉了】)
说说vue虚拟dom的算法
核心:
- 渲染函数:渲染函数是用来生成Virtual DOM的。Vue推荐使用模板来构建我们的应用界面,在底层实现中Vue会将模板编译成渲染函数,当然我们也可以不写模板,直接写渲染函数,以获得更好的控制。
- VNode 虚拟节点:它可以代表一个真实的 dom 节点。通过 createElement 方法能将 VNode 渲染成 dom 节点。简单地说,vnode可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点。
- patch(也叫做patching算法):虚拟DOM最核心的部分,它可以将vnode渲染成真实的DOM,这个过程是对比新旧虚拟节点之间有哪些不同,然后根据对比结果找出需要更新的的节点进行更新。这点我们从单词含义就可以看出, patch本身就有补丁、修补的意思,其实际作用是在现有DOM上进行修改来实现更新视图的目的。Vue的Virtual DOM Patching算法是基于Snabbdom的实现,并在些基础上作了很多的调整和改进。
详细说明
虚拟DOM就是使用JS对象模拟真实的DOM结构,用JavaScript对象描述DOM的层次结构。
vue当中虚拟DOM的实现是参考一个库,snabbdom,仅在同级的vnode间做diff,递归地进行同级vnode的diff,最终实现整个DOM树的更新。
diff 算法包括几个步骤:
1、用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中2、当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异3、把所记录的差异应用到所构建的真正的DOM树上,视图就更新了
虚拟节点有哪些属性
children: 值可能是undefined(是undefined表示没有子元素),也可能使数组
data: {}
elm: undefined。elm是undefined说明这个节点还没有在DOM树上
key: undefined
sel: “div”。选择器
text:文字描述
patch函数
import { init, classModule, propsModule, styleModule, eventListenersModule, h, } from "snabbdom"; // 创建patch函数 let patch = init([classModule, propsModule, styleModule, eventListenersModule]); // 创建一个虚拟节点,但是它还没有在DOM树上。要想把它放在DOM树上,需要patch函数 let vnode1 = h('a', {props: {href: 'http://www.baidu.com',target:'_blank'}}, '百度一下') let container = document.getElementById('container'); // 让虚拟结点上树,patch函数只能让一个虚拟结点上树。如果vnode2和vnode3要上树,需要把这个注释掉 patch(container, vnode1); let vnode2 = h('div',{class:{"box":true}},'我是一个盒子'); let vnode3 = h('ul',{},[ h('li','苹果'), // 第二个参数可以没有 h('li','香蕉'), // 这里已经调用了h函数 h('li',[ h('p',{},'桔子'), h('p',{},'哈哈') ]), h('li','西瓜'), h('li',h('span','火龙果')) // 如果children只有一个子元素,第三个参数可以不要数组 ]);
vue-router两种模式的区别
hash模式
原理
window.onhashchange = function(event){ console.log(event.oldURL, event.newURL); let hash = location.hash.slice(1); document.body.style.color = hash; }
每次 hash 值的变化,会触发hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。然后我们便可以监听hashchange来实现更新页面部分内容的操作。
HashHistory有两个方法:HashHistory.push()将新路由添加到浏览器访问历史的栈顶 和 HashHistory.replace()替换掉当前栈顶的路由
history模式
原理
window.history.pushState(stateObject, title, URL) window.history.replaceState(stateObject, title, URL)
pushState() 和 replaceState()。通过这两个 API
(1)可以改变 url 地址且不会发送请求,(2)不仅可以读取历史记录栈,还可以对浏览器历史记录栈进行修改。除此之外,还有popState().当浏览器跳转到新的状态时,将触发popState事件.
拓展:
$router与$route的区别
1. $route从当前router跳转对象里面可以获取name、path、query、params等(<router-link>传的参数由 this.$route.query或者 this.$route.params 接收)
2. $router为VueRouter实例。想要导航到不同URL,则使用$router.push方法;返回上一个history也是使用$router.go方法
vue-router的history模式在后端需要做什么配置
因为我们的应用是个单页客户端应用,所以呢,我们要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html
页面,这个页面就是我们app 依赖的页面。
以下选择一种方案即可:
nginx
location / { try_files $uri $uri/ /index.html; }
Apache
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L] </IfModule>
原生 Node.js
const http = require('http') const fs = require('fs') const httpPort = 80 http.createServer((req, res) => { fs.readFile('index.htm', 'utf-8', (err, content) => { if (err) { console.log('We cannot open "index.htm" file.') } res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }) res.end(content) }) }).listen(httpPort, () => { console.log('Server listening on: http://localhost:%s', httpPort) })
当然也有其他不同的方案,按需自取
三、最后
这套题目,考到的知识点比较多,有很多是日常开发中会涉及到的知识点。在日常开发工作中,我们可以多做些总结,对某些问题进行归类。从一点一滴提升自己的综合能力。