Vue源码核心模块解析

基于 vue2.6.11

GitHub - vuejs/vue at v2.6.11

  • 代码目录
bash 复制代码
├─dist                   # 项目构建后的文件
├─scripts                # 与项目构建相关的脚本和配置文件
├─flow                   # flow的类型声明文件
├─src                    # 项目源代码
│    ├─complier          # 与模板编译相关的代码
│    ├─core              # 通用的、与运行平台无关的运行时代码
│    │  ├─observe        # 实现变化侦测的代码
│    │  ├─vdom           # 实现virtual dom的代码
│    │  ├─instance       # Vue.js实例的构造函数和原型方法
│    │  ├─global-api     # 全局api的代码
│    │  └─components     # 内置组件的代码
│    ├─server            # 与服务端渲染相关的代码
│    ├─platforms         # 特定运行平台的代码,如weex
│    ├─sfc               # 单文件组件的解析代码
│    └─shared            # 项目公用的工具代码
└─test                   # 项目测试代码
  • Vue 的整个项目包含了类型检测相关、单元测试相关、与平台无关的核心代码以及跨平台运行的相关代码。
  • 这里只是学习 Vue.js 的设计思想以及代码实现的相关逻辑,所以暂不去关心类型检测、单元测试以及特定平台运行等相关逻辑实现,仅关注它的核心代码,即 src/core 和 src/complier 这两个目录下的代码,并且接下来后续的学习也都是只在这两个目录的范围之内

学习路线

  1. 响应式实现:学习 Vue 中如何实现数据的响应式系统,从而达到数据驱动视图。
  2. 虚拟 DOM:学习什么是虚拟 DOM,以及 Vue 中的 DOM-Diff 原理
  3. 模板编译:学习 Vue 内部是怎么把 template 模板编译成虚拟 DOM,从而渲染出真实 DOM
  4. 实例方法:学习 Vue 中所有实例方法(即所有以$开头的方法)的实现原理
  5. 全局 API:学习 Vue 中所有全局 API 的实现原理
  6. 生命周期:学习 Vue 中组件的生命周期实现原理
  7. 指令:学习 Vue 中所有指令的实现原理
  8. 过滤器:学习 Vue 中所有过滤器的实现原理
  9. 内置组件:学习 Vue 中内置组件的实现原理

Vue.js 的响应式实现

众所周知,Vue 最大的特点之一就是数据驱动视图,那么什么是数据驱动视图呢?在这里,我们可以把数据理解为状态,而视图就是用户可直观看到页面。页面不可能是一成不变的,它应该是动态变化的,而它的变化也不应该是无迹可寻的,它或者是由用户操作引起的,亦或者是由后端数据变化引起的,不管它是因为什么引起的,我们统称为它的状态变了,它由前一个状态变到了后一个状态,页面也就应该随之而变化,所以我们就可以得到如下一个公式:

UI = render(state) 上述公式中:状态 state 是输入,页面 UI 输出,状态输入一旦变化了,页面输出也随之而变化。我们把这种特性称之为数据驱动视图。

Object 的响应式实现

  1. Vue.js 2.x
    • Object.defineProperty 定义 getter 和 setter
    • 无法检测到对象属性的添加或删除
  2. Vue.js 3.x
    • Proxy
    • Reflect
js 复制代码
let car = {};
let val = 3000;
Object.defineProperty(car, "price", {
	configurable: true,
	enumerable: true,
	get() {
		console.log("price 属性被访问了");
		return val;
	},
	set(newValue) {
		console.log("price 属性被修改了");
		val = newValue;
	},
});
使对象变得可观测

首先定义一个observer类,将一个普通的object对象转换为可观测的object。并且会给value新增一个__ob__属性,相当于给value打了一个标记,表示这个value已经转化为响应式数据,避免重复操作 判断数据的类型,是否是数组,只有object的数据才调用walk把每一个属性转换成getter/setter的形式监听

