乐趣区

关于vue.js:校招前端一面必会vue面试题指南

写过自定义指令吗?原理是什么

答复范例

  1. Vue有一组默认指令,比方 v-modelv-for,同时 Vue 也容许用户注册自定义指令来扩大 Vue 能力
  2. 自定义指令次要实现一些可复用低层级 DOM 操作
  3. 应用自定义指令分为定义、注册和应用三步:
  4. 定义自定义指令有两种形式:对象和函数模式,前者相似组件定义,有各种生命周期;后者只会在 mounte d 和updated 时执行
  • 注册自定义指令相似组件,能够应用 app.directive() 全局注册,应用 {directives:{xxx}} 部分注册
  • 应用时在注册名称前加上 v- 即可,比方v-focus
  • 我在我的项目中罕用到一些自定义指令,例如:
  • 复制粘贴 v-copy
  • 长按 v-longpress
  • 防抖 v-debounce
  • 图片懒加载 v-lazy
  • 按钮权限 v-premission
  • 页面水印 v-waterMarker
  • 拖拽指令 v-draggable
  • vue3中指令定义产生了比拟大的变动,次要是钩子的名称放弃和组件统一,这样开发人员容易记忆,不易犯错。另外在 v3.2 之后,能够在 setup 中以一个小写 v 结尾不便的定义自定义指令,更简略了

根本应用

当 Vue 中的外围内置指令不可能满足咱们的需要时,咱们能够定制自定义的指令用来满足开发的需要

咱们看到的 v- 结尾的行内属性,都是指令,不同的指令能够实现或实现不同的性能,对一般 DOM 元素进行底层操作,这时候就会用到自定义指令。除了外围性能默认内置的指令 (v-modelv-show),Vue 也容许注册自定义指令

// 指令应用的几种形式:// 会实例化一个指令,但这个指令没有参数 
`v-xxx`

// -- 将值传到指令中
`v-xxx="value"`  

// -- 将字符串传入到指令中,如 `v-html="'<p> 内容 </p>'"`
`v-xxx="'string'"` 

// -- 传参数(`arg`),如 `v-bind:class="className"`
`v-xxx:arg="value"` 

// -- 应用修饰符(`modifier`)`v-xxx:arg.modifier="value"` 

注册一个自定义指令有全局注册与部分注册

// 全局注册注册次要是用过 Vue.directive 办法进行注册
// Vue.directive 第一个参数是指令的名字(不须要写上 v - 前缀),第二个参数能够是对象数据,也能够是一个指令函数
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()  // 页面加载实现之后主动让输入框获取到焦点的小性能}
})

// 部分注册通过在组件 options 选项中设置 directive 属性
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {el.focus() // 页面加载实现之后主动让输入框获取到焦点的小性能
    }
  }
}

// 而后你能够在模板中任何元素上应用新的 v-focus property,如下:<input v-focus />

钩子函数

  1. bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。
  2. inserted:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。
  3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。
  4. componentUpdated:被绑定元素所在模板实现一次更新周期时调用。
  5. unbind:只调用一次,指令与元素解绑时调用。

所有的钩子函数的参数都有以下:

  • el:指令所绑定的元素,能够用来间接操作 DOM
  • binding:一个对象,蕴含以下 property

    • name:指令名,不包含 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否扭转都可用。
    • expression:字符串模式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个蕴含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 {foo: true, bar: true}
    • vnodeVue 编译生成的虚构节点
    • oldVnode:上一个虚构节点,仅在 updatecomponentUpdated 钩子中可用

除了 el 之外,其它参数都应该是只读的,切勿进行批改。如果须要在钩子之间共享数据,倡议通过元素的 dataset 来进行

<div v-demo="{color:'white', text:'hello!'}"></div>
<script>
    Vue.directive('demo', function (el, binding) {console.log(binding.value.color) // "white"
    console.log(binding.value.text)  // "hello!"
    })
</script>

利用场景

应用自定义组件组件能够满足咱们日常一些场景,这里给出几个自定义组件的案例:

  1. 防抖
