前端之vue2原理(全)

一、组件通信总结(有些不建议使用,打破vue单向数据流,可能出问题)

  • 常用父子组件通信:1. prop event ;2. $ref $refs ;3. $slots $scopedSlots(插槽);
    4..sync(修饰符),5. $listeners  //子组件通过 $listeners 获取父组件传递过来的所有处理函数、$parent $children //可以获取子组件或父组件的实例;
    6. attribute //(父组件在使用子组件时定义了一些属性,子组件并没声明这些属性,在子组件可以通过this.$attrs获取,并且会在子组件的根标签自动加上这些属性)
  • 跨组件通信:1. eventbus ;2. vuex;3. router 4. store模式(js定义数据(export default{ demo :{value:1} }),组件先引入这个js,然后通过store.xxx使用(data函数中使用方式));

二、虚拟DOM

本质是普通的js对象,映射页面显示的元素,在vue中每个组件都有一个render函数用于创建虚拟dom,每个组件都有自己的render函数对于自己虚拟dom树,虚拟dom可提升渲染速度,因为创建/更新/插入真实dom会非常消耗性能,最终根据虚拟dom创建真实dom

三、v-model 原理

v-model 可以用于表单元素 ,也可以用于自定义组件,无论用在哪里它就是一个语法糖,
对于input元素 它通过绑定value 和监听input事件 实现,
对复选框来说 它通过绑定checked 和监听change事件实现,
模板最终会通过render函数生成虚拟节点(vnode),所以这些可以通过打印 this._vnode 中查看;
用于自定义组件:默认它会生成一个value属于 和input事件,
如:子组件需要用到父组件传递的数据,在子组件更改是无法实现数据双向数据绑定,需要通过$emit 抛出一个input事件和参数,然后父组件通过绑定value和监听这个input事件从而改变数据,这些的写法可以直接通过v-model来实现,也可以通过组件的model配置来改变生成的属性和事件,如:model :{ prop : "number" , event : "change" }prop 默认为value属性 ,event默认为input事件。

四、数据响应式原理(数据双向数据绑定原理) 

vue首先会把每条数据通过 Object.defineProperty 方法循环遍历每一条属性将其变成拥有getter、setter属性的数据,读取属性运行getter ,设置属性运行setter,然后vue模板会被render函数生成虚拟DOM树,最终生成真实Dom,从而渲染页面,当数据改变会运行getter函数收集依赖,同时 warcher 会监听数据变化 在次触发render函数,生成新的虚拟dom树,生成真实dom然后渲染页面;
核心部件(细节):
Observer : 把普通对象变成响应式对象,把每条数据通过Object.defineProperty 循环遍历将每一条数据变成拥有getter、setter属性的数据,读取属性运行getter ,设置属性运行setter ,如果遍历遇到对象,就递归,最终变成这种{a :{ get() {},set(){} }},Observer发生在beforeCreate之后,created之前。另外如果在函数中通过 obj.a =123 去增加一个属性(data函数obj中原本没有 a 这个属性) 是监控不到的,视图是无法渲染这条数据的,可以通过vue提供的 $set方法去操作,还有push pop sort unshift等方法 都是vue自己搞的一个对象,包含push等方法,然后这个对象指向Array.prototype,而正确情况 push等方法 原型直接指向 Array.prototype ,不会实现数据响应式;
Dep : 解决读取、改变属性要做些什么事,因为数据改变要运行getter函数 然后执行render,但每个组件都有render 函数,在observer并不知道运行哪个render,dep来处理,首先会把响应式对象中的属性、对象本身、数组本身创建一个Dep实例,每个dep实例会记录依赖(表示谁在用我)、派发更新(我变了,我要通知那些人用到了我);
watcher : dep 想知道一个属性哪里在用它,dep 过程是不知道谁在用,需要通过watcher配合才知道,vue并不是直接运行render函数,是通过 new Watcher( render() ),Watcher 在执行之前 会设置一个全局变量,这个全局变量等于this,然后执行,在执行过程中如果发生了依赖记录,那么dep就会把这个全局变量记录下来,表示有一个watcher用到了我这个属性,每个watcher都会对应一个函数,每个组件也有一个对应的watcher,这个watcher 对应该组件的render函数;
Scheduler (调度器) :防止render函数多次重复触发(可能会多次操作数据),watcher也不是立即执行,会放到调度器中(是个数组)等待被调用,调度器会维护一个队列,队列中同一个watcher仅会存在一次,队列中的watcher不会立即执行,会通过 nextTick 方法(通过promise执行,会放到微队列中执行)

