JavaScript之实现一个简单的Vue示例


Posted in Javascript onJanuary 17, 2019

vue的使用相信大家都很熟练了,使用起来简单。但是大部分人不知道其内部的原理是怎么样的,今天我们就来一起实现一个简单的vue

Object.defineProperty()

实现之前我们得先看一下Object.defineProperty的实现,因为vue主要是通过数据劫持来实现的,通过getset来完成数据的读取和更新。

var obj = {name:'wclimb'}
var age = 24
Object.defineProperty(obj,'age',{
  enumerable: true, // 可枚举
  configurable: false, // 不能再define
  get () {
    return age
  },
  set (newVal) {
    console.log('我改变了',age +' -> '+newVal);
    age = newVal
  }
})

> obj.age
> 24

> obj.age = 25;
> 我改变了 24 -> 25
> 25

从上面可以看到通过get获取数据,通过set监听到数据变化执行相应操作,还是不明白的话可以去看看Object.defineProperty文档。

流程图

JavaScript之实现一个简单的Vue示例

html代码结构

<div id="wrap">
  <p v-html="test"></p>
  <input type="text" v-model="form">
  <input type="text" v-model="form">
  <button @click="changeValue">改变值</button>
  {{form}}
</div>

js调用

new Vue({
    el: '#wrap',
    data:{
      form: '这是form的值',
      test: '<strong>我是粗体</strong>',
    },
    methods:{
      changeValue(){
        console.log(this.form)
        this.form = '值被我改变了,气不气?'
      }
    }
  })

Vue结构

class Vue{
    constructor(){}
    proxyData(){}
    observer(){}
    compile(){}
    compileText(){}
  }
  class Watcher{
    constructor(){}
    update(){}
  }
  • Vue constructor 构造函数主要是数据的初始化
  • proxyData 数据代理
  • observer 劫持监听所有数据
  • compile 解析dom
  • compileText 解析dom里处理纯双花括号的操作
  • Watcher 更新视图操作

Vue constructor 初始化

class Vue{
    constructor(options = {}){
      this.$el = document.querySelector(options.el);
      let data = this.data = options.data; 
      // 代理data,使其能直接this.xxx的方式访问data,正常的话需要this.data.xxx
      Object.keys(data).forEach((key)=> {
        this.proxyData(key);
      });
      this.methods = options.methods // 事件方法
      this.watcherTask = {}; // 需要监听的任务列表
      this.observer(data); // 初始化劫持监听所有数据
      this.compile(this.$el); // 解析dom
    }
  }

上面主要是初始化操作,针对传过来的数据进行处理

proxyData 代理data

class Vue{
    constructor(options = {}){
      ......
    }
    proxyData(key){
      let that = this;
      Object.defineProperty(that, key, {
        configurable: false,
        enumerable: true,
        get () {
          return that.data[key];
        },
        set (newVal) {
          that.data[key] = newVal;
        }
      });
    }
  }

上面主要是代理data到最上层,this.xxx的方式直接访问data

observer 劫持监听

class Vue{
    constructor(options = {}){
      ......
    }
    proxyData(key){
      ......
    }
    observer(data){
      let that = this
      Object.keys(data).forEach(key=>{
        let value = data[key]
        this.watcherTask[key] = []
        Object.defineProperty(data,key,{
          configurable: false,
          enumerable: true,
          get(){
            return value
          },
          set(newValue){
            if(newValue !== value){
              value = newValue
              that.watcherTask[key].forEach(task => {
                task.update()
              })
            }
          }
        })
      })
    }
  }

同样是使用Object.defineProperty来监听数据,初始化需要订阅的数据。

把需要订阅的数据到pushwatcherTask里,等到时候需要更新的时候就可以批量更新数据了。?下面就是;
遍历订阅池,批量更新视图。

set(newValue){
    if(newValue !== value){
      value = newValue
      // 批量更新视图
      that.watcherTask[key].forEach(task => {
        task.update()
      })
    }
  }

compile 解析dom

