beforeCreate:实例初始化之后;数据观测和事件配置之前调用,组件的选项对象还没有创建,el挂载和data都没有初始化,无法访问方法和数据。
created:实例创建完成之后调用;已经完成了数据观测,属性方法的运算,watch/event 事件回调,data数据初始化已经完成,el挂载还没有开始。
beforeMount:挂载之前调用;el初始化已经完成,vdom已经完成data和模板的结合生成html,但是还没有挂载到html页面里。
mounted:挂载完成之后调用;模板中的html渲染到html页面里。
beforeUpdate:数据更新之前调用;发生在vdom重新渲染和打补丁之前,可以进一步更改状态,不会触发重新渲染。
updated:数据更新,dom渲染之后调用;要避免在这里更改状态,防止更新导致的无线循环。
beforeDestory:实例销毁之前调用;还可以获取到this,一般用于清除定时器和监听事件等。
destoryed:实例销毁之后调用;所有监听事件被清除,子实例被销毁。
activated:(keep-alive的生命周期)页面第一次进入的时候,钩子触发的顺序是created->mounted->activated
deactivated:(keep-alive的生命周期)页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated
vue 是通过 数据劫持 结合 发布者-订阅者 来实现双向绑定;数据劫持通过Object.defineProperty()方法的get和set方法来重新定义,set是设置属性值触发的函数,get是读取属性值触发的函数;
底层实现:
1、监听器observer,object.defineProperty 的set方法监听所有属性,有变动通知订阅者,在这里还需要创建一个消息订阅器Dep来收集订阅者
2、订阅者watcher,接收变化通知执行相应函数,更新view
3、解析器compile,扫描解析每一个节点的指令,初始化订阅器
详细过程参考
Vue3.x改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。
Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?
判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。
监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?
我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。
key 的主要功能是提高 vdom 的更新速度;因为 vue 在 patch (补丁)过程中的 diff 算法对新旧节点比对时是可以通过 key 精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使 patch 过程更高效。(源码位置:src/core/vdom/patch.js)
尽量避免使用index最为key值。
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
vdom 就是用 js 对象来描述真实 DOM,虚拟DOM的实现就是普通对象包含tag、data、children等属性对真实节点的描述。(本质上就是在JS和DOM之间的一个缓存);由于直接操作 DOM 性能低,但是 js 层的操作效率高,可以将 DOM 操作转化成对象操作,最终通过diff算法比对差异进行更新DOM (减少了对真实DOM的操作)。虚拟DOM不依赖真实平台环境从而也可以实现跨平台。
优点:
1、保证性能下限:可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能;
2、无需手动操作dom:极大提高我们的开发效率;
3、跨平台:虚拟 DOM 本质上是 JavaScript 对象;
缺点:
无法进行极致优化: 在一些性能要求极高的应用中虚拟DOM无法进行针对性的极致优化
结构:state(初始化数据)、action(异步处理数据)、mutation(唯一能够修改state的操作)、getter(从state中动态获取相关数据)、module(模块化)
区别:
1、action可以异步,mutation必须同步
2、mutation是唯一可以修改state的(commit)
3、action修改state需要经过mutation(dispatch)
原理:属于 XMLHttpRequest,是一个基于 Promise 的 http 请求库,可用于浏览器和 Node
优点:
1、Axios 是一个基于 promise 的 HTTP 库,支持 promise 所有的 API
2、它可以拦截请求和响应
3、对响应回来的内容自动转换成 JSON 类型的数据
4、安全性更高,客户端支持防御 XSRF
5、可以取消请求
6、在浏览器中发送请求会创建 XMLHttpRequests
7、在 node.js 发送请求会创建 http请求
中断请求:使用 CancelToken 函数
1)、使用 cancelToken.sourse 工厂方法创建 cancel token
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
}else {
// 处理错误
}
});
source.cancel('xxx');
2)、通过传递一个 executor 函数到CancelToken构造函数创建cancel token
const CancelToken = axios.CancelToken;
let cancel
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c){
cancel = c
})
});
cancel();
注意: executor 处理器函数接收两个函数 resolve 和 reject 作为参数,异步任务执行成功调用 resolve,否则调用reject。
1、不支持IE8.0及其以下版本;
2、入门快,但是高阶教程少;
3、生态环境不如其他框架;
4、社区不大;
相同:
1、都使用vdom;
2、都提供了响应式和组件化的视图组件;
3、核心都在UI层(比如路由,状态管理,数据请求交给其他库);
4、都是单向数据流;
5、轻量级
不同:
1、vue 基于 html 的模板语法,react 是 jsx 语法,可以 html 和 js 混写;
2、状态管理 vue 是 data,react 是 state 但是不能直接修改 state,需要 setState 方法;
3、vue 使用 solt 插槽进行嵌套传递,react 使用 props.children 传递;
4、vue 使用指令渲染,react 逻辑运算符;
5、父子组件传值,vue 使用 props 和 $emit,react 使用 props 和子组件触发父组件的方法,父组件通过setState修改;
6、路由,vue 路由组件都会渲染到 router-view 里面,react 是全局组件的方式,子组件作为 children 传递到父组件;
7、vue 实现双向绑定,react 没有;
8、vue 父组件更新子组件不会动,react 父更新子必更新,需要手动设置;
区别:
1、都是观察数据变化的(相同);
2、计算属性将会混入到 vue 的实例中,所以需要监听自定义变量;watch 监听 data 、props 里面数据的变化;
3、computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
4、watch 支持异步,computed 不支持;
5、watch 是一对多(监听某一个值变化,执行对应操作);computed 是多对一(监听属性依赖于其他属性)
6、watch 监听函数接收两个参数,第一个是最新值,第二个是输入之前的值;
7、computed 属性是函数时,都有 get 和 set 方法,默认走 get 方法,get 必须有返回值(return)
watch的参数:
deep:深度监听
immediate :组件加载立即触发回调函数执行
computed缓存原理:
conputed本质是一个惰性的观察者;当计算数据存在于 data 或者 props里时会被警告;
vue初次运行会对 computed 属性做初始化处理(initComputed),初始化的时候会对每一个 computed 属性用 watcher 包装起来 ,这里面会生成一个 dirty 属性值为 true;然后执行 defineComputed 函数来计算,计算之后会将 dirty 值变为 false,这里会根据 dirty 值来判断是否需要重新计算;如果属性依赖的数据发生变化,computed 的 watcher 会把 dirty 变为 true,这样就会重新计算 computed 属性的值。
区别:
1、 url 展示上,hash 有“#”,history 没有
2、hash 可以支持低版本浏览器和 IE8 ,history 只兼容到 IE10
3、history 模式需要后端配合将所有访问都指向 index.html,否则用户刷新页面,会导致404错误
原理:
hash:通过 onhashchange 事件监听 hash 变化,然后根据 hash 的变化更新页面部分内容(hash变化不会触发浏览器请求,但是会触发 hashchange 事件)。
history:主要是 H5 新增的两个 API(pushState、replaceState);他们可以改变url,但是不会发送请求,这样就可以通过 onpopstate 监听 url 变化来实现页面部分内容更新。
补充:pushState、replaceState 这两个方法应用于浏览器的历史记录栈,在当前已有的back、forward、go的基础上,他们提供了对当前浏览器进行修改的功能,只是当它们被修改时,虽然浏览器的URL发生
作用:vue 更新 dom 是异步更新的,数据变化,dom 的更新不会马上完成;nextTick 的回调是在下次 DOM 更新循环结束之后执行的延迟回调。
实现:(源码:src/core/util/next-tick.js)
原理是使用异步方法处理任务,vue 会根据当前环境优先使用 promise.then、MutationObserver 、setImmediate,如果都不支持就使用 setTimeout 把函数延迟到 DOM 更新之后再使用。(原因是宏任务消耗大于微任务,优先使用微任务,最后使用消耗最大的宏任务)
promise:可以将函数延迟到当前函数调用栈最末端;
MutationObserver :是h5新加的一个功能,其功能是监听dom节点的变动,在所有dom变动完成后,执行回调函数;
setImmediate:用于中断长时间运行的操作,并在浏览器完成其他操作(如事件和显示更新)后立即运行回调函数;
种类:
1)、匿名插槽:只能有一个
2)、实名插槽:可以有多个,在使用时必须使用name属性来标识
原理:
1、父组件先解析,把插槽当作子组件的子元素处理;
2、子组件解析,solt作为一个占位符,会被解析成一个函数;
3、函数传入参数执行,拿到第一步解析得到的插槽节点,并返回;
3)、作用域插槽:父组件获取子组 件solt 中携带的数据
为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 元素的一个 attribute 绑定上去:
//子组件
<span>
<slot v-bind:user="user">
{{ user.lastName }}
slot>
span>
绑定在 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
//父组件
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
template>
current-user>
我们选择将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。
原理:
1、父组件先解析,把插槽包装成一个函数保存给节点;
2、子组件解析,solt作为一个占位符,会被解析成一个函数;
3、函数传入参数执行,跟普通插槽一样。但是会多出来插槽传递出来的数据。
本题参考
keep-alive 主要是组件缓存,采用的是LRU算法。
常用的两个属性 include(要缓存的)/ exclude(不要缓存的),允许组件有条件的进行缓存;都可以用逗号分隔字符串、正则表达式或一个数组来表示
因为keep-alive会将组件保存在内存中,并不会销毁以及重新创建,所以不会重新调用组件的created等方法,所以它自己有两个生命周期activated / deactivated,用来得知当前组件是否处于活跃状态。
缓存实现过程:
1、在 vue 实例创建的时候,created 钩子会创建一个 cache 对象,用来作为缓存容器,保存 vdom 节点。
2、destroyed钩子则在组件被销毁的时候清除cache缓存中的所有组件实例。
3、在mounted钩子的 render 函数中会先获取 keep-alive 里面组件的名称,然后根据 include 和 exclude 来匹配看看是否要缓存,如果不匹配直接直接返回 vdom ;否则会根据 key 在 this.cache 缓存器里面查找,如果存在则说明之前已经缓存了,直接返回缓存的 vdom 覆盖当前的 vdom ;否则就会将当前的 vdom 存储在 cache 中。
4、最后将该组件实例的 keepAlive 设置为 true。
1)、MVVM 即 Model-View-ViewModel 的简写。即模型-视图-视图模型。模型(Model)指的是后端传递的数据。视图(View)指的是所看到的页面。视图模型(ViewModel)是 mvvm 模式的核心,它是连接 view 和 model 的桥梁。它有两个方向:一是将模型(Model)转化成视图(View),即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将视图(View)转化成模型(Model),即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。
2)、MVC 是 Model-View- Controller 的简写。即模型-视图-控制器。M 和 V 指的意思和 MVVM 中的 M 和 V 意思一样。C 即 Controller 指的是页面业务逻辑。使用 MVC 的目的就是将 M 和 V 的代码分离。MVC 是单向通信。也就是 View 跟 Model,必须通过 Controller 来承上启下。MVC和MVVM的区别并不是VM完全取代了C,只是在MVC的基础上增加了一层VM,只不过是弱化了C的概念,ViewModel 存在目的在于抽离 Controller 中展示的业务逻辑,而不是替代 Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用,使开发更高效,结构更清晰,增加代码的复用性。
可以参考阮一峰的文章很详细
template 的作用是模板占位符,可帮助我们包裹元素,但在循环过程当中,template不会被渲染到页面上
在 vue 实例初始化 $mount 的时候,先调用 render 函数如果 render 函数不存在,则调用 template 进行编译得到 render 函数。如果没有 template 则会调用 el 来获取 template。
渲染过程:
1、获取模板,并将模板通过 compile 编译器(通过正则等方式解析 template里面的指令,class、style等)编译成 AST 语法树;
2、把得到的 AST 通过 generate 转化成 render 渲染函数,render 函数的返回值是 vdom;
3、vue 构造函数直接使用 render 渲染函数渲染 dom;
SPA (single-page application:单页面应用程序)在页面初始化的时候加载相应的 HTML、js、css,一旦加载完成,页面不会因为用户的操作进行页面的重新加载或者跳转,而是使用路由机制实现HTML内容转换,避免页面重新加载。
优点:
1、 用户体验好,内容修改不需要重新渲染页面,避免不必要的跳转和渲染;
2、SPA 对服务器的压力比较小;
3、前后端分离,架构清晰,前端负责页面,后端负责数据;
缺点:
1、首屏加载慢;
2、所有内容在同一个页面动态展示,不利于 SEO 优化;
1)、去掉编译文件中的 map 文件
map文件主要是帮我们线上调试代码、查看样式的,通常线上不需要这些文件;找到 config/index.js 文件,将 productionSourceMap 设置为 false。
2)、开启 gzip 打包压缩 && 后台配合 gzip 访问
首先安装依赖 compression-webpack-plugin ,推荐使用 1.1.11 版本;找到 config/index.js 文件,将 productionGzip 设置为 true;
npm install --save-dev [email protected]
在nginx.conf 里面配置
http { //在 http中配置如下代码,
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 8; #压缩级别
gzip_buffers 16 8k;
#gzip_http_version 1.1;
gzip_min_length 100; #不压缩临界值
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
}
3)、路由懒加载
组件的延迟加载,可以把页面资源划分为多份,用到的时候才会按需加载,这样减少第一次加载的消耗。
4)、使用 CDN 引入第三方库减少服务器压力
首先、在index.html 文件中添加 CDN 相关的代码,以vue、vue-router、vuex 为例
<body>
<div id="app">div>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js">script>
<script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js">script>
<script src="https://cdn.bootcss.com/vue-router/3.0.4/vue-router.min.js">script>
body>
在 vue.config.js(vue-cli3) 文件 ,build/webpack.base.conf.js(vue-cli2)文件,增加 externals,将引入的外部模块导入
module.exports = {
entry: {
app: './src/main.js'
},
externals:{
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex':'Vuex'
}
最后、去掉 inport 以及 Vue.use(xxx)
//import Vue from 'vue'
//import Router from 'vue-router'
//import Vuex from 'vuex'
//Vue.use(Router)
///Vue.use(Vuex)
好处:加快打包速度,减轻服务器访问压力。
5)、尽量使用轻量级的工具库
moment是处理时间的标杆,但是它过于庞大,我们可以使用day.js替代
6)、对于短时间的大量操作(缩放、滚动)
使用防抖、节流函数
7)、代码精简,去除 console ,可复用的方法、组件提取出来
8)、不要写行内样式,避免dom重绘
9)、按需引入
比如我们只使用 elementUI 里面的部分组件,我们只引入需要用到的组件
10)、SPA页面采用keep-alive缓存组件
11)、key保证唯一性
12)、v-if 当值为false时内部指令不会执行,具有阻断功能,很多情况下使用v-if替代v-show
13、服务端渲染ssr
1)、加载过程:父beforeCreate->父cerated->父beforeMount->子beforeCreate->子cerated->子beforeMount>子mounted->父mounted
2)、子组件更新:父beforeUpdate->子beforeUpdate->子updated->父updated
3)、父组件更新:父beforeUpdate->父updated
4)、销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
作用:用来注册使用插件或者组件的方法。
原理:
1、检测组件是否注册,避免重复注册;
2、处理入参,将第一个参数之后的参数归集,并在首部插入 this 上下文;
3、第一个参数是对象就执行对象里面的install方法,是方法则直接执行这个方法,然后缓存;
4、返回;
Proxy 的优势如下:
1)Proxy 可以直接监听对象而非属性;
2)Proxy 可以直接监听数组的变化;
3)Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
4)Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
5)Proxy 作为新标准将受到浏览器厂商重点持续的性能优化;
Object.defineProperty 的优势如下:
兼容性好,支持 IE9,而Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
v-model本质就是一个语法糖,可以看成是value + input方法的语法糖,是 vue 的双向绑定指令。一方面 model 层通过 defineProperty 来劫持每个属性,一旦监听到变化通过相关的页面元素更新。另一方面通过编译模板文件,为控件的 v-model 绑定 input 事件,从而页面输入能实时更新相关 data 属性值。
可以通过model属性的prop和event属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性。
v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
1)text 和 textarea 元素使用 value 属性和 input 事件;
2)checkbox 和 radio 使用 checked 属性和 change 事件;
3)select 字段将 value 作为 prop 并将 change 作为事件。
1、v-if 是真正的条件渲染,直到条件第一次变为真时,才会开始渲染。
2、v-show 不管初始条件是什么都会渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
3、v-show 则适用于需要非常频繁切换条件的场景;不频繁切换使用 v-if 更合理。
1、v-for 优先于 v-if 被解析
2、如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能
3、要避免出现这种情况,则在外层嵌套template,在这一层进行v-if判断,然后在内部进行v-for循环
4、如果条件出现在循环内部,可通过计算属性提前过滤掉那些不需要显示的项
Vue.mixin的作用就是抽离公共的业务逻辑,原理类似“对象的继承”,在使用 mixin 的组件中引入后,mixin 中的方法和属性也就并入到该组件中,可以直接使用;当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并,如果混入的数据和本身组件中的数据冲突,会采用“就近原则”以组件的数据为准。
//全局引用 main.js
import mixin from './mixin'
Vue.mixin(mixin)
//在vue文件中引用
import '../mixin'; // 引入mixin文件
export default {
mixins: [mixin]
}
同名选项合并:
1、 数据对象(data)在内部会进行递归合并,在和组件的数据发生冲突时以组件数据优先。
2、同名钩子函数(created,mounted…)将混合为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
3、值为对象的选项(methods, components 和 directives)将被混合为同一个对象。两个对象键名冲突时,取组件对象的键值对。
起因:因为 ES5 的限制,vue 无法检测到对象属性的添加和删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
用法:Vue.set(target, key, value)
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性。(对象不可以是 Vue 实例)
执行流程:
1、判断在非生产环境,传入的 target 如果是undefined、null 或是原始类型(值是否有效),则直接抛出错误。
2、target 如果是个数组,并且 key 是索引的话,那么就取当前数组长度与 key 这两者的最大值作为数组的新长度,然后使用数组的 splice 方法将传入的索引 key 对应的 val 值添加进数组,进行重写数组。
3、如果传入的 target 不是数组,那就当作是对象来处理,vue实例直接退出,不是响应式数据则直接赋值然后返回;是响应式数据则将属性定义成响应式的 defineReactive(ob.value, key, val)
4、使用 ob.dep.notify() 触发更新视图
Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上,this.$set() 是将 set 函数绑定在 Vue原型上。
1、可以通过名字找到对应的组件( 递归组件:组件自身调用自身 )
2、可以通过 name 属性实现缓存功能 (keep-alive)
3、可以通过 name 来识别组件(跨级组件通信时非常重要)
4、使用 vue-devtools 调试工具里显示的组见名称是由 vue 中组件 name 决定的
1)、props / $emit 适用 父子组件通信(常规操作)
父组件通过 props 传值给子组件,子组件 $emit 把自己的消息发送给父组件
2)、ref 与 $parent / $children 适用 父子组件通信
ref:在 dom 元素上绑定 ref 属性,然后通过 $refs 获取该属性的所有 dom 元素和组件实例
$parent / $children:访问父 / 子实例(可以取值,不可修改)
3)、EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信
通过一个空的Vue实例作为中央事件总线,用它来触发事件和监听事件
4)、$ attrs/$listeners 适用于 隔代组件通信
$ attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=“$attrs” 传入内部组件。通常配合 interitAttrs 选项一起使用。
$ listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=“$listeners” 传入内部组件
简单来说:$ attrs与$ listeners 是两个对象,$ attrs 里存放的是父组件上绑定的非 Props 属性,$ listeners里存放的是父组件中绑定的非原生事件。
5)、provide / inject 适用于 隔代组件通信
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
6)、Vuex 适用于 父子、隔代、兄弟组件通信
组件是可复用的,一个组件被创建好之后,就可能被用在各个地方,而组件不管被复用了多少次,组件中的 data 数据都应该是相互隔离,互不影响的,基于这一理念,组件每复用一次,data 数据就应该被复制一次,之后,当某一处复用的地方组件内 data 数据被改变时,其他复用地方组件的 data 数据不受影响.
组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果。(对象为引用类型,当重用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题)
vue 把 data、props、store 等数据做成响应式,也就是会对这些响应式数据做深度监听,给每一个object类型的key(包括嵌套object)添加observer(vue3使用proxy)。所以如果我们不需要数据是响应式的,可以在.vue 文件头部直接使用 let、const 定义变量,在组件销毁的时候将该这些变量设为null。
答案是传递的是对象和数组可以修改,如果是基础数据类型也可以修改,但是控制台会报错;对象和数组修改之后父组件是可以监听到这个值的变化的。那么为什么呢?
对象和数组都是引用类型,父组件传递过来的是一个地址,子组件修改的是地址里面的内容,地址本身并没有变,所以不会报错,但是基础数据类型就不同了,他是直接修改了传递的值,但是 vue 不允许在子组件里面直接修改prop值,所以就会报错。
那么基础类型要怎么修改呢?
1)、可以使用.sync 修饰符来修改。参考:vue 父子组件双向绑定传值的实现
2)、子组件用 data 或者 computed 重新定义一个变量,然后修改新定义的变量。
Vue的生命周期钩子就是回调函数而已,当创建组件实例的过程中会调用对应的钩子方法。
内部主要是使用callHook方法来调用对应的方法。核心是一个发布订阅模式,将钩子订阅好(内部采用数组的方式存储),在对应的阶段进行发布。
作用:解析和转换.vue文件。提取出其中的逻辑代码 script,样式代码style,以及HTML 模板template,再分别把他们交给对应的loader去处理
用途:js可以写es6,style样式可以写scss或less
特性:
1、允许为 Vue 组件的每个部分使用其它的 webpack loader,例如在
css-loader:加载由vue-loader提取出的CSS代码
vue-template-compiler:把vue-loader提取出的HTML模板编译成可执行的javascript代码
1、使用函数劫持的方式,重写了数组的方法(push、pop、shift、unshift、sort、reverse、splice)
2、Vue 将 data 中的数组,进行了原型链重写。指向了自己定义的数组原型方法,这样当调用数组api时,可以通知依赖更新.如果数组中包含着引用类型。会对数组中的引用类型再次进行监控。
流程:
1、初始化传入 data 数据执行 initData
2、将数据进行观测 new Observer
3、将数组原型方法指向重写的原型
4、深度观察数组中的引用类型
1、 每一个属性都有自己的 dep 属性,来存放依赖的 Watcher,属性发生变化后会通知 Watcher 去更新。
2、在用户获取(getter) 数据时 Vue 给每一个属性都添加了 dep 属性来收集 Watcher。在用户 setting 设置属性值时 dep.notify() 通知 收集的Watcher 重新渲染。
3、Dep依赖收集类和 Watcher类 是多对多双向存储的关系。
4、每一个属性都可以有多个 Watcher 类,因为属性可能在不同的组件中被使用。
5、同时一个 Watcher 类 也可以对应多个属性。
注意:Dep 是一个用来负责收集 Watcher 的类,Watcher 是一个封装了渲染视图逻辑的类,用于派发更新的。Watcher 是不能直接更新视图的还需要结合 vdom 经过 patch() 中的 diff 算法才可以生成真正的 DOM
全局的路由钩子函数:beforeEach、afterEach(一般用于全局进行权限跳转)
单个的路由钩子函数:beforeEnter、beforeLeave(路由内部钩子,一般在路由表里)
组件内的路由钩子函数:beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate
beforeEach:每一次路由该变的之后页面加载之前执行,三个参数(to 将要进入的路由对象、from 即将离开的路由对象、next 跳转方法),next 必须调用
afterEach:每一次路由该变的之后页面加载之后执行;两个参数(to 将要进入的路由对象、from 即将离开的路由对象)
beforeEnter:进入指定路由跳转时需要执行的逻辑
beforeLeave:离开指定路由跳转时需要执行的逻辑
beforeRouteEnter、beforeRouteLeave、 beforeRouteUpdate都是写在组件里面,也有三个参数(to、from、next)
1、$route 是“路由信息对象”,包括 path , params , hash , query ,fullPath , matched , name 等路由信息参数。
2、⽽ $router 是“路由实例”对象包括了路由的跳转⽅法,钩⼦函数等
diff 算法的原理是递归加上遍历节点来比较节点复用性。diff 算法通过对比虚拟树的差异,将差异通过 patch (补丁)一边比较一边打补丁方式更新到真实 dom 节点上。patch 函数是 diff 流程的入口函数。在采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。
在数据发生变化的时候,set 方法就会调用 dep.notify 通知所有订阅者,订阅者会调用 patch 给真实的 dom 打补丁,更新相应的视图。
1、patch 函数接收两个参数 oldVnode 和 Vnode 分别代表旧节点和新节点,对比两个节点是否相同,不相同则创建新节点插入到旧节点之前,同时删除旧节点;相同则对节点打补丁。
2、判断节点相同是根据 sameVnode 方法,当 tag key inputType 完全相同时,我们认定节点可复用。
3、打补丁调用了 patchVnode 函数,先拿到真实 dom;判断 oldVnode 和 Vnode 是否指向同一个对象;是则直接 return,否则如果都有文本节点并且不相等,则把新节点的文本更新给真实 dom。
4、如果 oldVnode 有子节点而 Vnode 没有,则删除真实 dom的子节点。
5、如果 oldVnode 没有子节点而 Vnode 有,则将 Vnode 的子节点真实化之后添加到真实 dom。
6、 如果两者都有子节点,则执行 updateChildren 函数比较子节点。
7、首先将 oldVnode 和 Vnode 的子节点提取出来;
8、这两个子节点都有两个变量 StartIdx 和 EndIdx,通过这两个变量相互比较(开始与开始、结束与结束、开始与结束、结束与开始)相同则复用,同时移动指(节点)。
9、不同则遍历老节点,找到与 newStartIdx 对应元素的 key 一样的节点;把当前位置设置为 undefined,然后把找到的元素移到老节点对应位置,所有移动完成之后会将超出新节点长度的元素删除掉。
更详细内容参考
作⽤是扩展组件⽣成⼀个构造器,通常会与 $mount ⼀起使⽤。
<div id="mount-point">div>
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
这样div里面就有了下面内容
<p>Walter White aka Heisenbergp>
1、 mixin ⽤于全局混⼊,会影响到每个组件实例,通常插件都是这样做初始化的
2、 mixins 应该是我们最常使⽤的扩展组件的⽅式了。如果多个组件中有相同的业务逻辑, 就可以将这些逻辑剥离出来,通过 mixins 混⼊代码,⽐如上拉下拉加载数据这种逻辑等。
渲染:
1、把模板解析为render函数
2、触发响应式,监听data属性getter setter
3、执行render函数,生成vnode,patch(elem,vnode)
更新:
1、修改data,触发setter(此前在getter中已被监听)
2、重新执行render函数,生成newVnode
3、patch(vnode,newVnode)
因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染;所以为了性能考虑,Vue会在本轮数据更新后,再去异步更新视图。
原理:
1、调用 notify() 方法,通知watcher 进行更新操作
2、依次调用watcher 的 update 方法
3、对watcher 进行去重操作(通过id),放到队列里
4、执行完后异步清空这个队列, nextTick(flushSchedulerQueue) 进行批量更新操作
组件功能多打包出的结果会变大,我可以采用异步的方式来加载组件。
异步组件的核心是把组件定义成一个函数,主要依赖import()这个语法,可以实现文件的分割加载。
原理:在createComponent方法中,会有相应的异步组件处理
1、首先定义一个asyncFactory变量,然后进行判断,如果组件是一个函数,然后会去调resolveAsyncComponent方法;
2、然后将赋值在asyncFactory上的函数传进去,会让asyncFactory马上执行,执行的时候并不会马上返回结果,因为他是异步的,返回的是一个promise,这时候这个值就是undefined,然后就会先渲染一个异步组件的占位,空虚拟节点。
3、如果加载完之后会调factory函数传入resolve和reject两个参数,执行后返回一个成功的回调和失败的回调,promise成功了就会调resolve;
4、resolve中就会调取forceRender方法强制更新视图重新渲染,forceRender中调取的就是$forceUpdate,同时把结果放factory.resolved上;
5、如果强制刷新的时候就会再次走resolveAsyncComponent方法,这时候有个判断,如果有成功的结果就把结果直接放回去,这时候resolveAsyncComponent返回的就不是undefined了,就会接的创建组件,初始化组件,渲染组件。
⽤ timeline ⼯具。 通过 timeline 来查看每个函数的调⽤时常,定位出哪个函数的问题,从⽽能判断哪个组件出了问题
1、源码组织方式变化:使用 TS 重写
2、支持 Composition API:基于函数的API,更加灵活组织组件逻辑(vue2用的是options api)
3、响应式系统提升:Vue3中响应式数据原理改成proxy,可监听动态新增删除属性,以及数组变化
4、编译优化:vue2通过标记静态根节点优化diff,Vue3 标记和提升所有静态根节点,diff的时候只需要对比动态节点内容
5、打包体积优化:移除了一些不常用的api(inline-template、filter)
6、生命周期的变化:使用setup代替了之前的beforeCreate和created
7、Vue3的template模板支持多个根标签
8、Vuex状态管理:创建实例的方式改变,Vue2为new Store , Vue3为createStore
9、Route获取页面实例与路由信息:vue2通过this获取router实例,vue3通过使用 getCurrentInstance/ userRoute和userRouter方法获取当前组件实例
10、支柱Props的使用变化:vue2通过this获取props里面的内容,vue3直接通过 props
11、父子组件传值:vue3在向父组件传回数据时,如使用的自定义名称,如backData,则需要在emits中定义一下
https://blog.csdn.net/r657225738/article/details/115551368
watch 在监听对象的时候,如果 deep 值是 true,就会执行 traverse 这个方法,这个方法里就是做了个数组递归,如果是数组的话,会根据数组的每一项索引取值,进行递归追加依赖,如果是对象会拿 key 进行遍历取值,进行递归追加依赖,traverse 就是deep:true实现的核心。这样就会把数组或者对象的每一个属性都追加依赖进行监听,只要依赖发生变化就会通知视图更新。
1、可能会导致xss攻击,一定要保证你的内容是可以依赖的
2、v-html会替换掉标签内部的子元素
3、单页面文件里,scoped 样式不会作用在 v-html 内部,因为这部分内容没有被 模板编译器处理
4、v-html更新的是元素的 innerHTML 。内容按普通 HTML 插入, 不会作为 Vue 模板进行编译
5、包含的 js 不会执行,因为浏览器渲染的时候并不会渲染 js,这时要在$nextTick中动态创建script标签并插入
提供一个在页面上已存在的 DOM元素作为 Vue实例的挂载目标.可以是 CSS 选择器,也可以是一个 HTMLElement 实例,
动态组件就是几个组件放在一个挂载点下,然后根据父组件的某个变量来决定显示哪个,或者都不显示。
在挂载点使用 component 标签,然后使用 is =“组件名”,它会自动去找匹配的组件名,如果有,则显示;
<div id="app">
<component is="one">component>
div>
new Vue({
el: '#app',
components: {
one: {template: '<div>我是线路一div>'},
two: {template: '<div>我是线路二div>'},
thr: {template: '<div>我是线路三div>'}
}
})
可以有两种方式传递参数:params、query
1)、params:在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 router 对象的 params.id 获取。
//index.js
path:'/user/:id',
compunent: user
// 方法1:
<router-link :to="{ name: 'users', params: { id: 123}}">按钮router-link>
// 方法2:
this.$router.push({name:'users',params:{id:123}})
// 方法3:
this.$router.push('/user/' + 123)
2)、query:不需要配置路由格式,使用 router 对象的 query.id 获取。
// 方法1:
<router-link :to="{ name: 'users', query: { id: 123}}">按钮router-link>
// 方法2:
this.$router.push({ name: 'users', query:{ id:123}})
// 方法3:
<router-link :to="{ path: '/user', query: { id:123 }}">按钮router-link>
// 方法4:
this.$router.push({ path: '/user', query:{ id:123 }})
// 方法5:
this.$router.push('/user?uname=' + 123)
自定义指令的生命周期,有5个事件钩子,可以设置指令在某一个事件发生时的具体行为:
bind: 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新(详细的钩子函数参数见下)。
componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
unbind: 只调用一次, 指令与元素解绑时调用。
钩子函数的参数 (包括 el,binding,vnode,oldVnode)
el: 指令所绑定的元素,可以用来直接操作 DOM 。
binding: 一个对象,包含以下属性:name: 指令名、value: 指令的绑定值、oldValue: 指令绑定的前一个值、expression: 绑定值的字符串形式、arg: 传给指令的参数、modifiers: 一个包含修饰符的对象。
vnode: Vue 编译生成的虚拟节点。
oldVnode: 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
参考:vue 全局注册过滤器 filter
1)、extend:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件对象。
2)、mixins:可以混入多个,当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
var vm = new Vue({
created: function () { console.log(3) },
mixins: [mixinA ,mixinB ]
})
3)、extends:声明扩展另一个组件(可以是一个简单的选项对象或构造函数)无需使用 Vue.extend。这主要是为了便于扩展单文件组件。和 mixins 类似。但extends只能继承一个。
var otherComp= { ... }
// 继承 otherComp
var comp = {
extends: otherComp,
...
}
总结:
1、都是为了拓展组件的功能,优先级extend>extends>mixins
2、extend用于创建vue实例,是扩展组件的构造器
3、mixins可以混入多个mixin,extends只能继承一个
以上是我总结的 VUE 相关的知识点,还会继续更新,如有错误或者遗漏的地方欢迎指正!!!