vue源码分析(一)

vue 源码解析(一)

vue在单页面文件中使用

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

html 复制代码
<!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 的状态,

js 复制代码
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 对数据进行劫持。

js 复制代码
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。因此数组元素变化是采用重写数组的方法实现的

js 复制代码
//观察对象是否被劫持
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 对象的第一层属性

js 复制代码
//对data上的数据依次进行代理
function proxy(vm, target, key) {
	Object.defineProperty(vm, key, {
		get() {
			return vm[target][key];
		},
		set(newValue) {
			vm[target][key] = newValue;
		},
	});
}
相关推荐
持续前行33 分钟前
vscode 中找settings.json 配置
前端·javascript·vue.js
JosieBook41 分钟前
【Vue】11 Vue技术——Vue 中的事件处理详解
前端·javascript·vue.js
安逸点44 分钟前
Vue项目中使用xlsx库解析Excel文件
vue.js
一只小阿乐1 小时前
vue 改变查询参数的值
前端·javascript·vue.js·路由·router·网文·未花中文网
小酒星小杜2 小时前
在AI时代下,技术人应该学会构建自己的反Demo地狱系统
前端·vue.js·ai编程
Code知行合壹2 小时前
Pinia入门
vue.js
今天也要晒太阳4732 小时前
element表单和vxe表单联动校验的实现
vue.js
依赖_赖3 小时前
前端实现token无感刷新
前端·javascript·vue.js
hhcccchh4 小时前
学习vue第十三天 Vue3组件深入指南:组件的艺术与科学
javascript·vue.js·学习
zhengxianyi5154 小时前
ruoyi-vue-pro本地环境搭建(超级详细,带异常处理)
前端·vue.js·前后端分离·ruoyi-vue-pro