class Vue{
    constructor(options = {}){
      ......
    }
    proxyData(key){
      ......
    }
    observer(data){
      ......
    }
    compile(el){
      var nodes = el.childNodes;
      for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
        if(node.nodeType === 3){
          var text = node.textContent.trim();
          if (!text) continue;
          this.compileText(node,'textContent')        
        }else if(node.nodeType === 1){
          if(node.childNodes.length > 0){
            this.compile(node)
          }
          if(node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')){
            node.addEventListener('input',(()=>{
              let attrVal = node.getAttribute('v-model')
              this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'value'))
              node.removeAttribute('v-model')
              return () => {
                this.data[attrVal] = node.value
              }
            })())
          }
          if(node.hasAttribute('v-html')){
            let attrVal = node.getAttribute('v-html');
            this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'innerHTML'))
            node.removeAttribute('v-html')
          }
          this.compileText(node,'innerHTML')
          if(node.hasAttribute('@click')){
            let attrVal = node.getAttribute('@click')
            node.removeAttribute('@click')
            node.addEventListener('click',e => {
              this.methods[attrVal] && this.methods[attrVal].bind(this)()
            })
          }
        }
      }
    },
    compileText(node,type){
      let reg = /\{\{(.*?)\}\}/g, txt = node.textContent;
      if(reg.test(txt)){
        node.textContent = txt.replace(reg,(matched,value)=>{
          let tpl = this.watcherTask[value] || []
          tpl.push(new Watcher(node,this,value,type))
          if(value.split('.').length > 1){
            let v = null
            value.split('.').forEach((val,i)=>{
              v = !v ? this[val] : v[val]
            })
            return v
          }else{
            return this[value]
          }
        })
      }
    }
  }

这里代码比较多,我们拆分看你就会觉得很简单了

首先我们先遍历el元素下面的所有子节点,node.nodeType === 3 的意思是当前元素是文本节点,node.nodeType === 1 的意思是当前元素是元素节点。因为可能有的是纯文本的形式,如纯双花括号就是纯文本的文本节点,然后通过判断元素节点是否还存在子节点,如果有的话就递归调用compile方法。下面重头戏来了,我们拆开看:

if(node.hasAttribute('v-html')){
  let attrVal = node.getAttribute('v-html');
  this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'innerHTML'))
  node.removeAttribute('v-html')
}

上面这个首先判断node节点上是否有v-html这种指令,如果存在的话,我们就发布订阅,怎么发布订阅呢?只需要把当前需要订阅的数据pushwatcherTask里面,然后到时候在设置值的时候就可以批量更新了,实现双向数据绑定,也就是下面的操作

that.watcherTask[key].forEach(task => {
  task.update()
})

然后push的值是一个Watcher的实例,首先他new的时候会先执行一次,执行的操作就是去把纯双花括号 -> 1,也就是说把我们写好的模板数据更新到模板视图上。
最后把当前元素属性剔除出去,我们用Vue的时候也是看不到这种指令的,不剔除也不影响

至于Watcher是什么,看下面就知道了

Watcher

class Watcher{
  constructor(el,vm,value,type){
    this.el = el;
    this.vm = vm;
    this.value = value;
    this.type = type;
    this.update()
  }
  update(){
    this.el[this.type] = this.vm.data[this.value]
  }
}

之前发布订阅之后走了这里面的操作,意思就是把当前元素如:node.innerHTML = '这是data里面的值'、node.value = '这个是表单的数据'

那么我们为什么不直接去更新呢,还需要update做什么,不是多此一举吗?
其实update记得吗?我们在订阅池里面需要批量更新,就是通过调用Watcher原型上的update方法。

效果

在线效果地址,大家可以浏览器看一下效果,由于本人太懒了,gif效果图就先不放了,哈哈??

完整代码