// 1. 设置 v -throttle 自定义指令
Vue.directive('throttle', {bind: (el, binding) => {
    let throttleTime = binding.value; // 防抖工夫
    if (!throttleTime) { // 用户若不设置防抖工夫,则默认 2s
      throttleTime = 2000;
    }
    let cbFun;
    el.addEventListener('click', event => {if (!cbFun) { // 第一次执行
        cbFun = setTimeout(() => {cbFun = null;}, throttleTime);
      } else {event && event.stopImmediatePropagation();
      }
    }, true);
  },
});
// 2. 为 button 标签设置 v -throttle 自定义指令
<button @click="sayHello" v-throttle> 提交 </button>
  1. 图片懒加载

设置一个 v-lazy 自定义组件实现图片懒加载

const LazyLoad = {
    // install 办法
    install(Vue,options){
       // 代替图片的 loading 图
        let defaultSrc = options.default;
        Vue.directive('lazy',{bind(el,binding){LazyLoad.init(el,binding.value,defaultSrc);
            },
            inserted(el){
                // 兼容解决
                if('InterpObserver' in window){LazyLoad.observe(el);
                }else{LazyLoad.listenerScroll(el);
                }

            },
        })
    },
    // 初始化
    init(el,val,def){
        // src 贮存实在 src
        el.setAttribute('src',val);
        // 设置 src 为 loading 图
        el.setAttribute('src',def);
    },
    // 利用 InterpObserver 监听 el
    observe(el){
        let io = new InterpObserver(entries => {
            let realSrc = el.dataset.src;
            if(entries[0].isIntersecting){if(realSrc){
                    el.src = realSrc;
                    el.removeAttribute('src');
                }
            }
        });
        io.observe(el);
    },
    // 监听 scroll 事件
    listenerScroll(el){let handler = LazyLoad.throttle(LazyLoad.load,300);
        LazyLoad.load(el);
        window.addEventListener('scroll',() => {handler(el);
        });
    },
    // 加载实在图片
    load(el){
        let windowHeight = document.documentElement.clientHeight
        let elTop = el.getBoundingClientRect().top;
        let elBtm = el.getBoundingClientRect().bottom;
        let realSrc = el.dataset.src;
        if(elTop - windowHeight<0&&elBtm > 0){if(realSrc){
                el.src = realSrc;
                el.removeAttribute('src');
            }
        }
    },
    // 节流
    throttle(fn,delay){
        let timer; 
        let prevTime;
        return function(...args){let currTime = Date.now();
            let context = this;
            if(!prevTime) prevTime = currTime;
            clearTimeout(timer);

            if(currTime - prevTime > delay){
                prevTime = currTime;
                fn.apply(context,args);
                clearTimeout(timer);
                return;
            }

            timer = setTimeout(function(){prevTime = Date.now();
                timer = null;
                fn.apply(context,args);
            },delay);
        }
    }

}
export default LazyLoad;
  1. 一键 Copy 的性能
import {Message} from 'ant-design-vue';

const vCopy = { //
  /*
    bind 钩子函数,第一次绑定时调用,能够在这里做初始化设置
    el: 作用的 dom 对象
    value: 传给指令的值,也就是咱们要 copy 的值
  */
  bind(el, { value}) {
    el.$value = value; // 用一个全局属性来存传进来的值,因为这个值在别的钩子函数里还会用到
    el.handler = () => {if (!el.$value) {
      // 值为空的时候,给出提醒,我这里的提醒是用的 ant-design-vue 的提醒,你们随便
        Message.warning('无复制内容');
        return;
      }
      // 动态创建 textarea 标签
      const textarea = document.createElement('textarea');
      // 将该 textarea 设为 readonly 避免 iOS 下主动唤起键盘,同时将 textarea 移出可视区域
      textarea.readOnly = 'readonly';
      textarea.style.position = 'absolute';
      textarea.style.left = '-9999px';
      // 将要 copy 的值赋给 textarea 标签的 value 属性
      textarea.value = el.$value;
      // 将 textarea 插入到 body 中
      document.body.appendChild(textarea);
      // 选中值并复制
      textarea.select();
      // textarea.setSelectionRange(0, textarea.value.length);
      const result = document.execCommand('Copy');
      if (result) {Message.success('复制胜利');
      }
      document.body.removeChild(textarea);
    };
    // 绑定点击事件,就是所谓的一键 copy 啦
    el.addEventListener('click', el.handler);
  },
  // 当传进来的值更新的时候触发
  componentUpdated(el, { value}) {el.$value = value;},
  // 指令与元素解绑的时候,移除事件绑定
  unbind(el) {el.removeEventListener('click', el.handler);
  },
};

