vue面试知识总结

1、vue的生命周期钩子函数:                                                                                                                                 

                            
                                                        
                                ,

钩子函数                                               触发的行为       在此阶段可以做的事情
beforeCreadted                 vue实例的挂载元素$el和数据对象data都为undefined,还未初始化。  加loading事件
created         vue实例的数据对象data有了,$el还没有 结束loading、    请求数据为mounted渲染做准备
beforeMount    vue实例的$el和data都初始化了,但还是虚拟的dom,节点具体的data.filter还未替换。  
mounted                vue实例挂载完成,data.filter成功渲染                           配合路由钩子使用
beforeUpdate  data更新时触发   
updated  data更新时触发 数据更新时,做一些处理(此处也可以用watch进行观测)  
beforeDestroy

beforeDestroy 组件销毁时触发 
数据更新时,做一些处理(此处也可以用watch进行观测)
destroyed 组件销毁时触发,vue实例解除了事件监听以及和dom的绑定(无响应了),但DOM节点依旧存在 组件销毁时进行提示 组件销毁时进行提示

 

2、v-show 与 v-if 区别

    1. v-hsow和v-if的区别:
      v-show是css切换,v-if是完整的销毁和重新创建。
    2. 使用
      频繁切换时用v-show,运行时较少改变时用v-if
    3. v-if=‘false’ v-if是条件渲染,当false的时候不会渲染。

 

3、计算属性和 watch 的区别

计算属性是自动监听依赖值的变化,从而动态返回内容,监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些事情。
所以区别来源于用法,只是需要动态值,那就用计算属性;需要知道值的改变后执行业务逻辑,才用 watch,用反或混用虽然可行,但都是不正确的用法。
说出一下区别会加分
computed 是一个对象时,它有哪些选项?
computed 和 methods 有什么区别?
computed 是否能依赖其它组件的数据?
watch 是一个对象时,它有哪些选项?

  1. 有get和set两个选项
  2. methods是一个方法,它可以接受参数,而computed不能,computed是可以缓存的,methods不会。
  3. computed可以依赖其他computed,甚至是其他组件的data
  4. watch 配置
    handler
    deep 是否深度
    immeditate 是否立即执行

总结

当有一些数据需要随着另外一些数据变化时,建议使用computed。
当有一个通用的响应数据变化的时候,要执行一些业务逻辑或异步操作的时候建议使用watcher

4、事件修饰符:

.stop 是阻止冒泡行为,不让当前元素的事件继续往外触发,如阻止点击div内部事件,触发div事件
.prevent 是阻止事件本身行为,如阻止超链接的点击跳转,form表单的点击提交
.self 是只有是自己触发的自己才会执行,如果接受到内部的冒泡事件传递信号触发,会忽略掉这个信号
.capture 是改变js默认的事件机制,默认是冒泡,capture功能是将冒泡改为倾听模式
.once 是将事件设置为只执行一次,如 .click.prevent.once 代表只阻止事件的默认行为一次,当第二次触发的时候事件本身的行为会执行
 

5、组件中 data 为什么是函数

为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?

因为组件是用来复用的,JS 里对象是引用关系,这样作用域没有隔离,而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

6、组件间的通信

  1. 父子 props/event $parent/$children ref provide/inject
  2. 兄弟 bus vuex
  3. 跨级 bus vuex provide.inject

7、路由的跳转方式

一般有两种

  1. <router-link to='home'> router-link标签会渲染为<a>标签,咋填template中的跳转都是这种;
  2. 另一种是编程是导航 也就是通过js跳转 比如 router.push('/home')

8、Vue.js 2.x 双向绑定原理

这个问题几乎是面试必问的,回答也是有深有浅。基本上要知道核心的 API 是通过 Object.defineProperty() 来劫持各个属性的 setter / getter,在数据变动时发布消息给订阅者,触发相应的监听回调,这也是为什么 Vue.js 2.x 不支持 IE8 的原因(IE 8 不支持此 API,且无法通过 polyfill 实现)。

