顶部左侧内容
百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 在线教程 > 正文

MVVM实现原理(数据劫持+发布订阅模式)的文字版总结

gosiye 2024-08-28 15:10 6 浏览 0 评论

如果有关注本头条号的话,相信大家已经看过MVVM实现原理的视频版了,如果还没关注的话,希望大家动动手指点波关注,后面会陆续发一些质量相对比较高的教学视频(不限题材),如果有想看的教学视频,也可以私信或者留言本号,本号会尽量满足大家的需求。

言归正传,今天这篇文章就是对前面有关MVVM视频的一些总结。根据视频数量,本文也分为8个小节进行总结,方便那些没时间看视频的同学了解和学习MVVM的实现原理,让自己在工作或者面试中掌握主动权。

第一节:了解ES5的Object.defineProperty属性

// 使用Object.defineProperty来定义对象
let obj = {}
Object.defineProperty(obj,'person',{
  value:'mike', // key:person,value:mike
  configurable:true, // 是否可配置,默认false
  writable:true, // 是否可写,默认false
  enumerable:true, // 是否可枚举,默认false
  get() { // 获取obj.person的值时,会调用get方法
  	return 'mike'
  },
  set(val) { // 更新obj.person的值时,会调用set方法
  	console.log(val)
  }
})
// 需要注意:get和set方法不能与value和writable属性同时使用,否则出现报错如下
// Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
// at Function.defineProperty (<anonymous>)

第二节:数据劫持

数据劫持:使用Object.defineProperty递归定义Vue实例中data的所有属性。

// 创建一个Vue构造函数
function Vue(options = {}) {
	this.$options = options	// 将所有属性挂着在$options
  let data = this._data = this.$options.data
  observe(data)
}

// 创建一个Observe构造函数,方便递归调用
function Observe(data) {
	for(let key in data) {
  	let val = data[key]
    observe(val) // 当前val可能也是个对象,所有需要递归进行数据劫持
    Object.defineProperty(data,key,{
    	enumerable:true,
      get() {
      	return val
      },
      set(newVal) {
      	if(newVal == val) return
        val = newVal // 当赋值操作是,也可能赋值一个对象,也需要数据劫持
        observe(newVal)
      }
    })
  }
}

// 观察对象并给对象增加Object.defineProperty
function observe(data) {
  if(typeof data !== 'object') return // 递归到不是对象的时候停止
	return new Observe(data)
}

// new一个Vue实例
let vm = new Vue({
		el:'#app',
  	data:{
    	a:1
    }
})

// 调用
vm._data.a // 1

第三节:数据代理

数据代理:将vm._data.a的取值方式改为vm.a,即vm实例代理vm._data。

/// 创建一个Vue构造函数
function Vue(options = {}) {
	this.$options = options	// 将所有属性挂着在$options
  let data = this._data = this.$options.data
  observe(data)
  // this代理this._data
  for(let key in data) {
  	Object.defineProPerty(this,key,{
    	enumerable:true,
      get() {
      	return this._data[key]
      },
      set(newVal){
      	this._data[key] = newVal
      }
    })
  }
}

第四节:编译模板

编译模板:通过在内存中创建文档碎片来递归节点,然后通过正则匹配模板符号{{}}得到模板符号里面的值,最后匹配data里面的key,替换到节点的内容中。

// 创建一个Vue构造函数
function Vue(options = {}) {
	this.$options = options	// 将所有属性挂着在$options
  let data = this._data = this.$options.data
  observe(data)
  // this代理this._data
  ....
  // 编译模板
  new Compile(options.el,this)
}

// 编译
function Compile(el,vm) {
	vm.$el = document.querySelector(el)
  let fragment = document.createDocumentFragment()
  while(child = vm.$el.firstChild) { // 将#app中的内容移入内存中
  	  fragment.appendChild(child)
  }
  replace(fragment)
  //模板符号中的值替换称vm中对应的值
  function replace(fragment) {
  	  Array.form(fragment.childNodes).forEach((node) => { // 循环每一层节点
        let text = node.textContent
        let reg = /\{\{(.*)\}\}/
        if(node.nodeType == 3 && reg.test(text)){
					let arr = RegExp.$1.split('.'); // [a,a] [b]
          let val = vm
          arr.forEach((k) => {
          	val = val[k]
          })
          node.textContent = text.replace(reg,val)
        }
        if(node.childNodes) { // 存在子节点,递归
        	replace(node)
        }
      })
  }
  
}

第五节:发布订阅模式原理

