IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 七、数据代理————温开水的复习笔记 -> 正文阅读

[JavaScript知识库]七、数据代理————温开水的复习笔记

?本次笔记的记录思路是:由里及表,自下而上

先回顾一下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中的数据代理

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

使用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>其他都只是复习

<!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>

参考一下Vue数据代理图示

Vue数据代理

数据代理小总结

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

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

三、问题

为什么在视频中修改vm.name会让页面自动改变,试着从模板语法中寻找答案



?

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-30 18:50:47  更:2022-01-30 18:50:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:57:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码