乐趣区

关于vue.js:fedtask0301手写-Vue-Router手写响应式虚拟-DOM-和-Diff-算法

文章内容输入起源:拉勾教育大前端高薪训练营
文章内容包含:模块作业、学习笔记

简答题

1、当咱们点击按钮的时候动静给 data 减少的成员是否是响应式数据,如果不是的话,如果把新增成员设置成响应式数据,它的外部原理是什么。

let vm = new Vue({
    el: '#el'
    data: {
        o: 'object',
        dog: {}},
    method: {clickHandler () {
            // 该 name 属性是否是响应式的
            this.dog.name = 'Trump'
        }
    }
})

答:题目中通过 this.dog.name = 'Trump' 给 dog 减少的成员 不是响应式数据。

在 Vue 中能够通过 Vue.set(target, propertyName/index, value) 或者 this.$set(target, propertyName/index, value) 的形式为 target 对象动静增加响应式数据。Vue 2.x 中的原理是:相似于调用 defineReactive(obj, key, val) 办法,利用 Object.defineProperty 的 getter 和 setter 实现响应式数据。

2、请简述 Diff 算法的执行过程

答:

DOM 操作是很耗性能的,因而须要尽量减少 DOM 操作。找出本次 DOM 必须更新的节点来更新,其余的不更新,这个“找出”的过程,就须要 diff 算法。

diff 算法次要执行过程:

  • patch(container, vnode),首次渲染,将 container 转为 vnode,并比照新旧 VNode 是否雷同节点而后更新 DOM
  • patch(vnode, newVnode),数据扭转二次渲染,比照新旧 VNode 是否雷同节点而后更新 DOM
  • createElm(vnode, insertedVnodeQueue),先执行用户的 init 钩子函数,而后把 vnode 转换成实在 DOM(此时没有渲染到页面),最初返回新创建的 DOM
  • updateChildren(elm, oldCh, ch, insertedVnodeQueue), 如果 VNode 有子节点,并且与旧 VNode 子节点不雷同则执行 updateChildren(),比拟子节点的差别并更新到 DOM

编程题

我的项目地址 https://github.com/luxiancan/…

1、模仿 VueRouter 的 hash 模式的实现,实现思路和 History 模式相似,把 URL 中的 # 前面的内容作为路由的地址,能够通过 hashchange 事件监听路由地址的变动。

答:参考我的项目 ./vue-router-lxc,文件门路:./vue-router-lxc/src/vuerouter_hash/index.js

2、在模仿 Vue.js 响应式源码的根底上实现 v-html 指令,以及 v-on 指令。

答:参考我的项目 ./mini-vue-lxc,文件门路:./mini-vue-lxc/js/compiler.js

局部代码

    // 解决 v-html 指令
    htmlUpdater (node, value, key) {
        node.innerHTML = value
        new Watcher(this.vm, key, newValue => {node.innerHTML = newValue})
    }
    // 解决 v-on 指令
    onUpdater (node, value, key, eventType) {node.addEventListener(eventType, value)
        new Watcher(this.vm, key, newValue => {node.removeEventListener(eventType, value)
            node.addEventListener(eventType, newValue)
        })
    }

3、参考 Snabbdom 提供的电影列表的示例,利用 Snabbdom 实现相似的成果

答:参考我的项目 ./snabbdom-demo

我的项目文件阐明

  • notes : 笔记
  • mini-vue-lxc : 小型 vue 框架,实现了响应式、插值表达式、局部指令等性能
  • vue-router-lxc : 基于 vue-cli 模仿实现了 vue-router 的两种模式 history 和 hash
  • snabbdom-demo : 应用 snabbdom 开发的小 demo,用于学习和坚固 snabbdom 的根本用法
    • *

学习笔记

Vue.js 根底回顾

根底构造

  • 应用 new Vue({el: '#app', data: {} })
  • 应用 new Vue({data: {}, render(h) {}}).$mount('#app')

生命周期

  • new Vue() 新建 Vue 实例
  • beforeCreate 初始化事件 & 生命周期
  • created 初始化注入 & 校验
  • 是否指定“el”选项和“template”选项
  • beforeMount
  • mounted 创立 vm.$el 并用其替换 el
  • 当 data 被批改时,触发 beforeUpdate
  • 虚构 DOM 从新渲染并利用更新,触发 updated
  • 当调用 vm.$destroy() 函数时,触发 beforeDestroy
  • destroyed 解除绑定,销毁子组件以及事件监听器
  • 销毁结束