五、diff 算法 

首先当组件创建或依赖的属性发生变化会运行render函数,生成虚拟DOM树,之后传入 _update(this._render())函数中,进行新旧树对比,最终生成真实dom的更新(diff 也是发生在 _update 函数中),代码如下:
var updateComponent = () => { this._update( this._render() ) }
new Watcher( updateComponent ) ,这个Watcher就是用于监听该函数执行期间用到了那些响应式数据,并记录,数据变化就是重新运行watcher ,就是运行这个update函数,会先对比虚拟节点标签的类型、key值是否都相同,input多判断type是否相同,标签类型/key值均相同在去判断内容,这样不用删除当前虚拟节点,直接改变内容即可;然后判断是否有新建元素,有就新建;然后判断是否有要删除的元素,有就删除;有数据更新就对比节点(深度优先对比)对比通过头尾指针的方式进行对比,将新旧两颗树进行对比,找到差异,最终更新真实dom(v-for会根据key值进行对比)

六、computed (计算属性) 

发生在beforeCreate 之后,create之前,会遍历computed中的每一个属性,并且对每一个属性创建一个watcher对象并且传入一个函数,传入的就是computed的get()函数,进行依赖收集,get函数不会立即执行,但是watcher函数还是会被创建,在创建watcher时,vue使用了lazy配置,使watcher不会立即执行,当lazy为true时,watcher会保存两个属性实现缓存,一个是value,一个是dirty,value属性用于保存watcher函数运行的结果,最开始是undefined,dirty表示当前value是否为脏值,最开始为true,在组件使用到计算属性时,先会检查对应的watcher是否脏值,如果是则会运行get函数,运行get函数会进行依赖收集,之后value就会根据依赖发生改变,之后dirty值变为false,等在重复使用的时候由于dirty为false 直接取value的值,这就是缓存的实现;
当计算属性变化时不仅会运行计算属性的watcher, 还会运行组件的watcher,
当计算属性改变,直接把dirty设为true,然后在执行组件的warcher(渲染页面)时由于用到了计算属性,就会检查dirty是否为true(true表示脏值),是脏值就会运行计算属性的get函数 重新收集依赖,然后改变value值;

七、生命周期详解(new Vue()之后发生的事情,如果有混合先执行混合的生命周期函数 )

1.运行 beforeCreate

2.进入注入流程,将处理属性(observer启动)计算属性、methods、data等挂载到vue实例中 ;
3 运行 created ,可以拿到响应式数据、方法、计算属性等;
4. 把模板编辑成render函数;
5. 运行 beforeMount
6. 创建一个watcher 把update放到warcher执行,update(render()),生成虚拟dom树,然后对比是否有新旧两颗树,如果有就对比,否则直接用当前这颗树,最终生成真实dom,创建时如果遇到组件的vnode 则会递归生命周期函数(执行顺序:父created,子created,子mounted,父mounted),创建好组件实例会挂载到vnode的componentInstance属性中,以便复用,另外组件的属性(数据)、方法等会存在contentOptions属性中;
7.运行mounted 函数 ,可拿到真实dom、ref等
8. 当数据改变,所有依赖该数据的watcher均会重新运行,watcher不会立即执行,是放到nextTick中运行,避免多个数据改变多次运行同一个watcher,运行beforeUpdate
9. 然后对应的watcher函数执行,运行update函数 、运行render函数,重新收集依赖,通过path算法对比新旧两颗树,最终生成真实的DOM,运行upDateed
10.beforeDestroy (销毁前)
11. destroyed  (销毁后)

八、vue 中 data为什么通过一个函数返回,而不是一个普通对象

1.vue中组件是用来复用的,为了防止data复用,将其定义为函数。
2.vue组件中的data数据都应该是相互隔离,互不影响的,组件每复用一次,data数据就应该被复制一次,之后,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响,就需要通过data函数返回一个对象作为组件的状态。
3.当我们将组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,拥有自己的作用域,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。
4.当我们组件的date单纯的写成对象形式,这些实例用的是同一个构造函数,由于JavaScript的特性所导致,所有的组件实例共用了一个data,就会造成一个变了全都会变的结果。

