创作者
当我们使用vue时,我们使用
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
这里有一个生成的Vue示例,当然new关键字后面的Vue是一个ES6类或构造函数。检查源代码,看看它是否是构造函数。
// new Vue会生产出一个对象,这个对象是真正在内存中工作的Vue实例对象。
function Vue (options) {
// 在用户new Vue时,执行的_init就是下面initMixin添加的方法
this._init(options)
}
// 使用构造函数而不使用class语法的好处是:
// 通过 xxxMixin 的函数调用,将Vue的功能拆分到多个模块去实现。
// 它们的功能都是给 Vue 的 prototype 上扩展一些方法。
// 这种方式是用 Class 难以实现的。
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
Vue 源码中使用了 ES6 语法,但没有用类声明。这是因为如果类写成类声明的话,类会很大,而Vue的内容很多,需要共享,所以我们还是使用构造函数语法,然后使用Vue的函数输入方式。不同模块的构造函数。
选项对象、Vue构造函数、Vue实例对象
在Vue官方文档中你可以找到这样的句子。
日常开发中,写元素的时候,是这样的:
<template>
...
</template>
<script>
// 这里跟React定义组件不同,不是写继承Component的类,而是写一个简单对象,这只是个选项对象
export default {
...
}
</script>
首先要明确的是,我们在日常进度中所写的内容只是可选的。 React 类(或 React 组件构造函数)与 React 的直接定义不同。
重点关注主元素中添加的选项对象,数据属性是一个对象。除了主要元素,我们编写的可选对象之外,数据属性是一个方法。造成这种差异的原因是,主要组件是一个new Vue,它直接创建一个Vue实例对象。我们首先编写的可选对象将 Vue.extend 扩展为代码运行时从 Vue 构造函数继承的子 Vue 构造函数。然后使用子 Vue 构造函数声明组件实例(即实际的 Vue 组件对象)。因此,我们编写的选项对象的data属性是所有组件实例所共有的,因此data属性应该是针对不同组件实例返回特定数据的方法。
总之,区分和解释选项对象、Vue构造函数和Vue实例对象的概念将有助于我们更好地理解Vue的工作原理。从上面的分析可以看出,作者对于面向对象编程的思想是很好的。
关闭
Vue源码中也有一些闭包的实现。
缓存变量
// 在讲普通属性变成响应式属性时,dep变量存在于Object.defineProperty方法下的get/set函数的作用域链上,使get/set方法能共享dep对象
export function defineReactive (
const dep = new Dep()
Object.defineProperty(obj, key, {
get: function reactiveGetter () {
...
// 依赖收集
dep.depend()
}
set: function reactiveSetter (newVal) {
...
// 派发更新
dep.notify()
}
}
}
由于闭包,defineReactive方法执行后,dep对象不会被垃圾回收,而是在get/set方法的字段链中。
工作咖喱
用于最终将VNode转换为DOM的patch方法。由于补丁方法是平台相关的,因此Web和Weex环境中的DOM和相关操作是不同的。因此,将场景的各个部分作为参数,调用一个名为createPatchFunction的方法来为不同的场景创建patch方法。
// nodeOps, modules是跟平台相关的逻辑,传递给createPatchFunction方法,返回值是patch方法,patch方法内部可以使用nodeOps, modules来进行一些真实dom的操作。
export const patch: Function = createPatchFunction({ nodeOps, modules })
应用以关闭咖喱。这里,使用柯里化(currying)来实现补丁模式下的多态性。
这样,在将VNode转换为真实DOM节点的过程中,会调用patch方法,只需要考虑区分新旧VNode树的逻辑,没有必要。考虑定制实际 DOM 的逻辑。
这个生命周期压力指的是
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
// => "a is: 1"
这也是我们第一次学习Vue时感觉有点奇怪的地方。为什么这指的是数据中的数据?
因为我们写的是可选对象,它是和组件构造函数结合在一起的。使用构造函数创建组件来获取vm实例。当vm示例调用钩子循环函数时
// 执行时会用call方法,将this指向vm实例
handlers[i].call(vm)
接下来的问题是vm实例如何访问数据。
Vue 将数据转换为响应对象并将其设置为 vm 的 _data 属性。然后通过代理将每个值vm._data.xxx代理到vm.xxx。这里的代理应用到了Object.defineProperty
// 原理如下代码,比如是data里的a属性。
// 用户在写this.a 相当于访问vm.a 也就是调用了下面代码的get:this._data.a
Object.defineProperty(vm, 'a', {
get: function proxyGetter () {
return this._data.a
},
set: function proxySetter (val) {
this._data.a = val
}
})
生命周期
创建前:
在这个hook中,无法设置props和data中指定的值,也无法调用methods中指定的函数。
vue-router 源码中有这样的代码:
// 我们之所以在写代码时可以this.$router。下面的代码逻辑起了作用。
Vue.mixin({
beforeCreate () {
this._routerRoot = this
this._router = this.$options.router
}
})
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
在Create之前挂上写vm实例代码时用到的变量对象就可以了,非常方便。
同样,在Vuex源码中,$store变量是使用beforeCreate添加到vm实例中的。
创建者:
如果需要访问props、data等数据,则需要使用创建的钩子函数。
我们编写的应用程序的组件树中,创建的执行顺序是从parent到child,依次执行。
附:
安装完真实dom后,就安装完成了。
在我们编写的应用程序的组件树中,与created不同,set是从child到parent依次执行的。
破损:
是分解元素的功能。
我们编写的应用程序的组件树中,和set类似,执行顺序是从child到parent,依次执行。
可做可停
已工作且可拆卸的挂钩专为带电元件设计。
以下详细信息行
nextTick 默认使用微任务 Promise Web 应用程序。如果不会处理宏函数(JS事件循环知识点)
学习diff算法时的困惑
之前学习vue diff算法的时候,让我困惑的是,比较新旧DOM树时,如果节点是element类型的VNode会怎么样?
首先要澄清的一点是,大多数介绍diff算法的文章都没有考虑元素类型的VNode节点。因此,当您阅读这些文章时,您需要将每个节点视为可以映射到实际 dom 节点的 VNode 节点,并考虑它们解释的算法。
这有助于更集中地研究 diff 算法的逻辑。 Vue 的 diff 算法基于 Snabbdom 库,它是一个组件无节点 diff 算法。
Vnode节点类型
时的逻辑差异当新旧VNode树中不存在某个元素类型的VNode节点时,patch负责比较两棵树之间的差异,然后处理实际的DOM。然而,当VNode树中存在VNode元素类型时,patch方法就有更多的责任,例如生成VNode元素类型的子树。
给出一个场景,差异在于新树比旧树多了一个 Vnode 节点。你只需要添加一个真实的dom,将这个Vnode映射到真实的dom树上。然而,当存在具有元素类型VNode的旧VNode树和新VNode树时,差异显示新树具有与旧树不同的元素类型VNode。 element类型的Vnode不能直接映射到实际的dom,但会创建一个子VNode树。创建子树的过程与创建父树类似,都会产生重复的结果。
所以页面上的Vnode树应该是一棵大树,里面有很多子树。它们的逻辑在某些情况下没有区别,除了子树位于主树中的Vnode之外。已添加或删除节点。
从上面的假设场景中可以看出,弄清楚一般的 vue diff 逻辑仍然需要一些努力。具体逻辑还是需要去源码中学习。
算法当你研究diff算法的逻辑时,你可以看到作者的一些算法思想。
首先,虚拟dom树采用的是树形结构。比较时,会遍历树。虽然比较是分层比较,但是遍历采用的是DFS(深度优先遍历)。
在Vue3.x中,比较两个Vnode的Child变换时,也采用最长迭代算法(对于leecode查询,可以使用贪婪+二分法解决问题),保证最小值。非常好的手机。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。