• Object.defineProperty属性实现双向绑定


    什么是双向绑定?

    1.当一个对象(或变量)的属性改变,那么调用这个属性的地方显示也应该改变,模型到视图(model => view)

    2.当调用属性的这个地方改变了这个属性(通常是一个表单元素),那么这个对象(或变量)的属性也会改为最新的值 ,即视图到模型(view => model)

    那么vue利用es5的defineProperty特性实现双向绑定的原理是什么呢?

    例如:

    var person= {};
    Object.defineProperty(person, "name", {
      value: '张三'
    })
    console.log(person.name); // 张三

    传参

    • 第一个参数:要设置的目标对象(必填)

    • 第二个参数:需要定义的属性或方法的名称(必填)

    • 第三个参数:目标属性所拥有的特性。(descriptor)(必填)

          三个参数都是必填项,重点介绍第三个参数 descriptor

    descriptor

    • value:属性的值

    • writable:如果为false,属性的值就不能被重写, 只能为只读了

    • configurable:总开关,一旦为false,就不能再设置他的(value,writable,configurable)

    • enumerable:是否可枚举(是否能在for...in循环中遍历出来或在Object.keys中列举出来)

    • get:后面会重点讲解

    • set:后面会重点讲解

    descriptor 默认值

    var person = {};
    Object.defineProperty(person ,"name",{
      value: '张三',
      writable :false,
      enumerable: false,
      configurable: false
    });
    console.log(person.name); // 张三

    这里需要注意的是!!!!

    configurable

    总开关,第一次设置 false 之后,,第二次什么设置也不行了。

    writable

    如果设置为fasle,就变成只读了。

    enumerable

    属性特性 enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

    set 和 get

    在 descriptor 中不能同时设置访问器(get 和 set)和 wriable 或 value,否则会错,就是说想用 get 和 set,就不能用 writable 或 value 中的任何一个。

    var user = {};
    var defaultName = "狂奔的蜗牛";
    Object.defineProperty(user,"name",{
      get:function(){
        console.log("你是不是来获取值啦");
        return defaultName;
      },
      set:function(value){
        console.log("你是不是来设置值啦");
        defaultName = value;
      }
    })
     
    console.log(user.name);
    user.name = "狂奔的萝卜";
    console.log(user.name);

    每当我获取user.name属性时,get方法被调用,get 方法对应的函数被执行,输出 你是不是来获取值啦;每当我设置user.name属性时,set方法对应的函数被执行,输出 你是不是来设置值啦 ; 是的,我们监控到了代码对user.name属性的存取。

    模型到视图(model => view)的同步

    说明 假设id="model" 的元素的 value 是user.name的值,既然我们可以在改变属性的执行日志输出(console.log("你是不是来设置值啦");),那么,我们在设置值的时候给id="model" 的元素设置下新值,不就实现了从模型到视图

    <body>
      手写一个简单双向绑定<br/>
      <input type="text" id="model"><br/>
      <div id="modelText"></div>
    </body>
    <script>
    var user = {};
    var defaultName = "狂奔的蜗牛";
     
    document.querySelector("#model").value = defaultName;
    document.querySelector("#modelText").textContent = defaultName;
     
    //定义属性 监控改变
    Object.defineProperty(user,"name",{
      get:function(){
        console.log("你是不是来获取值啦");
        return defaultName;
      },
      set:function(newValue){
        console.log("设置新值");
        defaultName = newValue;
        console.log("实现 模型 => 视图");
        document.querySelector("#model").value = newValue;
        document.querySelector("#modelText").textContent = newValue;
      }
    })
     
    console.log("2s 后改变值");
     
    setTimeout(() => {
      //改变值
      user.name = "狂奔的萝卜";
    }, 2000);
    </script>

    视图到模型(view => model)的同步

    问: 我们能捕捉到view对值更改吗?

    答:可以!! id="model" 的input元素的 value 是user.name的值,填充在这个文本框里面,文本框有个“ keyup” 事件,当我们在文本框中输入文字的时候,文本框的值会跟着改变,并且会连续触发keyup事件,那么我们只需要监听这个事件,是不是就可以捕捉到view对值的更改了??既然文本框的值会跟着改变,我们获取最新的值再把新值更新到user.name属性,不就实现了视图到模型(view => model)的同步

    <body>
      手写一个简单双向绑定<br/>
      <input type="text" id="model"><br/>
      <div id="modelText"></div>
    </body>
    <script>
      var user = {};
      var defaultName = "狂奔的蜗牛";
      var model = document.querySelector("#model");
      var modelText = document.querySelector("#modelText");
     
      model.value = defaultName;
      modelText.textContent = defaultName;
     
      //定义属性 监控改变
      Object.defineProperty(user,"name",{
        get:function(){
          console.log("你是不是来获取值啦");
          return defaultName;
        },
        set:function(newValue){
          console.log("设置新值");
          defaultName = newValue;
          model.value = newValue;
          modelText.textContent = newValue;
        }
      })
     
      model.addEventListener("keyup", function () {
        user.name = this.value;
        console.log("实现 视图 => 模型");
      }, false)
    </script>

    【最终源码】

    在上述代码的基础上,加入了 用户输入中文的判断(用户输入中文时,频繁触发 keyup事件,但实际上输入并没有结束。)

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>双向绑定</title>
    </head>
    <body>
      手写一个简单双向绑定<br/>
      <input type="text" id="model"><br/>
      <div id="modelText"></div>
    </body>
    <script>
      var model = document.querySelector("#model");
      var modelText = document.querySelector("#modelText");
      var defaultName = "defaultName";
      var userInfo = {}
      model.value = defaultName;
      Object.defineProperty(userInfo, "name", {
        get: function () {
          return defaultName;
        },
        set: function (value) {
          defaultName = value;
          model.value = value;
          console.log("-----value");
          console.log(value);
          modelText.textContent = value;
        }
      })
     
      userInfo.name = "new value";
      var isEnd = true;
     
      model.addEventListener("keyup", function () {
        if (isEnd) {
          userInfo.name = this.value;
        }
      }, false)
      //加入监听中文输入事件
      model.addEventListener("compositionstart", function () {
        console.log("开始输入中文");
        isEnd = false;
      })
      model.addEventListener("compositionend", function () {
        isEnd = true;
        console.log("结束输入中文");
      })
    </script>
    </html>

    这样基于object.definePropety特性就完成了双向绑定。

    下面我们再来看一下object.definePropety的用法及其他属性

    Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

    语法

    Object.defineProperty(obj, prop, descriptor)

    参数

    obj      要在其上定义属性的对象。
    prop      要定义或修改的属性的名称。
    descriptor  将被定义或修改的属性描述符。

    返回值

        被传递给函数的对象。

    描述

     该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for...in 或 Object.keys 方法), 这些属性的值可以被改变,也可以被删除。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的。

    属性描述符

     对象里目前存在的属性描述符有两种主要形式:数据描述符存取描述符数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。

    数据描述符和存取描述符均具有以下可选键值:

    configurable
    当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
    enumerable
    当且仅当该属性的enumerabletrue时,该属性才能够出现在对象的枚举属性中。默认为 false。

    数据描述符同时具有以下可选键值:

    value
    该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
    writable
    当且仅当该属性的writabletrue时,value才能被赋值运算符改变。默认为 false

    存取描述符同时具有以下可选键值

    get
    一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。
    默认为 undefined
    set
    一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。
    默认为 undefined
    如果一个描述符不具有value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。

    记住,这些选项不一定是自身属性,如果是继承来的也要考虑。为了确认保留这些默认值,你可能要在这之前冻结 Object.prototype,明确指定所有的选项,或者通过 Object.create(null)__proto__属性指向null

  • 相关阅读:
    flink-cdc读取postgres报异常,没有发布表
    yum 安装高版本Git
    分布式存储FastDFS搭建
    ElasticSearch6.5.1集群部署
    CentOS7 OpenSSH编译安装升级
    K8S使用ceph实现持久化存储
    ceph分布式集群的搭建
    canal服务搭建
    MySQL-5.7.31的搭建
    基于CentOS7.6使用KubeOperator安装Kubernetes集群
  • 原文地址:https://www.cnblogs.com/Narkea/p/10554715.html
Copyright © 2020-2023  润新知