九、keep-alive (用于缓存组件内部实例,必须是组件)路由切换可以考虑用(router-view) 

  • 标签介绍

<keep-alive></keep-alive>  //  生命周期不走destroyed,缺点占用更多的内存空间;
属性::include="['组件名字1','组件名字2']"(哪些组件进入缓存) 和 exclude(哪些组件不缓存)、:max="2" // 最多缓存2个组件,最久没有用到的组件会清除缓存;

  • 生命周期

1. activated   // 激活 ; 2. deactivated   // 失活

  • 触发顺序

首先执行组件A created mounted activated ,切换组件B后, 先执行组件B created 然后执行组件A deactivated 再执行组件B mounted ,切换回组件A 后,先执行组件A activated(这个时候由于缓存了 不会触发组件A的created、mounted);

  • 原理

内部维护了key数组和一个缓存对象(cache),key数组记录目前缓存的组件key值,没有指定key值会自动生成  ,cache对象以key为属性名(键),每个属性对应一个vnode,
keep-alive 实际上使用了默认插槽,会先得到第一个组件的虚拟dom,在获取这个组件的名字,然后开始判断,判断key值是否在缓存中(cache对象),存在表示有缓存,之后把key数组对应的值移除掉,在添加到数组的最后一位,数组靠前的就是很久没使用的组件,没缓存,就把这个组件缓存下来,push到key中。当设置了最大缓存,会移除key数组第一个值;

十、优化 

1. key:列表循环一定要有key ,否则列表的改变很消耗性能,因为path算法是根据key进行对比的,改变数据时如果没有key会把当前位不同的数据改变,有key,会比较key,有相同的把相同的数据移动过来使用;
2. 函数式组件: 一个组件只负责接受参数显示数据,不会改变任何东西,这样的组件没有生命周期,不需要创建组件实例,代码:<template functional>{{ props.count }}</template>,export default :{ functional : true,props :{ count : Number, } };
3. 计算属性:有缓存;
4. v-show / v-if  :大量的dom元素 使用v-show,避免频繁的新增和修改元素
5. 延迟装载(defer):解决首页白屏,主要产生的问题如:
        ⑴. 打包体积过大(npm run serve 实际也是会经历打包过程,只是不生成文件),可以尝试减少打包体积;
        ⑵. 需要立即渲染的内容太多,js传输完成后,浏览器开始执行js构造页面,但可能一开始要渲染的组件太多,不仅js执行时间很长,而且执行完后浏览器渲染的元素太多,导致页面白屏,可以通过路由懒加载解决;如果第一个加载的组件元素过大可以通过defer处理(时间分片加载元素);

6.CDN  // 如果你的项目是依赖外网的,可以使用CDN托管资源,比如vuex/vue-router都可以从CDN上获取这些外部资源,使用CDN mian.js Vue.use(VueRouter) 是不能写的,引入CDN也污染全局遍历,可以根据这个全局遍历判断 if(!window.router){ Vue.use(VueRouter) };Vuex也是同样判断,而且在main.js 中不能 Vue.use(xxx)且必须配置webpack 如下:  configureWebpack: {
    externals:{
      jquery:"jquery"
    }
  }
7.启动现代浏览器(去除core.js)  // 如果不考虑旧版浏览器直接去掉babel.config.js 的一些配置:
presets: [
    ['@vue/cli-plugin-babel/preset', {
    }]
  ]
如果新旧浏览器都能用通过 "build": "vue-cli-service build --modern", 
    package.js配置后现代浏览器将不会打包core.js,但旧版浏览器还会有,这样现代浏览器的访问速度要比旧版浏览器访问速度快一些,core.js是做兼容性处理,旧版本浏览器有些API不存在,所有用到了core.js这个库,这个库从ES3 开始把所有函数、对象等都模拟了一遍,但是是按需引入的;
8. 优化项目包体积  // 路由懒加载,动态引入组件(异步组件);
9. 首屏响应  //处理白屏 ,提示加载中... 在index.html div 下放一个元素,提示加载中;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值