前端面试题干货汇总

230 阅读4分钟

一、前端学科面试题

1、HTML页面进行重绘和重排(回流)

  • 考察html重的优化和重要概念

讲解:

浏览器的运行机制

1、当页面打开以后,浏览器的渲染引擎解析HTML文档,首先将标签DOM树中的DOM节点生成内容
2、构建渲染树,会解析对应的CSS样式文件信息(包括js生成的样式和外部css文件),而这些文件信息以及HTML中的DOM树里面的东西生成渲染树,此时才是css去渲染的树,渲染完成后,每个node节点都有了自己的样式,不包含display:none,还要head节点,因为这些节点不会呈现

3、布局渲染树:从根节点递归调用,计算每一个元素的的大小和位置,给出每个节点的精准坐标

4、位置渲染树:遍历渲染树,使用UI曾绘制每个节点。

重绘(repaint或者redraw)

当盒子的位置、大小以及其他的属性,例如颜色、字体大小确定之后,浏览器便会把这些元素都按照各自的特性绘制一边,将内容呈现在页面上。

重绘是指一个元素外观的改变触发浏览器的行为,浏览器会根据元素的新属性重新绘制,使得元素呈现 新的外观。

 触发重绘条件:改变元素的外观属性:colorbackground-color等等元素外观.
 

重排(重构、回流/reflow)

