vue数据响应式处理

项目结构:
在这里插入图片描述kvue01.js

//数组响应式
//替换数组中原型中的七个方法
const orginalProto = Array.prototype;
//Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
const arrayProto = Object.create(orginalProto); //这里是备份一份数组的原型
// console.log(arrayProto);//arrayProto 的__proto__ 是Array.prototype,且arrayProto 是一个对象
['push', 'pop', 'shift', 'unshift'].forEach(method => {
	arrayProto[method] = function() {
		//原始操作
		//方法覆盖
		orginalProto[method].apply(this, arguments);
		console.log(method)
	}
});



//属性拦截:defineProperty()
//闭包:
// function makeFunc() {
//     var name = "Mozilla";
//     function displayName() {
//         alert(name);
//     }
//     return displayName;
// }

// var myFunc = makeFunc();
// myFunc();
// 第一眼看上去,也许不能直观地看出这段代码能够正常运行。
// 在一些编程语言中,一个函数中的局部变量仅存在于此函数的执行期间。
// 一旦 makeFunc() 执行完毕,你可能会认为 name 变量将不能再被访问。
// 然而,因为代码仍按预期运行,所以在 JavaScript 中情况显然与此不同。
// 原因在于,JavaScript中的函数会形成了闭包。 
// 闭包是由函数以及声明该函数的词法环境组合而成的。
// 该环境包含了这个闭包创建时作用域内的任何局部变量。
// 在本例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用。
// displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。
// 因此,当 myFunc 被调用时,变量 name 仍然可用,其值 Mozilla 就被传递到alert中。
function defineReactive(obj, key, val) {
	//递归,看key是否是一个对象,如果是一个对象,会接着调用defineReactive,
	// 如果不是直接return,调用defineReactive,再次看当前key是否是一个对象
	observe(val);
	//这里用了闭包
	//val通过get方法和set方法暴露出去
	Object.defineProperty(obj, key, {
		get() {
			console.log('get', key)
			return val;
		},
		set(newval) {
			console.log('set', key)
			if (newval !== val) {
				//这里是说boj本身有的属性,是对象,
				// 然后更新这个对象的值,值变成其他值,这个值也是对象,
				// 在这里进行响应式处理,对这个新的值
				observe(newval);
				val = newval;
				// update();

			}
		}
	})
}

function set(obj, key, val) {
	defineReactive(obj, key, val)
}

let obj = {
	'foo': 'foo',
	'bar': 'bar',
	'a': {
		'n': 1
	},
	arr: []
};

// defineReactive(obj,'foo','foo1');
// obj.foo = 'foo02';

//实现new Vue({data{}})的响应式数据处理
function observe(obj) {
	if (typeof obj !== 'object') {
		return;
	}
	if (Array.isArray(obj)) {
		//覆盖原型,替换7个变更操作
		obj.__proto__ = arrayProto;
		Object.keys(obj).forEach(item => {
			observe(item);
		})
	} else {
		//Object.keys() 返回一个由一个给定对象的自身可枚举属性组成的数组
		//forEach()  为每个数组元素执行一次提供的函数
		Object.keys(obj).forEach(key => {
			defineReactive(obj, key, obj[key]);
		})
	}
}
observe(obj);

function update() {
	console.log(foo) //这里直接写 foo 可以获取到 id 为 foo 的 dom 元素
	app.innerText = obj.foo;
}
// setInterval(function(){
// 	obj.foo = new Date().toLocaleTimeString();
// },1000)

// defineReactive(obj,'bar','bar1');
// obj.foo;
// obj.foo = 'foo02';
// obj.a.n;

// obj.a = {'s':2};//重新对 a 赋值,且对这个新值进行响应式处理
// obj.a.s;

//对于新定义的值
//不能直接 obj.b = { 'w':3 } 这样定义
//这样会逃脱初始化的响应式处理
//所以需要像vue里的$set这样进行新加值
// set(obj,'b',{'w':3});
// obj.b;
// obj.b.w;

// obj.arr.push(4);
// set(obj,'arr1',[4]);
// obj.arr1;

// console.log(typeof [1,2,3]);
// typeof 返回的值:undefined,boolean,string,number,object(null算object),function


//如果把对象进行响应式处理对话
//需要覆盖扩展这七个改变数组的方法
//push    尾部插入
//pop    头部插入
//shift    头部删除
//unshift    尾部删除
//splice(index,howmany,item1,item2,...)    添加/删除项目  
//sort    排序
//reverse    翻转数组

//node运行js文件
//在该文件下打开终端
//保证安装了node后
//输入  node  文件名
//可以带后缀名,也可以不带后缀名

kvue02.js

