2

如果你在学习一种前端框架,如vue、angular等,那么你一定不会对数据的单向绑定陌生。

何为数据的单向绑定?

传统开发模式下,如使用jQuery开发,我们想将一个变量显示到html中,首先要定义一个变量name,然后通过jq代码操作dom将变量放到HTML中,如果name发生修改,还要再次通过jq代码操作dom将新的变量值放到HTML中。这就是传统的MVC框架,其中的Model和View是我们通过代码联系在一起的。
在MVVM框架中,我们不再过多的关注数据与视图间的操作,而是使用一种新的机制,数据的单/双向绑定。
我在刚接触angular的时候感觉这个绑定的机制简直不要再神奇,但是当慢慢深入学习后发现其实原理非常简单,通过简短的js代码就可以实现一个简单的数据单向绑定。

Proxy对象

Proxy可以当做在目标对象之前架设一层拦截,或者说是代理。任何对该对象的访问都要先经过这个代理。那么也就是说我们可以通过Proxy对象拦截到外界对一个对象的访问。
ES6中将Proxy标准化了,提供了Proxy构造函数,用来生成Proxy实例。下面就是官方的定义:

let p = new Proxy(target, handler);
  • target:用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
  • handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数。

再看一个栗子:

let p = new Proxy({}, {
    get: function(target, name){
        return name in target ? target[name] : 37;
    }
});
p.a = 1;
p.b = undefined;
console.log(p.a, p.b);    // 1, undefined
console.log('c' in p, p.c);    // false, 37

栗子中通过Proxy对象拦截了p对象的访问,当对象中不存在属性名时返回37,如果有就返回属性值。
到这已经了解了Proxy对象的工作方式,我们就要用Proxy来做点事了。

单向绑定

首先
如果你接触过一些mvvm框架,那么一定会对下面的代码非常熟悉

<div id="app">
  姓名:{{name}}
  <br> 年龄:{{age}}
</div>

“{{}}”叫做插值表达式,vue、angular中都是使用这种方式进行数据的单向绑定。在这我们也使用这种格式绑定数据。
然后

let el = document.getElementById('app');
let template = el.innerHTML;

上面两步,首先获取到根元素,然后将根元素下的html保存下来。这里为什么要保存原始的html呢?
因为接下来的数据变化会重新编译标签,既然要重新编译,那么就要保留最初的状态,否则编译一次后,第二次就无法正常编译了。
再然后

let _data = {
  name: '_BuzzLy',
  age: 25
};

定义一个_data对象,这个对象是给我们接下来创建的Proxy对象用的,并不是暴露出去供访问的。
接下来

let data = new Proxy(_data, {
  // 试图设置数据时调用
  // 参数:_data,属性名,值
  set(obj, name, value) {
    obj[name] = value;

    // 数据变了
    console.log(`数据变了,设置 ${name}=>${value}`);
    // 数据改变后重新渲染
    render();
  },
  // 试图获取数据的时调用,默认要什么就返回什么
  // get() {}
});

这步我们创建一个data,这个data就是对外的,是一个Proxy对象。
Proxy是原生的对象,可以将真正的数据对象隐藏,我们修改的是代理对象。
相当于我们想修改_data一定要经过一步代理,告诉代理我们要修改的对象及修改的值,然后代理去帮我们操作。
在修改完成后调用render函数去重新渲染视图。
最后
上面已经完成了最重要的部分,当一个数据发生改变后调用了render函数,这个函数就是将改变后的数据重新渲染到视图中去。

function render() {
    el.innerHTML = template.replace(/\{\{w+\}\}/g, str => {
        console.log(str); // 匹配出来的 {{name}}  {{age}} 
        // 截取字符串,得到属性key值
        str = str.substring(2, str.length - 2);
        // 从真实数据中拿到对应属性的值返回,替换{{key}}
        return _data[str];
    })
}

这里的实现很简单,就是匹配到html中{{key}},然后拿到key,再去_data中找到值即可。
注意这里的template,这就是我们在上面为什么要保存一份原始的html原因。
现在你可以去尝试手动修改data的值看看效果了。

data.name = 'halo wode';
data.age = 18;

到这一个最简单的数据单向绑定就实现了。


完整代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title></title>
</head>
<body>
  <div id="app">
    姓名:{{name}}
    <br> 年龄:{{age}}
  </div>
</body>
<script>
  let el = document.getElementById('app');
  let template = el.innerHTML;
  let _data = {
    name: '_BuzzLy',
    age: 25
  };

  let data = new Proxy(_data, {
    set(obj, name, value) {
      obj[name] = value;
      render();
    }
  });
  
  render();

  function render() {
    el.innerHTML = template.replace(/\{\{\w+\}\}/g, str => {
      str = str.substring(2, str.length - 2);
      return _data[str];
    });
  }
</script>
</html>

巴斯光年
274 声望23 粉丝