vue整理

219 阅读13分钟

vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。 vue: 

(一)对响应式数据的理解 

当数据改变后,vue会通知到使用该数据的代码。例如:视图渲染中用到了数据,数据改变后,视图会自动更新。 observer文件==》defineReacttive()是用于定义响应式数据的核心函数。新建一个dep对象,与当前数据对应,通过object.defineProperty重新定义对象属性,配置set,get。 响应式机制: 1.通过object.defineProperty替换配置对象属性的set,get方法,实现“拦截”。 2.watcher在执行getter函数时触发数据的get方法,从而建立依赖关系。 3.写入数据时触发数据的set方法,从而借助dep发布通知,进而watcher进行更新 

(二)vue如何检测数组变化 

创建拦截器去覆盖数组原型的方式来追踪变化,重写了数组的push,pop,shift,unshift,splice,sort,reverse 通过执行ob.dep.notify()将当前数组的变更通知给其订阅者 如果浏览器支持_proto_,则直接将当前数据的原型__proto__指向重写后的数组方法对象arrayMethods,如果浏览器不支持__proto__,则直接将arrayMethods上重写的方法直接定义到当前数据对象上;当数据类型为非数组时,继续递归执行数据的监听。 

(三)vue中模板编译原理 

第一步 将模板字符串转换成element ASTs 抽象语法树 (解析器parser)--1.截取字符串 2.对截取之后的字符串做解析 原理:一小段一小段的截取字符串,维护一个stack用来保存DOM深度。每截取到一段标签的开始就push到stack中,当所有字符串都截取完后也就解析出一个完整的AST。 第二步 对AST进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器optimizer) 好处:1.每次重新渲染时不需要为静态节点创建新节点。2.在virtual DOM中patching的过程可以跳过 步骤:(1)用递归的方式将所有节点添加static属性,标记是不是静态节点。 (2)标记所有静态根节点 第三步 使用element ASTs 生成render函数代码字符串(代码生成器code generator) genData genChildren 

(四)生命周期钩子是如何实现的?

 beforeCreate :初始化了部分参数,如果有相同的参数,做了参数合并,执行 beforeCreate ; 

 created :初始化了 Inject 、Provide 、 props 、methods 、data 、computed 和 watch,执行 created ;

 beforeMount :检查是否存在 el 属性,存在的话进行渲染 dom 操作,执行 beforeMount ; 

 mounted :实例化 Watcher ,渲染 dom,执行 mounted ;

 beforeUpdate :在渲染 dom 后,执行了 mounted 钩子后,在数据更新的时候,执行 beforeUpdate ;  

updated :检查当前的 watcher 列表中,是否存在当前要更新数据的 watcher ,如果存在就执行 updated ; 

beforeDestroy :检查是否已经被卸载,如果已经被卸载,就直接 return 出去,否则执行 beforeDestroy ; 

destroyed :把所有有关自己痕迹的地方,都给删除掉; 我主要看与它生命周期有关的代码,我们可以看到,Vue先调用了initLifecycle(vm)、initEvents(vm)、initRender(vm)这三个方法,用于初始化生命周期、事件、渲染函数,这些过程发生在Vue初始化的过程(_init方法)中,并在调用beforeCreate钩子之前。 然后Vue通过callHook (vm: Component, hook: string)方法来去调用钩子函数(hook),它接收vm(Vue实例对象),hook(钩子函数名称)来去执行生命周期函数。在Vue中几乎所有的钩子(errorCaptured除外)函数执行都是通过callHook (vm: Component, hook: string)来调用的。我们来看一下callHook的代码,在/src/core/instance/lifecycle.js下: 它的逻辑也非常简单,根据传入的hook从实例中拿到对应的回调函数数组(在/packages/vue-template-compiler/browser.js下的LIFECYCLE_HOOKS),然后便利执行。 然后在初始化生命周期、事件、渲染函数之后调用了beforeCreate钩子,在这个时候:我们还没有办法获取到data、props等数据。 在调用了beforeCreate钩子之后,Vue调用了initInjections(vm)、initState(vm)、initProvide(vm)这三个方法用于初始化data、props、watcher等等,在这些初始化执行完成之后,调用了created钩子函数,在这个时候:我们已经可以获取到data、props等数据了,但是Vue并没有开始渲染DOM,所以我们还不能够访问DOM(PS:我们可以通过vm.nextTick来访问,在后面的章节我们会详细讲解)。在调用了created钩子之后,Vue开始进行DOM的挂载,执行vm.nextTick来访问,在后面的章节我们会详细讲解)。 在调用了created钩子之后,Vue开始进行DOM的挂载,执行vm.mount(vm.options.el),在VueDOM的挂载就是通过Vue.prototype.options.el),在Vue中DOM的挂载就是通过Vue.prototype.mount这个原型方法来去实现的。Vue.prototype.mount原型方法的声明是在/src/platforms/web/entryruntimewithcompiler.js,我们看一下这个代码的实现:(进行template模板的解析。从上面的代码中可以看出,el不允许被挂载到bodyhtml这样的根标签上面。然后判断是否有render函数>if(!options.render)...,然后判断有没有templatetemplate可以是string类型的idDOM节点。没有的话则解析el作为template。由上面的代码可以看出我们无论是使用单文件组件(.Vue)或是通过eltemplate属性,它最终都会通过render函数的形式来进行整个模板的解析。)由上面的代码可以看出在执行vm.render()之前,调用了callHook(vm,beforeMount),这个时候相关的render函数首次被调用,调用完成之后,执行了callHook(vm,mounted)方法,标记着el被新创建的vm.mount原型方法的声明是在/src/platforms/web/entry-runtime-with-compiler.js,我们看一下这个代码的实现: (进行template模板的解析。从上面的代码中可以看出,el不允许被挂载到body和html这样的根标签上面。然后判断是否有render函数 -> if (!options.render) {...},然后判断有没有template,template可以是string类型的id、DOM节点。没有的话则解析el作为template。由上面的代码可以看出我们无论是使用单文件组件(.Vue)或是通过el、template属性,它最终都会通过render函数的形式来进行整个模板的解析。) 由上面的代码可以看出在执行vm._render()之前,调用了callHook(vm, 'beforeMount'),这个时候相关的 render 函数首次被调用,调用完成之后,执行了callHook(vm, 'mounted')方法,标记着el 被新创建的 vm.el 替换,并被挂载到实例上。 然后就进入了我们页面正常交互的时间,也就是beforeUpdate和updated这两个回调钩子的执行时机。这两个钩子函数是在数据更新的时候进行回调的函数,Vue在/src/core/instance/lifecycle.js文件下有一个_update的原型声明:  

