Vue深入学习3—数据响应式原理

1、数据响应式原理

1.1、MVVM是什么?

简单来说,就是数据变了,视图也会跟着变,首先你得定义一个带有{{ }}的模板Model,当数据中的值变化了,视图View就会跟着变化;视图模型View-model是模板Model和视图View之间的桥梁,Vue属于非侵入式,React和小程序就是侵入式(数据变化的时候需要调用提前写好的API)

// Vue数据变化,非侵入式
this.a ++
// React、小程序数据变化,侵入式
this.setState({
	a: this.state.a + 1
});

1.2、数据响应式的中心思想?

通过重写数据的get和set属性方法,让数据在被渲染时,把所有用到该数据的订阅者,存放订阅者列表中;当数据发生改变时,Notify方法通知所有订阅了该数据的订阅者Watcher,达到重新渲染DOM的目的。——Vue官网解释

image-20210721195342776

是不是有点懵了?没关系,举个简单的栗子🌰:

《西游记》中的妖怪(Watcher)时刻惦记(订阅)着唐僧(Data),想吃唐僧肉,孙悟空(Component)在听到(get搜集依赖)唐僧被抓的消息后,做出反应(set触发依赖),准备救出师傅。于是来到了妖怪(Watcher)的老巢,跟它大战几个回合后,成功救出唐僧(Data),达到重新踏上(渲染)西天取经(Vittual DOM Tree)的目的!

2、尤大找到了一把”上帝的钥匙🔑“ Object.defineProperty()方法:

数据劫持、数据代理,MDN这样描述的:直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

Object.defineProperty(obj(定义那个对象)'a(定义这个对象的什么属性)'{
  //属性值定义为多少
	value : 3
})
//-----------------------------------🌰栗子🌰-------------------------------------------
var obj = {};
Object.defineProperty(obj, 'a', {
	get(){
		console.log('访问obj的a属性!');
		return 7;
	},
	set(){
		console.log('改变obj的a属性', newValue);
		temp = newValue;
	}
})
console.log(obj.a);  // 7
obj.a = 9;			 //修改a的值
console.log(obj.a);  // 7

3、弥补这把钥匙🔑的不足 defineReactive函数:

defineProperty()方法存在的问题:通过下标方式对数组属性进行修改或新增等操作时,无法拦截到数据的变化,比如通过下标方式修改数组数据或者给对象新增属性,Object.defineProperty()不能拦截到这些操作。

为了解决这个问题,get中并不能返回set刚刚修改过的值,再次调用会显示修改前的值,怎么解决这个问题?在外面定义一个全局变量,用来周转变量值。

// 解决defineProperty存在到的问题
defineProperty(data(数据对象),key(键名),val()){
	
}
//-----------------------------------🌰栗子🌰-------------------------------------------
var obj = {};
var temp;   //在外面定义一个全局变量,用来周转变量值。
function defineProperty(data, key, val) {
	Object.defineProperty(data, key, {
		// 可枚举
		enumerable: true,
		// 可被配置,比如被delete
		configurable: true,
		get(){
            console.log('访问obj的'+ key +'属性!');
            return temp;
        },
        set(){
            console.log('改变obj的'+ key +'属性!', newValue);
            if(val == newValue){
	            return;
            }
            temp = newValue;
        }
	});
}
defineReactive(obj, 'a',10)
 
console.log(obj.a); // 访问obj的a属性! 10
obj.a = 69;			//修改a的值
obj.a ++;
console.log(obj.a); // 修改obj的a属性!70
// definReactive 实现
    // 简化后的版本 
    function defineReactive( target, key, value, enumerable ) {
      // 折中处理后, this 就是 Vue 实例
      let that = this;

      // 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )
      if ( typeof value === 'object' && value != null && !Array.isArray( value ) ) {
        // 是非数组的引用类型
        reactify( value ); // 递归
      }

      Object.defineProperty( target, key, {
        configurable: true,
        enumerable: !!enumerable,

        get () {
          console.log( `读取 ${key} 属性` ); // 额外
          return value;
        },
        set ( newVal ) {
          console.log( `设置 ${key} 属性为: ${newVal}` ); // 额外

          value = reactify( newVal );

        }
      } );
    }

