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;
		},
	});
}
相关推荐
leoZ23110 小时前
Claude 驱动的全栈开发:Spring Boot + Vue 的 BS 架构实践
vue.js·spring boot·架构
荒诞英雄14 小时前
从 17MB Vendor 大包到按需加载:Vite 多入口 Vue 组件库首屏优化实践
vue.js·vite
jsonbro14 小时前
手把手带你实现发布订阅模式
前端·vue.js·设计模式
Cobyte16 小时前
23.Vue Vapor 组件 attrs 的实现
前端·javascript·vue.js
斯特凡今天也很帅17 小时前
xml页面可以打开,不报错,但是显示数据的地方也不显示,我是怎么解决的
xml·vue.js
柯克七七18 小时前
给老项目加了 TypeScript,本来只想自己爽,结果全公司代码审查标准被我抬高了
前端·vue.js·typescript
自然 醒20 小时前
前端如何实现在线预览office常见文件功能?
前端·vue.js
肉肉不吃 肉21 小时前
前端调试跨域如何解决
前端·vue.js
卓怡学长21 小时前
w268基于springboot + vue 物流系统
java·数据库·vue.js·spring boot·spring·intellij-idea
2601_962851741 天前
计算机毕业设计之基于SSM的网络安全课程资源分享平台
vue.js·算法·安全·web安全·yolo·目标检测·课程设计