js 复制代码
if (Array.isArray(value)) {
    if (hasProto) {
        protoAugment(value, arrayMethods);
    } else {
        copyAugment(value, arrayMethods, arrayKeys);
    }
    this.observeArray(value);
} else {
    this.walk(value);
}

// walk 方法,遍历对象的每一个属性,调用 defineReactive 方法
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

defineReactive 方法,将一个对象转换成响应式对象,核心就是使用Object.defineProperty方法,给对象的属性添加getter/setter,当访问属性时就会触发getter,修改属性时就会触发setter

js 复制代码
export function defineReactive(
	obj: Object,
	key: string,
	val: any,
	customSetter?: ?Function,
	shallow?: boolean
) {
	// 实例化一个依赖管理器,生成一个依赖管理数组 dep
	const dep = new Dep();

	// Object.getOwnPropertyDescriptor(obj, prop) // obj:叫查找其属性的对象;prop:要查找的属性名或symbol
	// MDN: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
	const property = Object.getOwnPropertyDescriptor(obj, key);
	if (property && property.configurable === false) {
		return;
	}

	// cater for pre-defined getter/setters
	const getter = property && property.get;
	const setter = property && property.set;
	if ((!getter || setter) && arguments.length === 2) {
		val = obj[key];
	}

	let childOb = !shallow && observe(val);
	Object.defineProperty(obj, key, {
		enumerable: true,
		configurable: true,
		get: function reactiveGetter() {
			const value = getter ? getter.call(obj) : val;
			if (Dep.target) {
				// 在 getter 中收集依赖
				dep.depend();
				if (childOb) {
					childOb.dep.depend();
					if (Array.isArray(value)) {
						dependArray(value);
					}
				}
			}
			return value;
		},
		set: function reactiveSetter(newVal) {
			const value = getter ? getter.call(obj) : val;
			/* eslint-disable no-self-compare */
			if (newVal === value || (newVal !== newVal && value !== value)) {
				return;
			}
			/* eslint-enable no-self-compare */
			if (process.env.NODE_ENV !== "production" && customSetter) {
				customSetter();
			}
			// #7981: for accessor properties without setter
			if (getter && !setter) return;
			if (setter) {
				setter.call(obj, newVal);
			} else {
				val = newVal;
			}
			childOb = !shallow && observe(newVal);
			// 在 setter 中通知依赖更新
			dep.notify();
		},
	});
}
依赖收集

我们可以监听到一个数据的变化,然后去更新视图,但是不可能数据变化就把全部视图更新。 因此需要知道,那些视图依赖了哪些数据,需要给每个数据建立一个依赖数组(一个数据可以被多处使用),谁依赖了(用到了)这个数据,就把谁放到依赖数组中,当这个数据变化,我们就去对应的依赖数组中,通知每个依赖,去更新视图。 所谓谁依赖了这个数据,就是谁用到了这个数据,比如{{name}}{{age}},当渲染模板的时候,就会把nameage放到依赖数组中,当nameage变化,就会通知依赖数组中的每个依赖,去更新视图。

getter中收集依赖,在setter中通知依赖更新

我们应该给每一个数据都建立一个依赖管理器,把这个数据依赖的视图放到这个依赖管理器中,当数据变化,就通知这个依赖管理器中的每个依赖去更新视图。

js 复制代码
/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
	static target: ?Watcher;
	id: number;
	subs: Array<Watcher>;

	constructor() {
		this.id = uid++;
		this.subs = []; // 存放依赖
	}

	addSub(sub: Watcher) {
		this.subs.push(sub);
	}

	removeSub(sub: Watcher) {
		remove(this.subs, sub);
	}

	depend() {
		if (Dep.target) {
			Dep.target.addDep(this);
		}
	}

	notify() {
		// stabilize the subscriber list first
		const subs = this.subs.slice();
		if (process.env.NODE_ENV !== "production" && !config.async) {
			// subs aren't sorted in scheduler if not running async
			// we need to sort them now to make sure they fire in correct
			// order
			subs.sort((a, b) => a.id - b.id);
		}
		for (let i = 0, l = subs.length; i < l; i++) {
			subs[i].update();
		}
	}
}

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null;
const targetStack = [];

