简易 Dep/Watcher

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="root"><div class="c1"><div title="tt1" id="id">233</div><div title="tt2">{{ age }}</div><div title="tt3">{{ gender }}</div><ul><li>111{{name.firstName}}-{{age}}</li><li>{{name.lastName}}</li><li>{{numArr}}</li><li>{{someThing.aaa}}</li><li>3</li></ul></div></div><script>let depid = 0class Dep {constructor() {this.id = depid++this.subs = [] // 存储的是与 当前 Dep 关联的 watcher}/** 添加一个 watcher */addSub(sub) {this.subs.push(sub)}/** 移除 */removeSub(sub) {for (let i = this.subs.length - 1; i >= 0; i--) {if (sub === this.subs[i]) {this.subs.splice(i, 1)}}}/** 将当前 Dep 与当前的 watcher ( 暂时渲染 watcher ) 关联*/depend() {// 就是将 当前的 dep 与当前的 watcher 互相关联if (Dep.target) {this.addSub(Dep.target) // 将 当前的 watcher 关联到 当前的 dep 上Dep.target.addDep(this) // 将当前的 dep 与 当前渲染 watcher 关联起来}}/** 触发与之关联的 watcher 的 update 方法, 起到更新的作用 */notify() {// 在真实的 Vue 中是依次触发 this.subs 中的 watcher 的 update 方法// 此时, deps 中已经关联到 我们需要使用的 那个 watcher 了let deps = this.subs.slice()deps.forEach((watcher) => {watcher.update()})}}// 全局的容器存储渲染 WatcherDep.target = nulllet targetStack = []/** 将当前操作的 watcher 存储到 全局 watcher 中, 参数 target 就是当前 watcher */function pushTarget(target) {targetStack.unshift(target) // vue 的源代码中使用的是 pushDep.target = target}/** 将 当前 watcher 踢出 */function popTarget() {targetStack.shift() // 踢到最后就是 undefinedDep.target = targetStack[targetStack.length - 1]}/*** 在 watcher 调用 get 方法的时候, 调用 pushTarget( this )* 在 watcher 的 get 方法结束的时候, 调用 popTarget()*/let watcherid = 0/** Watcher 观察者, 用于 发射更新的行为 */class Watcher {/**** @param {Object} vm JGVue 实例* @param {String|Function} expOrfn 如果是渲染 watcher, 传入的就是渲染函数, 如果是 计算 watcher 传入的就是路径表达式, 暂时只考虑 expOrFn 为函数的情况.*/constructor(vm, expOrfn) {this.vm = vmthis.getter = expOrfnthis.id = watcherid++this.deps = [] // 依赖项this.depIds = {} // 是一个 Set 类型, 用于保证 依赖项的唯一性 ( 简化的代码暂时不实现这一块 )// 一开始需要渲染: 真实 vue 中: this.lazy ? undefined : this.get()this.get()}/** 计算, 触发 getter */get() {pushTarget(this)this.getter.call(this.vm, this.vm) // 上下文的问题就解决了popTarget()}/*** 执行, 并判断是懒加载, 还是同步执行, 还是异步执行:* 我们现在只考虑 异步执行 ( 简化的是 同步执行 )*/run() {this.get()// 在真正的 vue 中是调用 queueWatcher, 来触发 nextTick 进行异步的执行}/** 对外公开的函数, 用于在 属性发生变化时触发的接口 */update() {this.run()}/** 清空依赖队列 */cleanupDep() {}/** 将 当前的 dep 与 当前的 watcher 关联 */addDep(dep) {this.deps.push(dep)}}/***  #0.* 属于重要辅助函数*/// 如何得到虚拟dom// 即:将真实的DOM元素转换成js对象// 构建虚拟dom对象class VNode {constructor(tag, data, value, type) {this.tag = tag && tag.toLowerCase() // 标签名this.data = data // 属性this.value = value // 标签内容this.type = type // 节点类型this.children = [] // 子节点}// 添加虚拟dom子节点appendChild(vnode) {this.children.push(vnode)}}// TODO:#0.1 将真实dom转换成虚拟dom,这个函数当做 compiler 函数function getVNode(node) {const type = node.nodeTypelet _vnode = nullif (type === 1) {// 标签节点const tag = node.nodeName // 标签名const attrs = node.attributes // attributes 真实dom里存放标签属性 数组对象const data = {} // 虚拟dom中 属性就只是对象for (let i = 0; i < attrs.length; i++) {data[attrs[i].nodeName] = attrs[i].nodeValue // 数组每一项}const value = undefined_vnode = new VNode(tag, data, value, type)const children = node.childNodes// 子节点for (let i = 0; i < children.length; i++) {_vnode.appendChild(getVNode(children[i]))}} else if (type === 3) {// 文本节点_vnode = new VNode(undefined, undefined, node.nodeValue, type)}// 将每一个虚拟节点返回return _vnode}// TODO:#0.2 将虚拟 DOM 转换成真正的 DOMfunction parseVNode(vnode) {let oDom = null// 标签节点if (vnode.type === 1) {oDom = document.createElement(vnode.tag)const data = vnode.data// 属性节点Object.keys(data).forEach((key) => {let attrName = keylet attrValue = data[key]oDom.setAttribute(attrName, attrValue)})// 子节点for (let i = 0; i < vnode.children.length; i++) {oDom.appendChild(parseVNode(vnode.children[i]))}} else if (vnode.type === 3) {// 创建文本节点oDom = document.createTextNode(vnode.value)}return oDom}// 对象解析函数 a.b.c.d这种格式function getValueByKey(obj, str) {const arr = str.split('.')let prop = nullwhile ((prop = arr.shift())) {obj = obj[prop]}return obj}const reg = /\{\{(.+?)\}\}/g// TODO:#0.3 将带有坑的 Vnode 与数据 data 结合, 得到填充数据的 VNode: 模拟 AST -> VNodefunction combine(vnode, data) {let _type = vnode.typelet _data = vnode.datalet _value = vnode.valuelet _tag = vnode.taglet _children = vnode.childrenlet _vnode = nullif (_type === 3) {// 文本节点// 对文本处理_value = _value.replace(reg, function (_, g) {return getValueByKey(data, g.trim())})// 重新创建虚拟dom对象_vnode = new VNode(_tag, _data, _value, _type)} else if (_type === 1) {// 元素节点// 重新创建虚拟dom对象_vnode = new VNode(_tag, _data, _value, _type)_children.forEach((_subvnode) => _vnode.appendChild(combine(_subvnode, data)))}return _vnode}// TODO:#0.4 函数重写const ARRAY_METHOD = ['push', 'pop', 'shift', 'unshift', 'reverse', 'splice', 'sort']// 原型继承 array_method.__proto__ 指向 Array.prototypeconst array_method = Object.create(Array.prototype)// TODO:数组部分方法改造成响应式  方法重写(拦截)ARRAY_METHOD.forEach((key) => {array_method[key] = function () {// 方法重写(拦截)// 将新添加的数据进行响应式化for (let i = 0; i < arguments.length; i++) {if (typeof arguments[i] === 'object' && arguments[i] != null) {def(arguments[i], '__dep__', new Dep())}observe(arguments[i])}setTimeout(() => {this.__dep__.notify()}, 0)// 执行数组原型上的方法return Array.prototype[key].apply(this, arguments)}})function def(target, key, val) {Object.defineProperty(target, key, {value: val})}// #5.2 定义得到响应式数据的方法function defineReactiveProperty(target, key, value, enumerable) {let dep = new Dep()dep.__propName__ = key// TODO:对非数组的引用类型进行拦截,进行响应式注册if (typeof value === 'object' && value != null) {def(value, '__dep__', dep)observe(value)}Object.defineProperty(target, key, {configurable: true, // 可配置的enumerable: !!enumerable, // 是否可枚举get() {// console.log(`读取 ${key} 属性`); // 额外// 依赖收集 ( 暂时略 )dep.depend()return value},set(newVal) {if (value === newVal) return// ! 新添加的值响应式化if (typeof newVal === 'object' && newVal != null) {observe(newVal)}console.log('赋值成功了')value = newVal// 派发更新, 找到全局的 watcher, 调用 updatedep.notify()}})}// #5.1 递归实现所有数据的响应式注册function observe(data, isRoot) {if (typeof data === 'object' && data != null && isRoot) {def(data, '__dep__', new Dep())}if (Array.isArray(data)) {// 数组方法重写data.__proto__ = array_method// 如果是数组则遍历注册,这里是对数组内部进行响应式注册data.forEach((item) => {observe(item)})} else {Object.keys(data).forEach((key) => {let value = data[key]// 除数组外的数据类型均在这里进行响应式注册defineReactiveProperty(data, key, value, true)})}}// #5.3 代理 -> 映射function proxy(target, middle, key) {Object.defineProperty(target, key, {enumerable: true,configurable: true,get() {return target[middle][key]},set(newVal) {target[middle][key] = newVal}})}function MyVue(obj) {// 习惯: 内部的数据使用下划线 开头, 只读数据使用 $ 开头// 数据this._data = obj.data// 根元素let el = document.querySelector(obj.el) // vue 是字符串, 这里是 DOM// 根模板this._template = el// 为什么要拿父节点this._parent = this._template.parentNode// TODO:#5 响应式化 + 代理 映射this.initData()// TODO:#1this.mount()}// 数据初始化MyVue.prototype.initData = function () {observe(this._data, true)Object.keys(this._data).forEach((key) => {proxy(this, '_data', key)})}MyVue.prototype.mount = function () {// TODO:#2 需要提供一个 render 方法: 生成 虚拟 DOM// 这里得到了一个未执行的函数  该函数执行后的结果是合并数据后的VNodethis.render = this.createRenderFunction()// 视图更新this.mountComponent()}MyVue.prototype.mountComponent = function () {let mount = () => {// TODO:#4// this.render() 会得到数据合并后的VNode// 更新函数执行得到真实DOM并渲染到页面this.update(this.render())}// TODO:#3// 这个 Watcher 就是全局的 Watcher, 在任何一个位置都可以访问他了 ( 简化的写法 )new Watcher(this, mount) // 相当于这里调用了 mount}// 渲染函数MyVue.prototype.createRenderFunction = function () {// #0.1  VNode 模拟 AST   获得VNodeconst ast = getVNode(this._template)return function () {// #0.3 将 带有 坑的 VNode 转换为 待数据的 VNodereturn combine(ast, this._data)}}// 更新视图MyVue.prototype.update = function (vnode) {// 转换成真实DOMlet realDOM = parseVNode(vnode)this._parent.replaceChild(realDOM, document.querySelector('#root'))}// 创建实例const mv = new MyVue({el: '#root',data: {name: { firstName: 'wang', lastName: 'wu' },age: 18,gender: '男',someThing: { aaa: 'a' },numArr: [1, 2]}})</script></body>
</html>

简易 Vue 构建--终相关推荐

  1. 简易 Vue 构建--篇四

    data数据响应式.数据代理 <!DOCTYPE html> <html lang="en"><head><meta charset=&q ...

  2. 简易 Vue 构建--篇三

    模拟真实DOM转换成虚拟DOM(省略了AST语法树过程): VNode数据替换过程: VNode转换为真实DOM,更新到视图过程. <!DOCTYPE html> <html lan ...

  3. 简易 Vue 构建--篇二

    1.将 HTML DOM 转换成 js 对象 2.将 js 对象转换成 HTML DOM <!DOCTYPE html> <html lang="en">& ...

  4. 简易 Vue 构建--篇一

    真实 DOM 中的模板数据替换 <!DOCTYPE html> <!DOCTYPE html> <html lang="en"><head ...

  5. VUE构建工具-姜威-专题视频课程

    VUE构建工具-12043人已学习 课程介绍         随着vue2.0的发布,本课程以 vue1.0 和 webpack 为基础,主要讲解项目的构建的,涉及到的vue-router和vuex, ...

  6. py WebsocketServer创建的服务器与vue构建前端页面相互收发信息——猪猪侠方丈-px

    py WebsocketServer创建的服务器与vue构建前端页面相互收发信息 环境:py3 pycharm WebsocketServer0.5.1 vue2 废话少说上代码pycharm中.py ...

  7. vue 构建根组件_构建迷你图Vue组件

    vue 构建根组件 Sparklines can be used to quickly visualize data variance. They are small and intuitive to ...

  8. axios下载大文件_用Vue构建一个github“可视化大数据平台”

    GitDataV,是一个github"大数据可视化平台",通过它你可以更直观的看到你在github里的一些数据,(之所以打双引号,是因为我觉得这个还没到大数据可视化的程度).其实我 ...

  9. Cordova+Vue构建Hybrid APP简易实操

    当下APP市场,因为Native APP开发成本高,Web APP不稳定,混合开发APP大行其道,成为越来越多开发者的首选.Hybrid APP开发框架也比较多,Weex.Ionic.PhoneGap ...

最新文章

  1. C# 获得系统AppData路径
  2. 【转自聊聊架构公众号】 Redis大key图形化统计及展示
  3. Logging with Log4net (二)
  4. 对js数组去重的研究
  5. 蓝桥杯 123 二分+打表
  6. 树的同构模板题(法1.最小表示法+法2.树哈希)
  7. 软件构造-犯错的艺术——健壮性与正确性,异常,防御式编程,debugging与test的思考与总结...
  8. 【剑指offer】面试题30:包含min函数的栈
  9. linux以下哪个属于块设备,1222.在大多数Linux发行版本中,以下哪个属于块设备( )...
  10. 使用TeraTerm自带SSH SCP功能,传送文件
  11. python数据可视化第三方库有哪些_数据可视化!看看程序员大佬都推荐的几大Python库...
  12. ORB-SLAM3:一个用于视觉、视觉惯性和多地图SLAM系统
  13. Topic Model的分类总结(LDA变种)
  14. 【网络攻防原理与技术】第6章:特洛伊木马
  15. java算法面试题及答案pdf,中信银行Java笔试题库
  16. 数学分析教程(科大)——1.11笔记+习题
  17. 计算摄影学基础知识(1)
  18. asp.net 邮件功能
  19. 怎么用软件测试睡眠质量差怎么办,睡眠监测 App 到底有没有用?我睡了 34 晚,做了一个实验...
  20. 项目管理 | 项目资源管理(一)

热门文章

  1. 结构体中初始化vector resize_Java-深入HashMap原理及内部存储结构
  2. 设计师找灵感?集设用作品打动世界的窗口
  3. 金色装饰素材,用这个技巧创造节日的感觉,完美
  4. PNG免扣+高清背景素材,帮电商美工\设计师快速出稿!
  5. 设计实用素材|促销海报设计技巧
  6. java中注释的嵌套,java – 使用mybatis注释获取嵌套对象
  7. Xen Documentation - Hypercall Interfaces
  8. 2021年四月中旬推荐文章
  9. 监督式学习、 非监督式学习、强化学习
  10. 【HTML+CSS网页设计与布局 从入门到精通】第6章-标题h1,h1字体格式的设置方式