开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第32天,点击查看活动详情
MVVM、MVP、MVC
- MVVM
- model:代表数据模型,数据和业务逻辑都在model层中定义
- view:代表UI视图层,负责数据的展示
- viewModel:负责监听model中数据的改变并且控制视图的更新,处理用户交互操作
- model与view并无直接关联,而是通过viewModel来进行联系的,model和viewModel之间有着双向数据绑定的联系,因此当model中的数据改变时会触发view层的刷新,view中由于用户交互操作而改变的数据也会在model中同步。这种模式实现了model和view的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作DOM。缺点:model和view可能有耦合,即MVC仅仅将应用抽象,并未限制数据流。
- MVC
- model:代表数据模型
- view:视图层
- controller:逻辑层
- view和model应用了观察者模式,当model层发生改变的时候它会通知有关view层更新页面,controller层是view层和model层的纽带,他主要负责用户与应用的响应操作,当用户与页面产生交互的时候,controller中的事件触发器就开始工作了,通过调用model层,来完成对model的修改,然后model层再去通知view层更新。
- MVP
- model:代表数据
- view:视图层
- presenter [prɪˈzentər]
- 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还包含了其他的响应逻辑。MVP模式也有问题,它的问题在于presenter的负担很重,presenter需要知道view和model的结构,并且在model变化时需要手动操作view,增加编码负担,降低代码维护性。
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的基本原理
- vue2:
- 当一个vue实例创建时,vue会遍历data中所有属性,用ObjectdefineProperty,将他们转为setter和getter。并且在内部追踪相关依赖,在属性被访问或修改时通知变化。每个组件实例都有相应的watcher程序实例。它会在组件渲染过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它相关联的组件得以更新。但是ObjectdefineProperty有缺陷,无法拦截数组的一些操作,和对象添加新属性。从而导致组件无法重新渲染
- vue3采用proxy进行代理,从而实现数据劫持,使用proxy的好处是能拦截到数据的变化。它相当于在目标对象前架设一层拦截器。外界对该对象的访问,都必须通过这层拦截器,可以对外界的访问进行过滤和改写。可以直接监听对象而非属性,可以直接监听数组的变化,性能上的提升。
- 有两个参数target:要包装的对象,可以是任何东西,包含函数
- handler:代理配置,带有“钩子”(traps,拦截操作的方法)的对象,比如get钩子用于读取target属性,set钩子写入target属性等等,当handler为空没有任何钩子时,proxy是一个target的透明包装。
- MVVM模式
- MVVC分为model、view、viewModel
- model:获取数据
- view:视图,与viewModel双向绑定
- viewModel:做逻辑处理的
computed和watch的区别
- computed
- 它支持缓存,只有依赖的数据发生了改变,才会重新计算
- 可以侦听多个值
- 不支持异步,当computed中有异步操作时,无法监听数据的变化
- computed不能直接修改data中的数据
- computed必须有返回值
-
watch
- 他不支持缓存,数据变化时,它会触发相应的操作
- 只能侦听一个值
- 支持异步监听
- 监听的函数接收两个参数,第一个参数是最新的值,第二个参数是变化之前的值
- 当一个属性发生变化时,就需要执行相应的操作
- 监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个参数
- immediate[ɪˈmiːdiət]:组件加载立即触发回调函数
- deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化
-
运用场景
- 要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch
- 当需要进行数值计算,并且依赖于其他数据时,应该使用computed,因为可以利用computed的缓存特性,避免每次获取值是都要重新计算。
slot是什么?有什么作用?原理是什么?
- slot又名插槽,是vue的内容分发机制(类似于为eact中的outlet,outlet相当于this.props.children),组件内部的模板引擎使用slot元素作为承载分发内容的出口,插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot分为三类。默认插槽、具名插槽和作用域插槽
- 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候。默认为default插槽。一个组件内只有一个匿名插槽
- 写法:在父组件内调用子组件时,在组件之间直接写内容,在子组件内部通过slot标签来接收。如果没写东西的话可以设置默认值,在slot上设置
- 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候。默认为default插槽。一个组件内只有一个匿名插槽
- 具名插槽,带有具体名字的插槽,也就是带有name属性值的slot,一个组件可以出现多个插槽。
- 写法:在父组件内调用子组件时,在组件标签内使用template标签,该标签上使用v-slot指令,设置插槽的名称。在子组件内部通过slot标签的name属性来对应插槽。如果有没被包裹的为默认插槽
- 作用域插槽:在父组件内访问子组件的数据
- 写法:父组件内调用子组件,子组件标签内的template标签上的v-slot指令设置name。在template标签内使用双大括号name名点子组件对应插槽的动态属性名,拿到子组件内对应name属性的插槽的动态属性的值。也就拿到了子组件的数据
- 如果是默认插槽,可以直接在v-slot指令写对象子组件的动态属性名,拿到动态属性设置的数据,也可以将v-slot换成#default,称为解构插槽
插槽的实现原理:当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.slot.default,具名插槽为vm.slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
传值
- 父组件向子组件传值
- 通过props传递数据
- 写法:在父组件调用子组件上通过动态属性传递数据,在子组件内通过props来接值。props值可以写成数组,只接值,也可写成对象,可以设置props的类型和必填等
- 通过props传递数据
- 子向父传值
- 通过this.emit("自定义事件",传递的参数),触发自定义事件并且可以传参。
- 写法:在父组件内调用的子组件上设置自定义事件。在子组件内通过this.$emit("监听子组件绑定的自定义事件",传递的数据)。在父组件内调用自定义事件接收的参数就是子组件传过来的数据
- 通过this.emit("自定义事件",传递的参数),触发自定义事件并且可以传参。
- 非父子组件传值(没有关系的组件)
- 中央事件总线
- 写法:先创建一个空的vue实例作为中央事件总线(相当于中转站一样)const Bus=new Vue()。发送值由Bus来发布。通过Bus.on来接收。有两个参数,一个是监听传值的那个自定义事件名,另一个是回调函数,接收的参数就是传递过来的数据,如果是this的话记得要用箭头函数
- 写法:先创建一个空的vue实例作为中央事件总线(相当于中转站一样)const Bus=new Vue()。发送值由Bus来发布。通过Bus.on来接收。有两个参数,一个是监听传值的那个自定义事件名,另一个是回调函数,接收的参数就是传递过来的数据,如果是this的话记得要用箭头函数
- 中央事件总线
- v-model可以用在自定义组件上,能达到双向传值的效果(vue2中v-model相当于v-bind:value和v-on:input的语法糖动态属性和自定义事件的语法糖)
- 写法:在自定义子组件上使用v-model指令,传递数据。
访问根实例
在根实例上写数据,包裹的组件都能得到。通过root.对象的属性名
缺点:所有数据都放在根实例上,所有的人都可以改,容易混乱,中大型的项目通常不这样用
访问父级组件实例
和parent property可以用来从一个子组件访问父组件的实例。可以替代将数据以props方式传入子组件的方式。
- 嵌套太多,比如父亲的父亲,中间一层修改的话,很难找到是哪一个修改了,还是要用原始传值方式(只适合简单的小型项目)
访问子组件示例或子元素
- 在子组件调用的时候,给子组件加一个ref属性,属性值自定义
- 在事件处理函数里面通过this.$refs.属性名.子组件的data名即可
父组件:
子组件:
data没变也想让组件更新,可以强制更新,但极少出现,可以通过this.$forceUpdate()
v-once
通过v-once创建低开销的静态组件(只能用在静态组件上)确保内容只计算一次然后缓存起来
注意:不要过度使用这个模式。当你需要渲染大量静态内容时,极少数的情况下他会给你带来遍历,除非你非常留意渲染变慢了,不然他完全是没有必要的,再加上他在后期会带来很多困惑。例如,摄像另一个开发者并不熟悉v-once或漏看了他在模板中,他们可能会花很多个小时去找出模板为什么无法正确更新。
动态组件
有些场景需要在两个组件间来回切换,比如Tab界面,可以实现多个组件进行切换。一般用在Tab标签页,仅限于组件的销毁和重建,尽量不要用在侧导航,侧导航一般使用路由来实现的
- 写法:
- 利用component元素添加一个is属性来实现。is属性的值是哪个组件名就渲染哪个组件,通过动态修改渲染组件。
在动态组件上使用keep-alive
keep-alive包裹动态组件时,会缓存不活动的组件实例,他不会生成真正的DOM节点。
- keep-alive为什么不会生成真正的DOM节点
- vue在初始化生命周期的时候,为组件实例建立父子关系会根据abstract属性决定是否忽略某个组件,在keep-alive中,设置了abstract:true,那vue就会跳过该组件实例。
- keep-alive的属性
- keep-alive可以指定动态组件的缓存,通过include属性,指定组件。exclude:不缓存那个组件。max:最多缓存多少个动态组件。如果超出的删除掉最近最久没有使用的组件实例(即是下标为0 的那个key)
- keep-alive的原理
- keep-alive是一个组件,但不会生成真实的DOM节点。vue在初始化生命周期的时候,keep-alive设置了abstract为true。vue会跳过该组件实例。这个组件有三个属性include、exclude 和max。,在created中创建缓存列表和缓存组件的key列表,销毁的时候会做一个循环销毁清空所有的缓存和key,当mounted时会监控include和exclude属性,进行组件的缓存处理。如果发生变化会动态的添加和删除缓存。渲染的时候会去拿默认插槽,只缓存第一个组件,取出组件的名字,判断是否在缓存中,如果在就缓存,不在就直接return掉。缓存的时候,如果组件没有key,就自己通过组件的标签、key和cid拼接一个key。如果该组件缓存过,就直接拿到组件实例,如果没有缓存过就把当前的VNode缓存,和key做一个对应关系,这里面有一个算法叫LRU,如果有key就不停的取,如果超限了就采用LRU进行删除最近最久未使用的。
- 源码:
访问根实例
在根实例上写数据。包裹的组件都能得到。通过$root访问根实例
- 把所有数据都放在根实例上,所有的人都可以改,中大型的项目通常不这样用
动画
切换组件时在插入/更新或溢出DOM时。
- 通过css时,利用css3里面的过渡动画实现,和使用第三方的动画库
- 通过js在过渡钩子函数中。和第三方的js动画库
首选css3(一般用css3来写)
- vue提供了transition的封装组件,在下列情况下,可以给任何元素和组件添加entering/leaving过渡
- 条件渲染(使用v-if),条件展示(使用v-show),动态组件,组件根节点。
- transition组件中只有一个的时候可以只写transition元素,使用时在style区域内通过点v-enter/点v-leave
- 过渡的类名有6个
- enter:定义进入过渡的开始状态,在元素被插入之前生效,在元素被插入之后的下一帧移除
- enter-active:定义进入过渡生效时的状态。
- enter-to:定义进入过渡的结束状态
- leave:定义离开过渡的开始状态
- leave-active:定义离开过度生效时的状态
- leave-to:定义离开过渡结束的状态
- 过渡的类名有6个
- 还可以使用第三方库,比如animation.css
- 下载animation.css的依赖 cnpm i animation.css --save
- 对应的网址,搜想要的样式 http://animation.style/
- 在script区域内引入animation.css import "animation.css"
- 在transition元素内使用自定义class,值为基础的animation__animated空格加上复制的想要的动画样式
- 自定义的过渡类名
- enter-class
- enter-active-class
- enter-to-class
- leave-class
- leave-active-class
- leave-to-class
- 过渡时有时会因为当前元素和新元素的进入和离开有所冲突,所以可以用两种过渡模式进行解决。在transition元素内写在mode属性里
- out-in:当前元素先进行,完成之后新元素过渡进入(用的比较多)
- in-out:新元素先进行过渡,完成之后当前元素过渡离开
初始渲染的过渡
可以通过appear attribute设置节点在初始渲染的过渡
多个组件的过渡
可以用transition包裹动态组件来进行设置
列表过渡
使用transition-group元素,可以有name属性,定义动画名字。tag属性设置渲染出来的元素标签到底是什么。transition-group包裹的必须是span标记。通过v-for来循环列表过渡
mixin(混入)可复用
- 作用
- 封装组件里的某个东西,进行基本的封装来重复使用。
- mixin本身是一个对象,来分发vue组件中的复用功能,对象可以包含任意组件选项。与其他组件合并,混入使用。(白话理解:创建了一个对象(普通的json对象)里面有methods,还有一个组件,该组件本身也有methods,可以将创建的对象与该组件混合合并,那么在当前组件里也可以创建对象里面的methods。原来组件与混入发生冲突时,优先原组件data
全局混入(放在入口文件里)
一旦使用全局混入,它将影响每一个之后创建的vue实例,使用恰当时,可以用来为自定义选项注入处理逻辑
- 使用App对象点mixin
vue中常用的指令和作用
- v-html:转化html标签
- v-bind:动态属性,可以简写成冒号
- v-on:绑定事件,可以简写成@
- v-for:循环
- v-if/v-else/v-else-if:条件渲染
- v-show:加上display样式来进行渲染,基于css进行切换
- v-model:数据绑定
- v-once:能执行一次性插值,当数据改变时,插值出的内容不会更新
9. v-text:给一个标签加上v-text会覆盖标签内原先的内容
自定义指令
- 如何自定义指令
- 全局注册指令:直接注册在vue对象上。通过vue.directive("指令名称",{指令相关的内容注册多个的话,就写多个vue.directive.
- 局部指令在组件对象里注册
- 自定义指令的钩子函数
- bind:只调用一次,指令第一次绑定到元素时调用,在这里可以进行一次性的初始化设置
- inserted/in'sə:tid/:被绑定元素插入父节点时调用(只要有父节点,并且放在父节点里面就可以被调用
- update:所在组件的虚拟DOM更新是调用。但也有可能发生在其子虚拟DOM更新之前。指令的值发生了改变,也有可能没有(组件更新了就会调用,但是值并没有发生改变)
- componentUpdated:更新之后才调用,指令所在组件的虚拟DOM及子组件全部更新之后调用
- unbind:只调用一次,指令与元素解绑时调用
参数
:- el:都能接收到这个参数,都能接收到节点。获取绑定的节点
- binding:都能接收到这个参数,这是个对象,属性有name、value、oldValue
- value:指令的绑定值,接收的是表达式的结果
- oldValue:只有更新的两个钩子上有,指令绑定前的一个值
- expression:表达式本身,不运行表达式(用得少)字符串形式的指令表达式
- arg:传给指令的参数,冒号后面(比如:v-bind:(就是这个冒号))
- modifiers:包含修饰符的对象,接收修饰符 -VNode:虚拟节点
- oldVNode:上一个虚拟节点,仅在update和componentUpdate钩子中可用。
- 注意:自定义指令里除了el之外,其他参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素dataset来进行
路由守卫
- 全局守卫(能监听传值,监听路由切换)是router上的方法。
- 全局前置守卫:router.beforeEach:当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫resolve完之前一致处于等待中。在路由组件渲染之前
- 全局解析守卫:router.beforeResolve:在所有组件内守卫和异步路由组件被解析之后,他才会调用。
- 全局后置守卫:router.afterEach:在组件已经渲染之后触发的
- 路由独享守卫
- beforeEnter:
- beforeLeave
- 组件内的守卫
- beforeRouteEnter:渲染真实DOM之前会触发
- beforeRouteUpdate:更新的时候触发,在当前路由改变,但是该组件被复用时调用。监听路由传值变化,除了watch的监听方法以外的另一种监听方法
- beforeRouteLeave:离开时触发
使用插件
通过全局方法Vue.use()使用插件,它需要在你调用new vue()启动应用之前完成
例如:调用MyPlugin
Vue.use(MyPlugin)
new Vue({ ... })
也可以传入一个可选的选项对象,第一个参数插件名,第二个参数数据
Vue.use(MyPlugin,{someOption:true})
Vue.use会自动阻止多次注册相同插件,即使多次调用也只会注册一次该插件。vuejs官方提供的一些插件(例如vue-router)在检测到vue是可访问的全局变量时会自动调用Vue.use().然而在像CommonJS这样的模块环境中,应该始终显式地调用Vue.use()
用Browserify或webpack提供的commonJS模块环境时
var Vue=require(‘vue’)
var VueRouter=require('vue-router')
Vue.use(VueRouter)
生命周期
- vue2生命周期
- beforeCreate(创建前):此时组件的选项对象还未创建,el和data并未初始化,因此无法访问methods,data,computed等方法和数据(一般不做任何操作,摆设而已。告诉你生命周期开始了)
- created(创建后):在创建之后使用,主要用于数据观测。属性和方法的运算,watch/event事件回调,完成了data数据的初始化,el还没有,然而,挂载阶段还没有开始
- beforeMount(挂载前):用于在挂载之前使用,在这个阶段是获取不到dom操作的,把data里面的数据和模板生成html,完成了data等初始化,注意此时还没有挂载html到页面上(虚拟dom生成了,但真实dom还没有生成,在这里更改数据不会触发update。这是最后一次更改data的机会)
- mounted(挂载后):用于挂载之后使用,在这个时候可以获取到dom操作,比如可以获取到ref等操作的dom,在这个时候只能调用一次ajax,在这个时候el和data都可以获取的到。mounted不会保证所有的子组件也都被挂载完成,如果你希望等到整个视图都渲染完毕再执行某些操作,可以在mounted内部使用vm.$nextTick.
- vm.$nextTick([callback]):将回调延迟到下次DOM更新循环之后执行,在修改数据之后立即使用它,然后等待DOM更新,它跟全局方法vue.nextTick一样,不同的是回调的this自动绑定到调用它的实例上。
vm.$nextTick的原理
:nextTick就是一个异步方法。nextTick方法主要使用了宏任务和微任务(事件循环机制)定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过异步方法清空当前队列,所以这个nextTick方法就是异步方法。当调用nextTick方法时会传入两个参数,回调函数和执行回调函数的上下文环境,如果没有提供回调函数,那么将返回promise对象。首先将拿到的回调函数存放到数据中,判断是否正在执行回调函数,如果当前没有在pending的时候,就会执行timeFunc,多次执行nextTick只会执行一次timeFunc,timeFunc其实就是执行异步的方法,在timeFunc方法中选择一个异步方法(首先判断是否支持promise,如果支持就将flushCallbacks放在promise中异步执行,并且标记使用微任务。如果不支持promise就看是否支持MutationObserver方法,如果支持就new一个MutatioObserver类,创建一个文本节点进行监听,当数据发生变化了就会异步执行flushCallbacks方法。如果以上两个都不支持就看是否支持setImmediate,如果支持setImmediate就去异步执行flushCallbacks方法,如果以上三种方法都不支持就是用setImmediate),然后异步去执行flushCallbacks方法,flushCallbacks中就是将传递的函数依次执行,nextTick多次调用会维持一个数组,之后会异步的把数组中的方法依次执行,这样的话用户就会在视图更新之后在获取到真实的DOM元素。
- 继续生命周期
- beforeUpdate(更新前):在数据更新之前被调用,法还是能在虚拟DOM重新渲染,也可以在该钩子中进一步地更改状态,不会触发重新渲染过程
- update(更新后):在由于数据更改导致虚拟DOM重新渲染和更新完毕后会调用,调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作,然后在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环,(如果要改的话最好使用计算属性或watch来代替)但是在服务器端渲染期间不被调用,可以用于监听某些数据的时候使用钩子。update不能保证所有的子组件也都被重新渲染完毕,如果你希望等到整个视图都渲染完毕,可以在update中使用vm.$nextTick()
- beforeDestroy(销毁前):在这个时候还是可以用this来获取,可以用于销毁计时器时候使用,为了防止跳转到其它页面该事件还在执行,还可以清除dom(清除计时器,清除非指令绑定的事件)事件等。
- destroy(销毁后):在实例销毁之后调用,调用后,所有的事件监听器会被移除,所有的子例也会被销毁。
- activated:被KeepAlive缓存的组件激活时调用。服务端渲染期间不被调用
- deactivated:被KeepAlive缓存的组件失活时调用。服务端渲染期间不被调用。
- errorCaptured:在捕获一个来自后代组件的错误时调用,此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回false以阻止该错误继续向上传播。(可以在此钩子中修改组件的状态。因此在捕获错误时,在模板或渲染函数中有一个条件判断来绕过其它内容就很重要,不然组件可能会进入一个无限的渲染循环)
- vue3的生命周期
- beforeCreate->使用setup()
- created->使用setup()
- beforeMount->使用onBeforeMount:在挂载开始之前被调用:相关的render函数首次被调用
- mounted->使用onMounted:组件挂载时调用
- beforeUpdate->使用onBeforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前。这里适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器。
- updated->使用onUpdated:由于数据更改导致虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。
- beforeDestroy->使用onBeforeUnmount:在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
- destroyed->使用onUnmounted:卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
- onActivated:被KeepAlive缓存的组件激活时调用
- onDeactivated:被KeepAlive缓存的组件失活时调用。
- onErrorCaptured:当捕获一个来自子孙组件的错误被调用,此钩子会收到三个参数:错误对象、发生错误
vue2和vue3的区别
- 重构响应式系统,使用proxy替换object.defineProperty,使用proxy的优势可以监听数组类型的数据变化。监听的目标为对象本身,不需要向object.defineProperty一样遍历每个属性,有一定的性能提升。可拦截apply、ownKeys、has等13种方法,而object.defineProperty不行。直接实现对象属性的新增/删除
- 新增compositionAPI,更好的逻辑复用和代码组织。setup是compositionAPI的入口。vue的API设计迫使开发者使用watch、computed、methods选项组织代码,而不是实际的业务逻辑。vue2缺少一种较为简洁的低成本的机制来完成逻辑复用,虽然可以使用mixins完成逻辑复用,但是当mixin变多的时候,会使得难以找到methods来源于那个mixin,使得类型推断难以进行。compositionAPI的出现,主要也是为了解决optionAPI带来的问题,可以让开发者根据业务逻辑阻止自己的代码,让代码具备更好的可读性和可扩展性。实现代码的逻辑提取与复用。
- 重构虚拟DOM。模板编译时的优化,将一些静态节点编译成常量。slot优化,将slot编译成lazy函数,将slot的渲染的决定权交给子组件。模板中内联事件的提取并重用。(原本每次渲染都重新生成内联函数)代码结构调整,更便于Tree shaking,使得体积更小。使用ts替换flow。
- 移除了static文件夹,新增public文件夹,并且index.html移动到public中。
- vue-cli3.0移除了配置文件目录,config和build文件夹。从config/index.js挪到了vue.config.js中。
- vue2中new出来的实例对象,所有的东西都在这个vue对象上,这样其实无论用到还是没用到,都会跑一遍。vue3可以使用esmodule imports按需引入。如:keepalive内纸组件。
vue和react框架理解
- vue是一个构建数据驱动的渐进性框架,它的目标是通过API实现响应数据绑定和视图更新。
- vue的优缺点:数据驱动视图、组件化、强大且丰富的API提供一系列的API能满足业务发开中各类需求、采用虚拟DOM、生态好、社区活跃
- 语法上vue并不限制必须es6+完全js形式编写页面。可以视图和js逻辑尽可能分离。react使用jsx。vue是把html、css、js组合在一起,用各自的处理方法。vue有单文件组件,可以把html、css、js写到一个文件中,html提供了模板引擎来处理
- react整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流。vue的思想是响应式的,也就是数据是可变的,通过对每一个属性建立watcher来监听,当属性变化的时候,响应式的更新对应虚拟DOM。
- react的性能优化需要手动去做,而vue的性能优化是自动的,但是vue的响应式机制也有问题,但是当state特别多的时候,watcher也会很多,会导致卡顿,所以大型应用(状态特别多)一般用react,更加可控
- react是类式的写法,api很少,而vue是声明式的写法,通过传入各种options。api和参数都很多,所以react结合ts更容易一起写。vue稍微复杂。vue结合vue-class-component也可以实现类式的写法,但是还是需要通过decorator来添加声明。并不纯粹。
- react可以通过高阶组件来扩展,而vue需要通过mixins来扩展
- react做的事情很少,很多都交给社区去做,vue很多东西都是内置的,写起来更加方便一些。
宏任务和微任务
- 宏任务:消息队列(回调队列)中的任务称为宏任务,是由宿主环境(浏览器或者node)提供的,不断地从消息队列中提取并被事件循环执行,宏任务在执行时,他不能获取到任务外的上下文。
宏任务和微任务的执行顺序
执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务 执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。