function defineReactive(obj, key, val) {
	//拦截的是Kvue data里的属性
	//递归,看key是否是一个对象,如果是一个对象,会接着调用defineReactive,
	// 如果不是直接return,调用defineReactive,再次看当前key是否是一个对象
	observe(val);

	//创建dep实例
	//因为这里是闭包环境,所以每一个key被拦截到,就会创建一个dep实例,key和dep实例是一一对应的
	//Kvue data里的每一个数据有一个dep对象
	//在下面,当要set数据时,可以直接拿相对应当dep实例去执行更新函数
	const dep = new Dep();

	//这里用了闭包
	//val通过get方法和set方法暴露出去
	Object.defineProperty(obj, key, {
		get() {
			console.log('get', key);
			//当创建一个watcher实例时,保存一个Dep.target
			//Dep.target 保存的是watcher实例
			//下面读取key时,触发了这里的get
			//判断这里的target存不存在,如果存在
			//把当前的watcher实例add到当前的Dep中
			Dep.target && dep.addDep(Dep.target);
			return val;
		},
		set(newval) {
			console.log('set', key)
			if (newval !== val) {
				//这里是说boj本身有的属性,是对象,
				// 然后更新这个对象的值,值变成其他值,这个值也是对象,
				// 在这里进行响应式处理,对这个新的值
				observe(newval);
				val = newval;
				//set 数据的时候,去执行dep的更新函数
				dep.notify();

			}
		}
	})
}

function observe(obj) {
	if (typeof obj !== 'object') {
		return;
	}
	new Observer(obj); //交给观察者,判断数据类型,并根据不同类型做不同对响应式操作
}

function proxy(vm) {
	Object.keys(vm.$data).forEach(key => {
		Object.defineProperty(vm, key, {
			get() {
				return vm.$data[key];
			},
			set(v) {
				vm.$data[key] = v;
			}
		})
	})
}
//Observer:观察者,作用是判断是对象还是数组,来做不同的响应式操作
class Observer {
	constructor(val) {
		if (Array.isArray(val)) {
			//to do something
		} else {
			//object
			this.walk(val); //进行遍历数据,并且对其做响应式处理
		}
	}
	walk(obj) {
		//Object.keys() 返回一个由一个给定对象的自身可枚举属性组成的数组
		//forEach()  为每个数组元素执行一次提供的函数
		Object.keys(obj).forEach(key => {
			defineReactive(obj, key, obj[key]);
		})
	}
}

class Compile {
	constructor(el, vm) {
		this.$vm = vm;
		//类似 Jquery 的元素获取方法,不需要额外的 Jquery 来支持
		//指定一个或多个匹配元素的 CSS 选择器。 
		// 可以使用它们的 id, 类, 类型, 属性, 属性值等来选取元素。
		// 对于多个选择器,使用逗号隔开,返回第一个匹配的元素。
		this.$el = document.querySelector(el);

		//执行编译
		this.compile(this.$el);

	}
	compile(el) {
		// childNodes  元素的子节点集合
		el.childNodes.forEach(node => {
			if (node.nodeType === 1) {
				console.log('编译元素')
				//是一个 element
				this.compileElement(node);
				//遍历特性

				//递归循环整棵DOM树
				if (node.childNodes.length > 0) {
					this.compile(node)
				}
			} else if (this.isInter(node)) {
				console.log('编译文本')
				this.compileText(node);
			}
		})
	}
	isInter(node) { //判断是一个插值  {{ XXX }}
		//node.nodeType === 3    文本节点
		//  /\{\{(.*)\}\}/    小括号分组使用,可以用  $1  来拿小括号里的东西
		//  .  匹配任何字符    *  匹配前面的表达式零次或多次
		return node.nodeType === 3 && /^\{\{([\s\S]*)\}\}$/.test(node.textContent)
	}
	compileText(node) {
		//RegExp.$1  取的是上面小括号里面的东西
		//指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串
		//总共可以有99个匹配
		//this.$vm  指的是 KVue 实例,KVue 是一个对象
		//this.$vm[RegExp.$1]  获取的是 count 的值
		// node.textContent = this.$vm[RegExp.$1.trim()];
		this.update(node, RegExp.$1, 'text')
	}
	compileElement(node) {
		//属性的获取
		//attributes 属性返回指定节点的属性集合,即 NamedNodeMap。
		//是一个对象
		const attrs = node.attributes;
		//Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。
		Array.from(attrs).forEach(a => {
			//例:  k-text='number'
			const attrName = a.name; //k-text
			const exp = a.value; //number
			//startsWith() 方法用于检测字符串是否以指定的前缀开始。
			if (attrName.startsWith('k-')) {
				//substring() 方法用于提取字符串中介于两个指定下标之间的字符。
				//一个新的字符串,
				// 该字符串值包含 start 处到 stop-1 处的所有字符,其长度为 stop 减 start。
				const dir = attrName.substring(2);
				//如果 this[dir] 存在  运行 this[dir](node,exp)
				this[dir] && this[dir](node, exp);
			}
			//事件处理
			//@click = 'add'
			if (this.isEvent(attrName)) {
				const dir = attrName.substring(1); //click
				this.eventHandler(node, exp, dir);
			}

		})
		// console.log( attrs)
	}
	isEvent(attrName) {
		return attrName.indexOf('@') == 0
	}
	eventHandler(node, exp, dir) {
		const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp];
		node.addEventListener(dir, fn.bind(this.$vm))
	}
	text(node, exp) {
		// node.textContent = this.$vm[exp]
		this.update(node, exp, 'text')
	}
	textUpdater(node, val) {
		node.textContent = val;
	}
	html(node, exp) {
		// node.innerHTML = this.$vm[exp]
		this.update(node, exp, 'html')
	}
	htmlUpdater(node, val) {
		node.innerHTML = val;
	}
	model(node, exp) {
		//赋值并更新data里的那个数据
		this.update(node, exp, 'model');
		//事件监听,监听到发生了input的value发生了变化,就要把新的value赋值给data里的相应的数据
		node.addEventListener('input', (e) => {
			// console.log(this);//箭头函数的this指向定义该函数对象,默认使用父级的this,指向上下文的this
			this.$vm[exp] = e.target.value;
		})
	}
	modelUpdater(node, val) {
		node.value = val;
	}
	update(node, exp, dir) {
		//这里这样获取,会丢失this指向
		const fn = this[dir + 'Updater'];
		fn && fn(node, this.$vm[exp.trim()]);
		//watcher 的创建:出现一个动态的数据,就需要一个Watcher
		//在编译的时候,会遍历dom树,发现插值和指令,插值是动态数据,指令上也会有动态的数据
		//都需要创建一个watcher实例
		new Watcher(this.$vm, exp, function(val) {
			fn && fn(node, val);
		})
	}
}

