Vue 组件间通信有哪几种形式?
Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。
(1)props / $emit
实用 父子组件通信
这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。
(2)ref
与 $parent / $children
实用 父子组件通信
ref
:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例$parent
/$children
:拜访父 / 子实例
(3)EventBus($emit / $on)
实用于 父子、隔代、兄弟组件通信
这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。
(4)$attrs
/$listeners
实用于 隔代组件通信
$attrs
:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 (class 和 style 除外)。当一个组件没有申明任何 prop 时,这里会蕴含所有父作用域的绑定 (class 和 style 除外),并且能够通过v-bind="$attrs"
传入外部组件。通常配合 inheritAttrs 选项一起应用。$listeners
:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过v-on="$listeners"
传入外部组件
(5)provide / inject
实用于 隔代组件通信
先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。
(6)Vuex 实用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state)。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
- 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。
Vue template 到 render 的过程
vue 的模版编译过程次要如下:template -> ast -> render 函数
vue 在模版编译版本的码中会执行 compileToFunctions 将 template 转化为 render 函数:
// 将模板编译为 render 函数 const {render, staticRenderFns} = compileToFunctions(template,options// 省略}, this)
CompileToFunctions 中的次要逻辑如下∶ (1)调用 parse 办法将 template 转化为 ast(形象语法树)
constast = parse(template.trim(), options)
- parse 的指标:把 tamplate 转换为 AST 树,它是一种用 JavaScript 对象的模式来形容整个模板。
- 解析过程:利用正则表达式程序解析模板,当解析到开始标签、闭合标签、文本的时候都会别离执行对应的 回调函数,来达到结构 AST 树的目标。
AST 元素节点总共三种类型:type 为 1 示意一般元素、2 为表达式、3 为纯文本
(2)对动态节点做优化
optimize(ast,options)
这个过程次要剖析出哪些是动态节点,给其打一个标记,为后续更新渲染能够间接跳过动态节点做优化
深度遍历 AST,查看每个子树的节点元素是否为动态节点或者动态节点根。如果为动态节点,他们生成的 DOM 永远不会扭转,这对运行时模板更新起到了极大的优化作用。
(3)生成代码
const code = generate(ast, options)
generate 将 ast 形象语法树编译成 render 字符串并将动态局部放到 staticRenderFns 中,最初通过 new Function(
` render`)
生成 render 函数。
动静给 vue 的 data 增加一个新的属性时会产生什么?怎么解决?
Vue 不容许在曾经创立的实例上动静增加新的响应式属性
若想实现数据与视图同步更新,可采取上面三种解决方案:
Vue.set()
Object.assign()
$forcecUpdated()
Vue.set()
Vue.set(target, propertyName/index, value)
参数
{Object | Array} target
{string | number} propertyName/index
{any} value
返回值:设置的值
通过 Vue.set
向响应式对象中增加一个 property
,并确保这个新 property
同样是响应式的,且触发视图更新
对于 Vue.set
源码(省略了很多与本节不相干的代码)
源码地位:src\core\observer\index.js
function set (target: Array<any> | Object, key: any, val: any): any {
...
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
这里无非再次调用 defineReactive
办法,实现新增属性的响应式
对于 defineReactive
办法,外部还是通过 Object.defineProperty
实现属性拦挡
大抵代码如下:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {get() {console.log(`get ${key}:${val}`);
return val
},
set(newVal) {if (newVal !== val) {console.log(`set ${key}:${newVal}`);
val = newVal
}
}
})
}
Object.assign()
间接应用 Object.assign()
增加到对象的新属性不会触发更新
应创立一个新的对象,合并原对象和混入对象的属性
this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})
$forceUpdate
如果你发现你本人须要在 Vue
中做一次强制更新,99.9% 的状况,是你在某个中央做错了事
$forceUpdate
迫使Vue
实例从新渲染
PS:仅仅影响实例自身和插入插槽内容的子组件,而不是所有子组件。
小结
- 如果为对象增加大量的新属性,能够间接采纳
Vue.set()
- 如果须要为新对象增加大量的新属性,则通过
Object.assign()
创立新对象 - 如果你切实不晓得怎么操作时,可采取
$forceUpdate()
进行强制刷新 (不倡议)
PS:vue3
是用过 proxy
实现数据响应式的,间接动静增加新属性仍能够实现数据响应式
对 SSR 的了解
SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端实现,而后再把 html 间接返回给客户端
SSR 的劣势:
- 更好的 SEO
- 首屏加载速度更快
SSR 的毛病:
- 开发条件会受到限制,服务器端渲染只反对 beforeCreate 和 created 两个钩子;
- 当须要一些内部扩大库时须要非凡解决,服务端渲染应用程序也须要处于 Node.js 的运行环境;
- 更多的服务端负载。
Vue 模版编译原理晓得吗,能简略说一下吗?
简略说,Vue 的编译过程就是将 template
转化为 render
函数的过程。会经验以下阶段:
- 生成 AST 树
- 优化
- codegen
首先解析模版,生成AST 语法树
(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是 将优化后的 AST 树转换为可执行的代码
。
assets 和 static 的区别
相同点: assets
和 static
两个都是寄存动态资源文件。我的项目中所须要的资源文件图片,字体图标,款式文件等都能够放在这两个文件下,这是相同点
不相同点:assets
中寄存的动态资源文件在我的项目打包时,也就是运行 npm run build
时会将 assets
中搁置的动态资源文件进行打包上传,所谓打包简略点能够了解为压缩体积,代码格式化。而压缩后的动态资源文件最终也都会搁置在 static
文件中跟着 index.html
一起上传至服务器。static
中搁置的动态资源文件就不会要走打包压缩格式化等流程,而是间接进入打包好的目录,间接上传至服务器。因为防止了压缩间接进行上传,在打包时会进步肯定的效率,然而 static
中的资源文件因为没有进行压缩等操作,所以文件的体积也就绝对于 assets
中打包后的文件提交较大点。在服务器中就会占据更大的空间。
倡议: 将我的项目中 template
须要的款式文件 js 文件等都能够搁置在 assets
中,走打包这一流程。缩小体积。而我的项目中引入的第三方的资源文件如iconfoont.css
等文件能够搁置在 static
中,因为这些引入的第三方文件曾经通过解决,不再须要解决,间接上传。
参考 前端进阶面试题具体解答
Vue 的性能优化有哪些
(1)编码阶段
- 尽量减少 data 中的数据,data 中的数据都会减少 getter 和 setter,会收集对应的 watcher
- v-if 和 v -for 不能连用
- 如果须要应用 v -for 给每项元素绑定事件时应用事件代理
- SPA 页面采纳 keep-alive 缓存组件
- 在更多的状况下,应用 v -if 代替 v -show
- key 保障惟一
- 应用路由懒加载、异步组件
- 防抖、节流
- 第三方模块按需导入
- 长列表滚动到可视区域动静加载
- 图片懒加载
(2)SEO 优化
- 预渲染
- 服务端渲染 SSR
(3)打包优化
- 压缩代码
- Tree Shaking/Scope Hoisting
- 应用 cdn 加载第三方模块
- 多线程打包 happypack
- splitChunks 抽离公共文件
- sourceMap 优化
(4)用户体验
- 骨架屏
- PWA
- 还能够应用缓存 (客户端缓存、服务端缓存) 优化、服务端开启 gzip 压缩等。
对 keep-alive 的了解,它是如何实现的,具体缓存的是什么?
如果须要在组件切换的时候,保留一些组件的状态避免屡次渲染,就能够应用 keep-alive 组件包裹须要保留的组件。
(1)keep-alive
keep-alive 有以下三个属性:
- include 字符串或正则表达式,只有名称匹配的组件会被匹配;
- exclude 字符串或正则表达式,任何名称匹配的组件都不会被缓存;
- max 数字,最多能够缓存多少组件实例。
留神:keep-alive 包裹动静组件时,会缓存不流动的组件实例。
次要流程
- 判断组件 name,不在 include 或者在 exclude 中,间接返回 vnode,阐明该组件不被缓存。
- 获取组件实例 key,如果有获取实例的 key,否则从新生成。
- key 生成规定,cid +”∶∶”+ tag,仅靠 cid 是不够的,因为雷同的构造函数能够注册为不同的本地组件。
- 如果缓存对象内存在,则间接从缓存对象中获取组件实例给 vnode,不存在则增加到缓存对象中。5. 最大缓存数量,当缓存组件数量超过 max 值时,革除 keys 数组内第一个组件。
(2)keep-alive 的实现
const patternTypes: Array<Function> = [String, RegExp, Array] // 接管:字符串,正则,数组
export default {
name: 'keep-alive',
abstract: true, // 形象组件,是一个形象组件:它本身不会渲染一个 DOM 元素,也不会呈现在父组件链中。props: {
include: patternTypes, // 匹配的组件,缓存
exclude: patternTypes, // 不去匹配的组件,不缓存
max: [String, Number], // 缓存组件的最大实例数量, 因为缓存的是组件实例(vnode),数量过多的时候,会占用过多的内存,能够用 max 指定下限
},
created() {
// 用于初始化缓存虚构 DOM 数组和 vnode 的 key
this.cache = Object.create(null)
this.keys = []},
destroyed() {
// 销毁缓存 cache 的组件实例
for (const key in this.cache) {pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted() {// prune 削减精简[v.]
// 去监控 include 和 exclude 的扭转,依据最新的 include 和 exclude 的内容,来实时削减缓存的组件的内容
this.$watch('include', (val) => {pruneCache(this, (name) => matches(val, name))
})
this.$watch('exclude', (val) => {pruneCache(this, (name) => !matches(val, name))
})
},
}
render 函数:
- 会在 keep-alive 组件外部去写本人的内容,所以能够去获取默认 slot 的内容,而后依据这个去获取组件
- keep-alive 只对第一个组件无效,所以获取第一个子组件。
- 和 keep-alive 搭配应用的个别有:动静组件 和 router-view
render () {
//
function getFirstComponentChild (children: ?Array<VNode>): ?VNode {if (Array.isArray(children)) {for (let i = 0; i < children.length; i++) {const c = children[i]
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {return c}
}
}
}
const slot = this.$slots.default // 获取默认插槽
const vnode: VNode = getFirstComponentChild(slot)// 获取第一个子组件
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions // 组件参数
if (componentOptions) { // 是否有组件参数
// check pattern
const name: ?string = getComponentName(componentOptions) // 获取组件名
const {include, exclude} = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
// 如果不匹配以后组件的名字和 include 以及 exclude
// 那么间接返回组件的实例
return vnode
}
const {cache, keys} = this
// 获取这个组件的 key
const key: ?string = vnode.key == null
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
// LRU 缓存策略执行
vnode.componentInstance = cache[key].componentInstance // 组件首次渲染的时候 componentInstance 为 undefined
// make current key freshest
remove(keys, key)
keys.push(key)
// 依据 LRU 缓存策略执行,将 key 从原来的地位移除,而后将这个 key 值放到最初面
} else {
// 在缓存列表外面没有的话,则退出,同时判断以后退出之后,是否超过了 max 所设定的范畴,如果是,则去除
// 应用工夫距离最长的一个
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 将组件的 keepAlive 属性设置为 true
vnode.data.keepAlive = true // 作用:判断是否要执行组件的 created、mounted 生命周期函数
}
return vnode || (slot && slot[0])
}
keep-alive 具体是通过 cache 数组缓存所有组件的 vnode 实例。当 cache 内原有组件被应用时会将该组件 key 从 keys 数组中删除,而后 push 到 keys 数组最初,以便革除最不罕用组件。
实现步骤:
- 获取 keep-alive 下第一个子组件的实例对象,通过他去获取这个组件的组件名
- 通过以后组件名去匹配原来 include 和 exclude,判断以后组件是否须要缓存,不须要缓存,间接返回以后组件的实例 vNode
- 须要缓存,判断他以后是否在缓存数组外面:
- 存在,则将他原来地位上的 key 给移除,同时将这个组件的 key 放到数组最初面(LRU)
- 不存在,将组件 key 放入数组,而后判断以后 key 数组是否超过 max 所设置的范畴,超过,那么削减未应用工夫最长的一个组件的 key
- 最初将这个组件的 keepAlive 设置为 true
(3)keep-alive 自身的创立过程和 patch 过程
缓存渲染的时候,会依据 vnode.componentInstance(首次渲染 vnode.componentInstance 为 undefined)和 keepAlive 属性判断不会执行组件的 created、mounted 等钩子函数,而是对缓存的组件执行 patch 过程∶ 间接把缓存的 DOM 对象直接插入到指标元素中,实现了数据更新的状况下的渲染过程。
首次渲染
- 组件的首次渲染∶判断组件的 abstract 属性,才往父组件外面挂载 DOM
// core/instance/lifecycle
function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) { // 判断组件的 abstract 属性,才往父组件外面挂载 DOM
while (parent.$options.abstract && parent.$parent) {parent = parent.$parent}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
- 判断以后 keepAlive 和 componentInstance 是否存在来判断是否要执行组件 prepatch 还是执行创立 componentlnstance
// core/vdom/create-component
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) { // componentInstance 在首次是 undefined!!!
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode) // prepatch 函数执行的是组件更新的过程
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
prepatch 操作就不会在执行组件的 mounted 和 created 生命周期函数,而是间接将 DOM 插入
(4)LRU(least recently used)缓存策略
LRU 缓存策略∶ 从内存中找出最久未应用的数据并置换新的数据。
LRU(Least rencently used)算法依据数据的历史拜访记录来进行淘汰数据,其核心思想是 “ 如果数据最近被拜访过,那么未来被拜访的几率也更高 ”。最常见的实现是应用一个链表保留缓存数据,具体算法实现如下∶
- 新数据插入到链表头部
- 每当缓存命中(即缓存数据被拜访),则将数据移到链表头部
- 链表满的时候,将链表尾部的数据抛弃。
diff 算法
<details open=””><summary>答案 </summary>
<p>
</p><p> 工夫复杂度: 个树的齐全 diff
算法是一个工夫复杂度为 O(n*3)
,vue 进行优化转化成 O(n)
。</p>
<p> 了解:</p>
<ul>
<li>
<p> 最小量更新, key
很重要。这个能够是这个节点的惟一标识,通知 diff
算法,在更改前后它们是同一个 DOM 节点 </p>
<ul>
<li> 扩大 v-for
为什么要有 key
,没有 key
会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加 key
只会挪动缩小操作 DOM。</li>
</ul>
</li>
<li>
<p> 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。</p>
</li>
<li>
<p> 只进行同层比拟,不会进行跨层比拟。</p>
</li>
</ul>
<p>diff 算法的优化策略:四种命中查找,四个指针 </p>
<ol>
<li>
<p> 旧前与新前(先比结尾,后插入和删除节点的这种状况)</p>
</li>
<li>
<p> 旧后与新后(比结尾,前插入或删除的状况)</p>
</li>
<li>
<p> 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)</p>
</li>
<li>
<p> 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)</p>
</li>
</ol>
<p></p>
</details>
— 问完下面这些如果都能很分明的话,根本 O 了 —
以下的这些简略的概念,你必定也是没有问题的啦😉
Vue 的长处
- 轻量级框架:只关注视图层,是一个构建数据的视图汇合,大小只有几十
kb
; - 简略易学:国人开发,中文文档,不存在语言障碍,易于了解和学习;
- 双向数据绑定:保留了
angular
的特点,在数据操作方面更为简略; - 组件化:保留了
react
的长处,实现了html
的封装和重用,在构建单页面利用方面有着独特的劣势; - 视图,数据,构造拆散:使数据的更改更为简略,不须要进行逻辑代码的批改,只须要操作数据就能实现相干操作;
- 虚构 DOM:
dom
操作是十分消耗性能的,不再应用原生的dom
操作节点,极大解放dom
操作,但具体操作的还是dom
不过是换了另一种形式; - 运行速度更快:相比拟于
react
而言,同样是操作虚构dom
,就性能而言,vue
存在很大的劣势。
template 和 jsx 的有什么别离?
对于 runtime 来说,只须要保障组件存在 render 函数即可,而有了预编译之后,只须要保障构建过程中生成 render 函数就能够。在 webpack 中,应用 vue-loader
编译.vue 文件,外部依赖的 vue-template-compiler
模块,在 webpack 构建过程中,将 template 预编译成 render 函数。与 react 相似,在增加了 jsx 的语法糖解析器 babel-plugin-transform-vue-jsx
之后,就能够间接手写 render 函数。
所以,template 和 jsx 的都是 render 的一种表现形式,不同的是:JSX 绝对于 template 而言,具备更高的灵活性,在简单的组件中,更具备劣势,而 template 尽管显得有些僵滞。然而 template 在代码构造上更合乎视图与逻辑拆散的习惯,更简略、更直观、更好保护。
常见的事件修饰符及其作用
.stop
:等同于 JavaScript 中的event.stopPropagation()
,避免事件冒泡;.prevent
:等同于 JavaScript 中的event.preventDefault()
,避免执行预设的行为(如果事件可勾销,则勾销该事件,而不进行事件的进一步流传);.capture
:与事件冒泡的方向相同,事件捕捉由外到内;.self
:只会触发本人范畴内的事件,不蕴含子元素;.once
:只会触发一次。
为什么 vue 组件中 data 必须是一个函数?
对象为援用类型,当复用组件时,因为数据对象都指向同一个 data 对象,当在一个组件中批改 data 时,其余重用的组件中的 data 会同时被批改;而应用返回对象的函数,因为每次返回的都是一个新对象(Object 的实例),援用地址不同,则不会呈现这个问题。
Vue.mixin 的应用场景和原理
- 在日常的开发中,咱们常常会遇到在不同的组件中常常会须要用到一些雷同或者类似的代码,这些代码的性能绝对独立,能够通过
Vue
的mixin
性能抽离公共的业务逻辑,原理相似“对象的继承”,当组件初始化时会调用mergeOptions
办法进行合并,采纳策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以失当的形式进行“合并”;如果混入的数据和自身组件的数据抵触,会以组件的数据为准 mixin
有很多缺点如:命名抵触、依赖问题、数据起源问题
根本应用
<script>
// Vue.options
Vue.mixin({ // 如果他是对象 每个组件都用 mixin 里的对象进行合并
data(){return {a: 1,b: 2}
}
});
// Vue.extend
Vue.component('my',{ // 组件必须是函数 Vue.extend => render(xxx)
data(){return {x:1}
}
})
// 没有 new 没有实例 _init()
// const vm = this
new Vue({
el:'#app',
data(){ // 根能够不是函数
return {c:3}
}
})
</script>
相干源码
export default function initMixin(Vue){Vue.mixin = function (mixin) {
// 合并对象
this.options=mergeOptions(this.options,mixin)
};
}
};
// src/util/index.js
// 定义生命周期
export const LIFECYCLE_HOOKS = [
"beforeCreate",
"created",
"beforeMount",
"mounted",
"beforeUpdate",
"updated",
"beforeDestroy",
"destroyed",
];
// 合并策略
const strats = {};
// mixin 外围办法
export function mergeOptions(parent, child) {const options = {};
// 遍历父亲
for (let k in parent) {mergeFiled(k);
}
// 父亲没有 儿子有
for (let k in child) {if (!parent.hasOwnProperty(k)) {mergeFiled(k);
}
}
// 真正合并字段办法
function mergeFiled(k) {
// strats 合并策略
if (strats[k]) {options[k] = strats[k](parent[k], child[k]);
} else {
// 默认策略
options[k] = child[k] ? child[k] : parent[k];
}
}
return options;
}
Vue 组件如何通信?
Vue 组件通信的办法如下:
props/$emit+v-on
: 通过 props 将数据自上而下传递,而通过 $emit 和 v -on 来向上传递信息。- EventBus: 通过 EventBus 进行信息的公布与订阅
- vuex: 是全局数据管理库,能够通过 vuex 治理全局的数据流
$attrs/$listeners
: Vue2.4 中退出的$attrs/$listeners
能够进行跨级的组件通信- provide/inject:以容许一个先人组件向其所有子孙后代注入一个依赖,不管组件档次有多深,并在起上下游关系成立的工夫里始终失效,这成为了跨组件通信的根底
还有一些用 solt 插槽或者 ref 实例进行通信的,应用场景过于无限就不赘述了。
用 VNode 来形容一个 DOM 构造
虚构节点就是用一个对象来形容一个实在的 DOM 元素。首先将 template
(实在 DOM)先转成 ast
, ast
树通过 codegen
生成 render
函数, render
函数里的 _c
办法将它转为虚构 dom
对 React 和 Vue 的了解,它们的异同
相似之处:
- 都将注意力集中放弃在外围库,而将其余性能如路由和全局状态治理交给相干的库;
- 都有本人的构建工具,能让你失去一个依据最佳实际设置的我的项目模板;
- 都应用了 Virtual DOM(虚构 DOM)进步重绘性能;
- 都有 props 的概念,容许组件间的数据传递;
- 都激励组件化利用,将利用分拆成一个个性能明确的模块,进步复用性。
不同之处:
1)数据流
Vue 默认反对数据双向绑定,而 React 始终提倡单向数据流
2)虚构 DOM
Vue2.x 开始引入 ”Virtual DOM”,打消了和 React 在这方面的差别,然而在具体的细节还是有各自的特点。
- Vue 声称能够更快地计算出 Virtual DOM 的差别,这是因为它在渲染过程中,会跟踪每一个组件的依赖关系,不须要从新渲染整个组件树。
- 对于 React 而言,每当利用的状态被扭转时,全副子组件都会从新渲染。当然,这能够通过 PureComponent/shouldComponentUpdate 这个生命周期办法来进行管制,但 Vue 将此视为默认的优化。
3)组件化
React 与 Vue 最大的不同是模板的编写。
- Vue 激励写近似惯例 HTML 的模板。写起来很靠近规范 HTML 元素,只是多了一些属性。
- React 举荐你所有的模板通用 JavaScript 的语法扩大——JSX 书写。
具体来讲:React 中 render 函数是反对闭包个性的,所以 import 的组件在 render 中能够间接调用。然而在 Vue 中,因为模板中应用的数据都必须挂在 this 上进行一次直达,所以 import 一个组件完了之后,还须要在 components 中再申明下。4)监听数据变动的实现原理不同
- Vue 通过 getter/setter 以及一些函数的劫持,能准确晓得数据变动,不须要特地的优化就能达到很好的性能
- React 默认是通过比拟援用的形式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的 vDOM 的从新渲染。这是因为 Vue 应用的是可变数据,而 React 更强调数据的不可变。
5)高阶组件
react 能够通过高阶组件(HOC)来扩大,而 Vue 须要通过 mixins 来扩大。
高阶组件就是高阶函数,而 React 的组件自身就是纯正的函数,所以高阶函数对 React 来说大海捞针。相同 Vue.js 应用 HTML 模板创立视图组件,这时模板无奈无效的编译,因而 Vue 不能采纳 HOC 来实现。
6)构建工具
两者都有本人的构建工具:
- React ==> Create React APP
- Vue ==> vue-cli
7)跨平台
- React ==> React Native
- Vue ==> Weex
data 为什么是一个函数而不是对象
JavaScript 中的对象是援用类型的数据,当多个实例援用同一个对象时,只有一个实例对这个对象进行操作,其余实例中的数据也会发生变化。
而在 Vue 中,更多的是想要复用组件,那就须要每个组件都有本人的数据,这样组件之间才不会互相烦扰。
所以组件的数据不能写成对象的模式,而是要写成函数的模式。数据以函数返回值的模式定义,这样当每次复用组件的时候,就会返回一个新的 data,也就是说每个组件都有本人的公有数据空间,它们各自保护本人的数据,不会烦扰其余组件的失常运行。
vue3 中 watch、watchEffect 区别
watch
是惰性执行,也就是只有监听的值发生变化的时候才会执行,然而watchEffect
不同,每次代码加载watchEffect
都会执行(疏忽watch
第三个参数的配置,如果批改配置项也能够实现立刻执行)watch
须要传递监听的对象,watchEffect
不须要watch
只能监听响应式数据:ref
定义的属性和reactive
定义的对象,如果间接监听reactive
定义对象中的属性是不容许的(会报正告),除非应用函数转换一下。其实就是官网上说的监听一个getter
watchEffect
如果监听reactive
定义的对象是不起作用的,只能监听对象中的属性
看一下 watchEffect
的代码
<template>
<div>
请输出 firstName:<input type="text" v-model="firstName">
</div>
<div>
请输出 lastName:<input type="text" v-model="lastName">
</div>
<div>
请输出 obj.text:<input type="text" v-model="obj.text">
</div>
<div>【obj.text】{{obj.text}}
</div>
</template>
<script>
import {ref, reactive, watch, watchEffect} from 'vue'
export default {
name: "HelloWorld",
props: {msg: String,},
setup(props,content){let firstName = ref('')
let lastName = ref('')
let obj= reactive({text:'hello'})
watchEffect(()=>{console.log('触发了 watchEffect');
console.log(` 组合后的名称为:${firstName.value}${lastName.value}`)
})
return{
obj,
firstName,
lastName
}
}
};
</script>
革新一下代码
watchEffect(()=>{console.log('触发了 watchEffect');
// 这里咱们不应用 firstName.value/lastName.value,相当于是监控整个 ref, 对应第四点下面的论断
console.log(` 组合后的名称为:${firstName}${lastName}`)
})
watchEffect(()=>{console.log('触发了 watchEffect');
console.log(obj);
})
略微革新一下
let obj = reactive({text:'hello'})
watchEffect(()=>{console.log('触发了 watchEffect');
console.log(obj.text);
})
再看一下 watch 的代码,验证一下
let obj= reactive({text:'hello'})
// watch 是惰性执行,默认初始化之后不会执行,只有值有变动才会触发,可通过配置参数实现默认执行
watch(obj, (newValue, oldValue) => {
// 回调函数
console.log('触发监控更新了 new', newValue);
console.log('触发监控更新了 old', oldValue);
},{
// 配置 immediate 参数,立刻执行,以及深层次监听
immediate: true,
deep: true
})
- 监控整个
reactive
对象,从下面的图能够看到deep
理论默认是开启的,就算咱们设置为false
也还是有效。而且旧值获取不到。 - 要获取旧值则须要监控对象的属性,也就是监听一个
getter
,看下图
总结
- 如果定义了
reactive
的数据,想去应用watch
监听数据扭转,则无奈正确获取旧值,并且deep
属性配置有效,主动强制开启了深层次监听。 - 如果应用
ref
初始化一个对象或者数组类型的数据,会被主动转成reactive
的实现形式,生成proxy
代理对象。也会变得无奈正确取旧值。 - 用任何形式生成的数据,如果接管的变量是一个
proxy
代理对象,就都会导致watch
这个对象时,watch
回调里无奈正确获取旧值。 - 所以当大家应用
watch
监听对象时,如果在不须要应用旧值的状况,能够失常监听对象没关系;然而如果当监听扭转函数外面须要用到旧值时,只能监听 对象.xxx` 属性 的形式才行
watch 和 watchEffect 异同总结
体验
watchEffect
立刻运行一个函数,而后被动地追踪它的依赖,当这些依赖扭转时从新执行该函数
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
count.value++
// -> logs 1
watch
侦测一个或多个响应式数据源并在数据源变动时调用一个回调函数
const state = reactive({count: 0})
watch(() => state.count,
(count, prevCount) => {/* ... */}
)
答复范例
watchEffect
立刻运行一个函数,而后被动地追踪它的依赖,当这些依赖扭转时从新执行该函数。watch
侦测一个或多个响应式数据源并在数据源变动时调用一个回调函数watchEffect(effect)
是一种非凡watch
,传入的函数既是依赖收集的数据源,也是回调函数。如果咱们不关怀响应式数据变动前后的值,只是想拿这些数据做些事件,那么watchEffect
就是咱们须要的。watch
更底层,能够接管多种数据源,包含用于依赖收集的getter
函数,因而它齐全能够实现watchEffect
的性能,同时因为能够指定getter
函数,依赖能够管制的更准确,还能获取数据变动前后的值,因而如果须要这些时咱们会应用watch
watchEffect
在应用时,传入的函数会立即执行一次。watch
默认状况下并不会执行回调函数,除非咱们手动设置immediate
选项- 从实现上来说,
watchEffect(fn)
相当于watch(fn,fn,{immediate:true})
watchEffect
定义如下
export function watchEffect(
effect: WatchEffect,
options?: WatchOptionsBase
): WatchStopHandle {return doWatch(effect, null, options)
}
watch
定义如下
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
source: T | WatchSource<T>,
cb: any,
options?: WatchOptions<Immediate>
): WatchStopHandle {return doWatch(source as any, cb, options)
}
很显著 watchEffect
就是一种非凡的 watch
实现。
Vue 实例挂载的过程中产生了什么
简略
TIP
剖析
挂载过程实现了最重要的两件事:
- 初始化
- 建设更新机制
把这两件事说分明即可!
答复范例
- 挂载过程指的是
app.mount()
过程,这个过程中整体上做了两件事:初始化 和建设更新机制 - 初始化会创立组件实例、初始化组件状态,创立各种响应式数据
- 建设更新机制这一步会立刻执行一次组件更新函数,这会首次执行组件渲染函数并执行
patch
将后面取得vnode
转换为dom
;同时首次执行渲染函数会创立它外部响应式数据之间和组件更新函数之间的依赖关系,这使得当前数据变动时会执行对应的更新函数
来看一下源码,在src/core/instance/index.js
中
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
能够看到 Vue
只能通过 new
关键字初始化,而后会调用 this._init
办法,该办法在 src/core/instance/init.js
中定义
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {initProxy(vm)
} else {vm._renderProxy = vm}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {vm.$mount(vm.$options.el)
}
}
Vue
初始化次要就干了几件事件,合并配置
,初始化生命周期
,初始化事件核心
,初始化渲染
,初始化 data
、props
、computed
、watcher
等