export function pushTarget(target: ?Watcher) {
	targetStack.push(target);
	Dep.target = target;
}

export function popTarget() {
	targetStack.pop();
	Dep.target = targetStack[targetStack.length - 1];
}
依赖到底是谁

在 Vue 中实现了一个Watcher类,Watcher类的实例就是我们前边说的"谁"。谁用到了数据,谁就是依赖,我们就给谁创建一个Watcher实例。这个类就是用来做依赖收集的,当渲染模板的时候,就会创建一个Watcher实例,这个Watcher实例就是依赖,当数据变化,就会通知这个依赖去更新视图。

js 复制代码
export default class Watcher {
	vm: Component;
	expression: string;
	cb: Function;
	id: number;
	deep: boolean;
	user: boolean;
	lazy: boolean;
	sync: boolean;
	dirty: boolean;
	active: boolean;
	deps: Array<Dep>;
	newDeps: Array<Dep>;
	depIds: SimpleSet;
	newDepIds: SimpleSet;
	before: ?Function;
	getter: Function;
	value: any;

	constructor(
		vm: Component,
		expOrFn: string | Function,
		cb: Function,
		options?: ?Object,
		isRenderWatcher?: boolean
	) {
		this.vm = vm;
		if (isRenderWatcher) {
			vm._watcher = this;
		}
		vm._watchers.push(this);
		// options
		if (options) {
			this.deep = !!options.deep;
			this.user = !!options.user;
			this.lazy = !!options.lazy;
			this.sync = !!options.sync;
			this.before = options.before;
		} else {
			this.deep = this.user = this.lazy = this.sync = false;
		}
		this.cb = cb;
		this.id = ++uid; // uid for batching
		this.active = true;
		this.dirty = this.lazy; // for lazy watchers
		this.deps = [];
		this.newDeps = [];
		this.depIds = new Set();
		this.newDepIds = new Set();
		this.expression =
			process.env.NODE_ENV !== "production" ? expOrFn.toString() : "";
		// parse expression for getter
		if (typeof expOrFn === "function") {
			this.getter = expOrFn;
		} else {
			this.getter = parsePath(expOrFn);
			if (!this.getter) {
				this.getter = noop;
				process.env.NODE_ENV !== "production" &&
					warn(
						`Failed watching path: "${expOrFn}" ` +
							"Watcher only accepts simple dot-delimited paths. " +
							"For full control, use a function instead.",
						vm
					);
			}
		}
		this.value = this.lazy ? undefined : this.get();
	}

	/**
	 * Evaluate the getter, and re-collect dependencies.
	 */
	get() {
		pushTarget(this);
		let value;
		const vm = this.vm;
		try {
			value = this.getter.call(vm, vm);
		} catch (e) {
			if (this.user) {
				handleError(e, vm, `getter for watcher "${this.expression}"`);
			} else {
				throw e;
			}
		} finally {
			// "touch" every property so they are all tracked as
			// dependencies for deep watching
			if (this.deep) {
				traverse(value);
			}
			popTarget();
			this.cleanupDeps();
		}
		return value;
	}

	/**
	 * Add a dependency to this directive.
	 */
	addDep(dep: Dep) {
		const id = dep.id;
		if (!this.newDepIds.has(id)) {
			this.newDepIds.add(id);
			this.newDeps.push(dep);
			if (!this.depIds.has(id)) {
				dep.addSub(this);
			}
		}
	}

