一、数据驱动
- 编写页面模板
- 直接在HTML标签中写 标签
- 使用template
- 使用单文件
- 创建vue的实例
- 在vue的构造函数中提供:data, methods, computed, watcher, props,...
- 将vue挂载到页面中(mount)
Vue 的执行流程
- 获得模板: 模板中 '有坑'
- 利用Vue搞糟函数中所提供的数据来填坑,得到可以在页面中显示的 '标签'了
- 将标签 特换页面中原来有坑的标签 Vue 利用我们提供的数据和 页面中 模板,生成了一个新的html标签(node 节点),替换到了页面中防止模板的位置
目标:
- 怎么将真正的DOM转换为虚拟DOM?
- 怎么将虚拟DOM转换为真正的DOM?
思路与深拷贝类似
参考资料:
概念:
- 柯里化: 一个函数原本有多个参数,只传入一个参数,生成一个新函数,由新函数来接收剩下的参数来运行得到结果
- 偏函数: 一个函数原本有多个参数,只传入一部分参数,生成一个新函数,由新函数来接收剩下的参数来运行得到结果
- 高阶函数: 一个函数参数是一个函数,该函数对参数这个函数进行加工,得到一个函数,这个加工用的函数就是高阶函数
为什么要使用柯里化? 为了提升性能,我们使用柯里化可以缓存一部分能力。 使用两个案例说明?
-
判断元素
-
虚拟DOM的render方法
-
判断元素:
Vue 本质上是使用HTML的字符串作为模板的,将字符串的模板作为AST,再转换为VNode
- 模板 -> AST
- AST -> VNode
- VNode -> DOM
哪一个阶段最消耗性能?
最消耗性能的是字符串解析(模板 -> AST) 例子: let s = "1 +2 * (3+4)" 写一个程序,解析这个表达式,得到结果(一般化) 一般会将这个表达式转换为"波兰式"表达式,然后用栈结构来运算
在Vue中每一个标签可以是真正的HTML标签,也可以是Vue组件,怎么区分?
在Vue源码中将所有可用的HTML标签已经存起来了
假设只考虑几个标签:
let tags = 'div, p, a, img, ul, li'.split(',')
需要一个函数,判断标签名是否为 内置的标签
function isHTMLTag (tagName) {
let tags = 'div, p, a, img, ul, li'.split(',')
tagName = tagName.toLowerCase()
return tags.some(val =>{
return val === tagName
})
}
模板是任意编写的,可以写的很简单,也可以写的很复杂, indexOf内部也是要循环的 如果有6中内置标签,而模板中有10个标签需要判断,那么至多需要执行60次循环
- 虚拟DOM的render方法
思考:vue项目 模板转换为抽象语法树 需要几次?
- 页面一开始加载需要渲染
- 每一个属性(响应式)数据在发生变化的时候 要渲染
- watch computed等等
day01 中的代码每次需要渲染的时候,模板就会被解析一次(注意,这里我们简化了解析方法) render的作用是将 虚拟DOM 转换为 真正的DOM加到页面中
- 虚拟DOM可以降级理解为AST
- 一个项目运行的时候模板是不会变的,就表示AST是不会变的
我们可以将代码进行优化,将虚拟DOM缓存起来,生成一个函数,函数只需要传入数据,就可以得到真正的DOM
- 我们在使用Vue的时候, 赋值数型获得属性都是直接使用的Vue实例
- 我们在设计属性值的时候, 页面的数据更新
Object.defineProperty(obj, name, {
writeble,
configurble,
enumerble,
set,
get
})
实际开发中 对象一般是由多级的
let o = {
list: {
{}
},
ads: [
{}
],
user: {
}
}
怎么处理? 递归或者队列 对于对象,可以使用递归来响应式化, 但是数组我们也需要处理
- push
- pop
- shift
- unshift
- reverse
- sort
- splice
要做什么事情呢?
- 在改变数组的数据的时候要发出通知
- Vue2 的缺陷,数组发生变化,设置length没发通知(Vue3 中使用Proxy语法ES6的语法解决了这个问题)
- 加入的元素应该变成响应式的
技巧:
如果一个函数已经定义了,但是我们需要扩展其功能,一般的处理办法:
- 使用一个临时的函数名称存储函数
- 重新定义原来的函数
- 定义扩展的功能
- 调用临时的那个函数
扩展数组的push和pop怎么处理呢?
- 直接修改prototype不行 因为这意味着所有数组的prototype都改了
- 修改要进行响应式化的数组的原型(proto)
练习: 已经将对象改成响应式的了,但是如果直接给对象赋值,赋值另一个对象,就不是响应式的了,怎么办?? 在set的时候,把value响应式化
任务: - 作业 - 代理方法(app.name, app._data.name) - 事件模型(node: event 模块) - vue 中observer与watcher和Dep的关系
代理方法就是要将app._data中的成员映射到app上
由于需要在更新数据的时候,更新页面的内容 所以 app._data访问的成员 与 app 访问的成员应该是同一个成员
由于 app._data 已经是响应式的对象了,所以只需要让app访问的成员去访问app._data的对应成员就可以了
app.name 转换为 app._data.name
引入了一个函数Proxy(target, src, prop) 将target与src的成员映射到一起
这里是因为当时没有Proxy
语法(ES6)
我们之前处理的reactify方法已经不行了,我们需要一个新的方法来处理
提供一个Observer的方法,在这个方法当中 对属性进行处理 可以将这个方法封装到initData方法中
app._data.name
// vue设计,不希望访问_开头的数据
// vue中有一个规则:
// _开头的数据时私有数据
// $开头的是只读数据
app.name
// 将对 _data.xxx的访问交给了实例
// 重点
// 访问app的xxx就是在访问app._data.xxx
假设:
var o1 = {name: '张三'}
// 要有一个对象o2,在访问o2的name时访问的是o1的name
目的: 解耦,让各个模块之间没有紧密的联系
现在的处理方法是 属性在更新的时候调用 mountComponent方法
问题: mountComponent更新的是什么? (现在)全部的页面
在Vue中,整个的更新是按照组件为单位进行 判断,以节点为单位进行更新
-
如果代码中没有自定义组件,那么在比较算法的时候,我们会将全部的模板 对应的虚拟DOM进行比较
-
如果代码中含有自定义组件, 那么在比较算法的时候,就会判断更新的是哪一些组件中的属性,置灰判断更新数据的组件,其它组件不会更新
复杂的页面是由很多组件构成,每一个属性要更新的时候都要调用 更新的方法?
目标: 如果修改了什么属性,就尽可能值更新这些属性对应的页面 DOM
这样就一定不能将更新的代码写死
例子: 预售可能一个东西没有现货,告诉老板,如果东西到了 就告诉我
老板是发布者, 订阅什么东西作为中间媒介, 我是订阅者
使用代码的结构来描述: 1. 老板提供一个账簿(数组) 2. 我可以根据需求订阅我的商品(老板记录下 谁 定了什么东西,在数组中存储 某些东西) 3. 等待,可以做其他的事情 4. 当货品来到的时候,老板就查看账簿,挨个的打电话(遍历数组, 取出数组里面的元素来使用)
实际上就是 事件模型
- 有一个event对象
- on, off, emit 方法
实现事件模型,思考怎么用?
- event是一个全局对象
- event.on('事件名', 处理函数) 订阅事件
- 事件可以连续订阅
- 可以移除: event.off()
- 移除所有
- 移除一个类型的事件
- 移除某一个类型的某一个处理函数
- 写别的代码
- event.emit('事件名,参数),先前注册的事件处理函数就会依次调用
原因:
-
描述发布订阅模式
-
后面会使用到事件
发布订阅模式(形式不局限于函数,形式可以是对象等):
- 中间的全局的容器,用来存储可以被触发的东西(函数,对象等)
- 需要一个方法,可以往容器中传入东西(函数,对象等)
- 需要一个方法,可以将容器中的东西取出来使用(函数调用,对象的方法调用)
Vue 模型
页面中的变更(diff)是以组件为单位
- 如果页面中只有一个组件(Vue 实例),不会有性能损失
- 但是如果页面中有多个组件(多watcher的一种情况),第一次会有多个组件的watcher存入到全局watcher中
- 如果修改了局部的数据(例如其中一个组件的数据)
- 表示只会对该组件进行diff算法,也就是说只会重新生成该组件的抽象语法树
- 只会访问该组件的watcher
- 也就表示再次往全局存储的只有该组件的watcher
- 页面更新的时候,也就只需要更新一部分
缺陷:
- 无法处理数组
- 响应式无法再中间集成 watcher 处理
- 我们实现的reactify需要和实例紧紧的绑定在一起,需要解耦
问题:
- 模型(图)
- 关于 this 的问题
实现:
- 只考虑修改后刷新(响应式)
- 再考虑以来收集(优化)
在Vue 中提供一个构造函数Watcher Watcher 中会有一些方法:
- get()用来进行计算或执行处理函数
- update() 公共的外部方法,该方法会触发内部的run方法
- run() 运行,用来判断内部是使用异步运行还是同步运行等,这个方法最终会调用内部的get()方法
- cleanupDep() 简单理解为清楚队列
我们的页面渲染是上面哪一个方法执行的呢? get()方法
我们的watcher实例有一个属性vm,表示的就是 当前的Vue实例
该对象提供 依赖收集(depend)的功能,和 派发更新(notify)的功能
在notify中去调用watcher的update方法