七、数据代理————温开水的复习笔记

本次笔记的记录思路是:由里及表,自下而上。请带着自己的思考来看待这篇博客,可能表述不太准确,不过方向应该没问题。

先回顾一下Object.definedProperty这个应用在数据双向绑定中的方法,以及getter和setter(源码可以不看),然后讲一下数据代理以及代码的实现最后实现一下Vue的数据代理的底层实现和简洁的代码写法。

一、回顾Object.definedProperty方法------ES5

(一)配置对象中的常用的配置属性

Object.defineProperty(person,'sex',{//配置项

            value:'女',

            enumerable:true,         //控制属性是否可以枚举,默认值为false

            writable:true,           //控制属性是否可以被修改,默认值为false

            configurable:true        //控制属性是否可以被删除,默认值为false

        });

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>回顾Object.definedProperty方法</title>
</head>
<body>
    <script>
        let person = {
            name:"温开水",
            age:20
        }

        //原型对象的知识
        //添加sex属性和属性值
        Object.defineProperty(person,'sex',{//配置项
            value:'女',
            enumerable:true,         //控制属性是否可以枚举,默认值为false
            writable:true,           //控制属性是否可以被修改,默认值为false
            configurable:true        //控制属性是否可以被删除,默认值为false
        });
        console.log(Object.keys(person))//把对象的属性名提取出来并输出一个数组
        console.log(person);
        
    </script>
</body>
</html>

 问题:这个方法是不是只能添加属性和对应值这样的形式,不能添加函数

(二)配置对象中的get和set配置方法

监听读写操作,两对象相互影响

(1)getter

        完整写法:

        get : function(){

               //return语句 

        }

        目前我的理解是:getter会监听是否有读取操作发生(也就是是否有语句来获取对象属性值)。若有,则get内置函数调用,并返回值满足你要获取属性值的欲望,给你值)。随用随取的特性让对象的值是随return的值而改变的。

拥有getter的这个对象A的属性值 (return后)另一个对象B属性值的改变而改变,

B决定A,B变A也变

(2)setter

        完整写法:

        set : function(){

               //return语句 

        }

        目前我的理解是:setter会监听是否有修改操作发生(也就是是否有语句来修改对象属性值)。若有,则set内置函数调用,执行内部代码,一般为赋值语句,来让另一个对象的属性值也改变

A可以决定B,A变B也变。

Object.defineProperty(obj2,'x',{
    get(){
        console.log("get");
        return obj1.x;
    },
    set(value){
        console.log("set");
        obj1.x = value;
        //obj2.x = value;   
        //obj2.x = value;这句很魔性,但可以辅助我们理解set内置函数,运行这句会发生堆栈溢出
        //因为setter是监听obj2.x(对象属性)的变化值,发生变化,传入变化值value
        //分析:
        //当我在控制台输入 obj2.x=999 时,对象属性值改变!,setter监听到对象属性值的改变,开始传入value
        //然后value赋值给obj2.x(对象属性值改变!),这时setter又监听到对象属性值的改变,形成循环。
        //死循环一直执行,导致堆栈溢出。
    }
})

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>回顾Object.definedProperty方法</title>
</head>
<body>
    <script>
        let sex = '女';
        let person = {
            name:"温开水",
            age:20
        }

        //原型对象的知识
        //添加sex属性和属性值
        Object.defineProperty(person,'sex',{//配置项
            // value:'女',              //直接赋值
            // enumerable:true,         //控制属性是否可以枚举,默认值为false
            // writable:true,           //控制属性是否可以被修改,默认值为false
            // configurable:true        //控制属性是否可以被删除,默认值为false

            //当读取person的sex属性时,get函数就会被调用,且返回值就是age的值
            get: function(){
                return sex;
            }

        });
        console.log(Object.keys(person))//把对象的属性名提取出来并输出一个数组
        console.log(person);
        
    </script>
</body>
</html>

(三) getter和setter源码解读

尊重原创:地址如下

Vue setter/getter 是何原理? - kimoon - 博客园 (cnblogs.com)https://www.cnblogs.com/the-last/p/11525483.html

1 、 defineProperty 重定义对象

JS原生es5版本提供对象重新定义的接口 defineProperty 

defineProperty 可以修改对象的访问器属性,对象属性值发生变化前后可以触发回调函数。

对象的访问器属性包括 2 种类型:数据描述符、 存取描述符

 1.1 数据描述符
value:对象key的值,默认是 空字符串 ''
writeable:是否可写,默认 true
configurable:true是否可配置,默认 true
enumerable:true是否可枚举, 默认 true

