Skip to content

Latest commit

 

History

History
308 lines (226 loc) · 10 KB

README.md

File metadata and controls

308 lines (226 loc) · 10 KB

一、数据驱动

vue与模板

  1. 编写页面模板
    1. 直接在HTML标签中写 标签
    2. 使用template
    3. 使用单文件
  2. 创建vue的实例
    1. 在vue的构造函数中提供:data, methods, computed, watcher, props,...
  3. 将vue挂载到页面中(mount)

数据驱动模型

Vue 的执行流程

  1. 获得模板: 模板中 '有坑'
  2. 利用Vue搞糟函数中所提供的数据来填坑,得到可以在页面中显示的 '标签'了
  3. 将标签 特换页面中原来有坑的标签 Vue 利用我们提供的数据和 页面中 模板,生成了一个新的html标签(node 节点),替换到了页面中防止模板的位置

简单的模板渲染

虚拟DOM

目标:

  1. 怎么将真正的DOM转换为虚拟DOM?
  2. 怎么将虚拟DOM转换为真正的DOM?

思路与深拷贝类似

函数柯里化

参考资料:

概念:

  1. 柯里化: 一个函数原本有多个参数,只传入一个参数,生成一个新函数,由新函数来接收剩下的参数来运行得到结果
  2. 偏函数: 一个函数原本有多个参数,只传入一部分参数,生成一个新函数,由新函数来接收剩下的参数来运行得到结果
  3. 高阶函数: 一个函数参数是一个函数,该函数对参数这个函数进行加工,得到一个函数,这个加工用的函数就是高阶函数

为什么要使用柯里化? 为了提升性能,我们使用柯里化可以缓存一部分能力。 使用两个案例说明?

  1. 判断元素

  2. 虚拟DOM的render方法

  3. 判断元素:

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次循环

  1. 虚拟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

要做什么事情呢?

  1. 在改变数组的数据的时候要发出通知
    1. Vue2 的缺陷,数组发生变化,设置length没发通知(Vue3 中使用Proxy语法ES6的语法解决了这个问题)
    2. 加入的元素应该变成响应式的

技巧:

如果一个函数已经定义了,但是我们需要扩展其功能,一般的处理办法:

  1. 使用一个临时的函数名称存储函数
  2. 重新定义原来的函数
  3. 定义扩展的功能
  4. 调用临时的那个函数

扩展数组的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方法中

解释Proxy

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. 当货品来到的时候,老板就查看账簿,挨个的打电话(遍历数组, 取出数组里面的元素来使用)

实际上就是 事件模型

  1. 有一个event对象
  2. on, off, emit 方法

实现事件模型,思考怎么用?

  1. event是一个全局对象
  2. event.on('事件名', 处理函数) 订阅事件
    1. 事件可以连续订阅
    2. 可以移除: event.off()
      1. 移除所有
      2. 移除一个类型的事件
      3. 移除某一个类型的某一个处理函数
  3. 写别的代码
  4. event.emit('事件名,参数),先前注册的事件处理函数就会依次调用

原因:

  1. 描述发布订阅模式

  2. 后面会使用到事件

发布订阅模式(形式不局限于函数,形式可以是对象等):

  1. 中间的全局的容器,用来存储可以被触发的东西(函数,对象等)
  2. 需要一个方法,可以往容器中传入东西(函数,对象等)
  3. 需要一个方法,可以将容器中的东西取出来使用(函数调用,对象的方法调用)

Vue 模型

页面中的变更(diff)是以组件为单位

  • 如果页面中只有一个组件(Vue 实例),不会有性能损失
  • 但是如果页面中有多个组件(多watcher的一种情况),第一次会有多个组件的watcher存入到全局watcher中
  • 如果修改了局部的数据(例如其中一个组件的数据)
  • 表示只会对该组件进行diff算法,也就是说只会重新生成该组件的抽象语法树
  • 只会访问该组件的watcher
  • 也就表示再次往全局存储的只有该组件的watcher
  • 页面更新的时候,也就只需要更新一部分

改写observe函数

缺陷:

  • 无法处理数组
  • 响应式无法再中间集成 watcher 处理
  • 我们实现的reactify需要和实例紧紧的绑定在一起,需要解耦

引入watcher

问题:

  • 模型(图)
  • 关于 this 的问题

实现:

  1. 只考虑修改后刷新(响应式)
  2. 再考虑以来收集(优化)

在Vue 中提供一个构造函数Watcher Watcher 中会有一些方法:

  • get()用来进行计算或执行处理函数
  • update() 公共的外部方法,该方法会触发内部的run方法
  • run() 运行,用来判断内部是使用异步运行还是同步运行等,这个方法最终会调用内部的get()方法
  • cleanupDep() 简单理解为清楚队列

我们的页面渲染是上面哪一个方法执行的呢? get()方法

我们的watcher实例有一个属性vm,表示的就是 当前的Vue实例

引入Dep对象

该对象提供 依赖收集(depend)的功能,和 派发更新(notify)的功能

在notify中去调用watcher的update方法

Watcher 与 Dep