(五)vue.mixin的使用场景和原理 。 

原理:只是对我们在初始化vue实例时传递的配置对象的一个扩展。 我们在执行Vue.mixin方法时传递一个配置对象进去,通过源码我们可以看到这个传递进来的对象最终会和我们在初始化实例也就是new Vue(options)时的这个options合并(通过上面源码中的mergeOptions方法),保存在option上。 使用场景: 当我们需要全局去注入一些methods,filter,hooks时我们可以用mixin来做 

 (五)vue.extend的使用场景和原理 。 使用基础Vue构造器,创建一个“子类”。参数是一个包含组件选项的对象 原理:Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub 实现了sub对vue的继承 使用场景:当我们不需要全局去混入一些配置。extend得到的是一个Vue的子类,也就是构造函数。  

(六)vue.use的使用场景和原理 。 ①:Vue.use是通过initUse这个方法初始化的 ,Vue.use接受一个参数plugin,方法检测了installedPlugins这个数组中是否已经包含想要注册的组件,可知插件只允许被注册一次,二次注册是无效的。 ②:调用toArray将转入的参数转换成数组 ③:把当前Vue对象this插入转化后的数组前 ④:判断plugin中install是否是一个方法,如果是,则传入plugin及转化后的数组;此外,如果plugin本身就是一个方法,则传入转化后的数组,随后执行这个方法,由此可知Vue.use(插件)实际上会调用插件的install方法,并且调用use的时候是可以传参数的。 注:以上还可以知道,vue只会对plugin中的两种情况处理,即要么plugin中有install函数,要么plugin本身就是一个函数。 ⑤:将注册后的插件推进installedPlugins,避免重复注册,返回当前实例,代码执行结束。 区别: mixin是对Vue类的options进行混入。所有Vue的实例对象都会具备混入进来的配置行为。 extend是产生一个继承自Vue类的子类,只会影响这个子类的实例对象,不会对Vue类本身以及Vue类的实例对象产生影响。

 (七)vue为什么需要虚拟DOM? 1.vdom把渲染过程抽象化了,从而使得组件的抽象能力也得到提升,并且可以适配DOM以外的渲染目标。 2.可以渲染到DOM以外的平台,实现SSR、同构渲染这些高级特性,Weex等框架应用的就是这一特性。 3.操作 DOM 慢,js运行效率高。我们可以将DOM对比操作放在JS层,提高效率。 4.提升渲染性能 Virtual DOM的优势不在于单次的操作,而是在大量、频繁的数据更新下,能够对视图进行合理、高效的更新。  

(八)vue的diff算法 时间复杂度由O(n^3)变成了O(n) 1.只比较同一级,不跨级比较 2.tag不相同,直接删掉重建,不再深度比较 3.ag和key,两者都相同,则认为是相同节点,不在深度比较 创建节点:新的VNode中有而旧的oldVNode中没有,就在旧的oldVNode中创建。 删除节点:新的VNode中没有而旧的oldVNode中有,就从旧的oldVNode中删除。 更新节点:新的VNode和旧的oldVNode中都有,就以新的VNode为准,更新旧的oldVNode。 

 (九)key v-for 中 :key 到底有什么用? 主要用在vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes。如果不使用key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。 

 (十)组件渲染流程 开始组件页面渲染=》调用组件实例render方法=》将标签生成虚拟节点=》根据虚拟节点生成DOM-》第一条线1.直接生成DOM元素。=》添加到父级DOM元素中-》完成组件页面渲染2,生成组件实例-》调用组件实例render方法 

