教程 # 前端-群里口水站引起的悄悄写了下 MVVM 框架

zoeDylan · 2018年04月03日 · 最后由 Jim 回复于 2018年04月08日 · 298 次阅读

前端-群里口水站引起的悄悄写了下MVVM框架

注:

  1. 写本篇的原因是因为群里口水战vue相关导致
  2. 花了3个小时搞定,功能并不群面,兼容什么的也没做,主要用于研究研究吧。
  3. 模仿的vue,写这篇文章的时候并没有读过vue源码,好吧,其实其它的源码也没读过

Do

好久没写文章了,来到新公司比较忙,然后&…<< 不是重点

群里口水战,然后我冒了一句:XXX,手写一个双向绑定。-_-

坐等了5分钟,贴上来一个双向绑定的代码。 双向绑定

哎哟我去,还不错哦,5分钟写了12行代码。

然后就萌生一个自己写一个吧。

开始:不就是双向绑定嘛,上面贴图里面就给出绑定方式了,我就不用写了。

然后:我需要事件、#text文字展示和input输入控制.

// :="**" 绑定输入  
<input :="name" type="text" />
// @="**" 绑定事件
<button @click="clickFn">点我</button>
// {{**}} 绑定展示的文字
{{name}}

根据输入、事件和展示这三个功能规划插件。

function Zoe(param){
    //参数处理  

    //变量声明

    //方法创建

    //初始化调用

}

1. 参数处理

使用插件逻辑和vue差不多

new Zoe({
   el:'#app',
   data:{},
   methods:{}
})

so,参数处理

this.el = document.querySelector(param.el);
this.methods = param.methods;
this._data = param.data; //因为要使用源参数,所以加个下划线
this.data = {};

没错,整个插件全局参数就这些,


2. 变量声明

变量声明用于后面的方法创建,整个插件的处理逻辑是:参数处理=>初始化处理元素=>初始化处理展示数据=>事件

而这里的变量声明就3个

const textElemArr = [], //elem,text:源文字
  bindElemArr = [], //elem,name:绑定的变量
  eventElemArr = []; //elem,events:绑定的事件名数组,methods:绑定的事件方法数组

3. 方法

一共三个方法:bindElembindDatabindEvent,后面初始化也就是按顺序执行这三个方法就可以了。

4. 完整代码

function Zoe(param) {
  // :="**" 绑定输入
  // @="**" 绑定事件
  // {{**}} 绑定展示
  //**********************参数处理
  this.el = document.querySelector(param.el);
  this.methods = param.methods;
  this._data = param.data; //因为要使用源参数,所以加个下划线
  this.data = {};
  //**********************参数处理END
  //**********************变量声明
  //元素数组
  const textElemArr = [], //elem,text:源文字
    bindElemArr = [], //elem,name:绑定的变量
    eventElemArr = []; //elem,events:绑定的事件名数组,methods:绑定的事件方法数组
  //**********************变量声明END
  //**********************方法创建
  //元素绑定
  function bindElem(el) {
    //元素归类
    el.childNodes.forEach(elem => {
      //文本元素:用于展示
      switch (elem.nodeType) {
        case 1:
          //用于输入框的事件绑定
          elem.hasAttribute(":") &&
            bindElemArr.push({
              elem: elem,
              name: elem.getAttribute(":")
            });

          //用于原生事件的处理
          const attrs = elem.attributes,
            //事件数组
            events = [],
            //方法数组
            methods = [];

          for (let i = 0; i < attrs.length; i++) {
            const attr = attrs[i];

            if (attr.nodeName.indexOf("@") == 0) {
              events.push(attr.nodeName);
              methods.push(attr.nodeValue);
            }
          }

          events.length > 0 &&
            eventElemArr.push({
              elem: elem,
              events: events,
              methods: methods
            });

          break;
        case 3:
          const val = elem.nodeValue.replace(/ /g, "").match(/{{.+}}/g);
          val &&
            val.length > 0 &&
            textElemArr.push({
              elem: elem,
              text: elem.nodeValue.replace(/ /g, "")
            });
          break;
        default:
          break;
      }
      //存在子元素,递归
      elem.childNodes.length > 0 && bindElem.call(this, elem);
    });
  }
  //数据绑定
  function bindData() {
    //数据处理
    const self = this;
    Object.keys(self._data).forEach((val, i) => {
      self.data = Object.defineProperty(self.data, val, {
        get() {
          return self._data[val];
        },
        set(data) {
          self._data[val] = data;

          //文字渲染:这里挺复杂的
          textElemArr.forEach(nodeObj => {
            let text = nodeObj.text,
              textMatch = text.match(/{{.+?}}/g);
            textMatch &&
              textMatch.forEach(name => {
                name = name.replace(/{{|}}/g, "");
                if (Object.keys(self._data).indexOf(name) > -1) {
                  text = text.replace("{{" + name + "}}", self._data[name]);
                }
              });
            nodeObj.elem.nodeValue = text;
          });
          bindElemArr.forEach(v => {
            v.name == val && (v.elem.value = data);
          });
        }
      });
      self.data[val] = self._data[val];
    });
  }

  //事件处理
  function bindEvent() {
    const self = this;
    //输入框相关
    bindElemArr.forEach(v => {
      v.elem.oninput = () => {
        const type = typeof self.data[v.name];
        self.data[v.name] =
          type == "number" ? Number(v.elem.value) : v.elem.value;
      };
    });

    //其它事件
    eventElemArr.forEach(nodeObj => {
      const self = this,
        elem = nodeObj.elem;

      nodeObj.events.forEach((evName, index) => {
        elem.addEventListener(
          evName.replace("@", ""),
          self.methods[nodeObj.methods[index]].bind(self)
        );
      });
    });
  }

  //**********************方法创建END

  //**********************初始化
  //元素
  bindElem.call(this, this.el);
  //数据
  bindData.call(this);
  //事件
  bindEvent.call(this);
}

5. 使用

懒得写了,点过去自己看

6. 其它

整个插件也就140行代码,还包括了注解部分。实际代码也就80行左右吧,写这个也就对双向绑定自己写一个看看。

知识点

  1. node节点操作和attribute属性节点操作
  2. Object的访问器操作
  3. this作用域和call/bind改变当前作用域的this对象.
共收到 3 条回复

可以,骚气

这是真的厉害!!!

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册