Vue 语法和概念

  • 插值表达式
  • 指令
  • 计算属性和侦听器
  • Class 和 Style
  • 条件渲染 / 列表渲染
  • 表单输出绑定
  • 组件
  • 插槽
  • 插件
  • 混入 mixin
  • 深刻响应式原理
  • 不同构建版本的 Vue

Vue-Router 原理实现

Hash 模式和 History 模式的区别

  • 不论哪种形式,都是客户端路由的实现形式,当门路发生变化不会向服务器发送申请
  • 表现形式的区别

    • Hash 模式:https://music.163.com/#/playl…
    • History 模式:https://music.163.com/playlis…
  • 原理的区别

    • Hash 模式是基于锚点,以及 onhashchange 事件
    • History 模式是基于 HTML5 中的 History API,history.pushState() IE 10 当前才反对,history.replaceState

History 模式的应用

  • History 须要服务器的反对
  • 单页利用中,服务端不存在 http://www/testurl.com/login 这样的地址会返回找不到该页面
  • 在服务端应该除了动态资源外都返回单页利用的 index.html

History 模式 – Node.js

/* app.js */
const path = require('path')
// 导入解决 history 模式的模块
const history = require('connect-history-api-fallback')
// 导入 express
const express = require('express')

const app = express()
// 注册解决 history 模式的中间件
app.use(history())
// 解决动态资源的中间件,网站根目录 ../web
app.use(express.static(path.join(__dirname, '../web')))

// 开启服务器,端口是 3000
app.listen(3000, () => {console.log('服务器开启,端口:3000')
})

History 模式 – nginx

  • 从官网下载 nginx 压缩包 http://nginx.org/en/download….
  • 把压缩包解压到 c 盘根目录,c:nginx-1.18.0 文件夹
  • 关上命令行,切换到目录 c:nginx-1.18.0

nginx 相干命令

# 启动
start nginx
# 重启
nginx -s reload
# 进行
nginx -s stop

nginx.conf 文件

server {
  # ...
  location / {
    root   html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }
}

VueRouter 实现原理

Hash 模式

  • URL 中 # 前面的内容作为门路地址
  • 监听 hashchange 事件
  • 依据以后路由地址找到对应组件从新渲染

History 模式

  • 通过 history.pushState() 办法扭转地址栏
  • 监听 popstate 事件
  • 依据以后路由地址找到对应组件从新渲染

Vue 的构建版本

  • 运行时版:不反对 template 模板,须要打包的时候提前编译
  • 完整版:蕴含运行时和编译器,体积比运行时版大 10K 左右,程序运行的时候把模板转换成 render 函数

模仿 Vue.js 响应式原理

数据驱动

数据响应式、双向绑定、数据驱动

数据响应式

  • 数据模型仅仅是一般的 JS 对象,而当咱们批改数据时,视图会进行更新,防止了繁琐的 DOM 操作,进步开发效率

双向绑定

  • 数据扭转,视图扭转;试图扭转,数据也随之扭转
  • 咱们能够应用 v-model 在表单元素上创立双向数据绑定

数据驱动

  • 数据驱动是 vue 最独特的个性之一
  • 开发过程中仅须要关注数据自身,不须要关怀数据是如何渲染到视图的

响应式的外围原理

Vue 2.x

  • Object.defineProperty
  • 浏览器兼容 IE8 以上(不兼容 IE8)

Vue 3.x

  • Proxy
  • 间接监听对象,而非属性
  • ES6 中新增,IE 不反对,性能由浏览器优化

公布订阅模式和观察者模式

公布 / 订阅模式

  • 订阅者
  • 发布者
  • 信号核心

咱们假设,存在一个“信号核心”,某个人物执行实现,就向信号核心“公布”(publish)一个信号,其余工作能够向信号核心“订阅”(subscribe)这个信号,从而晓得什么时候本人能够开始执行。这就叫做“公布 / 订阅模式”(publish-subscribe pattern)

Vue 的自定义事件

let vm = new Vue()

vm.$on('dataChange', () => {consloe.log('dataChange1')
})

vm.$on('dataChange', () => {consloe.log('dataChange2')
})

vm.$emit('dataChange')