Object.getOwnPropertyDescriptors(obj);
{
    k:{
      configurable: true,
      enumerable: true,
      value: 90,
      writable: true
    }
}

 1.2 存取描述符

set:function(){}属性访问器 进行写操作时调用该方法
get:function(){}属性访问器 进行读操作时调用该方法
属性描述符: configurable 、enumerable
configurable 、enumerable、 set 、 get

对象中新增key的value发生变化时会经过set和get方法。

var obj = {};
var temp = '';
Object.defineProperty(obj, 'name', {
      configurable: true, 
      enumerable: true,
      get: function () {
           return temp;
      },
      set: function (newValue) {
           temp = newValue
      }
});  //   需要维护一个可访问的变量 temp

或写在 obj对象内,如下:

1

2

3

4

5

6

7

8

9

10

11

12

var obj = {

      tempValue: 'duyi',

      get name () {

           return this.tempValue;

      },

      set name (newValue) {

           this.tempValue = newValue;

      }

};

obj.name = 10;

console.log( obj.name ); // 10

 小结一次:到这里来基本上知道 getter setter是可以实现的,基于这个简单理论作出一个复杂逻辑。

2 、Observer 源码

 /**
   * Observer类方法将对象修改为可被观察。
   * 一旦应用了这个类方法, 对象的每一个key会被转换成 getter/setter
   * 用于收集依赖项和触发更新。
   */
  var Observer = function Observer (value) {
    this.value = value;          // 保存被观察的值到方法的实例属性
    this.dep = new Dep();        // 建立一个Dep实例
    this.vmCount = 0;            // vmCount 记录vm结构的个数
    def(value, '__ob__', this);  // value 对象 增加 ‘__ob__’ key,并赋值 this
    if (Array.isArray(value)) {  // value 对象如果是数组
      if (hasProto) {            // 表示在[]上可以使用 __proto__ 
        
        protoAugment(value, arrayMethods); // 将arrayMethods 添加到value的__proto__。arrayMethods 好像是重写了数组的一部分原生方法,后面再看
      } else {
        copyAugment(value, arrayMethods, arrayKeys);  // 说不定他不支持 __proto__ ,调用def方法增强对象。
      }
      this.observeArray(value);  // 然后将这个增强后的数组,每一项都执行observe
    } else {
      this.walk(value);          // walk 在Observer的原型上,对象转换为 getter setter。
    }
  };

  /**
   * 遍历所有属性将它们转换为 getter/setter,仅当值类型为对象时调用。
   */
  Observer.prototype.walk = function walk (obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
      defineReactive$$1(obj, keys[i]);  //  defineReactive$$1 () 方法,这个方法才是实现 getter / setter 的原方法!!!
    }
  };

  /**
   * 观察数组项列表
   */
  Observer.prototype.observeArray = function observeArray (items) {
    for (var i = 0, l = items.length; i < l; i++) {
      observe(items[i]);                // observe 方法
    }
  };

  
  /**
   * 截获原型链使用 __proto__ 的方式来
   * 增强一个目标的对象或数组,简称原型增强。
   */
  function protoAugment (target, src) {
    /* eslint-disable no-proto */
    target.__proto__ = src;
    /* eslint-enable no-proto */
  }

  /**
   *  增加对象原型properties
   */
  function copyAugment (target, src, keys) {
    for (var i = 0, l = keys.length; i < l; i++) {
      var key = keys[i];
      def(target, key, src[key]);
    }
  }

  /**
   *  在Observer方法的原型属性 observerArray 上有用到!
   *  大概意思是创建可观察实例,返回可观察实例或已有的可观察对象。
   */
  function observe (value, asRootData) {
    
    // 如果不是object时,或是 VNode 的实例不进行 observe
    if (!isObject(value) || value instanceof VNode) {
      return
    }
    var ob;
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
      ob = value.__ob__;
    } else if (
      shouldObserve &&
      !isServerRendering() &&
      (Array.isArray(value) || isPlainObject(value)) &&
      Object.isExtensible(value) &&
      !value._isVue
    ) {
      ob = new Observer(value);
    }
    if (asRootData && ob) {
      ob.vmCount++;
    }
    return ob
  }

  /**
   * 这里是实现getter setter的关键代码
   * 这里是实现getter setter的关键代码
   * 这里是实现getter setter的关键代码
   *
   *  在对象上定义一个 有反应的原型 
   *  传参:obj对象,key关键字,val值,customSetter在set的时候会被执行(第4个参数),shallow 默认为undefined,为 true 时不执行 observe 函数。
  */
  function defineReactive$$1 (
    obj,
    key,
    val,
    customSetter,
    shallow
  ) {
    var dep = new Dep();

    var property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable === false) {
      return
    }

    // 预定义的getter和setter
    var getter = property && property.get;
    var setter = property && property.set;
    if ((!getter || setter) && arguments.length === 2) {
      val = obj[key];
    }

    var childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        var value = getter ? getter.call(obj) : val;
        if (Dep.target) {
          dep.depend();
          if (childOb) {
            childOb.dep.depend();
            if (Array.isArray(value)) {
              dependArray(value);
            }
          }
        }
        return value
      },
      set: function reactiveSetter (newVal) {
        var value = getter ? getter.call(obj) : val;
        /* eslint-disable no-self-compare */
        if (newVal === value || (newVal !== newVal && value !== value)) {
          return
        }
        /* eslint-enable no-self-compare */
        if (customSetter) {
          customSetter();
        }
        // #7981: for accessor properties without setter
        if (getter && !setter) { return }
        if (setter) {
          setter.call(obj, newVal);
        } else {
          val = newVal;
        }
        childOb = !shallow && observe(newVal);
        dep.notify();
      }
    });
  }


 订阅observe对象的变化,通知触发update,参考订阅-发布模式。

  /**
   * Dep 是可以有多个指令订阅的可观察对象,目的就是对一个目标深层处理
   */
  
  var uid = 0;
  var Dep = function Dep () {
    this.id = uid++;    //  添加了2个实例属性,id 用于排序和 subs 数组统计sub
    this.subs = [];
  };
  
  // 在 subs中添加 sub
  Dep.prototype.addSub = function addSub (sub) {
    this.subs.push(sub);
  };
  
  // 从 subs中移除 sub
  Dep.prototype.removeSub = function removeSub (sub) {
    remove(this.subs, sub);
  };


  Dep.prototype.depend = function depend () {
    if (Dep.target) {
      Dep.target.addDep(this);  // addDep 是 Watcher 的原型方法,用于指令增加依赖
    }
  };

  Dep.prototype.notify = function notify () {
    // 稳定订阅列表
    var subs = this.subs.slice();
    if (!config.async) {
      // 如果不是在异步运行,在程序调度中 subs 不可以被排序!
      // 然后排序以确保正确的顺序。
      subs.sort(function (a, b) { return a.id - b.id; });
    }
    for (var i = 0, l = subs.length; i < l; i++) {
      // 然后顺序触发 Watcher 原型的 update 方法
      subs[i].update();
    }
  };

  // 当前目标程序被评估,这个评估全局唯一,一次只要一个观察者可以被评估
  Dep.target = null;
  var targetStack = [];

  // 将目标程序推送到目标栈
  function pushTarget (target) {
    targetStack.push(target);
    Dep.target = target;
  }
  // 执行出栈先去掉
  function popTarget () {
    targetStack.pop();
    Dep.target = targetStack[targetStack.length - 1];
  }




 全局def方法,定义对象。

  // def 方法比较简单
  /**
   * 定义一个原型
   * obj 
   * key 对象的key关键字
   * val  对象的value值
   * 是否可枚举,默认可写可配置
   */

  function def (obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
      value: val,
      enumerable: !!enumerable,
      writable: true,
      configurable: true
    });
  }

