vue面试合集

一、vue的双向绑定原理是什么?里面的关键点在哪里?

Vue通过Object.defineProperty()数据劫持和发布订阅者模式实现双向绑定。
首先,要有一个订阅者类,用于管理如何更新dom元素;然后,要有一个发布者类,发布者类用于收集每个订阅者的信息,和通知订阅者更新。
vue在创建文档碎片的时候,创建一个订阅者对象(该对象定义了如何更新内容的方法),并同时将这个对象存进发布者对象的数组里。
通过Object.defineProperty()方法劫持数据对象属性的getter\setter(取值\赋值)操作,
只要我们为vue中data数据重新赋值了,这个赋值动作会被vue监听到,通过发布者把数据的变化通知到每个订阅者(dom元素),订阅者根据最新数据,更新自己的内容(重新渲染自己的dom)

1、reduce()函数介绍

用法:
1、数值的累加计算
2、链式获取对象属性的值
arr.reduce((prev,cur,index,arr)=>{},init)
prev:上一次调用 callbackFn 时的返回值。在第一次调用时,若指定了初始值 init,其值则为 init,否则为数组索引为 0 的元素 arr[0]。
cur:数组中正在处理的元素。在第一次调用时,若指定了初始值 init,其值则为数组索引为 0 的元素 arr[0],否则为 arr[1]。
index:数组中正在处理的元素的索引。若指定了初始值 init,则起始索引号为 0,否则从索引 1 起始。
arr:用于遍历的数组。

2、发布订阅模式

(1)Dep类

负责进行依赖收集
首先有个数组存放所有订阅信息
其次,提供一个向数组中追加订阅信息的方法,add
然后,还要提供一个循环,循环触发数组中的每一个订阅信息

(2)Watcher类

负责订阅一些事件
(注:只要我们为vue中data数据重新赋值了,这个赋值动作会被vue监听到
然后 vue 要把数据的变化通知到每个订阅者(dom元素)
接下来,订阅者要根据最新数据,更新自己的内容(重新渲染自己的dom))

3、Object.defineProperty()

使用Object.defineProperty()实现数据劫持对象属性取值和赋值的操作

4、简单实现vue的双向绑定

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <h3>姓名是:{{name}}</h3>
    <h3>年龄是:{{ age }}</h3>
    <h3>info.a的值是:{{info.a}}</h3>
    <div>name的值:<input type="text" v-model="name" /></div>
    <div>info.a的值:<input type="text" v-model="info.a" /></div>
  </div>

  <script src="./vue.js"></script>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        name: 'zs',
        age: 20,
        info: {
          a: 'a1',
          c: 'c1'
        }
      }
    })

    console.log(vm)
  </script>
</body>

</html>
class Vue {
  constructor(options) {
    this.$data = options.data

    // 调用数据劫持的方法
    Observe(this.$data)

    // 属性代理
    Object.keys(this.$data).forEach((key) => {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          return this.$data[key]
        },
        set(newValue) {
          this.$data[key] = newValue
        },
      })
    })

    // 调用模板编译的函数
    Compile(options.el, this)
  }
}

// 定义一个数据劫持的方法
function Observe(obj) {
  // 这是递归的终止条件
  if (!obj || typeof obj !== 'object') return
  const dep = new Dep()

  // 通过 Object.keys(obj) 获取到当前 obj 上的每个属性
  Object.keys(obj).forEach((key) => {
    // 当前被循环的 key 所对应的属性值
    let value = obj[key]
    // 把 value 这个子节点,进行递归
    Observe(value)
    // 需要为当前的 key 所对应的属性,添加 getter 和 setter
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 只要执行了下面这一行,那么刚才 new 的 Watcher 实例,
        // 就被放到了 dep.subs 这个数组中了
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      set(newVal) {
        value = newVal
        Observe(value)
        // 通知每一个订阅者更新自己的文本
        dep.notify()
      },
    })
  })
}

