Vue响应

news/2024/4/27 3:43:44/文章来源:https://blog.csdn.net/qq_52476758/article/details/129246868

Vue响应

        • 1. 初始化Vue
          • 1.1 使用Vue
          • 1.2 初始化Vue
        • 2. 数据劫持
          • 2.1 对象的单层劫持
          • 2.2 对象的深层劫持
          • 2.3. 数组的劫持
          • 2.4 数据代理
          • 2.5 数组的深层劫持

1. 初始化Vue

Vue的官网解释

  • Vue 是一套用于构建用户界面的渐进式框架,

  • Vue 并没有完全支持 MVVM 模型,但 Vue 的设计受到了它的启发,

  • 变量名 vm 是 vue model 的缩写,表示 vue 实例;

1.1 使用Vue
  1. 在index.html中初始化Vue

    <script><!-- 初始化 Vue,传入 options 对象 -->let vm = new Vue({el: '#app',// 1,data 是对象data: {msg: "zhiyu"}// 2,data 是函数,返回一个对象// data() {//   return { msg: "zhiyu" }// }});
    </script>
    

    Vue在初始化时,会传入el挂载点、data数据等,在初始化完成之后,data中的数据会变成响应式数据(在根组件时data可以使对象也可以是函数,因为根组件不会被共享,而非根组件data必须是函数,否则数据会被多组件共享)

1.2 初始化Vue
  1. 在src下新建index.js文件,然后在Vue原型上扩展一个_init方法,用于Vue的初始化操作

    /*** vue中的所有功能,都是通过原型扩展的方式添加的* @param {*} options  new Vue时传入的 options 配置对象*/
    function Vue(options){this._init(options); // 调用Vue原型上_init方法
    }
    // 在Vue原型上扩展一个原型方法_init,用于vue的初始化操作
    Vue.prototype._init = function(options) {}
    export default Vue;
    
  2. 在src下新建init.js文件,用于初始化操作的原型方法_init,单独抽离成一个独立的initMixin,导入src/index.js文件使用

    // index.js
    import { initMixin } from "./init";
    function Vue(options){// 初始化this._init(options);
    }
    initMixin(Vue);
    export default Vue
    
    // init.js
    // src/init.js
    export function initMixin(Vue) {Vue.prototype._init = function (options) {console.log(options)}
    }
    

    注意:原型方法_init的this指向当前vm实例

  3. 用户通过new Vue实例化时会传入options对象,为了便于Vue中其他方法便于获取options对象,直接将options选项挂载到vm实例上,即vm.$options = options

    注意:vm.$xxx 变量命名方式,表示 vue 内部变量;

  4. 由于options里不仅有data, 还有props, watch, computed…所以需要一个统一的函数,对数据的初始化进行集中处理,initState状态初始化方法(src/initState.js)

    // src/index.js...// 在 new Vue 时,传入的 options 选项中包含 el 和 datavm.$options = options;// 状态的初始化initState(vm);  // 处理数据渲染并挂载到elif (vm.$options.el) {console.log("有el,需要挂载")}}
    }// initState.js
    export function initState(vm) {let ops = vm.$options;if(ops.props) {initProps(vm);}if(ops.data) {initData(vm);}// 还有其他比如methods、watch等
    };
    // vue2对 data初始化:
    function initData(vm) {};
    function initProps() {};
    

2. 数据劫持

2.1 对象的单层劫持

Vue 响应式原理核心是通过Object.defineProperty为属性添加 get、set 方法,从而实现对数据操作的劫持

  1. 首先在initData中可以获取到data数据,通过vm.$options.data获取

  2. 然后处理data的两种情况(对象和函数)

    • 如果data是函数,执行此函数,并得到函数内部返回的对象(此时this指向的是window,所以data是函数时,要改变this指向,使其指向当前vm实例)
    • 如果data是对象,无需处理
      data = typeof data === "function" ? data.call(vm) : data;
    
  3. 对数据进行观测:通过模块observer,创建入口文件src/observer/index.js, 经过initState.js文件处理之后,data一定是对象,所以在观测时在对data进行一次类型检测

    // src/observer/index.js
    export function observer(data) {if(typeof data != "object" || data == null) {return data;}
    }// initState.js
    import { observer } from "./observer/index";
    export function initState(vm) {let ops = vm.$options;// 判断实例上有没有这些属性if(ops.data) {initData(vm);}
    };
    // vue2对 data初始化:
    function initData(vm) {// 首先判断data是对象还是函数let data = vm.$options.data;// 注意:函数this本来指向全局对象window,所以需要改变this指向data = vm._data = typeof data === "function" ? data.call(vm) : data;// 对数据进行劫持observer(data);
    };
    
  4. 对对象进行观测:创建observer类,遍历data对象,使用Object.defineProperty重新定义data对象中的所有属性

    export function observer(data) {...return new Observer(data)
    }
    class Observer {// 类的构造函数 constructor(value){// 遍历对象中的属性,使用 Object.defineProperty 重新定义this.walk(value);}// 循环 data 对象,使用 Object.keys 不循环原型方法walk(data){// Object.keys(data).forEach(key => { //     defineReactive(data, key, data[key]);// });let keys = Object.keys(data); // 把对象中的所有属性转化为一个数组for(let i = 0; i < keys.length; i++) {// 对每个属性进行劫持let key = keys[i];let value = data[key];defineReactive(data, key, value);}}
    }/***    使用Object.defineProperty重新定义data对象中的属性* @param {*} obj   需要定义属性的对象* @param {*} key   给对象定义的属性名* @param {*} value 给对象定义的属性值*/
    function defineReactive(obj, key, value) {Object.defineProperty(obj, key, {get(){           return value;},set(newValue) {if (newValue === value) return;value = newValue;}})
    }
    
2.2 对象的深层劫持

描述:如果data数据中的对象存在多层嵌套(比如:return { obj: {name: "zhiyu"} }),使用前面的方法将不会被劫持

实现:在defineReactive中进行修改

function defineReactive(data, key, value) {// 对 key 进行观测前,调用 observer方法,如果属性值为对象则会继续向下找,实现深层递归观测observer(value); // 深度代理/深度劫持 {a: {b: 1}}Object.defineProperty(data, key, {get() {// console.log("获取");return value;},set(newValue) {// console.log("设置");if(newValue == value) return;// 当值被修改时,通过 observe 实现对新值的深层观测,此时,新增对象将被观测observer(newValue);value = newValue;}})
}
2.3. 数组的劫持

前言:其实通过前面对象的劫持也是可以实现数组的劫持,但是在 Vue2.x 中,是不支持通过修改数组索引或长度来触发更新的(出于性能的考虑)

对数组进行劫持的核心目标,还是要实现数组的响应式:

  • 在 Vue 中,认为这 7 个方法能够改变原数组:push、pop、splice、shift、unshift、reverse、sort;
  • 对以上 7 个方法进行特殊处理,使他们能够劫持到数组的数据变化,就能够实现数组的响应式;

实现思路:

  1. 根据分析,数组和对象不能采用相同的处理方式,在observer初始化时会walk遍历属性实现观测,所以,在此需要单独采取对应的逻辑

    import { ArrayMethods } from "./arr";class Observer {constructor(value) {if(Array.isArray(value)){// 对数组类型进行单独处理:重写 7 个变异方法}else{this.walk(value);}}
    }
    
  2. 新建observer/arr.js,实现数组方法重写、

    // 方法函数劫持,劫持数组方法 arr.push()
    // 重写数组
    // 1. 获取数组的原型方法
    let oldArrayProtoMethods = Array.prototype
    // 2. 原型继承
    export let ArrayMethods = Object.create(oldArrayProtoMethods);
    // 3. 重写能够导致原数组变化的七个方法
    let methods = ["push","pop","unshift","shift","splice","reverse","sort"
    ];
    // // 在数组自身上进行方法重写,以实现对链上同名方法的拦截效果
    methods.forEach(item => {ArrayMethods[item] = function(...args) {// console.log("劫持数组");}
    })
    
  3. 在new observer时, 对数组类型的数据进行链上方法的重写

    ...
    constructor(value) {// 分别处理 value 为数组和对象两种情况if(Array.isArray(value)){value.__proto__ = ArrayMethods; // 更改数组的原型方法}else{this.walk(value);}}
    ...
    
  4. 数组数据变化时需要在劫持到数据变化后,进行处理

    • 通过oldArrayPrototype[method].call(this, …args)执行push原生方法逻辑并绑定当前上下文,实现原数组的更新操作;
    • 收集通过splice、push、unshift方法新增的数据,放入inserted数组;
    • 遍历inserted数组,当数据为对象类型时,需要继续进行观测;
    // arr.js
    methods.forEach(item => {ArrayMethods[item] = function(...args) {// 绑定到当前调用上下文let result = oldArrayProtoMethods[item].apply(this, args);// 数组追加对象的情况 arr.push({a: 1})let inserted = null; // 收集新增的数据switch(item) {case "push":case "unshift":inserted = args;break;case "splice":inserted = args.splice(2); // 获取新增数据:从第三个参数开始都是新增数据break;}// 遍历 inserted 数组中的新增数据,对象类型需要继续进行观测}
    })
    
  5. 由于Observer类中的原型方法observeArray实现了数组的深层劫持,但此方法并未对外导出;所以,在当前模块中遍历inserted数组时,就无法调用到Observer类中的observeArray方法实现数据观测

  6. 为了让当前数组或对象与Observer实例产生一个关联关系,在Observer初始化时,为当前数组或对象value添加自定义属性__ob__,使valueObserver实例之间产生关联

    • value:为当前数组或对象,添加自定义属性__ob__ = this;(在 observe 方法中,只有值为对象类型时即数组或对象,才会执行new Observer创建实例,因此value必为数组或对象)
    • this:为当前Observer实例,通过实例可以调用到observeArray方法
    // src/observer/index.js 
    constructor(value) {// 给value定义一个属性Object.defineProperty(value, "__ob__", {enumerable: false, // 不能够进行枚举value: this, // 指向实例})...}// arr.js
    methods.forEach(item => {ArrayMethods[item] = function(...args) {...let ob = this.__ob__;if(inserted) {ob.observerArray(inserted); // 对添加的对象进行劫持}return result;}
    })
    
  7. 对象被重复观测:当对象被添加__ob__属性标识后,代表着当前对象已经被创建过Observer实例了,即当前对象已经被深层观测过了,在之后的处理中应避免重复观测

    export function observer(data) {// 1. 对象的数据处理,判断是不是对象以及是否为空if(typeof data != "object" || data == null) {return data;}if(data.__ob__){console.log("当前数据已经被观测过了,data = "+ data)return;}// 通过类进行劫持return new Observer(data);
    }
    
2.4 数据代理

含义:就是实现vm.msg$options.data.msg等效,所以要想办法将vm实例操作“代理”到$options.data上;这样,就实现了 “Vue 的数据代理”

实现思路:

  1. 首先,先做一次代理,将data挂载到vm._data下,这样vm实例就可以在外部通过vm._data.msg获取到vm.data
  2. 之后,再做一次代理,将vm实例操作vm.msg代理到vm._data上,这样,外部就可以直接通过vm.msg获取到data.msg

代码实现:

  1. 在Vue初始化阶段,通过observer()实现数据响应式之后,通过Object.defineProperty_data中的数据操作进行劫持;将vm.xxxvm实例上的取值操作,代理到vm._data.xxx

    // initState.js
    function initData(vm) {...// 对数据进行劫持observer(data);// 将data上的所有属性代理到实例上vm {a: 1, b: 2}for(let key in data) {proxy(vm, "_data", key);}
    };
    // vm: vm实例  source: 代理目标  key: 属性名
    function proxy(vm, source, key) {Object.defineProperty(vm, key, {get() {return vm[source][key];},set(newValue) {vm[source][key] = newValue;}});
    };
    
2.5 数组的深层劫持

分析:通过调试,发现之前的代码不能实现数组嵌套的劫持,而数组嵌套又分为数组嵌套数组和数组嵌套对象两种,想要对数组嵌套实现数据观测,就需要对数组内部的数据继续进行递归处理

  1. 在Observer类中,创建observerArray方法,对数组进行深层观测

    class Observer {constructor(value) {// 对数据进行判断是数组还是对象if(Array.isArray(value)) {value.__proto__ = ArrayMethods// 如果是数组对象this.observerArray(value); // 处理数组嵌套 [{a: 1}]} else {this.walk(value); // 进行遍历}}observerArray(value) {// 对数组中的每一项调用 observe 方法,继续进行深层观测处理;// observe 方法内:如果是对象类型,继续 new Observer 进行递归处理for(let i = 0; i < value.length; i++) {observer(value[i]);}}
    };
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_74860.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023前端二面经典手写面试题

实现一个call call做了什么: 将函数设为对象的属性执行&删除这个函数指定this到函数并传入给定参数执行函数如果不传入参数&#xff0c;默认指向为 window // 模拟 call bar.mycall(null); //实现一个call方法&#xff1a; Function.prototype.myCall function(context…

LLaMA-META发布单卡就能跑的大模型

2023年2月25日&#xff0c;Meta使用2048张A100 GPU&#xff0c;花费21天训练的Transformer大模型LLaMA开源了。 1.4T tokenstakes approximately 21 days 以下是觉得论文中重要的一些要点 1&#xff09;相对较小的模型也可以获得不错的性能 研究者发现在给定计算能力限制的情…

《高性能MySQL》读书笔记(下)

目录 Mysql查询性能的优化 慢查询基础 优化数据访问 是否向数据库请求了不需要的数据 查询了不需要的记录 多表联查中返回全部列 MySQL是否在扫描额外的记录 重写查询的方式 切分查询&#xff08;重点&#xff09; 分解连接查询&#xff08;重点&#xff09; MySQL如…

史上最全的大数据开发八股文【自己的吐血总结】

自我介绍 我本硕都是双非计算机专业&#xff0c;从研一下开始学习大数据开发的相关知识&#xff0c;从找实习到秋招&#xff0c;我投递过100公司&#xff0c;拿到过10的offer&#xff0c;包括滴滴、字节、蚂蚁、携程、蔚来、去哪儿等大厂&#xff08;岗位都是大数据开发&#…

算法练习(七)数据分类处理

一、数据分类处理 1、题目描述&#xff1a; 信息社会&#xff0c;有海量的数据需要分析处理&#xff0c;比如公安局分析身份证号码、 QQ 用户、手机号码、银行帐号等信息及活动记录。采集输入大数据和分类规则&#xff0c;通过大数据分类处理程序&#xff0c;将大数据分类输出…

喜讯!华秋电子荣获第六届“蓝点奖”十佳分销商奖

2 月 25 日&#xff0c;由深圳市电子商会主办的2023 中国电子信息产业创新发展交流大会暨第六届蓝点奖颁奖盛典在深圳隆重举行。 图&#xff1a;华秋商城渠道总监杨阳&#xff08;右三&#xff09; 深圳市电子商会连续六年举办“蓝点奖”评选活动&#xff0c;旨在表彰对电子信…

高端电器新十年,求解「竞速突围」

竞争激烈的高端电器品牌们&#xff0c;平时王不见王&#xff0c;但也有例外。海尔、博西、海信、创维、方太、老板等等近乎中国电器行业所有一线品牌副总裁级别以上高层&#xff0c;2月22日都现身于上海&#xff0c;来参加一场由红星美凯龙攒起来的高端电器局&#xff0c;2023中…

能在软路由docker给部署搭建teamsperk服务器么?并且设置好ddns

参考链接(4条消息) 【个人学习总结】使用docker搭建Teamspeak服务器_blcurtain的博客-CSDN博客_teamspeak3 docker(⊙﹏⊙)哎呀&#xff0c;崩溃啦&#xff01; (tdeh.top)TeamSpeak服务器搭建与使用 - 缘梦の镇 (cmsboy.cn)Openwrt X86 docker运行甜糖-软路由,x86系统,openwrt…

虚拟数字人直播带货相比人工有哪些优势?

新经济时代的到来&#xff0c;彻底改变了传统的消费方式。虚拟数字人的出现&#xff0c;标志着新一波的消费升级到来。虚拟数字人直播带货&#xff0c;不仅降低了商家的带货成本&#xff0c;拉近了商家与消费者的距离&#xff0c;也给消费者带来全新的消费方式。 花西子虚拟形象…

如何查看Spring Boot各版本的变化

目录 1.版本 2.基础特性和使用 3.新增特性和Bug修复 1.版本 打开Spring官网&#xff0c;点进Spring Boot项目我们会发现在不同版本后面会跟着不同的标签&#xff1a; 这些标签对应不同的版本&#xff0c;其意思如下&#xff1a; GA正式版本&#xff0c;通常意味着该版本已…

k8s学习之路 | Day16 k8s 中的容器初探

文章目录容器镜像镜像名称镜像拉取策略私有仓库的拉取策略容器的环境变量和启动命令容器的环境变量容器的启动命令容器的生命周期钩子postStartpreStop容器的探针startupProbelivenessProbereadinessProbek8s 集群中最小的管理单元就是一个Pod&#xff0c;而Pod里面才是容器&am…

利用GPT-3 Fine-tunes训练专属语言模型

利用GPT-3 Fine-tunes训练专属语言模型 文章目录什么是模型微调&#xff08;fine-tuning&#xff09;&#xff1f;为什么需要模型微调&#xff1f;微调 vs 重新训练微调 vs 提示设计训练专属模型数据准备清洗数据构建模型微调模型评估模型部署模型总结什么是模型微调&#xff0…

JavaScript split()方法

JavaScript split()方法 目录JavaScript split()方法一、定义和用法二、语法三、参数值四、返回值五、更多实例5.1 省略分割参数5.2 使用limit参数5.3 使用一个字符作为分割符一、定义和用法 split() 方法用于把一个字符串分割成字符串数组。 二、语法 string.split(separat…

NCRE计算机等级考试Python真题(四)

第四套试题1、以下选项中&#xff0c;不属于需求分析阶段的任务是&#xff1a;A.需求规格说明书评审B.确定软件系统的性能需求C.确定软件系统的功能需求D.制定软件集成测试计划正确答案&#xff1a; D2、关于数据流图&#xff08;DFD&#xff09;的描述&#xff0c;以下选项中正…

RTMP的工作原理及优缺点

一.什么是RTMP&#xff1f;RTMP&#xff08;Real-Time Messaging Protocol&#xff0c;实时消息传输协议&#xff09;是一种用于低延迟、实时音视频和数据传输的双向互联网通信协议&#xff0c;由Macromedia&#xff08;后被Adobe收购&#xff09;开发。RTMP的工作原理是&#…

IP-GUARD控制台账户输入多次错误密码锁定后该如何解锁?

其他管理员账户给锁定了,暂时只能等其锁定时间到了才可以再次输入,默认是设置是锁定30min; 1、如果急需此账户查看,可以使用admin系统管理员账户登录控制台,在工具-账户中清除这个账户的密码,重新登录设置密码。

NIO与零拷贝

目录 一、零拷贝的基本介绍 二、传统IO数据读写的劣势 三、mmap优化 四、sendFile优化 五、 mmap 和 sendFile 的区别 六、零拷贝实战 6.1 传统IO 6.2 NIO中的零拷贝 6.3 运行结果 一、零拷贝的基本介绍 零拷贝是网络编程的关键&#xff0c;很多性能优化都离不开。 在…

【云原生kubernetes】k8s 常用调度策略使用详解

一、前言 通过之前的学习&#xff0c;我们了解到k8s集群中最小工作单位是pod&#xff0c;对于k8s集群来说&#xff0c;一个pod的完整生命周期是由一系列调度策略来控制&#xff0c;这些调度策略具体是怎么工作的呢&#xff1f;本文将详细讨论下这个问题。 二、k8s调度策略简介…

【多目标优化算法】多目标蚱蜢优化算法(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…

APP测试的7大注意点。

1. 运行 1&#xff09; App安装完成后的试运行&#xff0c;可正常打开软件。 2&#xff09; App打开测试&#xff0c;是否有加载状态进度提示。 3&#xff09; App⻚面间的切换是否流畅&#xff0c;逻辑是否正确。 4&#xff09; 注册 同表单编辑⻚面 用户名密码⻓度 …