怎么了解 Vue 的单向数据流
数据总是从父组件传到子组件,子组件没有权力批改父组件传过来的数据,只能申请父组件对原始数据进行批改。这样会 避免从子组件意外扭转父级组件的状态 ,从而导致你的利用的数据流向难以了解
留神 :在子组件间接用 v-model
绑定父组件传过来的 prop
这样是不标准的写法 开发环境会报正告
如果切实要扭转父组件的 prop
值,能够在 data
外面定义一个变量 并用 prop
的值初始化它 之后用$emit
告诉父组件去批改
有两种常见的试图扭转一个 prop 的情景 :
- 这个
prop
用来传递一个初始值;这个子组件接下来心愿将其作为一个本地的prop
数据来应用。 在这种状况下,最好定义一个本地的data
属性并将这个prop
用作其初始值
props: ['initialCounter'],data: function () { return { counter: this.initialCounter }}
- 这个
prop
以一种原始的值传入且须要进行转换。 在这种状况下,最好应用这个prop
的值来定义一个计算属性
props: ['size'],computed: { normalizedSize: function () { return this.size.trim().toLowerCase() }}
Vue中如何检测数组变动
前言
Vue
不能检测到以下数组的变动:
- 当你利用索引间接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你批改数组的长度时,例如:
vm.items.length = newLength
Vue
提供了以下操作方法
// Vue.setVue.set(vm.items, indexOfItem, newValue)// vm.$set,Vue.set的一个别名vm.$set(vm.items, indexOfItem, newValue)// Array.prototype.splicevm.items.splice(indexOfItem, 1, newValue)
剖析
数组思考性能起因没有用defineProperty
对数组的每一项进行拦挡,而是抉择对7
种数组(push
,shift
,pop
,splice
,unshift
,sort
,reverse
)办法进行重写(AOP
切片思维)
所以在 Vue
中批改数组的索引和长度是无奈监控到的。须要通过以上 7
种变异办法批改数组才会触发数组对应的 watcher
进行更新
- 用函数劫持的形式,重写了数组办法,具体呢就是更改了数组的原型,更改成本人的,用户调数组的一些办法的时候,走的就是本人的办法,而后告诉视图去更新
- 数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象能力进行观测,观测过的也不会进行观测)
原理
Vue
将data
中的数组,进行了原型链重写。指向了本人定义的数组原型办法,这样当调用数组api
时,能够告诉依赖更新,如果数组中蕴含着援用类型。会对数组中的援用类型再次进行监控。
手写简版剖析
let oldArray = Object.create(Array.prototype);['shift', 'unshift', 'push', 'pop', 'reverse','sort'].forEach(method => { oldArray[method] = function() { // 这里能够触发页面更新逻辑 console.log('method', method) Array.prototype[method].call(this,...arguments); }});let arr = [1,2,3];arr.__proto__ = oldArray;arr.unshift(4);
源码剖析
// 拿到数组原型拷贝一份const arrayProto = Array.prototype // 而后将arrayMethods继承自数组原型// 这里是面向切片编程思维(AOP)--不毁坏封装的前提下,动静的扩大性能export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]methodsToPatch.forEach(function (method) { // 重写原型办法 const original = arrayProto[method] // 调用原数组的办法 def(arrayMethods, method, function mutator (...args) { // 这里保留原型办法的执行后果 const result = original.apply(this, args) // 这句话是要害 // this代表的就是数据自身 比方数据是{a:[1,2,3]} 那么咱们应用a.push(4) this就是a ob就是a.__ob__ 这个属性就是上段代码减少的 代表的是该数据曾经被响应式察看过了指向Observer实例 const ob = this.__ob__ // 这里的标记就是代表数组有新增操作 let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } // 如果有新增的元素 inserted是一个数组 调用Observer实例的observeArray对数组每一项进行观测 if (inserted) ob.observeArray(inserted) ob.dep.notify() // 当调用数组办法后,手动告诉视图更新 return result }) })this.observeArray(value) // 进行深度监控
vue3
:改用proxy
,可间接监听对象数组的变动
Vue 单页利用与多页利用的区别
概念:
- SPA单页面利用(SinglePage Web Application),指只有一个主页面的利用,一开始只须要加载一次js、css等相干资源。所有内容都蕴含在主页面,对每一个功能模块组件化。单页利用跳转,就是切换相干组件,仅仅刷新部分资源。
- MPA多页面利用 (MultiPage Application),指有多个独立页面的利用,每个页面必须反复加载js、css等相干资源。多页利用跳转,须要整页资源刷新。
$nextTick 原理及作用
Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种利用。
nextTick 的外围是利用了如 Promise 、MutationObserver、setImmediate、setTimeout的原生 JavaScript 办法来模仿对应的微/宏工作的实现,实质是为了利用 JavaScript 的这些异步回调工作队列来实现 Vue 框架中本人的异步回调队列。
nextTick 不仅是 Vue 外部的异步队列的调用办法,同时也容许开发者在理论我的项目中应用这个办法来满足理论利用中对 DOM 更新数据机会的后续逻辑解决
nextTick 是典型的将底层 JavaScript 执行原理利用到具体案例中的示例,引入异步更新队列机制的起因∶
- 如果是同步更新,则屡次对一个或多个属性赋值,会频繁触发 UI/DOM 的渲染,能够缩小一些无用渲染
- 同时因为 VirtualDOM 的引入,每一次状态发生变化后,状态变动的信号会发送给组件,组件外部应用 VirtualDOM 进行计算得出须要更新的具体的 DOM 节点,而后对 DOM 进行更新操作,每次更新状态后的渲染过程须要更多的计算,而这种无用功也将节约更多的性能,所以异步渲染变得更加至关重要
Vue采纳了数据驱动视图的思维,然而在一些状况下,依然须要操作DOM。有时候,可能遇到这样的状况,DOM1的数据产生了变动,而DOM2须要从DOM1中获取数据,那这时就会发现DOM2的视图并没有更新,这时就须要用到了nextTick
了。
因为Vue的DOM操作是异步的,所以,在下面的状况中,就要将DOM2获取数据的操作写在$nextTick
中。
this.$nextTick(() => { // 获取数据的操作...})
所以,在以下状况下,会用到nextTick:
- 在数据变动后执行的某个操作,而这个操作须要应用随数据变动而变动的DOM构造的时候,这个操作就须要办法在
nextTick()
的回调函数中。 - 在vue生命周期中,如果在created()钩子进行DOM操作,也肯定要放在
nextTick()
的回调函数中。
因为在created()钩子函数中,页面的DOM还未渲染,这时候也没方法操作DOM,所以,此时如果想要操作DOM,必须将操作的代码放在nextTick()
的回调函数中。
过滤器的作用,如何实现一个过滤器
依据过滤器的名称,过滤器是用来过滤数据的,在Vue中应用filters
来过滤数据,filters
不会批改数据,而是过滤数据,扭转用户看到的输入(计算属性 computed
,办法 methods
都是通过批改数据来解决数据格式的输入显示)。
应用场景:
- 须要格式化数据的状况,比方须要解决工夫、价格等数据格式的输入 / 显示。
- 比方后端返回一个 年月日的日期字符串,前端须要展现为 多少天前 的数据格式,此时就能够用
fliters
过滤器来解决数据。
过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在插值表达式 {{ }}
和 v-bind
表达式 中,而后放在操作符“ |
”前面进行批示。
例如,在显示金额,给商品价格增加单位:
<li>商品价格:{{item.price | filterPrice}}</li> filters: { filterPrice (price) { return price ? ('¥' + price) : '--' } }
data为什么是一个函数而不是对象
JavaScript中的对象是援用类型的数据,当多个实例援用同一个对象时,只有一个实例对这个对象进行操作,其余实例中的数据也会发生变化。
而在Vue中,更多的是想要复用组件,那就须要每个组件都有本人的数据,这样组件之间才不会互相烦扰。
所以组件的数据不能写成对象的模式,而是要写成函数的模式。数据以函数返回值的模式定义,这样当每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有本人的公有数据空间,它们各自保护本人的数据,不会烦扰其余组件的失常运行。
参考 前端进阶面试题具体解答
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 中 computed 和 watch 有什么区别?
计算属性 computed:
(1)**反对缓存**,只有依赖数据发生变化时,才会从新进行计算函数; (2)计算属性内**不反对异步操作**; (3)计算属性的函数中**都有一个 get**(默认具备,获取计算属性)**和 set**(手动增加,设置计算属性)办法; (4)计算属性是主动监听依赖值的变动,从而动静返回内容。
侦听属性 watch:
(1)**不反对缓存**,只有数据发生变化,就会执行侦听函数; (2)侦听属性内**反对异步操作**; (3)侦听属性的值**能够是一个对象,接管 handler 回调,deep,immediate 三个属性**; (3)监听是一个过程,在监听的值变动时,能够触发一个回调,并**做一些其余事件**。
Vue data 中某一个属性的值产生扭转后,视图会立刻同步执行从新渲染吗?
不会立刻同步执行从新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立刻变动,而是按肯定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只有侦听到数据变动, Vue 将开启一个队列,并缓冲在同一事件循环中产生的所有数据变更。
如果同一个watcher被屡次触发,只会被推入到队列中一次。这种在缓冲时去除反复数据对于防止不必要的计算和 DOM 操作是十分重要的。而后,在下一个的事件循环tick中,Vue 刷新队列并执行理论(已去重的)工作。
action 与 mutation 的区别
mutation
是同步更新,$watch
严格模式下会报错action
是异步操作,能够获取数据后调用mutation
提交最终数据
assets和static的区别
相同点: assets
和 static
两个都是寄存动态资源文件。我的项目中所须要的资源文件图片,字体图标,款式文件等都能够放在这两个文件下,这是相同点
不相同点:assets
中寄存的动态资源文件在我的项目打包时,也就是运行 npm run build
时会将 assets
中搁置的动态资源文件进行打包上传,所谓打包简略点能够了解为压缩体积,代码格式化。而压缩后的动态资源文件最终也都会搁置在 static
文件中跟着 index.html
一起上传至服务器。static
中搁置的动态资源文件就不会要走打包压缩格式化等流程,而是间接进入打包好的目录,间接上传至服务器。因为防止了压缩间接进行上传,在打包时会进步肯定的效率,然而 static
中的资源文件因为没有进行压缩等操作,所以文件的体积也就绝对于 assets
中打包后的文件提交较大点。在服务器中就会占据更大的空间。
倡议: 将我的项目中 template
须要的款式文件js文件等都能够搁置在 assets
中,走打包这一流程。缩小体积。而我的项目中引入的第三方的资源文件如iconfoont.css
等文件能够搁置在 static
中,因为这些引入的第三方文件曾经通过解决,不再须要解决,间接上传。
Vue template 到 render 的过程
vue的模版编译过程次要如下:template -> ast -> render函数
vue 在模版编译版本的码中会执行 compileToFunctions 将template转化为render函数:
// 将模板编译为render函数const { render, staticRenderFns } = compileToFunctions(template,options//省略}, this)
CompileToFunctions中的次要逻辑如下∶ (1)调用parse办法将template转化为ast(形象语法树)
constast = parse(template.trim(), options)
- parse的指标:把tamplate转换为AST树,它是一种用 JavaScript对象的模式来形容整个模板。
- 解析过程:利用正则表达式程序解析模板,当解析到开始标签、闭合标签、文本的时候都会别离执行对应的 回调函数,来达到结构AST树的目标。
AST元素节点总共三种类型:type为1示意一般元素、2为表达式、3为纯文本
(2)对动态节点做优化
optimize(ast,options)
这个过程次要剖析出哪些是动态节点,给其打一个标记,为后续更新渲染能够间接跳过动态节点做优化
深度遍历AST,查看每个子树的节点元素是否为动态节点或者动态节点根。如果为动态节点,他们生成的DOM永远不会扭转,这对运行时模板更新起到了极大的优化作用。
(3)生成代码
const code = generate(ast, options)
generate将ast形象语法树编译成 render字符串并将动态局部放到 staticRenderFns 中,最初通过 new Function(
` render`)
生成render函数。
谈谈对keep-alive的理解
keep-alive 能够实现组件的缓存,当组件切换时不会对以后组件进行卸载。罕用的2个属性
include/exclude ,2个生命周期
activated ,
deactivated
Vue 模板编译原理
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)第二步是对 AST 进行动态节点标记,次要用来做虚构DOM的渲染优化(优化器)第三步是 应用 element ASTs 生成 render 函数代码字符串(代码生成器)
MVVM、MVC、MVP的区别
MVC、MVP 和 MVVM 是三种常见的软件架构设计模式,次要通过拆散关注点的形式来组织代码构造,优化开发效率。
在开发单页面利用时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文件里。页面的渲染、数据的获取,对用户事件的响应所有的应用逻辑都混合在一起,这样在开发简略我的项目时,可能看不出什么问题,如果我的项目变得复杂,那么整个文件就会变得简短、凌乱,这样对我的项目开发和前期的我的项目保护是十分不利的。
(1)MVC
MVC 通过拆散 Model、View 和 Controller 的形式来组织代码构造。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 利用了观察者模式,当 Model 层产生扭转的时候它会告诉无关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它次要负责用户与利用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来实现对 Model 的批改,而后 Model 层再去告诉 View 层更新。
(2)MVVM
MVVM 分为 Model、View、ViewModel:
- Model代表数据模型,数据和业务逻辑都在Model层中定义;
- View代表UI视图,负责数据的展现;
- ViewModel负责监听Model中数据的扭转并且管制视图的更新,解决用户交互操作;
Model和View并无间接关联,而是通过ViewModel来进行分割的,Model和ViewModel之间有着双向数据绑定的分割。因而当Model中的数据扭转时会触发View层的刷新,View中因为用户交互操作而扭转的数据也会在Model中同步。
这种模式实现了 Model和View的数据主动同步,因而开发者只须要专一于数据的保护操作即可,而不须要本人操作DOM。
(3)MVP
MVP 模式与 MVC 惟一不同的在于 Presenter 和 Controller。在 MVC 模式中应用观察者模式,来实现当 Model 层数据发生变化的时候,告诉 View 层的更新。这样 View 层和 Model 层耦合在一起,当我的项目逻辑变得复杂的时候,可能会造成代码的凌乱,并且可能会对代码的复用性造成一些问题。MVP 的模式通过应用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的Controller 只晓得 Model 的接口,因而它没有方法管制 View 层的更新,MVP 模式中,View 层的接口裸露给了 Presenter 因而能够在 Presenter 中将 Model 的变动和 View 的变动绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还蕴含了其余的响应逻辑。
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
对 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
说说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
。
子组件能够间接扭转父组件的数据吗?
子组件不能够间接扭转父组件的数据。这样做次要是为了保护父子组件的单向数据流。每次父级组件产生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果这样做了,Vue 会在浏览器的控制台中收回正告。
Vue提倡单向数据流,即父级 props 的更新会流向子组件,然而反过来则不行。这是为了避免意外的扭转父组件状态,使得利用的数据流变得难以了解,导致数据流凌乱。如果毁坏了单向数据流,当利用简单时,debug 的老本会十分高。
只能通过 $emit
派发一个自定义事件,父组件接管到后,由父组件批改。
虚构DOM的优劣如何?
长处:
- 保障性能上限: 虚构DOM能够通过diff找出最小差别,而后批量进行patch,这种操作尽管比不上手动优化,然而比起粗犷的DOM操作性能要好很多,因而虚构DOM能够保障性能上限
- 无需手动操作DOM: 虚构DOM的diff和patch都是在一次更新中主动进行的,咱们无需手动操作DOM,极大进步开发效率
- 跨平台: 虚构DOM实质上是JavaScript对象,而DOM与平台强相干,相比之下虚构DOM能够进行更不便地跨平台操作,例如服务器渲染、挪动端开发等等
毛病:
- 无奈进行极致优化: 在一些性能要求极高的利用中虚构DOM无奈进行针对性的极致优化,比方VScode采纳间接手动操作DOM的形式进行极其的性能优化