// 对 HTML 结构进行模板编译的方法
function Compile(el, vm) {
  // 获取 el 对应的 DOM 元素
  vm.$el = document.querySelector(el)

  // 创建文档碎片,提高 DOM 操作的性能
  const fragment = document.createDocumentFragment()

  while ((childNode = vm.$el.firstChild)) {
    fragment.appendChild(childNode)
  }

  // 进行模板编译
  replace(fragment)

  vm.$el.appendChild(fragment)

  // 负责对 DOM 模板进行编译的方法
  function replace(node) {
    // 定义匹配插值表达式的正则
    const regMustache = /\{\{\s*(\S+)\s*\}\}/

    // 证明当前的 node 节点是一个文本子节点,需要进行正则的替换
    if (node.nodeType === 3) {
      // 注意:文本子节点,也是一个 DOM 对象,如果要获取文本子节点的字符串内容,需要调用 textContent 属性获取
      const text = node.textContent
      // 进行字符串的正则匹配与提取
      const execResult = regMustache.exec(text)
      console.log(execResult)
      if (execResult) {
        const value = execResult[1].split('.').reduce((newObj, k) => newObj[k], vm)
        node.textContent = text.replace(regMustache, value)
        // 在这个时候,创建 Watcher 类的实例
        new Watcher(vm, execResult[1], (newValue) => {
          node.textContent = text.replace(regMustache, newValue)
        })
      }
      // 终止递归的条件
      return
    }

    // 判断当前的 node 节点是否为 input 输入框
    if (node.nodeType === 1 && node.tagName.toUpperCase() === 'INPUT') {
      // 得到当前元素的所有属性节点
      const attrs = Array.from(node.attributes)
      const findResult = attrs.find((x) => x.name === 'v-model')
      if (findResult) {
        // 获取到当前 v-model 属性的值   v-model="name"    v-model="info.a"
        const expStr = findResult.value
        const value = expStr.split('.').reduce((newObj, k) => newObj[k], vm)
        node.value = value

        // 创建 Watcher 的实例
        new Watcher(vm, expStr, (newValue) => {
          node.value = newValue
        })

        // 监听文本框的 input 输入事件,拿到文本框最新的值,把最新的值,更新到 vm 上即可
        node.addEventListener('input', (e) => {
          const keyArr = expStr.split('.')
          const obj = keyArr.slice(0, keyArr.length - 1).reduce((newObj, k) => newObj[k], vm)
          const leafKey = keyArr[keyArr.length - 1]
          obj[leafKey] = e.target.value
        })
      }
    }

    // 证明不是文本节点,可能是一个DOM元素,需要进行递归处理
    node.childNodes.forEach((child) => replace(child))
  }
}

// 依赖收集的类/收集 watcher 订阅者的类
class Dep {
  constructor() {
    // 今后,所有的 watcher 都要存到这个数组中
    this.subs = []
  }

  // 向 subs 数组中,添加 watcher 的方法
  addSub(watcher) {
    this.subs.push(watcher)
  }

  // 负责通知每个 watcher 的方法
  notify() {
    this.subs.forEach((watcher) => watcher.update())
  }
}

// 订阅者的类
class Watcher {
  // cb 回调函数中,记录着当前 Watcher 如何更新自己的文本内容
  //    但是,只知道如何更新自己还不行,还必须拿到最新的数据,
  //    因此,还需要在 new Watcher 期间,把 vm 也传递进来(因为 vm 中保存着最新的数据)
  // 除此之外,还需要知道,在 vm 身上众多的数据中,哪个数据,才是当前自己所需要的数据,
  //    因此,必须在 new Watcher 期间,指定 watcher 对应的数据的名字
  constructor(vm, key, cb) {
    this.vm = vm
    this.key = key
    this.cb = cb

    // ↓↓↓↓↓↓ 下面三行代码,负责把创建的 Watcher 实例存到 Dep 实例的 subs 数组中 ↓↓↓↓↓↓
    Dep.target = this
    key.split('.').reduce((newObj, k) => newObj[k], vm)
    Dep.target = null
  }

  // watcher 的实例,需要有 update 函数,从而让发布者能够通知我们进行更新!
  update() {
    const value = this.key.split('.').reduce((newObj, k) => newObj[k], this.vm)
    this.cb(value)
  }
}

