虚构DOM真的比实在DOM性能好吗
- 首次渲染大量DOM时,因为多了一层虚构DOM的计算,会比innerHTML插入慢。
- 正如它能保障性能上限,在实在DOM操作的时候进行针对性的优化时,还是更快的。
MVVM的优缺点?
长处:
- 拆散视图(View)和模型(Model),升高代码耦合,提⾼视图或者逻辑的重⽤性: ⽐如视图(View)能够独⽴于Model变动和批改,⼀个ViewModel能够绑定不同的"View"上,当View变动的时候Model不能够不变,当Model变动的时候View也能够不变。你能够把⼀些视图逻辑放在⼀个ViewModel⾥⾯,让很多view重⽤这段视图逻辑
- 提⾼可测试性: ViewModel的存在能够帮忙开发者更好地编写测试代码
- ⾃动更新dom: 利⽤双向绑定,数据更新后视图⾃动更新,让开发者从繁琐的⼿动dom中解放
毛病:
- Bug很难被调试: 因为使⽤双向绑定的模式,当你看到界⾯异样了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得⼀个地位的Bug被疾速传递到别的地位,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的申明是指令式地写在View的模版当中的,这些内容是没方法去打断点debug的
- ⼀个⼤的模块中model也会很⼤,尽管使⽤⽅便了也很容易保障了数据的⼀致性,过后⻓期持有,不开释内存就造成了破费更多的内存
- 对于⼤型的图形应⽤程序,视图状态较多,ViewModel的构建和保护的老本都会⽐较⾼。
Vue模版编译原理晓得吗,能简略说一下吗?
简略说,Vue的编译过程就是将template
转化为render
函数的过程。会经验以下阶段:
- 生成AST树
- 优化
- codegen
首先解析模版,生成AST语法树
(一种用JavaScript对象的模式来形容整个模板)。 应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的DOM也不会变动。那么优化过程就是深度遍历AST树,依照相干条件对树节点进行标记。这些被标记的节点(动态节点)咱们就能够跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是将优化后的AST树转换为可执行的代码
。
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。
computed和watch有什么区别?
computed:
computed
是计算属性,也就是计算值,它更多用于计算值的场景computed
具备缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值扭转之后,下一次获取computed的值时才会从新调用对应的getter来计算computed
实用于计算比拟耗费性能的计算场景
watch:
- 更多的是「察看」的作用,相似于某些数据的监听回调,用于察看
props
$emit
或者本组件的值,当数据变动时来执行回调进行后续操作 - 无缓存性,页面从新渲染时值不变动也会执行
小结:
- 当咱们要进行数值计算,而且依赖于其余数据,那么把这个数据设计为computed
- 如果你须要在某个数据变动时做一些事件,应用watch来察看这个数据变动
$nextTick 是什么?
Vue 实现响应式并不是在数据产生后立刻更新 DOM,应用 vm.$nextTick
是在下次 DOM 更新循环完结之后立刻执行提早回调。在批改数据之后应用,则能够在回调中获取更新后的 DOM。
参考 前端进阶面试题具体解答
diff算法
<details open=""><summary>答案</summary>
<p>
</p><p>工夫复杂度: 个树的齐全 diff
算法是一个工夫复杂度为 O(n*3)
,vue进行优化转化成 O(n)
。</p>
<p>了解:</p>
<ul>
<li>
<p>最小量更新, key
很重要。这个能够是这个节点的惟一标识,通知 diff
算法,在更改前后它们是同一个DOM节点</p>
<ul>
<li>扩大 v-for
为什么要有 key
,没有 key
会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改DOM),加 key
只会挪动缩小操作DOM。</li>
</ul>
</li>
<li>
<p>只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。</p>
</li>
<li>
<p>只进行同层比拟,不会进行跨层比拟。</p>
</li>
</ul>
<p>diff算法的优化策略:四种命中查找,四个指针</p>
<ol>
<li>
<p>旧前与新前(先比结尾,后插入和删除节点的这种状况)</p>
</li>
<li>
<p>旧后与新后(比结尾,前插入或删除的状况)</p>
</li>
<li>
<p>旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)</p>
</li>
<li>
<p>旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)</p>
</li>
</ol>
<p></p>
</details>
--- 问完下面这些如果都能很分明的话,根本O了 ---
以下的这些简略的概念,你必定也是没有问题的啦
vue 中应用了哪些设计模式
1.工厂模式 - 传入参数即可创立实例
虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode
2.单例模式 - 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉
3.公布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.装璜模式: (@装璜器的用法)
6.策略模式 策略模式指对象有某个行为,然而在不同的场景中,该行为有不同的实现计划-比方选项的合并策略
vue的长处
轻量级框架:只关注视图层,是一个构建数据的视图汇合,大小只有几十kb;
简略易学:国人开发,中文文档,不存在语言障碍 ,易于了解和学习;
双向数据绑定:保留了angular的特点,在数据操作方面更为简略;
组件化:保留了react的长处,实现了html的封装和重用,在构建单页面利用方面有着独特的劣势;
视图,数据,构造拆散:使数据的更改更为简略,不须要进行逻辑代码的批改,只须要操作数据就能实现相干操作;
虚构DOM:dom操作是十分消耗性能的,不再应用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另一种形式;
运行速度更快:相比拟与react而言,同样是操作虚构dom,就性能而言,vue存在很大的劣势。
Vue中封装的数组办法有哪些,其如何实现页面更新
在Vue中,对响应式解决利用的是Object.defineProperty对数据进行拦挡,而这个办法并不能监听到数组外部变动,数组长度变动,数组的截取变动等,所以须要对这些操作进行hack,让Vue能监听到其中的变动。 那Vue是如何实现让这些数组办法实现元素的实时更新的呢,上面是Vue中对这些办法的封装:
// 缓存数组原型const arrayProto = Array.prototype;// 实现 arrayMethods.__proto__ === Array.prototypeexport 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。
Vue 修饰符有哪些
事件修饰符
- .stop 阻止事件持续流传
- .prevent 阻止标签默认行为
- .capture 应用事件捕捉模式,即元素本身触发的事件先在此处解决,而后才交由外部元素进行解决
- .self 只当在 event.target 是以后元素本身时触发处理函数
- .once 事件将只会触发一次
- .passive 通知浏览器你不想阻止事件的默认行为
v-model 的修饰符
- .lazy 通过这个修饰符,转变为在 change 事件再同步
- .number 主动将用户的输出值转化为数值类型
- .trim 主动过滤用户输出的首尾空格
键盘事件的修饰符
- .enter
- .tab
- .delete (捕捉“删除”和“退格”键)
- .esc
- .space
- .up
- .down
- .left
- .right
零碎润饰键
- .ctrl
- .alt
- .shift
- .meta
鼠标按钮修饰符
- .left
- .right
- .middle
说一下Vue的生命周期
Vue 实例有⼀个残缺的⽣命周期,也就是从开始创立、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。
- beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到data、computed、watch、methods上的办法和数据。
- created(创立后) :实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到
$el
属性。 - beforeMount(挂载前):在挂载开始之前被调用,相干的render函数首次被调用。实例已实现以下的配置:编译模板,把data外面的数据和模板生成html。此时还没有挂载html到页面上。
- mounted(挂载后):在el被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的html内容替换el属性指向的DOM对象。实现模板中的html渲染到html 页面中。此过程中进行ajax交互。
- beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。
- updated(更新后) :在因为数据更改导致的虚构DOM从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM曾经更新,所以能够执行依赖于DOM的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,
this
仍能获取到实例。 - destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
另外还有 keep-alive
独有的生命周期,别离为 activated
和 deactivated
。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated
钩子函数,命中缓存渲染后会执行 activated
钩子函数。
说说你对 proxy 的了解,Proxy 相比于 defineProperty 的劣势
Object.defineProperty()
的问题次要有三个:
- 不能监听数组的变动 :无奈监控到数组下标的变动,导致通过数组下标增加元素,不能实时响应
- 必须遍历对象的每个属性 :只能劫持对象的属性,从而须要对每个对象,每个属性进行遍历,如果属性值是对象,还须要深度遍历。
Proxy
能够劫持整个对象,并返回一个新的对象 - 必须深层遍历嵌套的对象
Proxy的劣势如下:
- 针对对象: 针对整个对象,而不是对象的某个属性 ,所以也就不须要对
keys
进行遍历 - 反对数组:
Proxy
不须要对数组的办法进行重载,省去了泛滥 hack,缩小代码量等于缩小了保护老本,而且规范的就是最好的 Proxy
的第二个参数能够有13
种拦挡方:不限于apply
、ownKeys
、deleteProperty
、has
等等是Object.defineProperty
不具备的Proxy
返回的是一个新对象,咱们能够只操作新的对象达到目标,而Object.defineProperty
只能遍历对象属性间接批改Proxy
作为新规范将受到浏览器厂商重点继续的性能优化,也就是传说中的新规范的性能红利
proxy具体应用点击查看(opens new window)
Object.defineProperty的劣势如下:
兼容性好,反对IE9
,而Proxy
的存在浏览器兼容性问题,而且无奈用polyfill
磨平
defineProperty的属性值有哪些
Object.defineProperty(obj, prop, descriptor)// obj 要定义属性的对象// prop 要定义或批改的属性的名称// descriptor 要定义或批改的属性描述符Object.defineProperty(obj,"name",{ value:"poetry", // 初始值 writable:true, // 该属性是否可写入 enumerable:true, // 该属性是否可被遍历失去(for...in, Object.keys等) configurable:true, // 定该属性是否可被删除,且除writable外的其余描述符是否可被批改 get: function() {}, set: function(newVal) {}})
相干代码如下
import { mutableHandlers } from "./baseHandlers"; // 代理相干逻辑import { isObject } from "./util"; // 工具办法export function reactive(target) { // 依据不同参数创立不同响应式对象 return createReactiveObject(target, mutableHandlers);}function createReactiveObject(target, baseHandler) { if (!isObject(target)) { return target; } const observed = new Proxy(target, baseHandler); return observed;}const get = createGetter();const set = createSetter();function createGetter() { return function get(target, key, receiver) { // 对获取的值进行喷射 const res = Reflect.get(target, key, receiver); console.log("属性获取", key); if (isObject(res)) { // 如果获取的值是对象类型,则返回以后对象的代理对象 return reactive(res); } return res; };}function createSetter() { return function set(target, key, value, receiver) { const oldValue = target[key]; const hadKey = hasOwn(target, key); const result = Reflect.set(target, key, value, receiver); if (!hadKey) { console.log("属性新增", key, value); } else if (hasChanged(value, oldValue)) { console.log("属性值被批改", key, value); } return result; };}export const mutableHandlers = { get, // 当获取属性时调用此办法 set, // 当批改属性时调用此办法};
Proxy
只会代理对象的第一层,那么Vue3
又是怎么解决这个问题的呢?
判断以后Reflect.get的
返回值是否为Object
,如果是则再通过reactive
办法做代理, 这样就实现了深度观测。
监测数组的时候可能触发屡次get/set,那么如何避免触发屡次呢?
咱们能够判断key
是否为以后被代理对象target
本身属性,也能够判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger
Vue生命周期钩子是如何实现的
vue
的生命周期钩子就是回调函数而已,当创立组件实例的过程中会调用对应的钩子办法- 外部会对钩子函数进行解决,将钩子函数保护成数组的模式
Vue
的生命周期钩子外围实现是利用公布订阅模式先把用户传入的的生命周期钩子订阅好(外部采纳数组的形式存储)而后在创立组件实例的过程中会一次执行对应的钩子办法(公布)
<script> // Vue.options 中会寄存所有全局属性 // 会用本身的 + Vue.options 中的属性进行合并 // Vue.mixin({ // beforeCreate() { // console.log('before 0') // }, // }) debugger; const vm = new Vue({ el: '#app', beforeCreate: [ function() { console.log('before 1') }, function() { console.log('before 2') } ] }); console.log(vm);</script>
相干代码如下
export function callHook(vm, hook) { // 顺次执行生命周期对应的办法 const handlers = vm.$options[hook]; if (handlers) { for (let i = 0; i < handlers.length; i++) { handlers[i].call(vm); //生命周期外面的this指向以后实例 } }}// 调用的时候Vue.prototype._init = function (options) { const vm = this; vm.$options = mergeOptions(vm.constructor.options, options); callHook(vm, "beforeCreate"); //初始化数据之前 // 初始化状态 initState(vm); callHook(vm, "created"); //初始化数据之后 if (vm.$options.el) { vm.$mount(vm.$options.el); }};// 销毁实例实现Vue.prototype.$destory = function() { // 触发钩子 callHook(vm, 'beforeDestory') // 本身及子节点 remove() // 删除依赖 watcher.teardown() // 删除监听 vm.$off() // 触发钩子 callHook(vm, 'destoryed')}
原理流程图
vue3中 watch、watchEffect区别
watch
是惰性执行,也就是只有监听的值发生变化的时候才会执行,然而watchEffect
不同,每次代码加载watchEffect
都会执行(疏忽watch
第三个参数的配置,如果批改配置项也能够实现立刻执行)watch
须要传递监听的对象,watchEffect
不须要watch
只能监听响应式数据:ref
定义的属性和reactive
定义的对象,如果间接监听reactive
定义对象中的属性是不容许的(会报正告),除非应用函数转换一下。其实就是官网上说的监听一个getter
watchEffect
如果监听reactive
定义的对象是不起作用的,只能监听对象中的属性
看一下watchEffect
的代码
<template><div> 请输出firstName: <input type="text" v-model="firstName"></div><div> 请输出lastName: <input type="text" v-model="lastName"></div><div> 请输出obj.text: <input type="text" v-model="obj.text"></div> <div> 【obj.text】 {{obj.text}} </div></template><script>import {ref, reactive, watch, watchEffect} from 'vue'export default { name: "HelloWorld", props: { msg: String, }, setup(props,content){ let firstName = ref('') let lastName = ref('') let obj= reactive({ text:'hello' }) watchEffect(()=>{ console.log('触发了watchEffect'); console.log(`组合后的名称为:${firstName.value}${lastName.value}`) }) return{ obj, firstName, lastName } }};</script>
革新一下代码
watchEffect(()=>{ console.log('触发了watchEffect'); // 这里咱们不应用firstName.value/lastName.value ,相当于是监控整个ref,对应第四点下面的论断 console.log(`组合后的名称为:${firstName}${lastName}`)})
watchEffect(()=>{ console.log('触发了watchEffect'); console.log(obj);})
略微革新一下
let obj = reactive({ text:'hello'})watchEffect(()=>{ console.log('触发了watchEffect'); console.log(obj.text);})
再看一下watch的代码,验证一下
let obj= reactive({ text:'hello'})// watch是惰性执行, 默认初始化之后不会执行,只有值有变动才会触发,可通过配置参数实现默认执行watch(obj, (newValue, oldValue) => { // 回调函数 console.log('触发监控更新了new', newValue); console.log('触发监控更新了old', oldValue);},{ // 配置immediate参数,立刻执行,以及深层次监听 immediate: true, deep: true})
- 监控整个
reactive
对象,从下面的图能够看到deep
理论默认是开启的,就算咱们设置为false
也还是有效。而且旧值获取不到。 - 要获取旧值则须要监控对象的属性,也就是监听一个
getter
,看下图
总结
- 如果定义了
reactive
的数据,想去应用watch
监听数据扭转,则无奈正确获取旧值,并且deep
属性配置有效,主动强制开启了深层次监听。 - 如果应用
ref
初始化一个对象或者数组类型的数据,会被主动转成reactive
的实现形式,生成proxy
代理对象。也会变得无奈正确取旧值。 - 用任何形式生成的数据,如果接管的变量是一个
proxy
代理对象,就都会导致watch
这个对象时,watch
回调里无奈正确获取旧值。 - 所以当大家应用
watch
监听对象时,如果在不须要应用旧值的状况,能够失常监听对象没关系;然而如果当监听扭转函数外面须要用到旧值时,只能监听 对象.xxx`属性 的形式才行
watch和watchEffect异同总结
体验
watchEffect
立刻运行一个函数,而后被动地追踪它的依赖,当这些依赖扭转时从新执行该函数
const count = ref(0)watchEffect(() => console.log(count.value))// -> logs 0count.value++// -> logs 1
watch
侦测一个或多个响应式数据源并在数据源变动时调用一个回调函数
const state = reactive({ count: 0 })watch( () => state.count, (count, prevCount) => { /* ... */ })
答复范例
watchEffect
立刻运行一个函数,而后被动地追踪它的依赖,当这些依赖扭转时从新执行该函数。watch
侦测一个或多个响应式数据源并在数据源变动时调用一个回调函数watchEffect(effect)
是一种非凡watch
,传入的函数既是依赖收集的数据源,也是回调函数。如果咱们不关怀响应式数据变动前后的值,只是想拿这些数据做些事件,那么watchEffect
就是咱们须要的。watch
更底层,能够接管多种数据源,包含用于依赖收集的getter
函数,因而它齐全能够实现watchEffect
的性能,同时因为能够指定getter
函数,依赖能够管制的更准确,还能获取数据变动前后的值,因而如果须要这些时咱们会应用watch
watchEffect
在应用时,传入的函数会立即执行一次。watch
默认状况下并不会执行回调函数,除非咱们手动设置immediate
选项- 从实现上来说,
watchEffect(fn)
相当于watch(fn,fn,{immediate:true})
watchEffect
定义如下
export function watchEffect( effect: WatchEffect, options?: WatchOptionsBase): WatchStopHandle { return doWatch(effect, null, options)}
watch
定义如下
export function watch<T = any, Immediate extends Readonly<boolean> = false>( source: T | WatchSource<T>, cb: any, options?: WatchOptions<Immediate>): WatchStopHandle { return doWatch(source as any, cb, options)}
很显著watchEffect
就是一种非凡的watch
实现。
Vue2.x 响应式数据原理
整体思路是数据劫持+观察者模式
对象外部通过 defineReactive
办法,应用 Object.defineProperty
来劫持各个属性的 setter
、getter
(只会劫持曾经存在的属性),数组则是通过重写数组7个办法
来实现。当页面应用对应属性时,每个属性都领有本人的 dep
属性,寄存他所依赖的 watcher
(依赖收集),当属性变动后会告诉本人对应的 watcher
去更新(派发更新)
Object.defineProperty根本应用
function observer(value) { // proxy reflect if (typeof value === 'object' && typeof value !== null) for (let key in value) { defineReactive(value, key, value[key]); }}function defineReactive(obj, key, value) { observer(value); Object.defineProperty(obj, key, { get() { // 收集对应的key 在哪个办法(组件)中被应用 return value; }, set(newValue) { if (newValue !== value) { observer(newValue); value = newValue; // 让key对应的办法(组件从新渲染)从新执行 } } })}let obj1 = { school: { name: 'poetry', age: 20 } };observer(obj1);console.log(obj1)
源码剖析
class Observer { // 观测值 constructor(value) { this.walk(value); } walk(data) { // 对象上的所有属性顺次进行观测 let keys = Object.keys(data); for (let i = 0; i < keys.length; i++) { let key = keys[i]; let value = data[key]; defineReactive(data, key, value); } }}// Object.defineProperty数据劫持外围 兼容性在ie9以及以上function defineReactive(data, key, value) { observe(value); // 递归要害 // --如果value还是一个对象会持续走一遍odefineReactive 层层遍历始终到value不是对象才进行 // 思考?如果Vue数据嵌套层级过深 >>性能会受影响 Object.defineProperty(data, key, { get() { console.log("获取值"); //须要做依赖收集过程 这里代码没写进去 return value; }, set(newValue) { if (newValue === value) return; console.log("设置值"); //须要做派发更新过程 这里代码没写进去 value = newValue; }, });}export function observe(value) { // 如果传过来的是对象或者数组 进行属性劫持 if ( Object.prototype.toString.call(value) === "[object Object]" || Array.isArray(value) ) { return new Observer(value); }}
说一说你对vue响应式了解答复范例
- 所谓数据响应式就是可能使数据变动能够被检测并对这种变动做出响应的机制
MVVM
框架中要解决的一个外围问题是连贯数据层和视图层,通过数据驱动利用,数据变动,视图更新,要做到这点的就须要对数据做响应式解决,这样一旦数据发生变化就能够立刻做出更新解决- 以
vue
为例阐明,通过数据响应式加上虚构DOM
和patch
算法,开发人员只须要操作数据,关怀业务,齐全不必接触繁琐的DOM操作,从而大大晋升开发效率,升高开发难度 vue2
中的数据响应式会依据数据类型来做不同解决,如果是 对象则采纳Object.defineProperty()
的形式定义数据拦挡,当数据被拜访或发生变化时,咱们感知并作出响应;如果是数组则通过笼罩数组对象原型的7个变更办法 ,使这些办法能够额定的做更新告诉,从而作出响应。这种机制很好的解决了数据响应化的问题,但在理论应用中也存在一些毛病:比方初始化时的递归遍历会造成性能损失;新增或删除属性时须要用户应用Vue.set/delete
这样非凡的api
能力失效;对于es6
中新产生的Map
、Set
这些数据结构不反对等问题- 为了解决这些问题,
vue3
从新编写了这一部分的实现:利用ES6
的Proxy
代理要响应化的数据,它有很多益处,编程体验是统一的,不须要应用非凡api
,初始化性能和内存耗费都失去了大幅改善;另外因为响应化的实现代码抽取为独立的reactivity
包,使得咱们能够更灵便的应用它,第三方的扩大开发起来更加灵便了
Vue 模板编译原理
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)第二步是对 AST 进行动态节点标记,次要用来做虚构DOM的渲染优化(优化器)第三步是 应用 element ASTs 生成 render 函数代码字符串(代码生成器)
Vue 的生命周期办法有哪些 个别在哪一步发申请
beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在以后阶段 data、methods、computed 以及 watch 上的数据和办法都不能被拜访
created 实例曾经创立实现之后被调用。在这一步,实例已实现以下的配置:数据观测(data observer),属性和办法的运算, watch/event 事件回调。这里没有$el,如果非要想与 Dom 进行交互,能够通过 vm.$nextTick 来拜访 Dom
beforeMount 在挂载开始之前被调用:相干的 render 函数首次被调用。
mounted 在挂载实现后产生,在以后阶段,实在的 Dom 挂载结束,数据实现双向绑定,能够拜访到 Dom 节点
beforeUpdate 数据更新时调用,产生在虚构 DOM 从新渲染和打补丁(patch)之前。能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程
updated 产生在更新实现之后,以后阶段组件 Dom 已实现更新。要留神的是防止在此期间更改数据,因为这可能会导致有限循环的更新,该钩子在服务器端渲染期间不被调用。
beforeDestroy 实例销毁之前调用。在这一步,实例依然齐全可用。咱们能够在这时进行善后收尾工作,比方革除计时器。
destroyed Vue 实例销毁后调用。调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
activated keep-alive 专属,组件被激活时调用
deactivated keep-alive 专属,组件被销毁时调用
异步申请在哪一步发动?
能够在钩子函数 created、beforeMount、mounted 中进行异步申请,因为在这三个钩子函数中,data 曾经创立,能够将服务端端返回的数据进行赋值。
如果异步申请不须要依赖 Dom 举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处:
- 能更快获取到服务端数据,缩小页面 loading 工夫;
- ssr 不反对 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
Vue的长处
- 轻量级框架:只关注视图层,是一个构建数据的视图汇合,大小只有几十
kb
; - 简略易学:国人开发,中文文档,不存在语言障碍 ,易于了解和学习;
- 双向数据绑定:保留了
angular
的特点,在数据操作方面更为简略; - 组件化:保留了
react
的长处,实现了html
的封装和重用,在构建单页面利用方面有着独特的劣势; - 视图,数据,构造拆散:使数据的更改更为简略,不须要进行逻辑代码的批改,只须要操作数据就能实现相干操作;
- 虚构DOM:
dom
操作是十分消耗性能的,不再应用原生的dom
操作节点,极大解放dom
操作,但具体操作的还是dom
不过是换了另一种形式; - 运行速度更快:相比拟于
react
而言,同样是操作虚构dom
,就性能而言,vue
存在很大的劣势。
写过自定义指令吗 原理是什么
指令实质上是装璜器,是 vue 对 HTML 元素的扩大,给 HTML 元素减少自定义性能。vue 编译 DOM 时,会找到指令对象,执行指令的相干办法。
自定义指令有五个生命周期(也叫钩子函数),别离是 bind、inserted、update、componentUpdated、unbind
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。2. inserted:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。4. componentUpdated:被绑定元素所在模板实现一次更新周期时调用。5. unbind:只调用一次,指令与元素解绑时调用。
原理
1.在生成 ast 语法树时,遇到指令会给以后元素增加 directives 属性
2.通过 genDirectives 生成指令代码
3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
4.当执行指令对应钩子函数时,调用对应指令定义的办法