完整代码已经放到github上了 -> MyVue

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
最短的javascript:地址栏载入脚本代码
Oct 13 Javascript
禁止IE用右键的JS代码
Dec 30 Javascript
jQuery遮罩层实现方法实例详解(附遮罩层插件)
Dec 08 Javascript
JS动态改变浏览器标题的方法
Apr 06 Javascript
AngularJS 打开新的标签页实现代码
Sep 07 Javascript
Vue.Draggable拖拽功能的配置使用方法
Jul 29 Javascript
JS实现盒子跟着鼠标移动及键盘方向键控制盒子移动效果示例
Jan 29 Javascript
jQuery pager.js 插件动态分页功能实例分析
Aug 02 jQuery
Vue路由对象属性 .meta $route.matched详解
Nov 04 Javascript
原生JavaScript实现滑动拖动验证的示例代码
Dec 06 Javascript
vue使用Sass时报错问题的解决方法
Oct 14 Javascript
Vue h函数的使用详解
Feb 18 Vue.js
如何能分清npm cnpm npx nvm
Jan 17 #Javascript
JavaScript设计模式之装饰者模式实例详解
Jan 17 #Javascript
npm 常用命令详解(小结)
Jan 17 #Javascript
JavaScript设计模式之享元模式实例详解
Jan 17 #Javascript
vuex页面刷新后数据丢失的方法
Jan 17 #Javascript
jquery获取img的src值实例介绍
Jan 16 #jQuery
js中int和string数据类型互相转化实例
Jan 16 #Javascript
You might like
三种php连接access数据库方法
2013/11/11 PHP
完美的2个php检测字符串是否是utf-8编码函数分享
2014/07/28 PHP
修复ShopNC使用QQ 互联时提示100010 错误
2015/11/08 PHP
YII Framework教程之异常处理详解
2016/03/14 PHP
CI框架无限级分类+递归的实现代码
2016/11/01 PHP
PHP+jQuery实现滚屏无刷新动态加载数据功能详解
2017/05/04 PHP
Cookie跨域问题解决方案代码示例
2020/11/24 PHP
jQuery 使用手册(五)
2009/09/23 Javascript
初窥JQuery(一)jquery选择符 必备知识点
2010/11/25 Javascript
ASP.NET jQuery 实例2 (表单中使用回车在TextBox之间向下移动)
2012/01/13 Javascript
js实现网页倒计时、网站已运行时间功能的代码3例
2014/04/14 Javascript
在HTML中插入JavaScript代码的示例
2015/06/03 Javascript
jQuery实现鼠标点击弹出渐变层的方法
2015/07/09 Javascript
javascript实现网页中涉及的简易运动(改变宽高、透明度、位置)
2015/11/29 Javascript
jQuery根据name属性进行查找的用法分析
2016/06/23 Javascript
Bootstrap登陆注册页面开发教程
2016/07/12 Javascript
canvas的神奇用法
2017/02/03 Javascript
BootStrap select2 动态改变值的方法
2017/02/10 Javascript
基于canvas的二维码邀请函生成插件
2017/02/14 Javascript
对vue v-if v-else-if v-else 的简单使用详解
2018/09/29 Javascript
大转盘抽奖小程序版 转盘抽奖网页版
2020/04/16 Javascript
解决vue addRoutes不生效问题
2020/08/04 Javascript
python爬取淘宝商品销量信息
2018/11/16 Python
用python写测试数据文件过程解析
2019/09/25 Python
np.newaxis 实现为 numpy.ndarray(多维数组)增加一个轴
2019/11/30 Python
python2.7使用scapy发送syn实例
2020/05/05 Python
python多线程爬取西刺代理的示例代码
2021/01/30 Python
一款恶搞头像特效的制作过程 利用css3和jquery
2014/11/21 HTML / CSS
党员创先争优承诺书
2014/03/26 职场文书
文明城市创建标语
2014/06/16 职场文书
设备收款委托书范本
2014/10/02 职场文书
2014年底工作总结
2014/12/15 职场文书
二年级学生期末评语
2014/12/26 职场文书
未中标通知书
2015/04/17 职场文书
六一儿童节园长致辞
2015/07/31 职场文书
毕业生自我鉴定范文
2019/05/13 职场文书