前端八股文之Vue

1. Vue的响应式原理

数据驱动视图,UI =render(state),state状态发生变化,vue经过模板编译,虚拟DOM,patch过程更新视图。vue2.x会使用object.defineProperty重新当以data中的所有属性,当页面使用对应属性,首先进行数据收集,如果是属性变化会通知相关依赖进行更新操作。vue3.x改用proxy替换object.defineproperty,因为Proxy可以直接监听对象和数组的变化。

1. Vue的双向绑定

双向绑定是通过数据劫持结合发布者-订阅者模式来实现的,get是收集依赖,set进行数据变化更新操作,实现一个监听器observer,用来劫持并监听所有属性,如果有变动,通知订阅者,实现一个订阅器watcher,可以收到属性的变化并执行响应的函数,从而更新视图,实现一个解析器compile,可以扫描解析每个节点的相关指令,并根据初始化模板数据一起初始化响应的订阅器

1. Vue的neextTick原理

在下次DOM更新循环之后执行延迟回调,nextTick主要使用了宏任务和微任务,根据执行环境分别尝试采用promise,mutationObserver,setImmediate定义一个异步方法,多次调用nextTick会将方法存入任务队列中,通过异步方法清空当前队列

4.Vue组件的生命周期

一个组件从创建,数据初始化,挂载,更新,渲染,更新,销毁的过程称为生命周期。

1. 创建前后:

  • beforeCreate:Vue实例的data和挂载元素el都未定义,还未初始化。
  • created阶段:Vue实例的数据对象data有了,el为undefined,实例创建完成,挂载未完成

2. 载入前后:

  • beforeMount:Vue实例的el和data都初始化了,还没有挂载,此时DOM为虚拟DOM。把需内中渲染好的模板结构替换到页面上。
  • mounted:Vue实例挂载完成,成功渲染视图,页面渲染完成。

3. 销毁前后:

  • beforeDestory:组件即将销毁,还没销毁,
  • destoryed:Vue实例销毁后调用,组件无法使用,已经解除了事件监听以及DOM的绑定

4. 更新前后:

  • beforeUpdate:当data变化后,数据更新的时候调用,把重新渲染好的模板结构放到页面上,
  • updated:页面更新完成,数据更新新数据

Vue父子组件的生命周期调用顺序

组件的调用顺序都是先父后子,渲染完成时先子后父
组件的销毁时先父后子,销毁完成是先子后父

  • 加载渲染完成:父createCreate–父created–父breateMount–子beforeCreate–子created–子breateMount–子mounted—父mounted
  • 子组件更新过程:父breateUpdate—子beforeUpdate----子updated----父updated
  • 父组件更新过程: 父beforeUpdate—父updated
  • 销毁过程: 父beforeDestory—子beforeDestory—子destoryed—父destoryed

computed和watch的区别

  • computed具有缓存功能,只有依赖的数据发生了变化才触发computed

  • watcher没有缓存功能,可以监听数据执行回调,深度监听的话对象中的属性时,可以打开deep-true选项,watcher支持异步,有两个参数,newData,和oldData,=,两个属性,deep:深入观察没一层层遍历对象,immediate:组件加载立即执行回调函数

组件data为什么是函数

一个组件被复用多次的话,也就会创建多个实例,本质上这些实例用的都是同一个构造函数,如果data是对象的话,对象属于引用类型,会影响到所有的实例

组件间如何通信

1. 父子组件通信
- 父组件通过props向子组件传值
- 子组件通过emit,on向父组件传值

  • 父子组件获取父子组件实例 p a r e n t , parent, parent,children
  • ref获取实例的方式调用组件的属性和方法

2. 兄弟组件通信

  • event bus实例跨组件通信
  • -Vue.prototype.$bus = new Vue()
  • vuex

3. 跨组件通信

  • vuex
  • a t t r s , attrs, attrs,listeners

diff算法事件复杂度

1. 判断是否是静态节点

  • 静态节点无需处理

2. VNode是否是文本节点

  • oldVNode不是文本阶段,直接使用setTextNode设置为文本节点,再把内容复制过去
  • oldVNode是文本节点,比较两个新旧节点是否相等,让旧的节点的内容等于新节点内容

3. VNode是元素节点

  • VNode不含有子节点,也不是文本节点,直接把旧节点的内容清空即可
  • VNode含有子节点
    1. 旧的节点里不包含子节点,就把新节点的内容创建一份插入到旧的节点里面
    2. 旧的节点里播包含子节点,就需要递归遍历对比更新子节点
    3. 如果旧的节点是文本节点,则把文本清空,然后把新节点的内容创建一份插入到旧节点里

正常比较DOM-Diff是 O(n^3),Vue2的diff算法采用了双端比较优化算法和只有当新旧children都为多个子节点时才需要用黑犀牛的Diff算法进行同层级比较时间复杂度就是O(n)

vue中keep-alive有什么用