4、数组的响应式处理

改写了7个属性,push(数组尾部推入)、pop(数组尾部移除)、shift(数组头部插入)、unshift(数组尾部移出)、splice(切割)、sort(就地排序)、reverse(排序位置颠倒);

// 得到Array.prototype
const arrayPrototype = Array.prototype
// 以Array.prototype为原型,创建arrayMethods对象,定义__proto方法
const arrayMethods = Object.create(arrayPrototype);
// 要被改写的7个数组方法
const methodsNeedChange = [
	'push','pop','shift','unshift','splice','sort','reverse'
];
//遍历
methdsNeedChange.forEach(methodName => {
	// 备份原来的方法
	const original = arrayPrototype[methodName];
	// 把数组身上的__obj__取出来,
	const ob = this.__obj__;
	// 有三种方法push/unshift/splice能够插入新项,把插入的新项变为observe
	let inserted = [];
	switch(methodName){
		case 'push':
		case 'unshift':
			inserted = arguments;
			break;
		case 'splice':
			// splice格式是splice(下标,数量,插入的新项)	
			inserted = arguments.slice(2);
			brack;
	}
	// 判断有没有要插入的新项,让新项也变为响应的
	if(inserted){
		ob.obsetveArray(inserted);
	}
	// 定义新的方法
	def(arrayMethods, methodName, function(){
		original.apply(this, arguments);
	},false);
});

面试题:数组中的响应式是怎么实现的?

答:以Array.prototype为原型,创建了一个arrayMethods的对象,用一个非常强硬的手段,Object.setPrototypeOf()让数组的_ proto _强制指向arrayMethods,这样就可以调用新的改写的7个方法。

5、什么是依赖?

需要用到数据的递归就是依赖,在getter中收集依赖,在setter中触发依赖。

收集依赖的代码封装成Dep类,每个Observer的实例都有一个Dep的实例;

Watcher是一个中介,数据发生变化时通过watcher中转,通知组件。

image-20210721212250356

再拿《西游记》说,妖怪(Watcher)是怎么知道唐僧(Data)途径此地的呢?那自然是派出去巡山(depend方法)的小妖精(Dep-订阅器)发现(搜集)的;这个小妖精(Dep)巡山有三个目的(属性):目标(target)、id、subs(所有巡山的信息),当唐僧(Data)经过某个提前布置好的陷阱(生命周期的hook)时,就会被抓,压入巢穴(targetStack栈顶),交给妖怪(Watcher)。

6、什么时候能够把Wather放入到Dep当中?

Dep类:封装搜集的代码,管理依赖。

Wather类:①将属性值更新;②执行watch中的回调函数handler(newVal, oldVal)

先把wather设置到全局指定位置,然后读取数据;getter函数当中,会从全局唯一的地方,读取正在读取数据的wather,并把wather再搜集到Dep当中。

//wather.js
export default class Dep{
	constructor(){
		// 用数组存储自己的订阅者,subs是subscribes订阅者的意思。
        // 数组里面存放的是wather的实例。
        this.subs = [];
    }
    // 添加订阅
    addSub(sub){
        this.shbs.push(sub);
    }
    // 添加依赖
    dpend(){
        // 指定全局的位置
        if(Dep.target){
            // 如果Dep.target存下,则推入到subs里面
            this.addSub(Dep.target);
            // 
            
        }
    }
    // 通知更新
    notify(){
        // 浅克隆一份
        
    }
}

7、Vue中怎么识别 a.b.c 的?

利用高阶函数,逐层取出里面的值。

image-20210721211629294

<script>
	// 深层套娃
	var o = {
		a: {
			b: {
				c: {
					d: 68
				}
			}
		}
	}
	var str = 'a.b.c.d';
	function parsePath(str){
		// 根据 . 来进行拆分
		var segments = str.split('.');
		// 返回接收对象的函数
		return(obj) => {
			// 遍历接收的函数
			for(let i = 0; i < segments.length; i++){
				// 判断obj存不存在
				if(!obj) return;
				// 一层一层的剥开 o 的心
				obj = obj[segments[i]]
			}
			// 高阶函数,函数内部返回一个函数
			return obj;
		}
	}
	// 调用一下
	var fn = parsePath(str);
	var v = fn(o);
	console.log(v);