	/**
	 * Clean up for dependency collection.
	 */
	cleanupDeps() {
		let i = this.deps.length;
		while (i--) {
			const dep = this.deps[i];
			if (!this.newDepIds.has(dep.id)) {
				dep.removeSub(this);
			}
		}
		let tmp = this.depIds;
		this.depIds = this.newDepIds;
		this.newDepIds = tmp;
		this.newDepIds.clear();
		tmp = this.deps;
		this.deps = this.newDeps;
		this.newDeps = tmp;
		this.newDeps.length = 0;
	}

	/**
	 * Subscriber interface.
	 * Will be called when a dependency changes.
	 */
	update() {
		/* istanbul ignore else */
		if (this.lazy) {
			this.dirty = true;
		} else if (this.sync) {
			this.run();
		} else {
			queueWatcher(this);
		}
	}

	/**
	 * Scheduler job interface.
	 * Will be called by the scheduler.
	 */
	run() {
		if (this.active) {
			const value = this.get();
			if (
				value !== this.value ||
				// Deep watchers and watchers on Object/Arrays should fire even
				// when the value is the same, because the value may
				// have mutated.
				isObject(value) ||
				this.deep
			) {
				// set new value
				const oldValue = this.value;
				this.value = value;
				if (this.user) {
					try {
						this.cb.call(this.vm, value, oldValue);
					} catch (e) {
						handleError(
							e,
							this.vm,
							`callback for watcher "${this.expression}"`
						);
					}
				} else {
					this.cb.call(this.vm, value, oldValue);
				}
			}
		}
	}

	/**
	 * Evaluate the value of the watcher.
	 * This only gets called for lazy watchers.
	 */
	evaluate() {
		this.value = this.get();
		this.dirty = false;
	}

	/**
	 * Depend on all deps collected by this watcher.
	 */
	depend() {
		let i = this.deps.length;
		while (i--) {
			this.deps[i].depend();
		}
	}

	/**
	 * Remove self from all dependencies' subscriber list.
	 */
	teardown() {
		if (this.active) {
			// remove self from vm's watcher list
			// this is a somewhat expensive operation so we skip it
			// if the vm is being destroyed.
			if (!this.vm._isBeingDestroyed) {
				remove(this.vm._watchers, this);
			}
			let i = this.deps.length;
			while (i--) {
				this.deps[i].removeSub(this);
			}
			this.active = false;
		}
	}
}

通过全局 API:Vue.useVue.delete触发对象的新增和删除

总结流程

  1. Object通过Observer转换成getter/setter的形式去追踪数据变化
  2. 当外界通过Watcher读取数据时,会触发getter从而将Watcher添加到依赖中
  3. 当数据发生变化,会触发setter,从而向Dep中的依赖发送通知
  4. Watcher收到通知,会向外界发送通知,可能更新视图,也有可能触发某个回调函数

Array 的响应式实现

数组响应式实现

依然还是要在获取数据的时候,收集依赖。在数据变化时,通知依赖更新

在哪里收集依赖
js 复制代码
data(){
    return{
        arr:[1,2,3,4],
        user:{},
                isLogin:true
    }
}
  • Array型数据还是在getter中收集
js 复制代码
let arr = [1, 2, 3, 4];
arr.push(5);
Array.prototype.newPush = function (val) {
	// 这里不能使用箭头函数简写,否则会找不到`this`
	console.info("arr push");
	this.push(val);
};
arr.newPush(6);
创建拦截器
  • 对数组的操作进行拦截封装。创建拦截器:arrayMethods
js 复制代码
/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from "../util/index";

const arrayProto = Array.prototype;
/* 创建一个对象,作为拦截器 */
export const arrayMethods = Object.create(arrayProto);

const methodsToPatch = [
	"push",
	"pop",
	"shift",
	"unshift",
	"splice",
	"sort",
	"reverse",
];

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
	// cache original method
	const original = arrayProto[method];
	def(arrayMethods, method, function mutator(...args) {
		const result = original.apply(this, args);
		const ob = this.__ob__;
		let inserted;
		switch (method) {
			case "push":
			case "unshift":
				inserted = args;
				break;
			case "splice":
				inserted = args.slice(2);
				break;
		}
		if (inserted) ob.observeArray(inserted);
		// notify change
		ob.dep.notify();
		return result;
	});
});
  • 使用拦截器:arrayMethods
