Skip to content

hahei89/web-base

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

一、数据驱动

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published