兄弟组件通信过程

// eventBus.js
// 事件核心
let eventHub = new Vue()

// ComponentA.vue
// 发布者
addTodo: function () {// 公布音讯(事件)
  eventHub.$emit('add-todo', { text: this.newTodoText})
  this.newTodoText = ''
}

// ComponentB.vue
// 订阅者
created: function () {// 订阅音讯(事件)
  eventHub.$on('add-todo', this.addTodo)
}

模仿 Vue 自定义事件的实现

class EventEmitter {constructor () {// { 'click': [fn1, fn2], 'change': [fn] }
      this.subs = Object.create(null)
  }
  $on (eventType, handler) {this.subs[eventType] = this.subs[eventType] || []
      this.subs[eventType].push(handler)
  }
  $emit (eventType) {if (this.subs[eventType]) {this.subs[eventType].forEach(handler => {handler()
          })
      }
  }
}

观察者模式

  • 观察者(订阅者)— Watcher

    • update(): 当事件产生时,具体要做的事件
  • 指标(发布者) — Dep

    • subs 数组: 存储所有的观察者
    • addSub(): 增加观察者
    • notify(): 当事件产生时,调用所有观察者的 update() 办法
  • 没有事件核心

总结

  • 观察者模式 是由具体指标调度,比方当事件触发,Dep 就会去调用观察者的办法,所以观察者模式的订阅者与发布者之间是存在依赖的
  • 公布 / 订阅模式 由对立调度核心调用,因而发布者和订阅者不须要晓得对方的存在

模仿 Vue 响应式原理

整体剖析

  • Vue 根本构造
  • 打印 Vue 实例察看
  • 整体构造
  • 把 data 中的成员注入到 Vue 实例,并且把 data 成员转成 getter/setter
  • Observer: 可能对数据对象的所有属性进行监听,如有变动可拿到最新值并告诉 Dep

Vue

  • 负责接管初始化的参数
  • 负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
  • 负责调用 observer 监听 data 中所有属性的变动
  • 负责调用 compiler 解析指令 / 插值表达式

Observer

  • 负责把 data 选项中的属性转换成响应式数据
  • data 中的某个属性也是对象,把该属性转换成响应式数据
  • 数据变动发送告诉

Compiler

  • 负责编译模板,解析指令 / 插值表达式
  • 负责页面的首次渲染
  • 当数据变动后从新渲染视图

Dep (Dependency)

  • 收集依赖,增加观察者(watcher)
  • 告诉所有观察者

Watcher

  • 当数据变动触发依赖,dep 告诉所有的 Watcher 实例更新视图
  • 本身实例化的时候往 dep 对象中增加本人

总结

问题

  • 给 data 中某个属性从新赋值成对象,是否是响应式的?— 是
  • 给 Vue 实例新增一个成员是否是响应式的?— 否

Virtual DOM 的实现原理

什么是 Virtual DOM

  • Virtual DOM(虚构 DOM),是由一般的 JS 对象来形容 DOM 对象,因为不是实在的 DOM 对象,所以叫 Virtual DOM
  • 能够应用 Virtual DOM 来形容实在的 DOM

为什么应用 Virtual DOM

  • 手动操作 DOM 比拟麻烦,还须要思考浏览器兼容问题,尽管 jQuery 等库简化了 DOM 操作,然而随着我的项目的简单 DOM 操作简单晋升
  • 为了简化 DOM 的简单操作于是呈现了各种 MVVM 框架,MVVM 框架解决了视图和状态的同步问题
  • 为了简化视图的操作咱们能够应用模板引擎,然而模板引擎没有解决跟踪状态变动的问题,于是 Virtual DOM 呈现了
  • Virtual DOM 的益处是当状态扭转时不须要立刻更新 DOM,只须要创立一个虚构树来形容 DOM,Virtual DOM 外部将弄清楚如何无效 (diff) 的更新 DOM

虚构 DOM 的作用

  • 保护视图和状态的关系
  • 简单视图状况下晋升渲染性能
  • 除了渲染 DOM 外,还能够实现 SSR(Nuxt.js/Next.js)、原生利用(Weex/React Native)、小程序(mpvue/uni-app) 等
  • 并不是所有状况应用虚构 DOM 都能晋升性能,只有在视图比较复杂的状况下,应用虚构 DOM 才会进步渲染性能
退出移动版