js 复制代码
constructor (value: any) {
  this.value = value
  this.dep = new Dep()
  this.vmCount = 0
  def(value, '__ob__', this)
  if (Array.isArray(value)) {
    if (hasProto) {
			// value.__proto__ = arrayMethods
      protoAugment(value, arrayMethods)
    } else {
      copyAugment(value, arrayMethods, arrayKeys)
    }
    this.observeArray(value)
  } else {
    this.walk(value)
  }
}
如何收集依赖
js 复制代码
observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
    observe(items[i])
  }
}
如何访问依赖
js 复制代码
methodsToPatch.forEach(function (method) {
	// cache original method
	const original = arrayProto[method];
	def(arrayMethods, method, function mutator(...args) {
		const result = original.apply(this, args);
		const ob = this.__ob__;
		let inserted;
		switch (method) {
			case "push":
			case "unshift":
				inserted = args;
				break;
			case "splice":
				inserted = args.slice(2);
				break;
		}
		if (inserted) ob.observeArray(inserted);
		// notify change
		ob.dep.notify();
		return result;
	});
});

深度监听

Vue中,不管是 Object 还是 Array 实现的响应式数据,都是深度监听。也就是不但要监听自身数据变化,还要监听数据中所有的子数据变化

js 复制代码
let arr = [
	{
		name: "Fa-ce",
		age: 18,
		info: {
			address: "xxx",
		},
	},
];

数组元素的新增:我们向数组中新增了一个元素,我们需要把新增的元素转换为响应式数据

不足

日常开发,可能这样写

js 复制代码
let arr = [1, 2, 3];
arr[0] = 4; // 通过下标修改数组元素,不会触发视图更新
arr.length = 0; // 通过修改数组长度,不会触发视图更新

这两种方法都无法监听到,也不会触发响应式更新. Vue2 提供了两个方法来弥补不足之处:

  • Vue.set:修改数组中的元素,会触发响应式更新。this.$set(this.list, index, newValue)
  • Vue.delete: 删除数组中的元素,会触发响应式更新。this.$delete(this.list, index)

虚拟 DOM

虚拟 DOM 是什么

虚拟 DOM 是一个 JavaScript 对象,通过对象的方式来描述一个 DOM 节点

js 复制代码
<div class="container" id="app">
	<a />
</div>;

// 虚拟 DOM
const VNode = {
	tag: "div" /* 元素标签 */,
	attrs: {
		class: "a",
		id: "b",
	} /* 属性 */,
	children: [
		/* 子元素 */
		{
			tag: "h1",
			children: "hello world",
		},
		{
			tag: "p",
			children: "hello vue",
		},
	],
};

我们把组成一个 DOM 节点必要东西通过一个JS对象表示出来,那么这个JS对象就可以用来描述这个DOM节点。 我们把这个JS对象就称为真实DOM节点的虚拟DOM节点

Vue 中的虚拟 DOM

VNode 类
js 复制代码
export default class VNode {
	tag: string | void;
	data: VNodeData | void;
	children: ?Array<VNode>;
	text: string | void;
	elm: Node | void;
	ns: string | void;
	context: Component | void; // rendered in this component's scope
	key: string | number | void;
	componentOptions: VNodeComponentOptions | void;
	componentInstance: Component | void; // component instance
	parent: VNode | void; // component placeholder node

