Vue面试题

2 篇文章 0 订阅

MVC 与 MVVM

  • vue框架中MVVM的M 就是后端的数据V 就是节点树VM 就是new出来的那个Vue({})对象

MVVM的优势

1、mvc和mvvm都是一种设计思想。 主要就是mvc中Controller演变成mvvm中的viewModel mvvm主要解决了mvc中大量DOM操作使页面渲染性能降低,加载速度变慢的问题 。

2、MVVM与MVC最大的区别就是:它实现了View和Model的自动同步:当Model的属性改变时,我们不用再自己手动操作Dom元素来改变View的显示,它会自动变化。

3、整体看来,MVVM比MVC精简很多,我们不用再用选择器频繁地操作DOM。

  • MVVM并不是用VM完全取代了C,ViewModel存在目的在于抽离Controller中 |展示| 的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。

v-model原理

双向绑定原理

let obj = {}
// 劫持对象get set操作
Object.defineProperty(obj,'username',{
    set(val){
        //TODO 操作视图
    },
    get(){

    }
})

data使用return

  • 闭包:每个组件都有自己的私有作用域,确保各组件数据不会相互干扰
  • 纯对象:相互干扰

v-show与v-if

  • v-if 不会挂载到DOM树 => 触发回流(重排)和重绘
  • v-show 会挂载到DOM树,改变display属性 => 只触发重绘

虚拟DOM

  • 它是一个object对象模型,用来模拟真实的dom
  • 在vue中,每个组件都有一个render函数,每一个render函数都会返回一个虚拟dom树,这也就意味着每个组件都对应一棵虚拟DOM树
  • 创建组件、响应式数据的变化都会重新渲染视图调用render函数,如果直接使用真实DOM会消耗性能,导致渲染效率降低。
  • 首次渲染效率比直接操作真实DOM低,因为在创建真实DOM之前要创建一次虚拟DOM,但是后续如果有数据更新就会体现优势

是什么

  1. vue2.x才有虚拟DOM
  2. 本质是js对象 => 跨平台(nodejs)

在vue中做了什么

  • 更新后的虚拟DOM树和旧的DOM树比较,找到最小更新量,更新必要的真实DOM节点

diff中的patch()算法

  • diff算法是用JavaScript来表示一个dom树的结构

  • 然后用这个dom去构建一个真实的dom 插入到文档中

  • 当状态变更的时候 重新构造一个dom树 比较新旧dom树 记录两个dom树的差异 并且通知视图开始更新

  • diff算法就 用来比较vdom结构的

// 1.初始化 patch(container,vnode)
// 2.更新 update(vnode,newVnode)

// 虚拟DOM转为真实DOM 初始化渲染
function patch(vnode){
    // 虚拟DOM生成的3个要素
    let tag = vnode.tag  // 目标元素
    let attrs = vnode.attrs || {} //属性
    let children = vnode.children || []  // 子节点

    if(!tag){
        return
    }
    // 1. 创建对应的DOM
    let el = document.createElement(tag)
    let attrsName
    // 2. 给DOM添加属性
    for(attrsName in attrs){
        if(attrs.hasOwnpProperty(attrsName)){
            el.setAttribute(attrsName,attrs[attrsName])
        }
    }
    //3. 添加子节点
    children.forEach(node =>{
        el.appendChild(createElement(node))
    })
    return el
}

// 更新节点 更新子节点
function update(vnode,newVnode){
    let children = vnode.children || []  // 现有节点
    let newChildren = newVnode.children || []  // 新节点
    children.forEach((childVNode,index) =>{
        // 循环拿到新节点的每一项, (与旧节点对应的一项,对比是否有变化)
        let newChildVnode = newChildren[index]
        // 第一层没变化 (包括对比attrs,任何变化)
        if(childVNode.tag === newChildVnode.tag){
            // 递归判断下一层
            update(childVNode,newChildVnode)
        }else{
            // 有变化直接替换
            replaceNode(childVNode,newChildVnode)
        }
    })
}

发布订阅+数据劫持defineProperty,触发更新视图,实现响应式

数据发生变化,通知模板,为什么能通知==>对数据进行了订阅,在set时不直接操作DOM,不具备通用性

// 响应式
// 1. 数据联动(双向绑定)
// 2. 需要捕获到修改

// 订阅器模型
let Dep = {
  chilentList:{},// 容器
  //添加订阅 添加数据劫持阶段
  listen:function(key,fn){
    if(!this.chilentList[key]){
      this.chilentList[key]=[]
    }
    this.chilentList[key].push(fn)
  },
  // 发布 数据set 执行绑定的事件
  trigger:function(){

    let key = Array.prototype.shift.call(arguments)
    let fns = this.chilentList[key];
    if (!fns || fns.length === 0){
      return false
    }
    for(fn of fns){
      console.log(fn)
      // 绑定到订阅对象
      fn.apply(this,arguments)
    }
  }
}