我们可以把一些不常变动的组件或者需要缓存的组件用包裹起来,这样就会帮我们把组件保存在内存中,而不是直接的销毁

  • 好处:保留组件的状态或避免多次渲染,提高了页面性能

  • 有一个淘汰策略LRU,

    1. 将新数据放到数组尾部
    2. 每当缓存命中,将缓存命中的数据放到数组尾部
    3. 当this.keys满的时候,将头部的数据销毁
  • 生命周期钩子:activated:缓存组件激活的时候,deactivated:缓存组件销毁的时候

Vue中history和hash实现的原理

https://juejin.cn/post/6844903589123457031

  • hash模式下单单修改#后面的部分,浏览器只会滚动到相应的位置,不会重新加载网页。他会触发hashchange()函数
class Routers {
     
    constructor() {
     
      // 储存hash与callback键值对
      this.routes = {
     };
      // 当前hash
      this.currentUrl = '';
      // 记录出现过的hash
      this.history = [];
      // 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
      this.currentIndex = this.history.length - 1;
      this.refresh = this.refresh.bind(this);
      this.backOff = this.backOff.bind(this);
      // 默认不是后退操作
      this.isBack = false;
      window.addEventListener('load', this.refresh, false);
      window.addEventListener('hashchange', this.refresh, false);
    }
  
    route(path, callback) {
     
      this.routes[path] = callback || function() {
     };
    }
  
    refresh() {
     
      this.currentUrl = location.hash.slice(1) || '/';
      if (!this.isBack) {
     
        // 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
        // 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
        // 避免再次造成指针的不匹配,我们直接截取指针之前的数组
        // 此操作同时与浏览器自带后退功能的行为保持一致
        if (this.currentIndex < this.history.length - 1) {
      
           this.history = this.history.slice(0, this.currentIndex + 1);
        }
        this.history.push(this.currentUrl);
        this.currentIndex++;
      }
      this.routes[this.currentUrl]();
      console.log('指针:', this.currentIndex, 'history:', this.history);
      this.isBack = false;
    }
    // 后退功能
    backOff() {
     
      // 后退操作设置为true
      this.isBack = true;
      this.currentIndex <= 0
        ? (this.currentIndex = 0)
        : (this.currentIndex = this.currentIndex - 1);
      location.hash = `#${
       this.history[this.currentIndex]}`;
      this.routes[this.history[this.currentIndex]]();
    }
  }
  1. 将路由的hash以及对应的callback函数存储
  2. 触发路由的hash变化后,执行对应的callback函数
  3. 监听对应的事件,我们只需要在实例化class的时候监听上面的事件即可

history模式:

  • 会调用pushState()和replaceState()方法,应用于记录站。
    常用的方法:
  1. window.history.back() 后退
  2. window.history.forward() 前进
  3. window.history.go(-3) 后退三个页面
  • history.pushState用于浏览历史中添加历史记录,但是并不触发跳转,此方法接收三个参数state,title,url)
  1. state:一个与指定网址相关的状态对象,popState事件触发时,该对象会传入回调函数
  2. title:新页面的标题,
  3. url:新的网址,必须与当前页面处于同一个域
  • history.replaceState方法的参数与pushState方法一摸一样。区别是它修改历览历史中当前记录,而非添加记录,同样不触发跳转
  • popState事件,每当同一个文档的浏览历史出现变化的时候,就会触发popState事件
class Routers {
     
    constructor() {
     
      this.routes = {
     };
      // 在初始化监听popstate事件
      this._bindPopState();
    }
    // 初始化路由 replaceState
    init(path) {
     
      history.replaceState({
     path: path}, null, path);
      this.routes[path] && this.routes[path]();
    }
    // 将路径和对应对调函数加入hashMap存储
    route(path, callback) {
     
      this.routes[path] = callback || function() {
     };
    }
    // 触发路由对应回调 pushState
    go(path) {
     
      history.pushState({
     path: path}, null, path);
      this.routes[path] && this.routes[path]();
    }
    // 监听popstate事件
    _bindPopState() {
     
      window.addEventListener('popstate', e => {
     
        const path = e.state && e.state.path;
        this.routes[path] && this.routes[path]();
      });
    }
  }
  
  window.Router = new Routers();
  Router.init(location.pathname); // 初始化路由
  const content = document.querySelector('body');
  const ul = document.querySelector('ul');
  function changeBgColor(color) {
     
    content.style.backgroundColor = color;
  }
  
  Router.route('/', function() {
     
    changeBgColor('yellow');
  });
  Router.route('/blue', function() {
     
    changeBgColor('blue');
  });
  Router.route('/green', function() {
     
    changeBgColor('green');
  });
  
  ul.addEventListener('click', e => {
     
    if (e.target.tagName === 'A') {
     
      e.preventDefault();
      Router.go(e.target.getAttribute('href'));
    }
  });

vuex的原理

https://juejin.cn/post/6844904081119510536

你可能感兴趣的:(面经,vue,vue.js)