(十一)组件data需要是一个函数 一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝: 

 (十二)v-if 和v-show的差别  

(十三)vue-router有几种钩子函数?具体是什么?执行流程是怎样的? 有7个 全局前置守卫 beforeEach 全局解析守卫 beforeResolve. 这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。 全局后置钩子 afterEach 路由独享守卫 beforeEnter 组件内守卫 beforeRouterEnter beforeRouterUpdate beforeRouterLeave 执行流程: beforeRouteLeave =》 beforeEach =》 beforeRouteUpdate =》 beforeEnter=》 beforeRouteEnter=》 beforeResolve =》afterEach =》DOM 更新=》beforeRouteEnter 导航被触发。 在失活的组件里调用 beforeRouteLeave 守卫。 调用全局的 beforeEach 守卫。 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。 在路由配置里调用 beforeEnter。 解析异步路由组件。 在被激活的组件里调用 beforeRouteEnter。 调用全局的 beforeResolve 守卫 (2.5+)。 导航被确认。 调用全局的 afterEach 钩子。 触发 DOM 更新。 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。 

 (十四)浅谈vuerouterrouter 和 route的区别 

route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。 

router是“路由实例对象”,包括了路由的跳转方法(push、replace),钩子函数等。 最近在学习vue的单页面应用开发,需要vue全家桶,其中用到了VueRouter,在路由的设置和跳转中遇到了两个对象routerrouter 和 route ,有些傻傻分不清,后来自己结合网上的博客和自己本地的Vue devtools结构了解了他们的区别 1.router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性。 举例:history对象 router.push(path:home);本质是向history栈中添加一个路由,在我们看来是切换路由,但本质是在添加一个history记录方法:router.push({path:'home'});本质是向history栈中添加一个路由,在我们看来是 切换路由,但本质是在添加一个history记录 方法: router.replace({path:'home'});//替换路由,没有历史记录 2.route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等 我们可以从vue devtools中看到每个路由对象的不同  

(十五)attrsvm.attrs vm.listeners 

attrs包含了父作用域中不作为prop被识别(且获取)attribute绑定(classstyle除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(classstyle除外),并且可以通过vbind="attrs 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="attrs" 传入内部组件——在创建高级别的组件时非常有用。 listeners包含了父作用域中的(不含.native修饰器的)von事件监听器。它可以通过von="listeners 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="listeners" 传入内部组件——在创建更高层次的组件时非常有用。 

(十六)watcher作用 

在自身实例化时往属性订阅器(dep)里面添加自己 自身必须有一个update()方法 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。 

(十七)watch、methods 和 computed 的区别? watch为了监听某个响应数据的变化。computed是自动监听依赖值的变化,从而动态返回内容。主要目的是简化模板内的复杂运算。所以区别来源于用法,只是需要动态值,那就用 computed ;需要知道值的改变后执行业务逻辑,才用 watch。 methods是一个方法,它可以接受参数,而computed 不能,computed 是可以缓存的,methods 不会。computed 可以依赖其他 computed,甚至是其他组件的 data。 

(十八)vue封装公共组件(通用组件)需要考虑到什么 1.可读性。写必要的注释 2.数据从父组件传入 3.在父组件处理事件 4.可扩展性,留slot 5.不要依赖vuex 6.合理运用scoped编写css (十九)vue 首屏加载优化 1. 把不常改变的库放到 index.html 中,通过 cdn 引入 2. vue 路由的懒加载 3. 不生成 map 文件 4. vue 组件尽量不要全局引入 5. 使用更轻量级的工具库 6. 开启gzip压缩 7. 首页单独做服务端渲染 如果首页真的有瓶颈,可以考虑用 node 单独做服务端渲染,而下面的子页面仍用 spa 单页的方式交互。 第一步:webpack-bundle-analyzer 分析 第二步:初步优化 1. 仔细考虑组件是否需要全局引入 2. 手动引入 ECharts 各模块 3.使用更轻量级的工具库 moment换成 date-fns 第三步:CDN优化 1.首先我们在index.html中,添加CDN代码 2.在vue.config.js中加入webpack配置代码,关于webpack配置中的externals 3. 去除vue.use相关代码 第四步:检查Nginx 是否开启 gzip 第五步:检查路由懒加载

 (二十)keep-alive原理 

第一步:获取keep-alive包裹着的第一个子组件对象及其组件名; 第二步:根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则执行第三步; 第三步:根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步; 第四步:在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max的设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。 第五步:最后并且很重要,将该组件实例的keepAlive属性值设置为true。