data数据响应式、数据代理

<!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>3</li></ul></div></div><script>/***  #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++) {observe(arguments[i])}// 执行数组原型上的方法return Array.prototype[key].apply(this, arguments)}})// #5.2 定义得到响应式数据的方法function defineReactiveProperty(target, key, value, enumerable) {// 拿到MyVue实例对象let that = this// TODO:对非数组的引用类型进行拦截,进行响应式注册if (typeof value === 'object' && value != null) {observe(value, that)}Object.defineProperty(target, key, {configurable: true, // 可配置的enumerable: !!enumerable, // 是否可枚举get() {console.log('调用:' + key)return value},set(newVal) {console.log('设置:' + key + '-->' + newVal)// ! 新添加的值响应式化if (typeof newVal === 'object' && newVal != null) {observe(newVal, that)}value = newVal// 数据修改则直接更新// TODO:这里省略了watcherthat.mountComponent()}})}// #5.1 递归实现所有数据的响应式注册function observe(data, vm) {if (Array.isArray(data)) {// 数组方法重写data.__proto__ = array_method// 如果是数组则遍历注册,这里是对数组内部进行响应式注册data.forEach((item) => {observe(item, vm)})} else {Object.keys(data).forEach((key) => {let value = data[key]// 除数组外的数据类型均在这里进行响应式注册defineReactiveProperty.call(vm, 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// TODO:#5 响应式化 + 代理 映射this.initData()// 根元素let el = document.querySelector(obj.el) // vue 是字符串, 这里是 DOM// 根模板this._template = el// 为什么要拿父节点this._parent = this._template.parentNode// TODO:#1this.mount()}// 数据初始化MyVue.prototype.initData = function () {observe(this._data, this)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:#3mount.call(this) // 本质应该交给 watcher 来调用}// 渲染函数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 node = parseVNode(vnode)this._parent.replaceChild(node, document.querySelector('#root'))}const options = {el: '#root',data: {name: { firstName: 'wang', lastName: 'wu' },age: 18,gender: '男'}}// 创建实例const mv = new MyVue(options)</script></body>
</html>

简易 Vue 构建--篇四相关推荐

  1. 简易 Vue 构建--篇三

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

  2. 简易 Vue 构建--篇二

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

  3. 简易 Vue 构建--篇一

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

  4. Vue实战篇四:创建多步骤表单

    系列文章目录 Vue基础篇一:编写第一个Vue程序 Vue基础篇二:Vue组件的核心概念 Vue基础篇三:Vue的计算属性与侦听器 Vue基础篇四:Vue的生命周期(秒杀案例实战) Vue基础篇五:V ...

  5. 简易 Vue 构建--终

    简易 Dep/Watcher <!DOCTYPE html> <html lang="en"><head><meta charset=&q ...

  6. Vue实战篇二十九:模拟一个简易留言板

    系列文章目录 Vue基础篇一:编写第一个Vue程序 Vue基础篇二:Vue组件的核心概念 Vue基础篇三:Vue的计算属性与侦听器 Vue基础篇四:Vue的生命周期(秒杀案例实战) Vue基础篇五:V ...

  7. Vue实战篇三十:实现一个简易版的头条新闻

    系列文章目录 Vue基础篇一:编写第一个Vue程序 Vue基础篇二:Vue组件的核心概念 Vue基础篇三:Vue的计算属性与侦听器 Vue基础篇四:Vue的生命周期(秒杀案例实战) Vue基础篇五:V ...

  8. Vue实战篇三十三:实现新闻的浏览历史

    系列文章目录 Vue基础篇一:编写第一个Vue程序 Vue基础篇二:Vue组件的核心概念 Vue基础篇三:Vue的计算属性与侦听器 Vue基础篇四:Vue的生命周期(秒杀案例实战) Vue基础篇五:V ...

  9. Vue实战篇三十一:实现一个改进版的头条新闻

    系列文章目录 Vue基础篇一:编写第一个Vue程序 Vue基础篇二:Vue组件的核心概念 Vue基础篇三:Vue的计算属性与侦听器 Vue基础篇四:Vue的生命周期(秒杀案例实战) Vue基础篇五:V ...

最新文章

  1. 转:ASP自动解压RAR文件
  2. 使用NuGet发布自己的类库包(Library Package)
  3. 数据库-数据库的介绍
  4. P8实战(二):分布式锁前置技能 etcd 集群搭建
  5. 编译原理习题(含答案)——4-7语法分析——MOOC哈尔滨工业大学陈鄞配套_学习通_慕课堂
  6. c语言使用正则,C语言中使用正则表达式
  7. 化妆definer是什么意思_化妆品上的r是什么意思
  8. 二级c语言光盘,二级c语言(光盘).doc
  9. MongoDB的使用
  10. 谷歌紧急更新,Chrome 今年第二个零日漏洞曝光
  11. sublime----------快捷键的记录
  12. 理解 GBK、Unicode、utf-8
  13. 多周期MIPS CPU硬布线控制器设计
  14. 项目管理工具——PDCA管理循环
  15. 36. 有效的数独(技巧)
  16. syswow64删除文件_什么是SysWow64文件夹 SysWow64文件夹可以删除吗
  17. 物联网开发笔记(31)- 使用Micropython开发ESP32开发板之手机扫二维码远程控制开关灯(1)
  18. Ubuntu安装MATLAB并设置桌面快捷方式!!!
  19. Android 炫酷进度条
  20. Linux桌面需要强制访问控制,Linux强制访问控制机制模块详细描述(1)

热门文章

  1. python编译环境对cpu要求高不高_解决Tensorflow 使用时cpu编译不支持警告的问题
  2. maven netty 配置_使用Springboot整合开发Netty(一个表白的小案例)
  3. 设计灵感|C4D卡通角色设计作品,你想要的模型集设都有
  4. UI设计灵感|时尚简约风格网页页面设计
  5. 设计素材psd分层模板|临摹搞定促销海报版式!
  6. 实用UI设计需要学什么软件?
  7. php读入输入_php-读取用户输入并检查数据类型
  8. testflight怎么做版本更新_《动物森友会》万圣节版本更新后,别忘了做这五件事情...
  9. CEF编译教程(手把手教学版)
  10. 数据库的设计与连接、站点的搭建