export default vCopy;
  1. 拖拽
<div ref="a" id="bg" v-drag></div>

  directives: {
    drag: {bind() {},
      inserted(el) {el.onmousedown = (e) => {
          let x = e.clientX - el.offsetLeft;
          let y = e.clientY - el.offsetTop;
          document.onmousemove = (e) => {
            let xx = e.clientX - x + "px";
            let yy = e.clientY - y + "px";
            el.style.left = xx;
            el.style.top = yy;
          };
          el.onmouseup = (e) => {document.onmousemove = null;};
        };
      },
    },
  }

原理

  • 指令实质上是装璜器,是 vueHTML 元素的扩大,给 HTML 元素减少自定义性能。vue 编译 DOM 时,会找到指令对象,执行指令的相干办法。
  • 自定义指令有五个生命周期(也叫钩子函数),别离是 bindinsertedupdatecomponentUpdatedunbind

原理

  1. 在生成 ast 语法树时,遇到指令会给以后元素增加 directives 属性
  2. 通过 genDirectives 生成指令代码
  3. patch 前将指令的钩子提取到 cbs 中, 在 patch 过程中调用对应的钩子
  4. 当执行指令对应钩子函数时,调用对应指令定义的办法

说一下 Vue 的生命周期

Vue 实例有⼀个残缺的⽣命周期,也就是从开始创立、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。

  1. beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到 data、computed、watch、methods 上的办法和数据。
  2. created(创立后):实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到 $el 属性。
  3. beforeMount(挂载前):在挂载开始之前被调用,相干的 render 函数首次被调用。实例已实现以下的配置:编译模板,把 data 外面的数据和模板生成 html。此时还没有挂载 html 到页面上。
  4. mounted(挂载后):在 el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的 html 内容替换 el 属性指向的 DOM 对象。实现模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。
  5. beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。
  6. updated(更新后):在因为数据更改导致的虚构 DOM 从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM 曾经更新,所以能够执行依赖于 DOM 的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。
  7. beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,this 仍能获取到实例。
  8. destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

另外还有 keep-alive 独有的生命周期,别离为 activateddeactivated。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。

Vue 中 computed 和 watch 有什么区别?

计算属性 computed

(1)** 反对缓存 **,只有依赖数据发生变化时,才会从新进行计算函数;(2)计算属性内 ** 不反对异步操作 **;(3)计算属性的函数中 ** 都有一个 get**(默认具备,获取计算属性)** 和 set**(手动增加,设置计算属性)办法;(4)计算属性是主动监听依赖值的变动,从而动静返回内容。

侦听属性 watch

(1)** 不反对缓存 **,只有数据发生变化,就会执行侦听函数;(2)侦听属性内 ** 反对异步操作 **;(3)侦听属性的值 ** 能够是一个对象,接管 handler 回调,deep,immediate 三个属性 **;(3)监听是一个过程,在监听的值变动时,能够触发一个回调,并 ** 做一些其余事件 **。

v-if 和 v -show 的区别

  • 伎俩:v-if 是动静的向 DOM 树内增加或者删除 DOM 元素;v-show 是通过设置 DOM 元素的 display 款式属性管制显隐;
  • 编译过程:v-if 切换有一个部分编译 / 卸载的过程,切换过程中适合地销毁和重建外部的事件监听和子组件;v-show 只是简略的基于 css 切换;
  • 编译条件:v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始部分编译; v-show 是在任何条件下,无论首次条件是否为真,都被编译,而后被缓存,而且 DOM 元素保留;
  • 性能耗费:v-if 有更高的切换耗费;v-show 有更高的初始渲染耗费;
  • 应用场景:v-if 适宜经营条件不大可能扭转;v-show 适宜频繁切换。

对 React 和 Vue 的了解,它们的异同