</script>

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

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

相关文章

C++ 类的初始化列表

C 中的类必须使用初始化列表的 4 种情况&#xff1a; 一&#xff0c;继承于一个基类&#xff0c;这个基类的构造函数有参数时。 #include <stdio.h> #include <stdlib.h>#pragma pack(4) class CBase { public:CBase(int value): bBaseValue(value){}~CBase(){}p…

代码随想录算法训练营31期day4,力扣24+19+02.07+142

24&#xff0c;动指针 class Solution { public:ListNode* swapPairs(ListNode* head) {//建立虚拟头结点auto dummynew ListNode(-1);dummy->nexthead;for(auto pdummy;p->next&&p->next->next;){auto ap->next;auto ba->next;p->nextb;a->n…

详解SpringCloud之远程方法调用神器Fegin

第1章:引言 咱们作为Java程序员,在微服务领域里,Spring Cloud可谓是个耳熟能详的大名。它提供了一套完整的微服务解决方案,其中就包括了服务间的通信。在这个微服务中,有一个成员特别引人注意,它就是Feign。 那Feign到底是什么呢?简单来说,Feign是一个声明式的Web服务…

linux条件判断练习

1.实现自动生成相应的压缩包 1.写一个脚本&#xff0c;完成如下功能 传递一个参数给脚本&#xff0c;此参数为gzip、bzip2或者xz三者之一&#xff1b; (1) 如果参数1的值为gzip&#xff0c;则使用tar和gzip归档压缩/etc目录至/backups目录中&#xff0c;并命名为/backups/etc-…

1块9毛钱,修复拓牛TC1D智能垃圾桶盖子不能正常开合的故障

前言 21年9月份买了拓牛的智能垃圾桶&#xff0c;一直用的很流畅&#xff0c;再加上屋里没啥有机垃圾&#xff0c;也没有宠物&#xff0c;用上之后每次投入垃圾&#xff0c;之后都会盖上盖子&#xff0c;没有很多的异味散发&#xff0c;屋里也没有蟑螂等害虫。 再加上门口有帘…

Android开发学习-Activity

启停活动页面 1、启动和停止 startActivity(new Intent(原页面.this,目标页面.this)); startActivity(new Intent(this,ActFinishActivity.class)) 从当前页面回到上一个页面&#xff0c;相当于关闭当前页面&#xff0c;返回代码如下&#xff1a; finish(); 2、生命周期 …

鸿蒙:@Link装饰器-父子双向同步

子组件中被Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。从API version 9开始&#xff0c;该装饰器支持在ArkTS卡片中使用。 需要注意&#xff1a;Link装饰的变量与其父组件中的数据源共享相同的值。Link装饰器不能在Entry装饰的自定义组件中使用。 一、装饰器使…

基于Python对二手车之家的数据采集与分析

1.1 用户需求 1.1.1 背景与现状 基于Python的二手车之家数据采集与分析的背景与现状分析 背景&#xff1a; 随着经济的发展和人们生活水平的提高&#xff0c;二手车市场逐渐兴起。二手车之家作为中国最大的二手车交易平台之一&#xff0c;提供了丰富的二手车信息&#xff0…

下载nacos 2.3 for arm64

客户组织安全测试&#xff0c;我们系统测出了好几个高危问题&#xff0c;其中大部分是关于nacos的。 原先的nacos版本太低了&#xff0c;是1.3的。现在&#xff08;2024.01&#xff09;已经是2.3了&#xff0c;应该装个新的。我们使用docker安装nacos&#xff0c;原本很简单的…

推荐几款便宜幻兽帕鲁游戏联机服务专用云服务器

