仅笔记使用,如果想要下面代码的视频,关注公众号“程序江”,回复001

html使用下面js文件的例子

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><div id="app"><input type="text" v-model="person.name"><div>{{person.name}}</div><div>{{person.age}}</div><!-- 如果数据不变化,视图就不会刷新,在内部会匹配{{}} -->{{getNewName}}<button v-on:click="change">更新</button><div v-html="message"></div></div><script src="index.js"></script><!-- <script src="node_modules/vue/dist/vue.min.js"></script> --><script>let vm = new Vue({el: '#app',data: {person: {name: 'fur',age: 10},message:'<h2>程序江</h2>'},computed: {getNewName() {return this.person.name + '在学习'}}, methods: {change(){this.person.name = 'furfur-jiang'}}})</script>
</body></html>

Js文件

//发布订阅
class Dep {constructor() {this.subs = []}//订阅addSub(watcher) {this.subs.push(watcher)}//发布notify() {this.subs.forEach(watcher => watcher.update())}
}//观察者:将数据劫持和页面联系起来
class Watcher {constructor(vm, expr, cb) {this.vm = vmthis.expr = exprthis.cb = cb//默认存放一个老值this.oldValue = this.get()}get() {Dep.target = this //先把自己放在全局上//取值,把观察者和数据联系起来let value = CompileUtil.getVal(this.vm, this.expr)//不取消任何取值都会添加watcherDep.target = nullreturn value}update() { //更新操作,数据变化后会调用观察者update方法let newVal = CompileUtil.getVal(this.vm, this.expr)if (newVal !== this.oldValue) {this.cb(newVal)}}
}//实现数据劫持
class Observer {constructor(data) {this.observer(data)}observer(data) {if (data && typeof data == 'object') {for (let key in data) {this.defineReactive(data, key, data[key])}}}defineReactive(obj, key, value) { //value还是对象的话要继续,才会给全部都赋予get和set方法this.observer(value)let dep = new Dep() //给每个属性都加上一个发布订阅功能Object.defineProperty(obj, key, {get() {//创建watcher时候,会取到对应内容,并且把watcher放到全局上Dep.target && dep.addSub(Dep.target)return value},set: (newVal) => { //若赋值的是一个对象,还需要继续监控if (newVal != value) {this.observer(newVal)value = newValdep.notify()}}})}
}class Compiler {constructor(el, vm) {//判断el属性this.el = this.isElementNode(el) ? el : document.querySelector(el)this.vm = vm//把当前节点中的元素获取到,并放到内存中let fragment = this.node2fragment(this.el)//把节点中内容进行替换//编译模板,用数据编译this.compile(fragment)//把内容塞回页面this.el.appendChild(fragment)}//判断是不是指令isDirective(attrName) {return attrName.startsWith('v-') //开头}//编译元素的方法compileElement(node) {let attributes = node.attributes; //类数组[...attributes].forEach(attr => {let {name,value: expr} = attrif (this.isDirective(name)) { //v-model v-html v-bindlet [, directive] = name.split('-') //v-on:clicklet [directiveName, eventName] = directive.split(':')//调用不同指令来处理CompileUtil[directiveName](node, expr, this.vm, eventName)}})}//编译文本的方法compileText(node) { //判断文本节点中是否包含{{}}let content = node.textContent//(.+?)匹配一个大括号内的,一个及以上,到第一个大括号结束时候结束if (/\{\{(.+?)\}\}/.test(content)) {CompileUtil['text'](node, content, this.vm)}}//编译的核心方法compile(node) {let childNodes = node.childNodes;[...childNodes].forEach(child => {if (this.isElementNode(child)) {this.compileElement(child)this.compile(child) //递归,获得内层} else {this.compileText(child)}});}node2fragment(node) {//创建一个文本碎片let fragment = document.createDocumentFragment()let firstChild;while (firstChild = node.firstChild) {//appendChild具有移动性fragment.appendChild(firstChild)}return fragment}isElementNode(node) {//判断是否为元素节点return node.nodeType === 1}
}//绑定处理事件的各种方法
CompileUtil = {//取得对应的数据getVal(vm, expr) { //vm.$data 'school.name'//返回namereturn expr.split('.').reduce((data, current) => {return data[current] //继续取值,取到name}, vm.$data)},setValue(vm, expr, value) {expr.split('.').reduce((data, current, index, arr) => {if (index == arr.length - 1) {return data[current] = value}return data[current]}, vm.$data)},model(node, expr, vm) { //node节点,expr是表达式,vm是当前实例let fn = this.updater['modeUpdater']//给输入框加一个观察者,稍后数据更新就会触发此方法,将新值给输入框赋予值new Watcher(vm, expr, (newVal) => {fn(node, newVal)})node.addEventListener('input', e => {let value = e.target.value //获取用户输入的内容this.setValue(vm, expr, value)})let value = this.getVal(vm, expr)fn(node, value)},html(node, expr, vm) {let fn = this.updater['htmlUpdater']new Watcher(vm, expr, (newVal) => {fn(node, newVal)})let value = this.getVal(vm, expr)fn(node, value)},getContentValue(vm, expr) {//遍历一个表达式,将内容重新替换成一个完整的内容,返还回去return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {return this.getVal(vm, args[1])})},on(node, expr, vm, eventName) { //  v-on:click="change"  expr就是changenode.addEventListener(eventName, (e) => {vm[expr].call(vm, e) //this.change})},text(node, expr, vm) {let fn = this.updater['textUpdater']let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {//给表达式每个{{}}都加个观察者new Watcher(vm, args[1], (newVal) => {fn(node, this.getContentValue(vm, expr)) //返回一个全的字符串})return this.getVal(vm, args[1])})fn(node, content)},//更新视图updater: {//把数据插入节点当中modeUpdater(node, value) {node.value = value},textUpdater(node, value) {node.textContent = value},htmlUpdater(node, value) {//xss攻击node.innerHTML = value}}
}// 基类,调度
class Vue {constructor(options) {this.$el = options.elthis.$data = options.datalet computed = options.computedlet methods = options.methodsif (this.$el) {//把数据全部转换成用Object.defineProperty来定义,数据劫持new Observer(this.$data)//{{getNewName}} reduce 取值是vm.$data.getNewNamefor (let key in computed) {Object.defineProperty(this.$data, key, {get: () => { //注意this指向el实例return computed[key].call(this)}})}for (let key in methods) {Object.defineProperty(this, key, {get: () => { //注意this指向el实例return methods[key]}})}//把数据获取操作vm上的取值操作都代理到vm.$data上this.proxyVm(this.$data)new Compiler(this.$el, this)}}proxyVm(data) {for (let key in data) {Object.defineProperty(this, key, {get() {//相当于在$data上取值,进行转换操作,不需要深层代理return data[key]},set(newVal){ //设置代理方法data[key] = newVal}})}}}

实现简易版vue2(数据劫持,观察者,发布订阅)相关推荐

  1. vue面试核心,双向数据绑定,数据代理,数据劫持,发布订阅,数据编译,看这个demo就够了

    vue面试核心,双向数据绑定,数据代理,数据劫持,发布订阅,数据编译,看这个demo就够了. 不在怕面试官,你给我怼过去.复制到html文档,浏览器运行即可. mvvm原理剖析 {{singer}}

  2. vue源码深入解读MVVM(视图模板引擎),你真的了解双向绑定(v-model),数据劫持(observe),发布订阅模式吗?带你手鲁mvvm引擎。源码奉上(详细注释)!

    文章目录 #1.vue的强大之处不必细说,vue的核心v-model的实现原理,网上都有很多.但是真正自己实现双向绑定,mvvm的源码却几乎没见过. #1.2本人根据源码的解读,理解,以及借鉴网上的视 ...

  3. 都市行V1.2正式版及数据制作工具发布(免费的基于J2ME手机公交查询软件) [转]

    都市行是基于J2ME的公交查询系统,使用简单,操作方便,查询速度快,并且具有良好的兼容性.是目前功能最全面的手机公交查询软件之一. 功能特点: ●提供多城市版本,可以自由切换查询城市.(NEW) ●支 ...

  4. javascript 观察者(发布订阅)模式详解

    写给读者的话 本人是千千万万前端小白中的一员,所以对前端小白的痛苦感同身受,面对一个新的知识点,很多时候感到束手无策.网上搜资料,有的不全,有的看不懂,所以本人作为小白,很有义务将自己觉得理解了的知识 ...

  5. Vue源码分析-手写Vue(简易版)

    1.Vue双向绑定/MVVM响应式原理/v-model的原理 vue.js通过数据劫持结合发布订阅者模式,通过Object.defineProperty来劫持各个属性的setter,getter,在数 ...

  6. 简单的写一个发布订阅器

    发布-订阅模式在开发中的应用其实是很广泛的,比如大家都知道的 Vue,使用数据驱动模板的改变,将我们的双手从繁琐的 dom 操作中解放出来,稍微懂一些原理的同学们都知道,其双向数据绑定就是通过数据劫持 ...

  7. Vue2 MVVM 双向绑定(数据劫持+发布者-订阅者模式)

    参考文献:https://www.cnblogs.com/libin-1/p/6893712.html https://juejin.im/post/5b2f0769e51d45589f46949e ...

  8. RabbitMQ实例教程:发布/订阅者消息队列

    消息交换机(Exchange) RabbitMQ消息模型的核心理念是生产者永远不会直接发送任何消息给队列,一般的情况生产者甚至不知道消息应该发送到哪些队列. 相反的,生产者只能发送消息给交换机(Exc ...

  9. 手写简易版Vue源码之数据响应化的实现

    当前,Vue和React已成为两大炙手可热的前端框架,这两个框架都算是业内一些最佳实践的集合体.其中,Vue最大的亮点和特色就是数据响应化,而React的特点则是单向数据流与jsx. 笔者近期正在研究 ...

最新文章

  1. 线性回归api初步使用
  2. doxygen相关问题 转
  3. android tesseract-ocr实例教程(包含中文识别)(附源码)
  4. Qt使用dmctk时的错误
  5. caffe blob操作
  6. Google大数据三论文
  7. Java本地缓存CaffeineCache集成
  8. axure中怎么把图片变圆_怎么将图片中的文字提取出来?收下这份识别教程
  9. 该弱磁算法采用单电流控制策略,额定转速以下采用MTPA控制,额定转速以上采用单电流控制
  10. Android studio 或java打开txt文件
  11. java爬取中央气象台天气预报
  12. DFS.01-寻路问题
  13. VLAN访问控制列表
  14. Docker磁盘空间满的解决办法
  15. matlab 中关于nargin 以及 varargin 函数的使用
  16. Java实现第八届蓝桥杯日期问题
  17. Spring 缓存的详解
  18. Yii2.0 后台UI框架以及权限管理扩展实现权限菜单管理
  19. 【SCADA】KingSCADA将数据插入SQLServer的方法
  20. vue项目打包成安卓app

热门文章

  1. 2019金华正睿集训总结
  2. 经典sql题目,给每个部门中工资最低的员工涨薪1000
  3. c语言如何实现一只蜗牛爬的循环,[工程科技]第五章 循环结构程序设计c语言程序设计.ppt...
  4. 哈工大视听觉信号处理——听觉部分报告——一种智能家居命令词识别系统的设计
  5. NEO改进协议提案2(NEP-2) 1
  6. HTML5自动换行的间距设置,设置EXCEL自动换行的行与行之间的间距的办法
  7. 基于多重继承与信息内容的知网词语相似度计算 - 论文及代码讲解
  8. 服务器怎么安装exis系统,exis 服务器安装
  9. linux电脑系统桌面文件怎么恢复出厂设置密码,树莓派如何恢复LXPanel面板默认设置-恢复出厂设置英文...
  10. mysql 1058_MySQL数据库之mysql启动服务报1058错误的解决方法