相似之处:

  • 都将注意力集中放弃在外围库,而将其余性能如路由和全局状态治理交给相干的库;
  • 都有本人的构建工具,能让你失去一个依据最佳实际设置的我的项目模板;
  • 都应用了 Virtual DOM(虚构 DOM)进步重绘性能;
  • 都有 props 的概念,容许组件间的数据传递;
  • 都激励组件化利用,将利用分拆成一个个性能明确的模块,进步复用性。

不同之处:

1)数据流

Vue 默认反对数据双向绑定,而 React 始终提倡单向数据流

2)虚构 DOM

Vue2.x 开始引入 ”Virtual DOM”,打消了和 React 在这方面的差别,然而在具体的细节还是有各自的特点。

  • Vue 声称能够更快地计算出 Virtual DOM 的差别,这是因为它在渲染过程中,会跟踪每一个组件的依赖关系,不须要从新渲染整个组件树。
  • 对于 React 而言,每当利用的状态被扭转时,全副子组件都会从新渲染。当然,这能够通过 PureComponent/shouldComponentUpdate 这个生命周期办法来进行管制,但 Vue 将此视为默认的优化。

3)组件化

React 与 Vue 最大的不同是模板的编写。

  • Vue 激励写近似惯例 HTML 的模板。写起来很靠近规范 HTML 元素,只是多了一些属性。
  • React 举荐你所有的模板通用 JavaScript 的语法扩大——JSX 书写。

具体来讲:React 中 render 函数是反对闭包个性的,所以 import 的组件在 render 中能够间接调用。然而在 Vue 中,因为模板中应用的数据都必须挂在 this 上进行一次直达,所以 import 一个组件完了之后,还须要在 components 中再申明下。4)监听数据变动的实现原理不同

  • Vue 通过 getter/setter 以及一些函数的劫持,能准确晓得数据变动,不须要特地的优化就能达到很好的性能
  • React 默认是通过比拟援用的形式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的 vDOM 的从新渲染。这是因为 Vue 应用的是可变数据,而 React 更强调数据的不可变。

5)高阶组件

react 能够通过高阶组件(HOC)来扩大,而 Vue 须要通过 mixins 来扩大。

高阶组件就是高阶函数,而 React 的组件自身就是纯正的函数,所以高阶函数对 React 来说大海捞针。相同 Vue.js 应用 HTML 模板创立视图组件,这时模板无奈无效的编译,因而 Vue 不能采纳 HOC 来实现。

6)构建工具

两者都有本人的构建工具:

  • React ==> Create React APP
  • Vue ==> vue-cli

7)跨平台

  • React ==> React Native
  • Vue ==> Weex

如何从实在 DOM 到虚构 DOM

波及到 Vue 中的模板编译原理,次要过程:

  1. 将模板转换成 ast 树, ast 用对象来形容实在的 JS 语法(将实在 DOM 转换成虚构 DOM)
  2. 优化树
  3. ast 树生成代码

参考 前端进阶面试题具体解答

过滤器的作用,如何实现一个过滤器

依据过滤器的名称,过滤器是用来过滤数据的,在 Vue 中应用 filters 来过滤数据,filters不会批改数据,而是过滤数据,扭转用户看到的输入(计算属性 computed,办法 methods 都是通过批改数据来解决数据格式的输入显示)。

应用场景:

  • 须要格式化数据的状况,比方须要解决工夫、价格等数据格式的输入 / 显示。
  • 比方后端返回一个 年月日的日期字符串 ,前端须要展现为 多少天前 的数据格式,此时就能够用fliters 过滤器来解决数据。

过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在 插值表达式 {{}}v-bind 表达式 中,而后放在操作符“|”前面进行批示。

例如,在显示金额,给商品价格增加单位:

<li> 商品价格:{{item.price | filterPrice}}</li>

 filters: {filterPrice (price) {return price ? ('¥' + price) : '--'
    }
  }

Vue 中封装的数组办法有哪些,其如何实现页面更新

在 Vue 中,对响应式解决利用的是 Object.defineProperty 对数据进行拦挡,而这个办法并不能监听到数组外部变动,数组长度变动,数组的截取变动等,所以须要对这些操作进行 hack,让 Vue 能监听到其中的变动。那 Vue 是如何实现让这些数组办法实现元素的实时更新的呢,上面是 Vue 中对这些办法的封装:

// 缓存数组原型
const arrayProto = Array.prototype;
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto);
// 须要进行性能拓展的办法
const methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse"
];

/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function(method) {
  // 缓存原生数组办法
  const original = arrayProto[method];
  def(arrayMethods, method, function mutator(...args) {
    // 执行并缓存原生数组性能
    const result = original.apply(this, args);
    // 响应式解决
    const ob = this.__ob__;
    let inserted;
    switch (method) {
    // push、unshift 会新增索引,所以要手动 observer
      case "push":
      case "unshift":
        inserted = args;
        break;
      // splice 办法,如果传入了第三个参数,也会有索引退出,也要手动 observer。case "splice":
        inserted = args.slice(2);
        break;
    }
    // 
    if (inserted) ob.observeArray(inserted);// 获取插入的值,并设置响应式监听
    // notify change
    ob.dep.notify();// 告诉依赖更新
    // 返回原生数组办法的执行后果
    return result;
  });
});

简略来说就是,重写了数组中的那些原生办法,首先获取到这个数组的__ob__,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 持续对新的值察看变动(也就是通过 target__proto__ == arrayMethods 来扭转了数组实例的型),而后手动调用 notify,告诉渲染 watcher,执行 update。

slot 是什么?有什么作用?原理是什么?

slot 又名插槽,是 Vue 的内容散发机制,组件外部的模板引擎应用 slot 元素作为承载散发内容的进口。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot 又分三类,默认插槽,具名插槽和作用域插槽。

  • 默认插槽:又名匿名查抄,当 slot 没有指定 name 属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
  • 具名插槽:带有具体名字的插槽,也就是带有 name 属性的 slot,一个组件能够呈现多个具名插槽。
  • 作用域插槽:默认插槽、具名插槽的一个变体,能够是匿名插槽,也能够是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,能够将子组件外部的数据传递给父组件,让父组件依据子组件的传递过去的数据决定如何渲染该插槽。

实现原理:当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,寄存在 vm.$slot 中,默认插槽为 vm.$slot.default,具名插槽为vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,应用$slot 中的内容进行替换,此时能够为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

Vue 单页利用与多页利用的区别

概念:

  • SPA 单页面利用(SinglePage Web Application),指只有一个主页面的利用,一开始只须要加载一次 js、css 等相干资源。所有内容都蕴含在主页面,对每一个功能模块组件化。单页利用跳转,就是切换相干组件,仅仅刷新部分资源。
  • MPA 多页面利用(MultiPage Application),指有多个独立页面的利用,每个页面必须反复加载 js、css 等相干资源。多页利用跳转,须要整页资源刷新。

说说 Vue 的生命周期吧

什么时候被调用?

  • beforeCreate:实例初始化之后,数据观测之前调用
  • created:实例创立万之后调用。实例实现:数据观测、属性和办法的运算、 watch/event 事件回调。无 $el .
  • beforeMount:在挂载之前调用,相干 render 函数首次被调用
  • mounted:了被新创建的 vm.$el 替换,并挂载到实例下来之后调用改钩子。
  • beforeUpdate:数据更新前调用,产生在虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
  • updated:因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
  • beforeDestroy:实例销毁前调用,实例依然可用。
  • destroyed:实例销毁之后调用,调用后,Vue 实例批示的所有货色都会解绑,所有事件监听器和所有子实例都会被移除

每个生命周期外部能够做什么?

  • created:实例曾经创立实现,因为他是最早触发的,所以能够进行一些数据、资源的申请。
  • mounted:实例曾经挂载实现,能够进行一些 DOM 操作。
  • beforeUpdate:能够在这个钩子中进一步的更改状态,不会触发重渲染。
  • updated:能够执行依赖于 DOM 的操作,然而要防止更改状态,可能会导致更新无线循环。
  • destroyed:能够执行一些优化操作,清空计时器,解除绑定事件。

ajax 放在哪个生命周期?:个别放在 mounted 中,保障逻辑统一性,因为生命周期是同步执行的, ajax 是异步执行的。复数服务端渲染 ssr 同一放在 created 中,因为服务端渲染不反对 mounted 办法。 什么时候应用 beforeDestroy?:以后页面应用 $on,须要解绑事件。分明定时器。解除事件绑定, scroll mousemove