	// strictly internal
	raw: boolean; // contains raw HTML? (server only)
	isStatic: boolean; // hoisted static node
	isRootInsert: boolean; // necessary for enter transition check
	isComment: boolean; // empty comment placeholder?
	isCloned: boolean; // is a cloned node?
	isOnce: boolean; // is a v-once node?
	asyncFactory: Function | void; // async component factory function
	asyncMeta: Object | void;
	isAsyncPlaceholder: boolean;
	ssrContext: Object | void;
	fnContext: Component | void; // real context vm for functional nodes
	fnOptions: ?ComponentOptions; // for SSR caching
	devtoolsMeta: ?Object; // used to store functional render context for devtools
	fnScopeId: ?string; // functional scope id support

	constructor(
		tag?: string,
		data?: VNodeData,
		children?: ?Array<VNode>,
		text?: string,
		elm?: Node,
		context?: Component,
		componentOptions?: VNodeComponentOptions,
		asyncFactory?: Function
	) {
		this.tag = tag;
		this.data = data;
		this.children = children;
		this.text = text; /* 节点的文本 */
		this.elm = elm; /* 当前虚拟节点对应的真实DOM节点 */
		this.ns = undefined; /* 命名空间 */
		this.context = context; /* 当前虚拟节点对应的Vue实例 */
		this.fnContext = undefined; /* 函数式组件对应的Vue实例 */
		this.fnOptions = undefined;
		this.fnScopeId = undefined;
		this.key =
			data &&
			data.key; /* 节点的key属性,被当作节点的标识,diff算法性能优化会用到 */
		this.componentOptions = componentOptions;
		this.componentInstance = undefined;
		this.parent = undefined;
		this.raw = false; /* 是否是原生HTML字符串,或者普通文本,innerHTML的时候为true */
		this.isStatic = false;
		this.isRootInsert = true;
		this.isComment = false; /* 是否注释节点 */
		this.isCloned = false;
		this.isOnce = false;
		this.asyncFactory = asyncFactory;
		this.asyncMeta = undefined;
		this.isAsyncPlaceholder = false;
	}

	// DEPRECATED: alias for componentInstance for backwards compat.
	/* istanbul ignore next */
	get child(): Component | void {
		return this.componentInstance;
	}
}

export const createEmptyVNode = (text: string = "") => {
	const node = new VNode();
	node.text = text;
	node.isComment = true;
	return node;
};

export function createTextVNode(val: string | number) {
	return new VNode(undefined, undefined, undefined, String(val));
}

// optimized shallow clone
// used for static nodes and slot nodes because they may be reused across
// multiple renders, cloning them avoids errors when DOM manipulations rely
// on their elm reference.
export function cloneVNode(vnode: VNode): VNode {
	const cloned = new VNode(
		vnode.tag,
		vnode.data,
		// #7975
		// clone children array to avoid mutating original in case of cloning
		// a child.
		vnode.children && vnode.children.slice(),
		vnode.text,
		vnode.elm,
		vnode.context,
		vnode.componentOptions,
		vnode.asyncFactory
	);
	cloned.ns = vnode.ns;
	cloned.isStatic = vnode.isStatic;
	cloned.key = vnode.key;
	cloned.isComment = vnode.isComment;
	cloned.fnContext = vnode.fnContext;
	cloned.fnOptions = vnode.fnOptions;
	cloned.fnScopeId = vnode.fnScopeId;
	cloned.asyncMeta = vnode.asyncMeta;
	cloned.isCloned = true;
	return cloned;
}
  1. VNode 的类型
  • 注释节点: isComment: true,createEmptyVNode(text: string = '')
  • 文本节点: isStatic: true,createTextVNode(val: string | number)
  • 元素节点:
js 复制代码
<div class="container" id="app">
	<a />