//依赖收集
//例如: 
// <p>{{ name1 }}</p>
// <p>{{ name2 }}</p>
// <p>{{ name1 }}</p>
//这里将会有三个更新函数,分别对应三个值:name1,name2,name1
//也会有三个watcher函数,watcher1,watcher2,watcher3,
//watcher1,watcher3就被放进dep1里
//一个watcher2会被放进一个dep2里
//这里当dep是指管家,当name1,name2,发生数据改变,
// 就会由dep1,dep2,告诉手下的watcher1,watcher2,watcher3,要去更新视图了
class Dep {
	constructor() {
		//存储所有的watcher
		this.deps = [];
	}
	addDep(watcher) {
		this.deps.push(watcher)
	}
	notify() {
		this.deps.forEach(watcher => {
			watcher.update()
		})
	}
}

//界面中出现一个动态当值,就需要创建一个watcher
class Watcher {
	constructor(vm, key, fn) {
		this.vm = vm;
		this.key = key;
		this.fn = fn;

		//触发依赖收集
		Dep.target = this; //保存当前实例
		this.vm[this.key]; //读取key,会触发getter
		Dep.target = null; //释放target 
	}
	update() {
		this.fn.call(this.vm, this.vm[this.key])
	}
}

//Kvue 类
class KVue {
	constructor(option) {
		this.$options = option;
		this.$data = option.data;
		//把数据做响应式处理
		observe(this.$data);
		//代理:用户可以直接通过KVue实例直接访问data里的响应式数据数据
		proxy(this);
		//编译模版
		new Compile(option.el, this);
	}
}

//发布-订阅者模式它定义对象间的一种一对多的依赖关系,
// 当一个对象的状态发生改变时,所有依赖于它的值都将得到通知

kvue02.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>

	</head>
	<body>
		<div id="app">
			<p>{{count}}</p>
			<div class="div1" style="width: 50px;height: 50px;background: #fc3605;"></div>
			<p k-text="count"></p>
			<p k-html="desc"></p>
			<p @click="add">{{number1}}</p>
			<input type="text" k-model="number1" />

		</div>
		<script src="kvue/kvue02.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			const app = new KVue({
				el: '#app',
				data: {
					count: 0,
					count2: 2,
					desc: '<span>我是span标签</span>',
					number: 0,
					number1: '我是number1'
				},
				methods: {
					add() {
						this.number++;
					}
				}
			})
			// setInterval(()=>{
			// 	app.count++
			// },1000)
		</script>

	</body>
</html>

在这里插入图片描述
vue数据响应式处理的图解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值