发布订阅:订阅就是往一个数组里面添加事件fn1,fn2,fn3...,发布就是遍历数组中的事件,逐个执行。

function Dep() {
	this.subs = []
}

Dep.prototype.addSub = function(sub) { // 订阅
	this.subs.push(sub)
}

Dep.prototype.notify = function() { // 发布
	this.subs.forEach(sub => sub.update())  // 假定每个事件都是有个update事件
}

function Watcher(fn) {
	this.fn = fn
}

Watcher.prototype.update = function() {
	this.fn()
}

let watcher = new Watcher(function() {  // 监听函数
	console.log(1)
})

let dep = new Dep()
dep.add(watcher1)  // 订阅watcher1
dep.add(watcher2)  // 订阅watcher2

dep.notify()

第六节:连接数据和试图

function Dep() {
	this.subs = []
}

Dep.prototype.addSub = function(sub) { // 订阅
	this.subs.push(sub)
}

Dep.prototype.notify = function() { // 发布
	this.subs.forEach(sub => sub.update())  // 假定每个事件都是有个update事件
}

function Watcher(vm,exp,fn) {
	this.fn = fn
  this.vm = vm
  this.exp = exp // 添加到订阅中
  Dep.target = this
  let val = vm
  let arr = exp.split('.').forEach((k) => { // 触发get方法
  	val = val[k]
  })
  Dep.target == null
}

Watcher.prototype.update = function() {
	let val = this.vm
  let arr = this.exp.split('.')
  arr.forEach((k) => {
  	val = val[k]
  })
  this.fn(val) // newVal
}

//模板符号中的值替换称vm中对应的值
function replace(fragment) {
  Array.form(fragment.childNodes).forEach((node) => {
    let text = node.textContent
    let reg = /\{\{(.*)\}\}/
    if(node.nodeType == 3 && reg.test(text)){
      let arr = RegExp.$1.split('.'); // [a,a] [b]
      let val = vm
      arr.forEach((k) => {
        val = val[k]
      })
      // // 添加代码 : 订阅
      new Watcher(vm,RegExp.$1,function(newVal){
      	node.textContent = text.replace(reg,val)
      })
    }
    if(node.childNodes) {
      replace(node)
    }
  })
}

// 创建一个Observe构造函数,方便递归调用
function Observe(data) {
	for(let key in data) {
  	let val = data[key]
    observe(val) 
    Object.defineProperty(data,key,{
    	enumerable:true,
      get() {
        Dep.target && dep.addSub(Dep.target) // 添加代码
      	return val
      },
      set(newVal) {
      	if(newVal == val) return
        val = newVal
        observe(newVal)
        dep.notify() // 添加的代码
      }
    })
  }
}

第七节:双向数据绑定的原理

//模板符号中的值替换称vm中对应的值
function replace(fragment) {
  Array.form(fragment.childNodes).forEach((node) => { // 循环每一层节点
		...
    // 添加代码
    if(node.nodeType == 1){
    	let nodeAttrs = node.attributes
      Array.from(nodeAttrs).forEach((attr) => {
      	let name = attr.name
        let exp = attr.value
        if(name.indexOf('v-') == 0) { // v-model
        	node.value = vm[exp]
        }
        new Watcher(vm,exp,function(newVal){
        	node.value = newVal // 当watcher触发时会自动将内容放到输入框里
        })
      })
    }
    node.addEventListener('input',function(e){
    	let newVal = e.target.value
      vm[exp] = newVal
    })
  })
  ...
}

第八节:computed实现原理

computed:可以缓存,只是把数据挂在vm上。

// 创建一个Vue构造函数
function Vue(options = {}) {
	this.$options = options	// 将所有属性挂着在$options
  let data = this._data = this.$options.data
  observe(data)
  // this代理this._data
  ....
  //初始化computed 属性
  initComputed.call(this)
  // 编译模板
  new Compile(options.el,this)
}

initComputed() {
	let vm = this
  let computed = this.$options.computed
  Object.keys(computed).forEach((key) => {
  	get:typeof computed[key] === 'function' ? computed[key] : computed[key].get,
    set(){}
  })
}

相关推荐

全球最大的H5网站模板库(h5页面模板下载)

当今社会,互联网迅猛发展,在网络营销中,客户往往通过企业的网站建设留下对该企业的第一印象,一个优秀的企业网站已成为企业发展的重要纽带,嗨创H5,拥有国内外一流的技术团队,潜心专研网站建设6年,是全球最...