二、vue生命周期

1、什么是生命周期

Vue 实例从创建到销毁的过程就是生命周期。从开始创建,初始化数据,编译模板,挂载 Dom 到渲染,更新到渲染,销毁等一系列过程,称之为生命周期
Vue生命周期共有8个阶段,分别为:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroye

2、生命周期八个阶段及作用

(1)beforeCreate

组件实例刚被创建,组件属性计算之前。

(2)created

组件实例刚被创建,属性已绑定,但 Dom 还未生成。

(3)beforeMount

模板编译/挂载之前。在挂载开始之前被调用,相关的 render 函数首次被调用,实例已完成以下的配置: 编译模板,把 data 里面的数据和模板生成 html,此时注意还没有挂载到 Html 页面上。

(4)mounted

模板编译/挂载之后。在 el 被新创建的 vm.el 替换,并挂载到实例上去之后被调用,实例已完成以下的配置:用上面编译好的的 html 内容替换 l 属性指向的 Dom 对象。完成模板中的 html 渲染到 html 页面中。此过程进行 ajax 交互。

(5)beforeUpdate

组件更新之前。在数据更新之前调用,发生在虚拟 Dom 重新渲染打补丁之前,可以在钩子函数中进一步的更改状态,不会出大附加的重渲染过程。

(6)updated

组件更新之后。在由于数据更改导致的虚拟 Dom 重新渲染和打补丁之后调用。调用是,组件 Dom 已经更新,所以可以执行依赖于 Dom 的操作,然而在大多数的情况下,应该避免在此期间更改状态,因为这可能会导致更新无线循环,该钩子函数在服务器端渲染期间不被调用。

(7)beforeDestroy

组件销毁前调用。在示例销毁之前调用,实例仍然完全可用。

(8)destroyed

组件销毁后调用。 在实例销毁之后调用。调用后,所有的时间监听会被移除,所有的子实例也会被销毁,该钩子函数在服务器端渲染器件不被调用

三、Vuex有几个属性及作用?

1、说明

Vuex是Vue.js的全局状态管理库。包含五个核心属性:state、getters、mutations、actions和modules。

2、五个核心属性

(1)state

state:vuex的基础数据,存储变量

(2)getters

getters:用于获取State中的状态,相当于state的计算属性。

(3)mutations

mutations:用于修改state中的数据,是唯一可以修改state的地方。mutations接收state作为第一个参数,接收payload作为第二个参数。mutation必须是同步函数,因为它们不能处理异步行为,异步行为应该放在Action中处理

(4)actions

actions:用于异步操作和提交mutations,在actions中可以进行任何异步操作,最后再提交(commit)到mutations中同步修改state。actions接收context作为第一个参数,其中包含了state、getters和commit等属性。

(5)modules

模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理和维护。

//使用方法示例
const store = new Vuex.Store({
  modules: {
    cart: {
      state: {
        items: []
      },
      mutations: {
        pushProductToCart (state, payload) {
          state.items.push({
            id: payload.id,
            quantity: 1
          })
        }
      },
      actions: {
        addProductToCart ({ state, commit }, product) {
          const cartItem = state.items.find(item => item.id === product.id)
          if (!cartItem) {
            commit('pushProductToCart', product)
          }
        }
      },
      getters: {
        cartItems: state => {
          return state.items
        }
      }
    }
  }
})

四、vue的导航守卫有哪些?

1、什么是导航守卫

vue-router提供的导航守卫主要用于导航的过程中重定向或取消路由、或者添加权限验证、数据获取等业务逻辑。
导航守卫分为三类:全局守卫、路由独享守卫和组件内守卫。
每一个导航守卫都有三个参数:to、from和next。

  • to:表示即将要进入的目标
  • from:当前导航正要离开的路由
  • next:函数,以下是next的常用方法
  • next(): 进行管道中的下一个钩子
  • next(false): 中断当前导航
  • next( ./xx ): 中断当前导航,并跳转至设置好的路径