MVC 和 MVVM 区别

MVC

MVC 全名是 Model View Controller,是模型 (model)-视图(view)-控制器(controller) 的缩写,一种软件设计榜样

  • Model(模型):是应用程序中用于解决应用程序数据逻辑的局部。通常模型对象负责在数据库中存取数据
  • View(视图):是应用程序中解决数据显示的局部。通常视图是根据模型数据创立的
  • Controller(控制器):是应用程序中解决用户交互的局部。通常控制器负责从视图读取数据,管制用户输出,并向模型发送数据

MVC 的思维:一句话形容就是 Controller 负责将 Model 的数据用 View 显示进去,换句话说就是在 Controller 外面把 Model 的数据赋值给 View。

MVVM

MVVM 新增了 VM 类

  • ViewModel 层:做了两件事达到了数据的双向绑定 一是将【模型】转化成【视图】,行将后端传递的数据转化成所看到的页面。实现的形式是:数据绑定。二是将【视图】转化成【模型】,行将所看到的页面转化成后端的数据。实现的形式是:DOM 事件监听。

MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的主动同步,也就是当 Model 的属性扭转时,咱们不必再本人手动操作 Dom 元素,来扭转 View 的显示,而是扭转属性后该属性对应 View 层显示会主动扭转(对应 Vue 数据驱动的思维)

整体看来,MVVM 比 MVC 精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不必再用选择器操作 DOM 元素。因为在 MVVM 中,View 不晓得 Model 的存在,Model 和 ViewModel 也察看不到 View,这种低耦合模式进步代码的可重用性

留神:Vue 并没有齐全遵循 MVVM 的思维 这一点官网本人也有阐明

那么问题来了 为什么官网要说 Vue 没有齐全遵循 MVVM 思维呢?

  • 严格的 MVVM 要求 View 不能和 Model 间接通信,而 Vue 提供了 $refs 这个属性,让 Model 能够间接操作 View,违反了这一规定,所以说 Vue 没有齐全遵循 MVVM。

$nextTick 是什么?

Vue 实现响应式并不是在数据产生后立刻更新 DOM,应用 vm.$nextTick 是在下次 DOM 更新循环完结之后立刻执行提早回调。在批改数据之后应用,则 能够在回调中获取更新后的 DOM

那 vue 中是如何检测数组变动的呢?

数组就是应用 object.defineProperty 从新定义数组的每一项,那能引起数组变动的办法咱们都是晓得的, pop push shift unshift splice sort reverse 这七种,只有这些办法执行改了数组内容,我就更新内容就好了,是不是很好了解。

  1. 是用来函数劫持的形式,重写了数组办法,具体呢就是更改了数组的原型,更改成本人的,用户调数组的一些办法的时候,走的就是本人的办法,而后告诉视图去更新。
  2. 数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象能力进行观测,观测过的也不会进行观测)

vue3:改用 proxy,可间接监听对象数组的变动。

vue 如何监听对象或者数组某个属性的变动

当在我的项目中间接设置数组的某一项的值,或者间接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为 Object.defineProperty()限度,监听不到变动。

解决形式:

  • this.$set(你要扭转的数组 / 对象,你要扭转的地位 /key,你要改成什么 value)
this.$set(this.arr, 0, "OBKoro1"); // 扭转数组 this.$set(this.obj, "c", "OBKoro1"); // 扭转对象
  • 调用以下几个数组的办法
splice()、push()、pop()、shift()、unshift()、sort()、reverse()

vue 源码里缓存了 array 的原型链,而后重写了这几个办法,触发这几个办法的时候会 observer 数据,意思是应用这些办法不必再进行额定的操作,视图主动进行更新。举荐应用 splice 办法会比拟好自定义, 因为 splice 能够在数组的任何地位进行删除 / 增加操作

vm.$set 的实现原理是:

  • 如果指标是数组,间接应用数组的 splice 办法触发相应式;
  • 如果指标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决(defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法)

v-model 能够被用在自定义组件上吗?如果能够,如何应用?

能够。v-model 实际上是一个语法糖,如:

<input v-model="searchText">

实际上相当于:

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

用在自定义组件上也是同理:

<custom-input v-model="searchText">

相当于:

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