wordpress集团公司网站模板:XSgr(wordpress建站公司)

小兽wordpress推出一款高端集团公司主题,打造高品质官网。高端是一种态度和坚持,因为我坚信贴合产品及品牌理念的高端深度定制才能最大化地呈现企业的务实严谨与产品的专业品质相比,某种程度上讲–...

私心推荐,小编酷爱的五款高逼格网站模板

建站宝盒的网站模板上千套之多,各有各的风格色彩,但是,弱水三千,小编我却只取一瓢饮,在这上千套模板之中,小编酷爱的网站模板有五套,让小编私心推荐一下吧!1、茶叶贸易公司网站模板小编对这款网站模板可是一...

「书讯」政府网站用户行为研究与应用

《政府网站用户行为研究与应用》作者:刘合翔著出版日期:2018年6月开本:16开出版社:经济管理出版社小编推荐《政府网站用户行为研究与应用》的主题是关于政府网站用户行为的特征规律及其在政府网站优...

免费服务器-搭建模板网站的操作流程(图文版)

之前发文《创业者的官网:如何搭建免费云服务器及操作面板(图文版)》,因为做了视频才发现,创业者对视频的需求,远远低于对图文解说的需求。因此,补充图文教程,不清楚的看官们,可以直接看视频版本进行细部学...

快收藏这些高逼格H5网站模板吧,不绕弯子直接下载

上面这些响应式H5网站是不是很炫酷,比起那些“在线一键生成”是不是好太多了?关键是,那些一键制作都不会开放源码给你,自定义性也很局限。不过说到底还是难看。今天笔者推荐大家一个模板网站,全都是高质量的响...

如何开发网站建设管理系统模板(如何开发网站建设管理系统模板图片)

根据用户网站需求文档设计美工图,并设计数据库结构,让网站开发人员可以更多地关注前台美工,先对照美工图,编写静态HTML页面,按网站建设管理系统模板语法,修改编写好的静态HTML页面,运行。不再需要对...

C语言的数据类型介绍(c语言的数据类型介绍是什么)

在计算机系统中,数据是放在内存中的,数字、文字、符号、图形、音频、视频等数据都是以二进制形式存储在内存中的,它们并没有本质上的区别,那么0001000该理解为数字8呢,还是图像中某个像素的颜色...

C 语言格式化输出函数中常用的格式符号

在之前介绍输入输出函数的文章中,有提到格式化输入输出函数都有包含一种特殊的符号——格式符号。那篇文章中关于格式符号也只是一笔带过,没有进行深入挖掘。本篇文章主要对输出函数(printf)中的一些常用格...

C#中的类型转换(c#数据转换类)

计算机存储的基本单位:字节我们知道一个字节(Byte)有8个比特(bit)构成,比特是存储的最小单位,表示0和1,但为什么计算机存储的基本单位是字节,而不是比特呢?假设我们要存储数字3(二进制:11...

Java8中String内存空间占用分析(电脑里下载的文件怎样删除才不会占用内存空间)

1.前言分析之前,简单回顾一下对象的内存分布。在HotSpot虚拟机中,对象在堆内存中的存储布局可以划分为三部分:对象头、实例数据和对齐填充。对象头包含两部分内容:MarkWord和类型指针。实例数据...

「每日C语言」数据类型大小和取值范围

对于c语言来说,数据类型是一个很重要的概念和知识点,它涉及到的是内存的空间,这在和硬件交互的时候是非常重要的。K&R给出了7个数据类型相关的关键字,分别是:int、long、short、uns...

【c语言学习笔记】数据类型(c语言里面的数据类型)

c语言学习笔记,欢迎大家能在评论区提出我学习错误的地方方便我进行改正~在计算机中,计算机用二进制来储存数据,在c语言中有许多的数据类型用来存储数据,当然不同的数据类型所用的内存占用也不一样,下面就来用...

关于MySQL varchar类型最大值,原来一直都理解错了

我是架构精进之路,点击上方“关注”,坚持每天为你分享技术干货,私信我回复“01”,送你一份程序员成长进阶大礼包。写在前面关于MySQLvarchar字段类型的最大值计算,也许我们一直都理解错误了,...

C语言数据类型的转换(c语言数据类型的转换方式)

类型转换在C语言程序中,经常需要对不同类型的数据进行运算,为了解决数据类型不一致的问题,需要对数据的类型进行转换。例如一个浮点数和一个整数相加,必须先将两个数转换成同一类型。C语言程序中的类型...

取消回复欢迎 发表评论: