简易实现 vue

首先创建一个 html 文件

<!DOCTYPE html>
<head>
  <title>简易Vue双向绑定</title>
  <script src="./twoway-bingding.js"></script> 
  // 引入实现vue的文件
  <script>
    // @:在DOMContentLoaded后完成vue初始化, 毕竟#app根元素还是要拿到的:)
    document.addEventListener("DOMContentLoaded", function (event) {
      var a = new Vue({
        el: '#app',
        data: {
          str: 'hello'
        }
      })
      setTimeout(function () {
        a.data.str = 'world'
      }, 3000)
    });
    </script>
</head>
<body>
  <div id="app">
    <input v-model="str"></input>
    <p>Content of str is: {{str}}</p>
  </div>
</body>
</html>

创建twoway-bingding.js

function Vue(option){
  this.data = option.data; // @:需要被劫持的data对象
  Object.defineProperty(this, '$el', {	// @:利用defineProperty方法,禁止挂在的根节点被更改
    configurable: false,
    enumerable: true,
    get: function() {
      return document.getElementById(option.el.split('#')[1])
    },
    set: function() {
      console.error('Cannot change $el')
    }
  })
  watch(this.data); // @:监听数据变化
  new Compile(this)// @:建立DOM和数据间的连接
}
function watch(data){
  // 合法性验证
  if (!data || typeof data !== 'object'){
    return;
  }

  Object.keys(data).forEach(function(key){
    defineReactive(data, key, data[key]); // @:为每个key添加双向绑定
  })
}
function defineReactive(data, key, value){
  var subject = new Subject();
  Object.defineProperty(data, key, {
    configurable: false,
    enumerable: true,
    get: function(){
      // @:触发getter的DOM节点都是数据改变时需要被通知到的节点
      Subject.target && subject.addObserver(Subject.target); // @:将DOM添加为key的观察者
      return value;
    },
    set: function(newVal){
      value = newVal;
      // @:数据被修改时会触发setter, 此时需要通知相关的DOM节点重新渲染
      subject.notify(newVal);
    }
  })
}

// 主题
function Subject() {
  this.observerList = [] // 订阅者列表
}
Subject.prototype = {
  addObserver: function(obs) {
    // @:将订阅者添加到列表中
    this.observerList.push(obs)
  },
  removeObserver: function(obs){
    // @:移除订阅者
    var index = this.observerList.findIndex(function(item){return item===obs});
    this.observerList.splice(index);
  },
  notify: function() {
    // @:当key变化时,调用key对应主题的notify方法
    this.observerList.forEach(function(obs){
      obs.update();
    })
  }
}

// 订阅者
function Observer(vm, key, cb) {
  this.vm = vm;
  this.key = key;
  this.cb = cb;
  this.register();	// @:在new Observer实例时调用,完成DOM传递
}
Observer.prototype = {
  update: function(){
    // @:当主题变化时,会调用所有订阅者的update方法
    this.cb(this.vm.data[this.key], this.value);	// @: 传递oldVal参数
  },
  register: function(){
  	// @:临时借用Subject原型对象
  	Subject.target = this;
  	// @:等号右侧取值,触发data[key]的getter方法,从而调用Subject.addObserver()
  	// @:同时将初始值保存在了观察者实例的value上
  	this.value = this.vm.data[this.key];
  	// @:还原Subject原型上的target, 以便其他Observer实例可以继续借用
  	Subject.target = null;
  }
}

/**
 * 编译对象
 *
 * @param {DOM} el - 根节点
 * @param {Object} vm - Vue对象
 */
function Compile(vm) {
  this.vm = vm
  this.compile(vm.$el)
}
Compile.prototype = {
  compile: function(el) {
    var _this = this
    var reg = /\{\{((\S)*)\}\}$/
    for (var i = 0; i < el.childNodes.length; i++) {
      // @:遍历$el下所有节点
      ;(function(_this) {
        var node = el.childNodes[i]
        if (node.nodeType == 1) {
          if (reg.test(node.textContent)) {
            // @:内容为{{key}}的文本节点
            node.textContent.replace(reg, function(str, key) {
              node.innerHTML = _this.vm.data[key] // @:初始化,将"{{key}}"替换为data[key]的值
              // @:将node注册为key的观察者,以便在数据变化时得到通知并作出响应
              new Observer(_this.vm, key, function(newVal, oldVal){
                console.log(newVal, oldVal);
                node.innerHTML = newVal;
                // FIXME:如何完成oldVal的保存
              })
            })
          } else if (node.tagName == 'INPUT' && node.hasAttribute('v-model')) {
            // @:绑定了v-model的input元素
            var key = node.getAttribute('v-model')
            node.value = _this.vm.data[key] // @:初始化,将input的value替换为data[key]的值
            node.oninput = function() {
              // @:用户输入,更新数据
              _this.vm.data[key] = node.value;
            }
            // @:将node注册为key的观察者,以便在数据变化时得到通知并作出响应
            new Observer(_this.vm, key, function(newVal, oldVal){
              console.log(newVal, oldVal);
              node.value = newVal;
              // FIXME:如何完成oldVal的保存
            })
          }
        } else {
          _this.compile(node)
        }
      })(this)
    }
  },
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值