</div>; // 虚拟 DOM
const VNode = {
	tag: "div" /* 元素标签 */,
	attrs: {
		class: "a",
		id: "b",
	} /* 属性 */,
	children: [
		{
			tag: "h1",
			children: "hello world",
		},
		{
			tag: "p",
			children: "hello vue",
		},
	],
};
  • 组件节点
    • 组件节点除了元素节点具有的属性,还有两个特定的属性
    • componentOptions:组件的选项,比如组件的名称,组件的 props 等
    • componentInstance:组件的实例,组件渲染完成后,组件的实例会保存在这个属性中
  • 函数式组件节点
    • fnContext:函数式组件的上下文
    • fnOptions:函数式组件的选项
  • 克隆节点:cloneVNode(vnode: VNode),把一个已经存在的节点进行复制一份,主要做模板编译优化时使用
  1. VNode 的作用
    • 我们在视图渲染之前,把写好的 template 模板先编译成VNode并缓存下来,等到数据发生变化需要重新渲染的时候,我们把数据发生变化后生成的VNode和之前缓存好的VNode进行对比,找出差异,然后有差异的 VNode 对应的真实 DOM 节点就是我们需要进行更新重新渲染的节点,最后根据有差异的 VNode 节点创建出真实的 DOM 节点插入到视图中,从而更新视图

Vue 中的 DOM-diff 算法

  • Patch

    • 指对旧的 VNode 进行修补,打补丁得到新的 VNode
    • 以新的 VNode 为基准,改造旧的 VNode 使其与新的 VNode 保持一致。这就是 patch 的过程
      • 创建节点:新的 VNode 中有而旧的 oldVNode 中没有,就在旧的 VNode 中创建
      • 删除节点:新的 VNode 中没有而旧的 oldVNode 中有,就从旧的 VNode 中删除
      • 更新节点:新的 VNode 中和旧的 oldVNode 中都有的节点,就以新的 VNode 为准,更新旧的 oldVNode
  • 创建节点

    • 创建节点只有三种类型:注释节点、文本节点、元素节点。Vue 创建节点的时候需要先判断在新的 VNode,会根据节点的类型,调用不同的创建方法插入到 DOM 中
    js 复制代码
    function createElm(vnode, parentElm, refElm) {
    	const tag = vnode.tag;
    	if (isDef(tag)) {
    		// 创建元素节点
    		vnode.elm = nodeOps.createElement(tag, vnode);
    		// 创建子节点
    		createChildren(vnode, children, insertedVnodeQueue);
    		// 插入 DOM
    		insert(parentElm, vnode.elm, refElm);
    	} else if (isTrue(vnode.isComment)) {
    		vnode.elm = nodeOps.createComment(vnode.text);
    		insert(parentElm, vnode.elm, refElm);
    	} else {
    		vnode.elm = nodeOps.createTextNode(vnode.text);
    		insert(parentElm, vnode.elm, refElm);
    	}
    }
  • 删除节点

    js 复制代码
    function removeNode(el) {
    	const parent = nodeOps.parentNode(el);
    	// element may have already been removed due to v-html / v-text
    	if (isDef(parent)) {
    		nodeOps.removeChild(parent, el);
    	}
    }

更新中ing......

相关推荐
海云前端18 分钟前
前端写简历有个很大的误区,就是夸张自己做过的东西。
前端
葡萄糖o_o27 分钟前
ResizeObserver的错误
前端·javascript·html
AntBlack28 分钟前
Python : AI 太牛了 ,撸了两个 Markdown 阅读器 ,谈谈使用感受
前端·人工智能·后端
MK-mm1 小时前
CSS盒子 flex弹性布局
前端·css·html
小小小小宇1 小时前
CSP的使用
前端
sunbyte1 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | AnimatedNavigation(动态导航)
前端·javascript·vue.js·tailwindcss
ifanatic1 小时前
[每周一更]-(第147期):使用 Go 语言实现 JSON Web Token (JWT)
前端·golang·json
烛阴1 小时前
深入浅出地理解Python元类【从入门到精通】
前端·python
米粒宝的爸爸1 小时前
uniapp中vue3 ,uview-plus使用!
前端·vue.js·uni-app
JustHappy2 小时前
啥是Hooks?为啥要用Hooks?Hooks该怎么用?像是Vue中的什么?React Hooks的使用姿势(下)
前端·javascript·react.js