某中型公司面试原题(下)

简介: 本文适合最近在考虑新机会的的小伙伴阅读

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的算法

image.png


核心:

  • 渲染函数:渲染函数是用来生成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)
  })

当然也有其他不同的方案,按需自取


三、最后


      这套题目,考到的知识点比较多,有很多是日常开发中会涉及到的知识点。在日常开发工作中,我们可以多做些总结,对某些问题进行归类。从一点一滴提升自己的综合能力。

相关文章
|
6月前
|
存储 缓存 Java
什么!?实战项目竟然撞到阿里面试的原题!???关于MyBatis Plus的缓存机制
什么!?实战项目竟然撞到阿里面试的原题!???关于MyBatis Plus的缓存机制
阿里6月面试原题出炉:Spring+SpringMvc+MyBatis(附答案)
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录
|
存储 缓存 前端开发
某大厂面试原题(下)
本文适合最近在考虑新机会的小伙伴阅读
某大厂面试原题(下)
|
存储 缓存 前端开发
某中型公司面试原题(上)
本文适合最近在考虑新机会的的小伙伴阅读
某中型公司面试原题(上)
|
存储 JavaScript 前端开发
百度某部门面试原题(上)
本文适合最近在考虑新机会的的小伙伴阅读
百度某部门面试原题(上)
|
存储 Web App开发 设计模式
某大厂面试原题(上)
本文适合最近在考虑新机会的小伙伴阅读
|
存储 域名解析 缓存
百度某部门面试原题(下)
本文适合最近在考虑新机会的的小伙伴阅读
|
算法 搜索推荐
LintCode 题解丨阿里巴巴面试原题:两个排序数组的中位数
LintCode 题解丨阿里巴巴面试原题:两个排序数组的中位数
LintCode 题解丨阿里巴巴面试原题:两个排序数组的中位数
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
12天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
下一篇
无影云桌面