// 数据劫持
let getData = function ({data,tag,datakey,selector}){
  let value = '',
  el = document.querySelector(selector)

  Object.defineProperty(data,datakey,{
    //取值
    get:function(){
      console.log('取值')
      return value
    },
    set:function(val){
      console.log('设置值')
      value = val
      // 此处使用发布模式,能通用
      // document.getElementById('name').innerHTML = val
      // 发布
      Dep.trigger(tag,val)
    }
  })

  // 订阅
  Dep.listen(tag,function(text){
    //  需要更改的属性值
    console.log(text)
    el.innerHTML = text
  })
}
<body>  
     订阅视图-1: <span class="box1"></span>
     订阅视图-2: <span class="box2"></span>

    <script src="/vue订阅发布.js"></script>
    <script>

        // 监听的数据对象
        let obj = {}

        getData({
            data:obj,
            tag:'view-1',
            datakey:'one',
            selector:'.box1'
        })

        getData({
            data:obj,
            tag:'view-2',
            datakey:'two',
            selector:'.box2'
        })
        //初始赋值
        obj.one = '视图-1'
        obj.two = '视图-2'

    </script>
</body>

应用层

$nextTick()

Vue更新DOM是异步的,在下一次DOM 更新循环结束之后执行延迟回调

vue 执行dom 更新是异步的, 只要是观察到数据变化, vue将会开启一个队列, 并缓冲在同一事件循环中发生的所有数据改变。 如果同一个watcher 被多次触发,只会被推如到队列中一次。 在这种缓冲时去除重复数据对于避免不必要的计算, 和DOM 操作非常重要。 然后下一个事件循环 “tick”中, vue 刷新队列执行实际(去重)工作。 在Vue 内部尝试对异步队列使用原生的promise.then 和 MessageChange, 如果执行环境不支持, 会采用setTime(()=> {} , 0)代替。

this.$nextTick(() => {
    this.twodiv = this.$refs.onediv.innerHTML; // 通过原生$ref获取到第一个div改变后的值,然后赋给第二给div
    console.log(this.twodiv);
});

单页与多页的区别及优缺点

单页应用(SPA):只有一个主页面应用

  • 组件 ==> 页面片段
  • 跳转 ==> 刷新局部资源
  • 场景 ==> PC端

优点:

  1. 体验好,快
  2. 改动内容,不用加载整个页面
  3. 前后端分离

缺点:

  1. 不利于SEO
  2. 首屏加载速度慢
  3. 页面复杂的高

多页应用

整页面刷新

v-for与v-if

v-for优先级高

vue-router与location.href有什么区别

  • location.href:简单方便,刷新页面
  • vue-router:实现了按需加载,减少的dom消耗; 封装原生js的history

Vue原理之侦听器实现

如何让数据变得可观测

class Obesrver{
    // value 观测数据
    constructor(value){
        this.value = value
        // 判断类型
        if (Array.isArray(value)){
            // 数组逻辑
            this.observeArr(value)
            
        }else{
            // 对象逻辑
            this.observeObj(value)
        }
    }

    // 数组逻辑
    observeArr(value){
        // 对数组变动检测:找到改变原数组的方法,然后对这些方法进行劫持处理。 不能直接修改其原型
        // 中间桥梁
        let newArr = function(){}

        const changeArr = [
            "splice",
            "shift",
            "unshift",
            "push",
            "pop",
            "reverse",
            "sort"
        ]

        for(let fn of changeArr){
            newArr.prototype[fn] = function (...args){
                console.log('数组变动')
                Array.prototype[fn].call(this,...args)
            } 
        }

        value.__proto__ = new newArr() // 这样调用数组方法会__

    }

    // 观测对象
    observeObj(value){
        // 获取属性值数组
        let keys = Object.keys(value)
        for(let key of keys){
           if(typeof value[key] === 'object'){
                // 递归
                new Obesrver(value[key])
           }else{
                //为每个属性添加数据劫持
                this.defineData(value,key)
           }
        }
    }

    //数据劫持
    defineData(obj,key){
        let value = obj[key]
        Object.defineProperty(obj,key,{
            
            // 可枚举
            enumerable:true,
            //可修改
            configurable:true,
            //存取描述,不可与数据描述同时存在 
            //value :该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
            //writable :当且仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false。
            get(){
                console.log(`读取了${key}属性值:${value}`)
                return value
            },
            set(val){
                value = val
                console.log(`设置了${key}属性值:${value}`)
            }   
        })
    }
}


let obj = {
    a:1,
    b:2,
    c:{
        f:222,
        g:333
    },
    h:[3,4,5]
}

new Obesrver(obj)

// obj.c.f //读取了f属性值:222
// obj.b //读取了b属性值:2

// obj.a = 3 //设置了a属性值:3
// obj.c.f = 666 //设置了f属性值:666
obj.h.push(1)
console.log(obj.h)

// 监听了每个属性值的变化

key作用,以及不用下标去设置

  • 提供给虚拟DOM唯一的值,提高更新效率,当数据发生改变时,Vue会根据新数据生成新的虚拟DOM,随后通过比较新旧虚拟DOM的tag和key值是否相同,进行DOM元素的就地复用或更新
  • 如果对数组进行增删操作,从修改项开始,与key的绑定关系就会发送变化,触发重新渲染,会影响性能,也会产生一些bug
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值