vue源码分析(一)

vue 源码解析(一)

vue在单页面文件中使用

vue在html的引入以及使用。vue本身是一个函数,采用new的方式创建实例,然后进行传入配置项,初始化vue实例的状态

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta
			name="viewport"
			content="width=device-width, initial-scale=1.0"
		/>
		<title>Document</title>
		<script src="./myVue.js"></script>
	</head>
	<body>
		<div
			id="app"
			style="color: red;background-color: aquamarine;font-size: 14px;"
		>
			<span style="color: yellow;">{{a}}</span>
			<div class="text">2332{{b}}</div>
			<div class="app"></div>
		</div>
		<script>
			// let obj={a:1}
			// // obj.prototype.b=123报错
			// console.log("obj",obj)
			let vm = new Vue({
                            el: "#app",
                            data() {
                                return {
                                    a: 99999,
                                    b: "bbbbb",
                                    c: {
                                        test: 12,
                                        str: "qwe",
                                     },
                                     hobby: [
                                            {
                                               label: "数组",
                                            },
                                            "aaaaaa",
                                            "bbbbbbbb",
                                    ],
                                };
                            },
			});
			vm.c.test = 1234;
			vm.hobby[0].ttt = 123;
			vm.hobby.push(4444);
			vm.hobby[3] = 66666;
			console.log("vm==", vm.hobby);

			//模板引擎
			//采用虚拟DOM
			//核心就是将模板变成Js语法生成虚拟DOM
			//就是将template里面的东西解析为一个render函数
		</script>
	</body>
</html>

初始化状态

vue 有多个配置项,比如:data,props,watch,computed 等配置,在初始化 vue 项目的时候,需要对这些配置项进行初始化,还有就是 vue 本身的方法和属性初始化,所以也是初始化 vue 的状态,

function Vue() {
	this._init(options);
}
initMixin(Vue);
//拓展vue原型方法
function initMixin(Vue) {
	//_init方法获取配置
	Vue.prototype._init = function (options) {
		//vue默认以$开头的为自己的属性
		const vm = this;
		vm.$options = options;
		//初始化状态
		initState(vm);
		if (options.el) {
			vm.$mount(options.el);
		}
	};
}
//初始化配置
function initState(vm) {
	const ops = vm.$options;
	//初始化data配置,并将数据设置成响应式数据
	if (ops.data) {
		initData(vm);
	}
}

初始化 data 响应式数据

初始化 vue 的 data 的数据时,因为 data 可能为对象或者函数,所以需要先获取真正的数据,data 中的对象(object)和基础类型(number,string 等)使用(Object.defineProperty)数据进行劫持,然后将数据绑定在 vm 的实例属性_data 上,即 vm._data 等于配置项的 data,至此 data 配置项的数据和 vm 实例实现绑定。
observe 函数主要用于对数据进行劫持之前会先判断 vue 数据是否已经为响应式,然后再使用 Observe 来设置数据,如果是数组则采用重写数组的方法来将数组劫持,如果是对象则采用 Object.defineProperty 对数据进行劫持。

function initData(vm) {
	let data = vm.$options.data;
	data = typeof data === "function" ? data.call(this) : data; //data可能为函数或者对象
	vm._data = data; //在vm上绑定data,用于绑定data配置的数据
	observe(data); //对数据进行劫持
	for (let key in data) {
		proxy(vm, "_data", key); //将数据代理到vm实例上,使用this来访问变量如this.name,并且只代理data数据的第一层
	}
}
//vue2的响应式原理,对数据的劫持
function observe(data) {
	if (typeof data !== "object" || data === null) {
		return; //data不是对象则不劫持
	}
	if (data.__ob__ instanceof Observe) {
		return data.__ob__;
	}
	//如果一个对象被劫持了,那就不需要再被劫持了(要判断一个对象是否被劫持过,可以增添一个实例,用实例来判断是否被劫持)
	return new Observe(data);
}
Observe 观察者

观察者主要用于对数据的劫持,首先在数据初始化的时候会给 data 绑定一个 ob 的属性,该属性指向 vm。有两个作用:1 是可以判断数据是否已经被劫持,2 是对数组的方法进行劫持的时候,数组的元素可能为对象,因此也需要回调观察者的方法进行递归观察

注意:数组本身的引用已经被劫持了,所以调用数组的时候会触发 setter,但是对数组内部的元素却没有劫持,因为元素的调用并不会触发 setter 或者 getter。因此数组元素变化是采用重写数组的方法实现的

//观察对象是否被劫持
class Observe {
	constructor(data) {
		//挂载this
		Object.defineProperty(data, "__ob__", {
			value: this,
			enumerable: false, //不可枚举,不可遍历
		});
		//判断数据是否是数组类型,然后再使用数组方式设置成响应式数据
		if (Array.isArray(data)) {
			data.__proto__ = reDefineArray.call(data, ...args);
			console.log("data.__proto__", data.__proto__);
			this.observeArray(data);
		} else {
			this.walk(data);
		}
	}
	walk(data) {
		//循环对象重新定义对象,因此也是vue2性能差的原因之一
		Object.keys(data).forEach((key) => {});
	}
	//监测数组的变化
	observeArray(data) {
		data.forEach((item) => observe(item));
	}
}
//对对象数据进行劫持
function defineReactive(target, key, value) {
	observe(value); //这里会递归调用
	Object.defineProperty(target, key, {
		get() {
			//取值的时候
			return value;
		},
		set(newValue) {
			if (newValue === value) return;
			value = newValue;
		},
	});
}

//重写数组的七个方法来对数组进行劫持
function reDefineArray() {
	let prototype = Array.prototype;
	let newArrayProto = Object.create(prototype); //复制原型对象
	let methods = ["push", "pop", "shift", "unshift", "sort", "splice"];
	methods.forEach((item) => {
		newArrayProto[item] = function (...args) {
			const result = prototype[item].call(this, ...args); //内部调用原来的方法,函数的劫持,切片原理
			let inserted;
			let ob = this.__ob__; //这里的this为调用方法的实例
			switch (item) {
				case "push":
				case "unshift":
					inserted = args;
					break;
				case "splice":
					inserted = args.slice(2);
				default:
					break;
			}
			if (inserted) {
				ob.observeArray(inserted);
			}
			return result;
		};
	});
	return newArrayProto;
}
proxy 代理函数

proxy 代理方法:代理_data/data 的数据,让 vm 可以直接访问即 this 来访问,如 this.a,并且只访问 data 对象的第一层属性

//对data上的数据依次进行代理
function proxy(vm, target, key) {
	Object.defineProperty(vm, key, {
		get() {
			return vm[target][key];
		},
		set(newValue) {
			vm[target][key] = newValue;
		},
	});
}

原文链接:https://juejin.cn/post/7316374543861121058 作者:用户610624794567

(0)
上一篇 2023年12月26日 下午4:44
下一篇 2023年12月26日 下午4:54

相关推荐

发表回复

登录后才能评论