显然,custom-input 与父组件的交互如下:

  1. 父组件将 searchText 变量传入 custom-input 组件,应用的 prop 名为value
  2. custom-input 组件向父组件传出名为 input 的事件,父组件将接管到的值赋值给searchText

所以,custom-input 组件的实现应该相似于这样:

Vue.component('custom-input', {props: ['value'],
  template: `    <input      v-bind:value="value"      v-on:input="$emit('input', $event.target.value)"    >  `
})

Vue 的基本原理

当一个 Vue 实例创立时,Vue 会遍历 data 中的属性,用 Object.defineProperty(vue3.0 应用 proxy)将它们转为 getter/setter,并且在外部追踪相干依赖,在属性被拜访和批改时告诉变动。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会告诉 watcher 从新计算,从而以致它关联的组件得以更新。

形容下 Vue 自定义指令

在 Vue2.0 中,代码复用和形象的次要模式是组件。然而,有的状况下,你依然须要对一般 DOM 元素进行底层操作,这时候就会用到自定义指令。
个别须要对 DOM 元素进行底层操作时应用,尽量只用来操作 DOM 展现,不批改外部的值。当应用自定义指令间接批改 value 值时绑定 v -model 的值也不会同步更新;如必须批改能够在自定义指令中应用 keydown 事件,在 vue 组件中应用 change 事件,回调中批改 vue 数据;

(1)自定义指令根本内容

  • 全局定义:Vue.directive("focus",{})
  • 部分定义:directives:{focus:{}}
  • 钩子函数:指令定义对象提供钩子函数

    o bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。

    o inSerted:被绑定元素插入父节点时调用(仅保障父节点存在,但不肯定已被插入文档中)。

    o update:所在组件的 VNode 更新时调用,然而可能产生在其子 VNode 更新之前调用。指令的值可能产生了扭转,也可能没有。然而能够通过比拟更新前后的值来疏忽不必要的模板更新。

    o ComponentUpdate:指令所在组件的 VNode 及其子 VNode 全副更新后调用。

    o unbind:只调用一次,指令与元素解绑时调用。

  • 钩子函数参数
    o el:绑定元素

    o bing:指令外围对象,形容指令全副信息属性

    o name

    o value

    o oldValue

    o expression

    o arg

    o modifers

    o vnode 虚构节点

    o oldVnode:上一个虚构节点(更新钩子函数中才有用)

(2)应用场景

  • 一般 DOM 元素进行底层操作的时候,能够应用自定义指令
  • 自定义指令是用来操作 DOM 的。只管 Vue 推崇数据驱动视图的理念,但并非所有状况都适宜数据驱动。自定义指令就是一种无效的补充和扩大,不仅可用于定义任何的 DOM 操作,并且是可复用的。

(3)应用案例

高级利用:

  • 鼠标聚焦
  • 下拉菜单
  • 绝对工夫转换
  • 滚动动画

高级利用:

  • 自定义指令实现图片懒加载
  • 自定义指令集成第三方插件

Vue 为什么没有相似于 React 中 shouldComponentUpdate 的生命周期?

考点: Vue 的变动侦测原理

前置常识: 依赖收集、虚构 DOM、响应式零碎

根本原因是 Vue 与 React 的变动侦测形式有所不同

React 是 pull 的形式侦测变动, 当 React 晓得发生变化后, 会应用 Virtual Dom Diff 进行差别检测, 然而很多组件实际上是必定不会发生变化的, 这个时候须要用 shouldComponentUpdate 进行手动操作来缩小 diff, 从而进步程序整体的性能.

Vue 是 pull+push 的形式侦测变动的, 在一开始就晓得那个组件产生了变动, 因而在 push 的阶段并不需要手动管制 diff, 而组件外部采纳的 diff 形式实际上是能够引入相似于 shouldComponentUpdate 相干生命周期的, 然而通常正当大小的组件不会有适量的 diff, 手动优化的价值无限, 因而目前 Vue 并没有思考引入 shouldComponentUpdate 这种手动优化的生命周期.

父子组件生命周期调用程序(简略)

渲染程序:先父后子,实现程序:先子后父

更新程序:父更新导致子更新,子更新实现后父

销毁程序:先父后子,实现程序:先子后父

退出移动版