二、Vue中的数据代理

(一)数据代理的底层实现

使用Object.definedProperty来创建新属性,再借助getter和setter来实现俩对象数据的绑定

数据代理概念:通过一个对象对另一个对象的属性进行操作(读/写)

实现的目标:修改 obj1 或者 obj2 任意一个对象中的 x 属性都会让另一个对象的 x 属性改变,让 obj1 和 obj2 的 x 属性值始终保持一致

 Vue数据双向绑定?    、

代码实现,注释仔细看

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>数据代理</title>
</head>
<body>
    <!--  数据代理:通过一个对象对另一个对象的属性进行操作(读/写)  -->
    <script>

        let obj1 = {x:100};
        let obj2 = {y:200};

        //实现的目标:修改 obj1 或者 obj2 任意一个对象中的 x 属性都会让另一个对象的 x 属性改变
        //Vue双向绑定?    obj1 和 obj2的 x 属性值始终保持一致
        Object.defineProperty(obj2,'x',{
            get(){
                console.log("get");
                return obj1.x;
                //也可以写return value = obj1.x
            },
            set(value){
                console.log("set");
                obj1.x = value;
                //obj2.x = value;   //这句很魔性,但可以辅助我们理解set内置函数,运行这句会发生堆栈溢出
                                    //因为setter是监听obj2.x(对象属性)的变化值,发生变化,传入变化值value
                                    //分析:
                                    //当我在控制台输入 obj2.x=999 时,对象属性值改变!,setter监听到对象属性值的改变,开始传入value
                                    //然后value赋值给obj2.x(对象属性值改变!),这时setter又监听到对象属性值的改变,形成循环。
                                    //死循环一直执行,导致堆栈溢出。
            }
        })
        console.log(obj2);
    </script>
