易用:简单,易学,上手快灵活:(渐进式)不断繁荣的生态系统,可以在一个库和一套完整框架之间自如伸缩。高效:20kBmin+gzip运行大小;超快虚拟DOM;最省心的优化双向绑定:开发效率高基于组件的代码共享Web项目工程化,增加可读性、可维护性
Vue.js2.0采用数据劫持(Proxy模式)结合发布者-订阅者模式(PubSub模式)的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
Vue.js3.0,放弃了Object.defineProperty,使用更快的ES6原生Proxy(访问对象拦截器,也称代理器)
步骤:
1.需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化2.compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图3.Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:①在自身实例化时往属性订阅器(dep)里面添加自己②自身必须有一个update()方法③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。4.MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化->视图更新;视图交互变化(input)->数据model变更的双向绑定效果。
比如现在需要监控data中,obj.a的变化。Vue中监控对象属性的变化你可以这样:
Object.defineProperty缺陷
1.监控到数组下标的变化时,开销很大。所以Vue.js放弃了下标变化的检测;2.Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象。3.Object.defineProperty需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。而Proxy直接代理对象,不需要遍历操作。4.Object.defineProperty对新增属性需要手动进行Observe。vue2时需要使用vm.$set才能保证新增的属性也是响应式5.Proxy支持13种拦截操作,这是defineProperty所不具有的6.Proxy作为新标准,长远来看,JS引擎会继续优化Proxy,但getter和setter基本不会再有针对性优化
运用场景:
1.当我们需要进行数值计算,并且依赖于其它数据时,应该使用computed,因为可以利用computed的缓存特性,避免每次获取值时,都要重新计算。2.当我们需要在数据变化时执行异步或开销较大的操作时,应该使用watch,使用watch选项允许我们执行异步操作(访问一个API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。3.多个因素影响一个显示,用Computed;一个因素的变化影响多个其他因素、显示,用Watch;
1.让我们不用直接操作DOM元素,只操作数据便可以重新渲染页面2.虚拟dom是为了解决浏览器性能问题而被设计出来的当操作数据时,将改变的dom元素缓存起来,都计算完后再通过比较映射到真实的dom树上3.diff算法比较新旧虚拟dom。如果节点类型相同,则比较数据,修改数据;如果节点不同,直接干掉节点及所有子节点,插入新的节点;如果给每个节点都设置了唯一的key,就可以准确的找到需要改变的内容,否则就会出现修改一个地方导致其他地方都改变的情况。比如A-B-C-D,我要插入新节点A-B-M-C-D,实际上改变的了C和D。但是设置了key,就可以准确的找到BC并插入
1.具备跨平台的优势2.操作DOM慢,js运行效率高。我们可以将DOM对比操作放在JS层,提高效率。3.提升渲染性能
1).stop:等同于JavaScript中的event.stopPropagation(),防止事件冒泡;2).prevent:等同于JavaScript中的event.preventDefault(),防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);3).capture:当元素发生冒泡时,先触发带有该修饰符的元素。若有多个该修饰符,则由外而内触发。如p1中嵌套p2中嵌套p3.capture中嵌套p4,那么执行顺序为:p3=》p4=》p2=》p14).self:只会触发自己范围内的事件,不包含子元素;5).once:只会触发一次。
v-show仅仅控制元素的显示方式,将display属性在block和none来回切换;而v-if会控制这个DOM节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。
作用在表单元素上v-model="message"等同于v-bind:value=“message”v-on:input="message=event.target.value"作用在组件上,本质是一个父子组件通信的语法糖,通过prop和.emit实现,等同于:value="message"@input="$emit('input',$event.target.value)"
JavaScript中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。而在Vue中,我们更多的是想要复用组件,那就需要每个组件都有自己的数据,这样组件之间才不会相互干扰。所以组件的数据不能写成对象的形式,而是要写成函数的形式。数据以函数返回值的形式定义,这样当我们每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有自己的私有数据空间,它们各自维护自己的数据,不会干扰其他组件的正常运行。
1.调用parse方法将template转化为ast(抽象语法树,abstractsyntaxtree)2.对静态节点做优化。如果为静态节点,他们生成的DOM永远不会改变,这对运行时模板更新起到了极大的优化作用。3.生成渲染函数.渲染的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名,子节点,文本等等)
调用parse方法将template转化为ast(抽象语法树,abstractsyntaxtree)对静态节点做优化。如果为静态节点,他们生成的DOM永远不会改变,这对运行时模板更新起到了极大的优化作用。生成渲染函数.渲染的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名,子节点,文本等等)
sass是一种CSS预编译语言安装和使用步骤如下。
1.用npm安装加载程序(sass-loader、css-loader等加载程序)。2.在webpack.config.js中配置sass加载程序。
Vue.js提供了一个v-cloak指令,该指令一直保持在元素上,直到关联实例结束编译。当和CSS一起使用时,这个指令可以隐藏未编译的标签,直到实例编译结束。用法如下
在开发业务时,经常会岀现异步获取数据的情况,有时数据层次比较深,如以下代码:,可以使用vm.$set手动定义一层数据:
Vue在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。换句话说,只要观察到数据变化,就会自动开启一个队列,并缓冲在同一个事件循环中发生的所以数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。
1.vue用异步队列的方式来控制DOM更新和nextTick回调先后执行2.microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
因为组件是可以复用的,JS里对象是引用关系,如果组件data是一个对象,那么子组件中的data属性值会互相污染。所以一个组件的data选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。
由于v-for的优先级比v-if高,所以导致每循环一次就会去v-if一次,而v-if是通过创建和销毁dom元素来控制元素的显示与隐藏,所以就会不停的去创建和销毁元素,造成页面卡顿,性能下降。
解决办法:
1.在v-for的外层或内层包裹一个元素来使用v-if2.用computed处理
1.v-model多用于表单元素实现双向数据绑定(同angular中的ng-model)2.v-bind动态绑定作用:及时对页面的数据进行更改3.v-on:click给标签绑定函数,可以缩写为@,例如绑定一个点击函数函数必须写在methods里面4.v-for格式:v-for=“字段名in(of)数组json”循环数组或json(同angular中的ng-repeat)5.v-show显示内容(同angular中的ng-show)6.v-hide隐藏内容(同angular中的ng-hide)7.v-if显示与隐藏(dom元素的删除添加同angular中的ng-if默认值为false)8.v-else-if必须和v-if连用9.v-else必须和v-if连用不能单独使用否则报错模板编译错误10.v-text解析文本11.v-html解析html标签12.v-bind:class三种绑定方法1、对象型‘{red:isred}’2、三元型‘isred“red”:“blue”’3、数组型‘[{red:“isred”},{blue:“isblue”}]’13.v-once进入页面时只渲染一次不在进行渲染14.v-cloak防止闪烁15.v-pre把标签内部的元素原位输出
1.父传子:子组件通过props[‘xx’]来接收父组件传递的属性xx的值2.子传父:子组件通过this.$emit(‘fnName’,value)来传递,父组件通过接收fnName事件方法来接收回调3.其他方式:通过创建一个bus,进行传值4.使用Vuex
当Vue.js用v-for正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM
Object.defineProperty本身有一定的监控到数组下标变化的能力,但是在Vue中,从性能/体验的性价比考虑,尤大大就弃用了这个特性(Vue为什么不能检测数组变动)。为了解决这个问题,经过vue内部处理后可以使用以下几种方法来监听数组
push();pop();shift();unshift();splice();sort();reverse();
由于只针对了以上7种方法进行了hack处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。
Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue2.x里,是通过递归+遍历data对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。Proxy可以劫持整个对象,并返回一个新的对象。Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
JS运行机制
JS执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:
所有同步任务都在主线程上执行,形成一个执行栈(executioncontextstack)。主线程之外,还存在一个"任务队列"(taskqueue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。主线程不断重复上面的第三步。
常见的macrotask有setTimeout、MessageChannel、postMessage、setImmediate
常见的microtask有MutationObsever和Promise.then
异步更新队列
可能你还没有注意到,Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue刷新队列并执行实际(已去重的)工作。Vue在内部对异步队列尝试使用原生的Promise.then、MutationObserver和setImmediate,如果执行环境不支持,则会采用setTimeout(fn,0)代替。在vue2.5的源码中,macrotask降级的方案依次是:setImmediate、MessageChannel、setTimeout
vue的nextTick方法的实现原理:
vue用异步队列的方式来控制DOM更新和nextTick回调先后执行microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕考虑兼容问题,vue做了microtask向macrotask的降级方案
Vue事件机制本质上就是一个发布-订阅模式的实现。
一种是全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。第二种:组件内的钩子;第三种:单独路由独享组件
简而言之,就是先转化成AST树,再得到的渲染函数返回VNODE(Vue公司的虚拟DOM节点)详情步骤:
首先,通过编译编译器把模板编译成AST语法树(抽象语法树即源代码的抽象语法结构的树状表现形式),编译是createCompiler的返回值,createCompiler是用以创建编译器的。负责合并选项。然后,AST会经过生成(将AST语法树转化成渲染功能字符串的过程)得到渲染函数,渲染的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名,子节点,文本等等)
答:包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染;使用:简单页面时缓存:不缓存:
相同点:都鼓励组件化,都有’props’的概念,都有自己的构建工具,Reat与Vue只有框架的骨架,其他的功能如路由、状态管理等是框架分离的组件。
不同点:React:数据流单向,语法—JSX,在React中你需要使用setState()方法去更新状态。Vue:数据双向绑定,语法–HTML,state对象并不是必须的,数据由data属性在Vue对象中进行管理。适用于小型应用,但对于对于大型应用而言不太适合。
beforeCreate是newVue()之后触发的第一个钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问。
created在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom。
beforeMount发生在挂载之前,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。
mounted在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作。
beforeUpdate发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
updated发生在更新完成之后,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。
beforeDestroy发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。
destroyed发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
简单来说,diff算法有以下过程
1.同级比较,再比较子节点2.先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)3.比较都有子节点的情况(核心diff)3.递归比较子节点
Vue3.x借鉴了ivi算法和inferno算法
在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。(实际的实现可以结合Vue3.x源码看。)
该算法中还运用了动态规划的思想求解最长递归子序列。
编码阶段
1.尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的2.watcher3.v-if和v-for不能连用4.如果需要使用v-for给每项元素绑定事件时使用事件代理5.SPA页面采用keep-alive缓存组件6.在更多的情况下,使用v-if替代v-show7.key保证唯一8.使用路由懒加载、异步组件9.防抖、节流10.第三方模块按需导入11.长列表滚动到可视区域动态加载12.图片懒加载
SEO优化
1.预渲染2.服务端渲染SSR
打包优化
1.压缩代码2.TreeShaking/ScopeHoisting3.使用cdn加载第三方模块4.多线程打包happypack5.splitChunks抽离公共文件6.sourceMap优化
用户体验
1.骨架屏2.PWA
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
location.hash的值实际就是URL中#后面的东西。
history实际采用了HTML5中提供的API来实现,主要有history.pushState()和history.replaceState()。
SPA(single-pageapplication)仅在Web页面初始化时加载相应的HTML、JavaScript和CSS一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转取而代之的是利用路由机制实现HTML内容的变换,UI与用户的交互,避免页面的重新加载优点:
1、用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染2、基于上面一点,SPA相对对服务器压力小3、前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理
缺点:
1、初次加载耗时多:为实现单页Web应用功能及显示效果,需要在加载页面的时候将JavaScript、CSS统一加载,部分页面按需加载2、前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理3、SEO难度较大:由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势
第一步:在components目录新建你的组件文件(indexPage.vue),script一定要exportdefault{}第二步:在需要用的页面(组件)中导入:importindexPagefrom‘@/components/indexPage.vue’第三步:注入到vue的子组件的components属性上面,components:{indexPage}第四步:在template视图view中使用,例如有indexPage命名,使用的时候则index-page
webpack中提供了require.ensure()来实现按需加载。以前引入路由是通过import这样的方式引入,改为const定义的方式进行引入。不进行页面按需加载引入方式:importhomefrom'../../common/home.vue'进行页面按需加载的引入方式:consthome=r=>require.ensure([],()=>r(require('../../common/home.vue')))
以组件功能命名只负责ui的展示和交互动画,不要在组件里与服务器打交道(获取异步数据等)可复用组件不会因组件使用的位置、场景而变化。尽量减少对外部条件的依赖。
在每一个Vue.js组件中都可以定义各自的CSS、JavaScript代码。如果希望组件内写的CSS只对当前组件起作用,只需要在Style标签添加Scoped属性即可
如果需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用keep-alive组件包裹需要保存的组件。
两个重要属性,include缓存组件名称,exclude不需要缓存的组件名称。
对“src”属性插值将导致404请求错误。应使用v-bind:src(简写:src)格式代替。
加载渲染过程:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted子组件更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated父组件更新过程:父beforeUpdate->父updated销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
1.state=>基本数据2.getters=>从基本数据派生的数据3.mutations=>修改数据,同步4.actions=>修改数据,异步(Action提交的是mutation,而不是直接变更状态)5.modules=>模块化Vuex
1.公共的数据部分可以提升至和他们最近的父组件,由父组件派发2.公共数据可以放到vuex中统一管理,各组件分别获取
1.如果请求来的数据是不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex的state里。
2.如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用,并包装成promise返回,在调用处用asyncawait处理返回的数据。如果不要复用这个请求,那么直接写在vue文件里很方便
actions与mutations作用类似,都是可以对状态进行修改。不同的是actions是异步操作的。
actions是可以调用Mutations里的方法的。
Action提交的是mutation,而不是直接变更状态。Action可以包含任意异步操作
通过children数组:
参数:有to(去的那个路由)、from(离开的路由)、next(一定要用这个函数才能去到下一个路由,如果不用就拦截)最常用就这几种
1、$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
1.$route.path字符串,对应当前路由的路径,总是解析为绝对路径如"/foo/bar"。2.$route.params一个key/value对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。3.$route.query一个key/value对象,表示URL查询参数。例如,对于路径/foouser=1,则有$route.query.user==1,如果没有查询参数,则是个空对象4.$route.hash当前路由的hash值(不带#),如果没有hash值,则为空字符串5.$route.fullPath完成解析后的URL,包含查询参数和hash的完整路径。6.$route.matched数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。7.$route.name当前路径名字8.$route.meta路由元信息
2、$router是“路由实例”对象包括了路由的跳转方法,钩子函数等
实例方法:
1)、push
1.字符串this.$router.push('home')2.对象this.$router.push({path:'home'})3.命名的路由this.$router.push({name:'user',params:{userId:123}})4.带查询参数,变成/registerplan=123this.$router.push({path:'register',query:{plan:'123'}})push方法其实和是等同的。注意:push方法的跳转会向history栈添加一个新的记录,当我们点击浏览器的返回按钮时可以看到之前的页面。
2)、go页面路由跳转前进或者后退this.$router.go(-1)//后退
3)、replacepush方法会向history栈添加一个新的记录,而replace方法是替换当前的页面,不会向history栈添加一个新的记录
vue-router模块的router-link组件
把不同路由对应的组件分割成不同的代码块,然后当路由被访问时才加载对应的组件即为路由的懒加载,可以加快项目的加载速度,提高效率
面试官:我难道问不倒这小子了(面试官持续懵逼中)对大家有帮助的话三连呀~持续更新