Vue里面如何追踪变化
当你把一个普通的JavaScript对象传入Vue实例作为data选项,Vue将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter.
这些getter/setter.对用户来说是不可见的,但是在内部他们让Vue能够追踪依赖,在属性被访问和修改时通知变更。
每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把”接触“过的数据属性记录为依赖。之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染。
做性能优化
响应原理图
demo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue.2.5.1.生命周期</title>
</head>
<body>
<div id="app"></div>
<script src="vue.2.5.1.生命周期.js"></script>
<script>
// count 自定义策略处理 parentVal == Vue.options.count = undefined, childVal === 1
// Vue.config.optionMergeStrategies.count = function (parentVal, childVal, vm) {
// return childVal >= 10 ? childVal : 10
// }
// Vue 构造函数
var vm = new Vue({
el: "#app",
props: [
'root'
],
data: { // 对象 !== function mergedInstanceDataFn
root: "max" // 不代理 $ _ 开头的key,为了避免自身属性的冲突
},
// methods: {
// root: function () { }
// },
});
// 组件的这些选项,组件时可复用的Vue的实例data,computed,watch,methods以及生命周期钩子等,仅有的例外是el这样根实例特有的选项
//
console.log(vm.$options)
// 响应式系统的搭建
// Vue.extend({
// data: { // data的选项必须是函数 创建组件
// }
// })
</script>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue-v2.6.14</title>
</head>
<body>
<div id="app">
<!-- <componen-a></componen-a> -->
</div>
<script src="vue-v2.6.14.js"></script>
<script>
// 组件选项的对象——注册局部组件的方式
var ComponentA = {
name: "ComponentA",
template: "<div>123</div>"
}
// 属性优先级 data > methods props > data > methods
// 他们三个都会成为vm的代理属性
var vm = new Vue({
name: 'App',
el: "#app",
props: ['root'],
data: { // 对象 !== function mergedInstanceDataFn
_root: "JN"
},
// methods: {
// root: function () { }
// }
})
console.log(vm._root)// 代理形式访问 undefined
console.log(vm._data._root)// 数据对象上的 _root JN
// console.log(vm.$options)
// json 对象 组件选项对象 Vue.extend() 创建一个子类 实例化子类(createComponentInstanceForVnode) => 组件
// createComponentInstanceForVnode 实例化组件
// 组件系统
// 数据响应系统主要解决的问题:
// 1.数据观察
// 2.依赖收集
// 3.触发更新
// 4.优化
</script>
</body>
</html>
Vue.生命周期+data处理.js
// 整体结构是立即执行函数。
// 带两个参数,一个global用作全局变量存放,一个factory工厂函数制造一些初始化方法。
// typeof判断变量类型,以此校验执行环境
// 需要理解逻辑运算符之间的优先级,
// 其中用到了几个不理解的变量,属性和方法,分别是 module、module.exports、define、define.amd
(function (global, factory) {// global = window, factory
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Vue = factory());
// 暴露出factory工厂函数
}(this, function () {
// ASSET_TYPES 常量复用
// Vue.options.components
// Vue.component.. Vue.directive..
var ASSET_TYPES = [
'component',
'directive',
'filter'
];
// 生命周期钩子常量组
var LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
];
var noop = function () { }
// Vue 全局配置对象
// 最终config会暴露为Vue.config
var config = {
/**
* 选项合并策略(用于core/util/options)
*/
//$flow-disable-line 禁用行
optionMergeStrategies: Object.create(null),
}
/**
* 检查对象是否具有该属性
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key)
}
// 类型检测 对象Object
function isPlainObject(obj) {
return toString.call(obj) === '[object Object]'
}
/**
* 检查字符串是否以$or开头_
*/
function isReserved(str) {
var c = (str + '').charCodeAt(0); // 获取Unicode编码 0~65535
return c === 0x24 || c === 0x5F // 十六进制的Unicode 编码 $=== 0x24 _=== 0x5F
}
/** 自定义策略
* 选项覆盖策略是处理
* 如何合并父选项值和子选项
* 将值转换为最终值。
*/
// 自定义处理用在了很多地方,如el,props,watch....
var strats = config.optionMergeStrategies;
// 自定义策略处理
strats.data = function (parentVal, childVal, vm) {
// 组件的基本原理
// 聚焦到vm,判别是根实例,还是组件
if (!vm) { // 组件,不是根实例
if (childVal && typeof childVal !== "function") {
console.error("data选项应该为函数 返回组件中每个实例的值");
return parentVal
}
// 处理子组件data的选项
return mergeDataOrFn(parentVal, childVal)
}
// 处理根实例data的选项
return mergeDataOrFn(parentVal, childVal, vm)
};
// 处理根data的选项
function mergeDataOrFn(
parentVal,
childVal,
vm
) {
if (!vm) { // 子组件data选项
// 在Vue.extend合并中,这两个函数都应该是函数
// if (!childVal) {
// return parentVal
// }
// if (!parentVal) {
// return childVal
// }
//当parentVal和childVal都存在时,
//我们需要返回一个函数,该函数返回
//两个函数的合并结果。。。没必要
//此处检查parentVal是否为函数,因为
//它必须是传递先前合并的函数。
// return function mergedDataFn() {
// return mergeData(
// typeof childVal === 'function' ? childVal.call(this, this) : childVal,
// typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
// )
// }
} else { // 根实例data选项
return function mergedInstanceDataFn() {
// 实例合并
return typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal;
}
}
}
// 所有钩子函数的自定义策略 parentVal === undefined childVal === function(){}
function mergeHook(parentVal, childVal) {
return childVal
? parentVal // 数组
? parentVal.concat(childVal) // 合并parentVal、childVal
: Array.isArray(childVal) // 是不是数组
? childVal
: [childVal] // [function(){}]
: parentVal;
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook;
})
/**
* "所有"选项默认策略
*/
var defaultStrat = function (parentVal, childVal) {
return childVal === undefined
? parentVal
: childVal
};
function mergeOptions(parent, child, vm) {
/* 选项规范检测 Components Props Inject Directives */
var options = {};
var key;
for (key in parent) {
//parent-> 'components', 'directives', 'filters'
mergeField(key);
}
for (key in child) {
if (!hasOwn(parent, key)) { // 父组件没有这个key,才会扩展key
mergeField(key);
}
}
// 选项的策略处理;要考虑el,data,生命周期的钩子函数...
// 自定义策略挂载在(strats对象) 默认策略
function mergeField(key) { // 组件也会调用,组件包含vue的除根实例特有的el等外的实例
// console.log(key);
// 属性值 || 默认策略
var strat = strats[key] || defaultStrat;
// 给options扩展key属性
options[key] = strat(parent[key], child[key], vm, key);
}
return options;
}
// 执行钩子函数方法
function callHook(vm, hook) {
var handlers = vm.$options[hook];
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
handlers[i].call(vm)
}
}
}
var sharedPropertyDefinition = {
enumerable: true, // 可枚举的
configurable: true, // 可配置的
get: noop,
set: noop
};
// 代理数据
// target === vm, sourceKey === "_data", key === key 属性名称
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key] // this === target === vm;vm._data.root
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val; // vm._data.root = val
};
// vm._data 数据对象
// vm root 监听 vm.root === vm._data.root; sharedPropertyDefinition 加的钩子
Object.defineProperty(target, key, sharedPropertyDefinition);
}
// 初始化状态值
function initState(vm) {
var opts = vm.$options;
if (opts.data) { // data == mergedInstanceDataFn
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
}
// 初始化data
function initData(vm) {
// 校验数据对象data是否是一个纯对象
var data = vm.$options.data;// data = mergedInstanceDataFn
// 因为beforeCreate钩子可以修改data,所以要再判断一次data当前值是否是函数
data = vm._data = typeof data === 'function'
? data(vm, vm)
: data || {};
if (!isPlainObject(data)) {
data = {};
console.error(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
// 校验data是否与props、methods冲突了【key】
// 实例上的代理数据
var keys = Object.keys(data); // 所有key的属性
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i];
{
if (methods && hasOwn(methods, key)) {
console.error(
// methods 对象上的 key 属性,已经被定义为 data 数据对象属性
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) {
console.error(
// data的数据属性 key 因为成为props 的prop;prop是该属性的默认值。
"The data property \"" + key + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(key)) {
// 数据代理的时候是否有不合理的属性
// vm._data === 以获取数据对象的引用
proxy(vm, "_data", key);
}
}
// 观察数据,开启响应式系统之路
// observe(data, true /* asRootData */);
}
function initMixin(Vue) {
Vue.prototype._init = function (options) {
var vm = this;
// a uid
// 选项合并,给$options插入方法,参数
vm.$options = mergeOptions(
// 返回vue选项的引用
Vue.options, // Vue.options
options || {}, // options
vm // Vue
);
// 执行钩子函数beforeCreate
callHook(vm, 'beforeCreate') // 这里可以修改data
initState(vm); // 数据初始化 data
};
}
// config ,Vue全局配置对象
function initGlobalAPI(Vue) {
var configDef = {};
configDef.get = function () { return config; };
{
configDef.set = function (newVal) {
console.error('不要尝试修改Vue.config的引用',
'Do not replace the Vue.config object, set individual fields instead.'
);
};
}
// 让Vue可访问config
// 不直接写Vue.config而是监听,就是防止Vue.config引用被篡改
Object.defineProperty(Vue, 'config', configDef);// 监听你对Vue.config属性的访问,暴漏出自定义策略接口
}
function initExtend(Vue) {
/**
*每个实例构造函数(包括Vue)都有一个唯一的
*cid。这使我们能够创建包装的“子对象”
*“构造函数”用于原型继承并缓存它们。
*/
// 用于原型继承 缓存构造函数
Vue.cid = 0;
var cid = 1;
/**
* 类继承
*/
Vue.extend = function (extendOptions) {
extendOptions = extendOptions || {};
var Super = this; // Super === Vue
var SuperId = Super.cid;
// 缓存检测 cachedCtors
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
// 缓存处理cachedCtors[0] = 子类的引用
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
var name = extendOptions.name || Super.options.name;
if (name) {
// validateComponentName(name); // 组件名称规范检查
}
// 子类 构造函数
var Sub = function VueComponent(options) {
this._init(options);
};
// 把Vue的原型引用赋给Sub
// {}.__proto__ = Super.prototype = Vue.prototype
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
// 组件在初始化 mergeOptions 选项的合并 => 规范的检测 => 策略的处理
Sub.options = mergeOptions(
Super.options, // Vue.options
extendOptions // 组件的选项对象
);
Sub['super'] = Super;
//对于props和computed属性,我们在
//在扩展原型上扩展时的Vue实例。这
//避免为创建的每个实例调用Object.defineProperty。
if (Sub.options.props) {
initProps$1(Sub);
}
if (Sub.options.computed) {
initComputed$1(Sub);
}
// 允许进一步扩展/混合/插件使用 extension/mixin/plugin
// Sub.extend = Super.extend;
// Sub.mixin = Super.mixin;
// Sub.use = Super.use;
//创建资产寄存器,以便扩展类
//也可以拥有他们的私人资产。
// Super == Vue Vue.component【注册全局组件的一种方法】 不等于 Vue.options.components【挂载内置抽象组件】
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type];
});
//启用递归自查找
// if (name) {
// Sub.options.components[name] = Sub;
// }
//在扩展时保留对超级选项的引用。
//稍后在实例化时,我们可以检查Super的选项是否有
//已更新。
// Sub.superOptions = Super.options;
// Sub.extendOptions = extendOptions;
// Sub.sealedOptions = extend({}, Sub.options);
// 缓存构造函数
cachedCtors[SuperId] = Sub;
return Sub
};
}
// 拿到空对象的引用
Vue.options = Object.create(null);
// 添加内置组件'components',指令'directives',过滤器'filters',他们都加一个空对象的引用
ASSET_TYPES.forEach(function (type) {
// ASSET_TYPES 常量复用
Vue.options[type + 's'] = Object.create(null);// Vue.options.components
});
function Vue(options) {
if (!(this instanceof Vue)) { // 得new一个Vue,不然就报错
console.error('Vue is a constructor and should be called with the `new` keyword');
}
// 初始化
this._init(options);
}
initMixin(Vue);
initGlobalAPI(Vue);
initExtend(Vue)
return Vue;
}));
watch.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Watch——vue-v2.6.14</title>
</head>
<body>
<div id="app">
</div>
<!-- <script src="vue-v2.6.14.js"></script> -->
<script>
// var vm = new Vue({
// el: "#app",
// data: {
// age: 30,
// },
// })
// vm.$watch("age", function () {
// console.log("age被篡改了")
// })
// vm.age = 31
// data 数据对象
// $watch 方法
// 数据观测
// data.list.a data.list.b 数组数据观测 ,observe【原型观测】
var data = {
root: "max",
age: 30,
nb: 'min',
arr: [1, 2, 3, 4],
list: {
a: 1,
b: 2
}
}
// 数据观察 依赖收集
function observe(data) {
// for中使用var 会变量提升,导致var的值都是最后赋的值
for (let key in data) {
let deps = [];// 依赖收集存储的地方
let val = data[key];// 原有值,
if (toString.call(val) === "[object Object]") {
observe(val)
}
Object.defineProperty(data, key, {
get: function () {
deps.push(target);
console.log(key)
return val;
},
set: function (newValue) {// 新值
if (newValue === val) { return }
val = newValue;
deps.forEach(function (dep) {
dep();
})
}
})
}
}
observe(data)
console.log(data)
var target = null;
function $watch(str, fn) {
target = fn;
var arr, obj = data; // 不改变数据源本身
if (typeof str === "function") {
str();
return;
}
if (/\./.test(str)) { // 正则判断str里有没有.
arr = str.split(".");
arr.forEach(function (key) { // list a
obj = obj[key];// 1: data.list = {} 2: list.a = 1;
})
return;
}
data[str]
// console.log(data[str]);
}
// 依赖
// $watch("list.a", function () {
// console.log("list.a 发生了篡改")
// });
// 触发更新
// 函数 render 渲染函数
function render() {
with (data) { // 作用域绑定 data.root data.age (get钩子函数)
console.log("姓名" + root + "年龄" + age)//重复依赖收集 数组做处理
}
}
$watch(render, render);
</script>
</body>
</html>
献上一张AI生成图~