9、什么是 MVVM,与 MVC 有什么区别

  • MVC 是后端的分层开发概念;
  • MVVM是前端视图层的概念,主要关注于 视图层分离,也就是说:MVVM把前端的视图层,分为了 三部分 Model, View , VM ViewModel

  1. 数据劫持: 使用 Object.defineProperty(obj, 'property',{})来定义属性,在定义时传入一些参数,包括set()get()函数,分别在设置和获取该对象的该属性时调用执行。
function MVVM(option = {}) { this.$option = option let data = this._data = this.$option.data // 对data进行数据劫持 observe(data) // 将数据代理到this中,在this中对this._data操作 for (let key in data) { Object.defineProperty(this, key, { enumerable: true, get () { return this._data[key] }, set (newVal) { this._data[key] = newVal } }) } } function Observe(data) { // 这里写逻辑,方便递归 for (let key in data) { // 把data上的属性通过defineProperty定义 let val = data[key] observe(val) // 递归 Object.defineProperty(data, key, { enumerable: true, get () { // 获取该属性 return val }, set (newVal) { // 更改这个属性 if (val === newVal) return // 值不改变 val = newVal observe(newVal) } }) } } // 给对象增加 Object.defineProperty function observe(data) { if(typeof data != "object") return return new Observe(data) } 

从代码看出,this代理了this._data(被劫持的数据),所以我们在用this.未定义属性时不会加上settergetter,应该去报错

  1. 编译页面
function VM(options = {}) { // MVVM框架核心类 this.$options = options let data = this._data = this.$options.data observe(data) // 对data进行数据劫持 for (let key in data) { // 将数据代理到this中,在this中对this._data操作 Object.defineProperty(this, key, { enumerable: true, get () { return this._data[key] }, set (newVal) { this._data[key] = newVal } }) } new Compile(options.el, this) // 编译页面 } function Observe(data) { // 这里写逻辑,方便递归 for (let key in data) { // 把data上的属性通过defineProperty定义 let val = data[key] observe(val) // 递归 Object.defineProperty(data, key, { enumerable: true, get () { // 获取该属性 return val }, set (newVal) { // 更改这个属性 if (val === newVal) return // 值不改变 val = newVal observe(newVal) } }) } } function observe(data) { // 给对象增加 Object.defineProperty if(typeof data != "object") return return new Observe(data) } function Compile (el, vm) { vm.$el = document.querySelector(el) // 获取到dom容器 // 新建一个文档碎片,将容器中的dom放入到文档碎片中(内存中),操作文档碎片编译(高效) let fragment = document.createDocumentFragment() while(child = vm.$el.firstChild) { fragment.appendChild(child) } replace(fragment) function replace (fragment) { Array.from(fragment.childNodes).forEach(node => { let text = node.textContent let reg = /\{\{(.*)\}\}/ if (node.nodeType === 3 && reg.test(text)) { // 处理模板 文本节点 let arr = RegExp.$1.split(".") let val = vm arr.forEach(key => { // 取到符合条件的值,这样便于取到嵌套值 {{a.a}} val = val[key] }) node.textContent = text.replace(reg, val) } if (node.childNodes) { replace(node) } }) } vm.$el.appendChild(fragment) } 

在实例化VM时,还要去编译页面,这里通过递归获取到指定容器中的{{ data }}写法的文本节点,通过正则匹配到其中的 data,再根据传入的vm实例中的data替换文本节点

  1. 发布订阅模式 + 数据劫持实现 vm
function VM(options = {}) { // MVVM框架核心类 this.$options = options let data = this._data = this.$options.data observe(data) // 对data进行数据劫持 for (let key in data) { // 将数据代理到this中,在this中对this._data操作 Object.defineProperty(this, key, { enumerable: true, get () { return this._data[key] }, set (newVal) { this._data[key] = newVal } }) } new Compile(options.el, this) // 编译页面 } function Observe(data) { // 这里写逻辑,方便递归 let dep = new Dep() for (let key in data) { // 把data上的属性通过defineProperty定义 let val = data[key] observe(val) // 递归 Object.defineProperty(data, key, { enumerable: true, get () { // 获取该属性 Dep.target && dep.addSub(Dep.target) // 订阅 return val }, set (newVal) { // 更改这个属性 if (val === newVal) return // 值不改变 val = newVal observe(newVal) dep.notify() } }) } } function observe(data) { // 给对象增加 Object.defineProperty if(typeof data != "object") return return new Observe(data) } function Compile (el, vm) { vm.$el = document.querySelector(el) // 获取到dom容器 // 新建一个文档碎片,将容器中的dom放入到文档碎片中(内存中),操作文档碎片编译(高效) let fragment = document.createDocumentFragment() while(child = vm.$el.firstChild) { fragment.appendChild(child) } replace(fragment) function replace (fragment) { Array.from(fragment.childNodes).forEach(node => { let text = node.textContent let reg = /\{\{(.*)\}\}/ if (node.nodeType === 3 && reg.test(text)) { // 处理模板 文本节点 let arr = RegExp.$1.split(".") let val = vm arr.forEach(key => { // 取到符合条件的值,这样便于取到嵌套值 {{a.a}} val = val[key] }) new Watcher(vm, RegExp.$1, function (newVal) { node.textContent = text.replace(reg, newVal) // 替换页面 }) node.textContent = text.replace(reg, val) } if (node.childNodes) { replace(node) } }) } vm.$el.appendChild(fragment) } // 发布订阅模式 function Dep () { this.subs = [] } Dep.prototype.addSub = function (sub) { this.subs.push(sub) } Dep.prototype.notify = function () { this.subs.forEach(sub => {sub.update()}) } function Watcher (vm, exp, fn) { this.fn = fn this.vm = vm this.exp = exp Dep.target = this // 在这里做一个取值操作,触发get函数,就会触发订阅 let val = vm let arr = exp.split(".") arr.forEach(key => { val = val[key] }) Dep.target = null } Watcher.prototype.update = function (newVal) { let val = this.vm let arr = this.exp.split(".") arr.forEach(key => { val = val[key] }) this.fn(val) } 

当我们在页面中编译一个data时,会创建一个watcher,此时传入了与这个数据相关的参数,在构造函数中根据这些参数获取这个数据,此时会触发它的get方法,在get中将watcher订阅到dep上,在set这个数据时执行dep.notify()会使得所有的watcherupdate函数执行,此时在update函数中获取到set的新值,再调用实例化时传入的函数更新视图。

  1. 指令的实现 v-model
function Compile (el, vm) { vm.$el = document.querySelector(el) // 获取到dom容器 // 新建一个文档碎片,将容器中的dom放入到文档碎片中(内存中),操作文档碎片编译(高效) let fragment = document.createDocumentFragment() while(child = vm.$el.firstChild) { fragment.appendChild(child) } replace(fragment) function replace (fragment) { Array.from(fragment.childNodes).forEach(node => { let text = node.textContent let reg = /\{\{(.*)\}\}/ if (node.nodeType === 3 && reg.test(text)) { // 文本替换和添加订阅 // 处理模板 文本节点 let arr = RegExp.$1.split(".") let val = vm arr.forEach(key => { // 取到符合条件的值,这样便于取到嵌套值 {{a.a}} val = val[key] }) new Watcher(vm, RegExp.$1, function (newVal) { node.textContent = text.replace(reg, newVal) // 替换页面 }) node.textContent = text.replace(reg, val) } if (node.nodeType === 1) { // 元素节点,获取属性,处理指令 let nodeAttrs = node.attributes // 获取dom属性 Array.from(nodeAttrs).forEach((attr) => { let name = attr.name let exp = attr.value if (name.indexOf("v-") == 0) { // 这是指令 这里默认认为是 v-model node.value = vm[exp] } new Watcher(vm, exp, function (newVal) { node.value = newVal }) // 添加输入事件监听 node.addEventListener("input", function (e) { let newVal = e.target.value vm[exp] = newVal }) }) } if (node.childNodes) { replace(node) } }) } vm.$el.appendChild(fragment) } 

在解析时,如果节点有v-属性说明是指令,双向数据绑定的逻辑处理要订阅数据,还需要在输入数据是去写数据触发set函数




10、nextTick()

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后,立即使用这个回调函数,获取更新后的 DOM。

// 修改数据
vm.msg = 'Hello'
// DOM 还未更新
Vue.nextTick(function () { // DOM 更新 })

猜你喜欢

转载自www.cnblogs.com/lilin520/p/12129779.html