2、导航守卫

(1)全局前置守卫

当从一个路由跳转到另一个路由的时候触发此守卫,它是跳转前触发的。

router.beforeEach((to, from, next) =>{
	if(to.name !== 'login) {
		next({name:'login})
	}else {
		next()
	}
})

(2)路由独享守卫

beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发
你可以直接在路由配置上定义 beforeEnter 守卫,你也可以将一个函数数组传递给 beforeEnter

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from, next) => {
      // reject the navigation
      return false
    },
  },
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: [removeQueryParams, removeHash],
  },
]

(3)组件内的守卫

你可以在路由组件内直接定义路由导航守卫
可用的配置 API:

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
    //不过,你可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数
    next(vm => {
	    // 通过 `vm` 访问组件实例
	})
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}

五、Vue的监听属性和计算属性有什么区别?

  • 计算属性computed在使用时,一定要注意,函数里面的变量都会被监听,只要里面的某一个值变动,便会将整个函数执行一遍,视图也会更新。 而 watch 只是监听某一个值,若是监听的值里面也有很多变量,也会全部监听
  • 计算后的属性可不在 data 中定义,如果定义会报错,因为对应的computed作为计算属性定义并返回对应的结果给这个变量,变量不可被重复定义和赋值。 而 watch 监听 data 中定义的变量变化

1、computed

特性:

  • 是计算值,每一个计算属性都包含一个getter和一个setter
  • 应用:就是简化tempalte里面{{}}计算和处理props或$emit的传值
  • 具有缓存性,页面重新渲染值不变化,计算属性会立即返回之前的计算结果,而不必再次执行函数

2、watch

特性:
1.是观察的动作,
2.应用:监听props,$emit或本组件的值执行异步操作
3.无缓存性,页面重新渲染时值不变化也会执行
用法:

  • watch监控自身属性变化
  • watch监控路由对象
new Vue({
    el: '#app',
    router: router, //开启路由对象
    watch: {
        '$route': function(newroute, oldroute) {
            console.log(newroute, oldroute);
            //可以在这个函数中获取到当前的路由规则字符串是什么
            //那么就可以针对一些特定的页面做一些特定的处理
        }
    }
})

watch监听对象的单个属性
watch如果想要监听对象的单个属性的变化,必须用computed作为中间件转化,因为computed可以取到对应的属性值。

data(){
  return{
    'first':{
      second:0
    }
  }
},
computed:{
  secondChange(){
    return this.first.second      }
},
watch:{
  secondChange(){
    console.log('second属性值变化了')
  }
},

3、简单实现computed和watch

function defineReactive(data, key, val, fn) {
     let subs = [] // 新增
     Object.defineProperty(data, key, {
       configurable: true,// 当前属性,允许被循环
       enumerable: true,//当前属性,允许被配置   delete
       get: function() {
         // 新增
       if (data.$target) {
        subs.push(data.$target)
       }
     		return val     },
    set: function(newVal) {
      if (newVal === val) return
      fn && fn(newVal)
      // 新增
      if (subs.length) {
        // 用 setTimeout 因为此时 this.data 还没更新
        setTimeout(() => {
          subs.forEach(sub => sub())
        }, 0)
      }
      val = newVal },
   })
 }

//computed 实现
function computed(ctx, obj) {
  let keys = Object.keys(obj)
  let dataKeys = Object.keys(ctx.data)
  dataKeys.forEach(dataKey => {
    defineReactive(ctx.data, dataKey, ctx.data[dataKey])
  })
  let firstComputedObj = keys.reduce((prev, next) => {
    ctx.data.$target = function() {
      ctx.setData({ [next]: obj[next].call(ctx) })
    }
    prev[next] = obj[next].call(ctx)
    ctx.data.$target = null
    return prev  }, {})
  ctx.setData(firstComputedObj)
}

//watch实现
function watch(ctx, obj) {
  Object.keys(obj).forEach(key => {
    defineReactive(ctx.data, key, ctx.data[key], function(value) {
      obj[key].call(ctx, value)
    })
  })
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值