随着互联网技术的发展&#xff0c;云服务器已经成为了许多游戏联机服务的首选。如果大家想要自行搭建幻兽帕鲁联机服务器&#xff0c;那么使用云服务器是一个很好的选择。下面&#xff0c;本文将推荐几款便宜幻兽帕鲁游戏联机服务专用云服务器。 幻兽帕鲁游戏对于服务器配置要求…

Windows 上面双网卡网络,配置为优先IPV4

多数网络游戏加速器是不支持IPV6的&#xff0c;即便支持IPV6也不好用&#xff0c;原因是IPV6在大陆并不是普及的状态&#xff0c;很多资源是没有的。 所以本文会教大家怎么让双IP栈的用户&#xff0c;怎么配置优先适用IPV4&#xff0c;并且IPV6也还可以用。 跟着我的步骤来&am…

互信息的简单理解

在介绍互信息之前&#xff0c;首先需要了解一下信息熵的概念&#xff1a;所谓信息熵&#xff0c;是指信息论中对一个随机变量不确定性的度量&#xff0c;对于随机变量x&#xff0c;信息熵的定义为&#xff1a; H ( x ) − ∑ x p ( x ) l o g p ( x ) H(x)-\sum_xp(x)logp(x) …

git安装步骤

安装环境&#xff1a;Windows10 64bit 下载 Git网址 &#xff1a;Git - Downloading Package 版本&#xff1a;Git-2.21.0-64-bit 第一步&#xff1a;双击下载后的Git-2.21.0-64-bit.exe&#xff0c;开始安装 安装开始 第二步&#xff1a;选择安装路径&#xff0c;点击[next]…

【云原生】Docker基于Dockerfile多级构建,实现缩小镜像体积

目录 一、基于上次的nginx的Dockerfile做多级构建 二、基于上次的php的Dockerfile修改做多级构建 三、基于上次的mysql的Dockerfile修改做多级构建 基于以上三个镜像构建 四、镜像体积是不是越小越好&#xff1f;为什么要缩减镜像体积&#xff1f; 五、缩小镜像体积的方法…

变相体罚的行为有哪些

你是否在无意中“伤害”了学生&#xff1f;每一位老师都如同辛勤的园丁&#xff0c;努力耕耘&#xff0c;期待花开。但在这个过程中&#xff0c;有些行为&#xff0c;虽不带皮肉之苦&#xff0c;却可能给学生的心灵留下难以愈合的创伤。今天&#xff0c;就让我们来谈谈这些隐蔽…

OpenHarmony—TypeScript到ArkTS约束说明

对象的属性名必须是合法的标识符 规则&#xff1a;arkts-identifiers-as-prop-names 级别&#xff1a;错误 在ArkTS中&#xff0c;对象的属性名不能为数字或字符串。通过属性名访问类的属性&#xff0c;通过数值索引访问数组元素。 TypeScript var x { name: x, 2: 3 };c…

2024最新幻兽帕鲁服务器多少钱一个?

幻兽帕鲁服务器多少钱&#xff1f;价格便宜&#xff0c;阿里云4核16G幻兽帕鲁专属服务器32元1个月、66元3个月&#xff0c;4核32G配置113元1个月、339元3个月&#xff1b;腾讯云4核16G14M服务器66元1个月、277元3个月、1584元一年。阿腾云atengyun.com分享阿里云和腾讯云palwor…

苹果笔记本MacBook电脑怎么卸载软件?三种方法快速卸载软件

苹果笔记本MacBook电脑是一款非常流行的电脑&#xff0c;但是有时候我们可能需要卸载一些不需要的软件。下面是一些简单的步骤&#xff0c;可以帮助您在MacBook电脑上卸载软件。 苹果笔记本MacBook电脑怎么卸载软件&#xff1f;三种实用方法快速卸载软件&#xff01; 方法一&a…

【开源】基于JAVA语言的公司货物订单管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 客户管理模块2.2 商品维护模块2.3 供应商管理模块2.4 订单管理模块 三、系统展示四、核心代码4.1 查询供应商信息4.2 新增商品信息4.3 查询客户信息4.4 新增订单信息4.5 添加跟进子订单 五、免责说明 一、摘要 1.1 项目…