</body>
</html>

(二)Vue中数据代理的实现

前面学过Vue的两种数据绑定,复习一下

(一)单向绑定(v-bind):数据只能从data流向页面

(二)双向绑定(v-model):数据不仅能从data流向页面,还可以从页面data

        备注:(1)双向绑定一般应用在表单元素上(如:input、select)

                   (2)v-model:value  可以简写为v-model ,因为v-model默认收集的就是value值

但我们并不使用,只是复习一下

代码示例

要注意这一句   <h2>{{name}}</h2> 和  <h2>{{_data.name}}</h2>,写成<h2>{{vm.name}}</h2>会报错其他都只是复习

①首先,回想一下数据代理的概念,是不是需要有两个对象,其实Vue数据代理的两个对象就是vm(Vue实例)和 _data对象(对应于Vue配置项的data对象),_data对象嵌套在vm对象里面。

②然后再去回想一下数据代理的特性,是不是两个对象的数据数据同步变化(一个变另一个也变),实现的具体代码方法就是使用Object.defineProperty()方法在vm对象中创建属性,使用get()和set(value)来为读取vm对象数据和修改vm对象数据的操作分别设置“事件绑定”(感jio很类似),getter里写return  _data.属性,setter里写 _data.属性=value

③最后我们回到Vue数据代理的写法中,也就是下面的代码。写法很简单,直接在Vue的配置项中data对象中写上你需要设置的属性(数据),创建了Vue实例vm时会自动完成上面两步,从而达到数据代理的目的,我们一般使用vm里的数据对象,也就是使用模板语法{{name}}这样形式的,而不是{{_data.name}}这种形式的。

最好仔细看看下面的参考图示,帮助理解。

<!DOCTYPE html>
<html">
<head>
    <meta charset="UTF-8">
    <title>数据绑定</title>
    <!--  引入vue -->
    <script type="text/javascript" src="../vuejs/vue.js"></script>
</head>
<body>
    <!-- 容器 -->
    <div id="root">
        单向数据绑定:<input type="text" v-bind:value="name"><br>
        双向数据绑定:<input type="text" v-model:value="name">
        <!-- 数据代理 -->
        <h2>{{name}}</h2>
    </div>

    <script type="text/javascript">
        //创建vue实例
        const vm = new Vue({
            el:'#root',  
            data:{
                name:"温开水",
                other:{           
                    url:"https://cn.vuejs.org",
                    name:"vue"
                }
                
            }
        });

    </script>
</body>
</html>
<!DOCTYPE html>
<html">
<head>
    <meta charset="UTF-8">
    <title>数据绑定</title>
    <!--  引入vue -->
    <script type="text/javascript" src="../vuejs/vue.js"></script>
</head>
<body>
    <!-- 容器 -->
    <div id="root">
        单向数据绑定:<input type="text" v-bind:value="name"><br>
        双向数据绑定:<input type="text" v-model:value="name">
        <!-- 数据代理 -->
        <h2>{{_data.name}}</h2>
    </div>

    <script type="text/javascript">
        //创建vue实例
        const vm = new Vue({
            el:'#root',  
            data:{
                name:"温开水",
                other:{           
                    url:"https://cn.vuejs.org",
                    name:"vue"
                }
                
            }
        });

    </script>
</body>
</html>

参考一下Vue数据代理图示

data.name应该写成 _data.name

Vue数据代理

数据代理小总结

1.Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
2. Vue中数据代理的好处:更加方便的操作data中的数据
3.基本原理:
通过Object.defineProperty( )把data对象中所有属性添加到vm上。为每个添加到vm上的属性, 都指定一个getter/setter。(图)在getter/setter内部去操作(读/写) data中对应的属性。

Vue数据代理这部分写的比较乱

三、问题

为什么在视频中修改vm.name会让页面自动改变?这是Vue带来的特性吗?怎么实现的?

如果你看懂了,那么可以回答下面的问题:

1)

2)

3)



 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值