当渲染树的一部分因为元素的尺寸规模、布局、隐藏等改变而需要重新构建,这就称为回流 。每个页面最少需要回流一次,就是页面第一次加载的时候。

  触发重排的条件:
  1、页面渲染初始化
  2、添加或者删除可见的DOM树
  3、元素的位置发生改变或者使用动画
  4、元素的尺寸的改变
  5、浏览器窗口尺寸的变化
  6、填充内容的改变,比如文本的改变或者图片大小改变而引起的计算值宽度和高度的改变。
  7、读取某些元素的属性(offsetleft/Top/Height/Width

回流和重绘的关系

在回流的时候,浏览器会使得渲染树中受到影响的部分失效,并且重新构造这一部分的渲染树,
完成回流以后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘,

所以重排必定会引发重绘,但是重绘不一定引发重排。

重绘重排的代价

 耗时、卡顿
 所以不要经常的操作DOM元素,操作DOM元素效率很低。

优化

 1、直接改变元素的className
 2、将需要多次重排的元素,positin设置为absolute或者fixed,脱离文档流了,他的变化不回影响其他的元素
 3、创建多个DOM节点,可以使用DocumentFragment创建完成后一次性的加入document。
 
 var fragment=document.createDocumentFragment();
 for(let i=0;i<100;i++){
      var li=document.createElement('li')
      li.innerHTML='apple'+i
      fragment.appendChild(li)
}
document.getElementById('fruit').appendChild(fragment);

2、vue的初始化过程都做了什么(new Vue(options))

讲解:

  • 首先处理组件配置项

     根组件的选项合并L:将全局配置合并到根组件的局部配置上
     初始化每个子组件的一些性能优化,将组件配置对象上的一些深层次的属性都放到vm.$option选项上,以提高代码的执行效率
    
  • 组件实例的关系属性的处理,比如 $parent$children$root$refs

  • 处理自定义事件

  • 调用beforeCreate钩子函数

  • 初始化组件的inject配置项,得到 ret[key]=val形式的配置对象,然后对配置对象进行响应式处理,并且代理每个key到vm实例上

  • 数据的响应式处理,处理props、methods、data、computed、watch等选项

  • 解析组件配置项的provide对象,将其挂载在 vm_provided属性上

  • 调用created钩子函数

  • 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,就不需要再手动调用 $mount,反之,没有 el 则必须手动调用 $mount

new Vue({
   data: {
     msg: 'hello vue'
   },
   methods: {
          print(){
              console.log(this.msg)
          }
      },

 }).$mount("#app")   //手动调用 $mount
  • 接下来则进入挂载阶段

vue源码对应函数:

位置:vue/src/core/instance/init.js

export function initMixin (Vue: Class<Component>) {
 // 负责Vue的初始化过程
 Vue.prototype._init = function (options?: Object) {
   // Vue实例
   const vm: Component = this
   // 每个vue实例都有一个—_uid,并且一次递增
   vm._uid = uid++
    
   // 主要做性能测量的,开始初始化
   let startTag, endTag
   /* istanbul ignore if */
   if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
     startTag = `vue-perf-start:${vm._uid}`
     endTag = `vue-perf-end:${vm._uid}`
     mark(startTag)
   }

   // a flag to avoid this being observed
   vm._isVue = true
   // merge options
   // 处理组件的配置项
   if (options && options._isComponent) {
     // optimize internal component instantiation
     // since dynamic options merging is pretty slow, and none of the
     // internal component options needs special treatment.
     //  初始化每一个子组件都走这里,性能优化,减少原型链的动态查找,提高执行效率
     initInternalComponent(vm, options)
   } else {
     // 非子组件(根组件)也就是根组件走这里,合并 Vue 的全局配置到根组件的局部配置,
     //比如 Vue.component 注册的全局组件会合并到 根实例的 components 选项中
     // 组件合并选项发生在三个地方
     // 1、Vue.component(compName,comp),做了选项合并,合并的是全局Vue内置的全局组件和用户自己组册的全局组件,最终都会放到全局的conponents选项中
     // 2、{componets:{XXXX}}  局部注册,执行编译器生成的render函数时做了选项合并,会合并全局配置项到组件局部配置项上
     // 3、这里跟组件的情况了
     vm.$options = mergeOptions(
       resolveConstructorOptions(vm.constructor),
       options || {},
       vm
     )
   }
   /* istanbul ignore else */
   if (process.env.NODE_ENV !== 'production') {
     // 设置代理,将vm实例上的属性代理到vm._renderProxy
     initProxy(vm)
   } else {
     vm._renderProxy = vm
   }
   console.log("VM",vm)
   // expose real self
   vm._self = vm

 //  整个初始化最重要的部分,也是核心
    
   // 初始化组件实例关系属性,比如 $parent、$children、$root、$refs 等
   initLifecycle(vm)
   /**
    * 初始化自定义事件,这里需要注意一点,所以我们在 <comp @click="handleClick" /> 上注册的事件,监听者不是父组件,
    * 而是子组件本身,也就是说事件的派发和监听者都是子组件本身,和父组件无关(也就是说谁触发谁监听)
    */
   initEvents(vm)
    // 解析组件的插槽信息,得到 vm.$slot,处理渲染函数,得到 vm.$createElement 方法,即 h 函数
   initRender(vm)
   // 调用 beforeCreate 钩子函数(生命周期函数)
   callHook(vm, 'beforeCreate')
   //inject选项是和provide配套使用的,用在库的开发生(高阶组件)
   // 初始化组件的 inject 配置项,得到 result[key] = val 形式的配置对象,然后对结果数据进行响应式处理,并代理每个 key 到 vm 实例
   initInjections(vm) // resolve injections before data/props
   // 数据响应式的重点,处理 props、methods、data、computed、watch
   initState(vm)
   // 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
   initProvide(vm) // resolve provide after data/props
   // 总结provide、inject实现原理
   /**
    * 首先,我们使用的时候,是provide去注入数据,inject去获取数据
    * 其实并没有真的去注入,而是子组件中inject主动去祖代组件中去拿这个数据,
    * (我们从子组件拿到对应的key,从祖代组件中一直去找key对应的value,匹配上了会把provide的值拿出来赋值给inject中key对应的值)
    * 组件自己去祖代组件中找到相应的值
    * **/
    // 调用 created 钩子函数(生命周期钩子函数,数据属性最早拿到数据的生命周期)
   callHook(vm, 'created')

   /* istanbul ignore if */
   if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
     vm._name = formatComponentName(vm, false)
     mark(endTag)
     measure(`vue ${vm._name} init`, startTag, endTag)
   }
    // 调用 created 钩子函数
    callHook(vm, 'created')

    // 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,就不需要再手动调用 $mount,反之,没有 el 则必须手动调用 $mount
   if (vm.$options.el) {
     // 调用 $mount 方法,进入挂载阶段
     vm.$mount(vm.$options.el)
   }
 }
}