Decided to follow huang Yi teacher vue2 source course to learn the source of vue2, learning process, as far as possible output their income, improve learning efficiency, the level is limited, wrong words please correct ~
Clone the vue source code locally and switch to branch 2.6.
Introduction
We’ve seen how vUE performs initial data rendering and componentization, but how the DOM is updated when the data changes.
To see a demo
Data changes:
<div id="app">hello</div>
Copy the code
Suppose you want to change Hello to Hello World when you click on it.
// 1. Modify data
const text = "hello world";
// 2. Get the DOM and listen for events
document.querySelector("#app").onclick = () = > {
// 3. Manually re-render the DOM
app.innerHTML = text;
};
Copy the code
If multiple changes were required, you would have to manually manipulate the DOM and re-render it each time.
Vue, on the other hand, removes the manual manipulation of the DOM and automatically manipulates the DOM based on data changes.
<div id="app" @click="changeMsg">{{ message }}</div>
<script src="/Users/zhm/mygit/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el: "#app".data: {
message: "Hello Vue!",},methods: {
changeMsg() {
// Just change the data, the rest is up to vue itself to update the DOM and re-render
this.message = "Hello World!"; ,}}});</script>
Copy the code
Responsive object
Object. DefineProperty you’ve probably heard about it all before.
General access to object properties, set object properties can use this, but it is more troublesome, so generally used in different scenarios.
/** Common way to create attributes: **/
var obj = { a: 1 };
/** defineProperty How to create, get, and set attributes: **/
var obj = {};
/ /! Note that you need to set another variable to store the value of the property.
let value = 1;
Object.defineProperty(obj, "a", {
get() {
console.log("get");
return value;
},
set(newValue) {
console.log("set"); value = newValue; }});/* Of course, the attributes and Settings are the same */
/ / to get
console.log(obj.a);
/ / set
obj.a = 2;
Copy the code
DefineProperty’s core is get and set. Because they’re functions, they can do a lot of things.
A hijack executes a get/set function every time a property is fetched /set, so it can do something else.
A reactive object is called a reactive object when one of its properties has a GET /set.
A simplified version of responsive Vue
First write a simple version of the response type Vue, roughly have an impression, the real source code processing scene more, understand the simple version, then look at the source code, not easy to get lost…
We mainly did the following things:
- with
vm._data
Point to theoptions.data
- will
vm._data
All of the above properties are proxied tovm
on vm._data.__ob__
Points to an Observer instance whose value is a response to data
const vm = new Vue({ data: { a: 1}});console.dir(vm);
function Vue(options) {
this.vm = this;
this.$options = options;
/* this._init() initState() starts */
initData(this);
/* this._init() initState() end */
}
function initData(vm) {
let data = vm.$options.data;
vm._data = data;
// Attach the attributes on data directly to the VM
Object.keys(data).forEach((key) = > {
/* proxy(vm, "_data", key) start */
Object.defineProperty(vm, key, {
enumerable: true.configurable: true.get() {
return vm._data[key];
},
set(newValue){ vm.data[key] = newValue; }});/* proxy(vm, "_data", key) end */
});
/* observe(data) Start */
data.__ob__ = data.__ob__ || new Observer(data);
/* observe(data) End */
}
function Observer(data) {
this.value = data;
// this.dep = new Dep();
data.__ob__ = this;
Object.keys(data).forEach((key) = > {
defineReactive(data, key);
});
}
// defineReactive Change obj.x to object.defineProperty (obj, 'x', {get(){}}
function defineReactive(data, key) {
// const dep = new Dep();
let value = data[key];
Object.defineProperty(data, key, {
enumerable: true.configurable: true.get: function reactiveGetter() {
// dep.depend();
return value;
},
set: function reactiveSetter(newValue) {
// dep.notify();value = newValue; }}); }Copy the code
initState
The Vue constructor starts with this._init(..). While vue.prototype. _init = function(){initState(this)}.
InitState is the key to making an instance of Vue a responsive object. This method performs various initialization operations on options, and this article focuses on handling data/props.
// src/core/instance/state.js
export function initState(vm: Component) {
vm._watchers = [];
const opts = vm.$options;
if (opts.props) initProps(vm, opts.props);
if (opts.methods) initMethods(vm, opts.methods);
if (opts.data) {
initData(vm);
} else {
observe((vm._data = {}), true /* asRootData */);
}
if (opts.computed) initComputed(vm, opts.computed);
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
Copy the code
The processing of props
Look at initProps, which is basically just
- traverse
options.props
- Each prop becomes responsive (SET/GET), and each prop is also synchronized to
vm._props.xx
- through
proxy
thevm._props.xxx
Access proxy tovm.xxx
上
// src/core/instance/state.js
function initProps(vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {};
const props = (vm._props = {});
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = (vm.$options._propKeys = []);
constisRoot = ! vm.$parent;// root instance props should be converted
if(! isRoot) { toggleObserving(false);
}
for (const key in propsOptions) {
keys.push(key);
const value = validateProp(key, propsOptions, propsData, vm);
defineReactive(props, key, value);
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if(! (keyin vm)) {
proxy(vm, `_props`, key);
}
}
toggleObserving(true);
}
Copy the code
The processing of the data
Initialization of data also does two things:
- traverse
data
Object, every one of themvm._data.xx
throughproxy
The agent tovm._data
- call
observe
To observe thedata
Change, and make it also reactive
// src/core/instance/state.js
function initData(vm: Component) {
let data = vm.$options.data;
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
if(! isPlainObject(data)) { data = {}; }// proxy data on instance
const keys = Object.keys(data);
const props = vm.$options.props;
const methods = vm.$options.methods;
let i = keys.length;
while (i--) {
const key = keys[i];
if(! isReserved(key)) { proxy(vm,`_data`, key); }}// observe data
observe(data, true /* asRootData */);
}
Copy the code
The proxy agent
One of the key operations to initialize data and props is to make them responsive and then propped to the VM.
The agent? It’s just an intermediary. It’s just a clue in and of itself that connects to the real source.
For example, if obj has a data property, now obj. A can also access a property.
In this case, obj.a is a mediation, and its real connection resource is obj.data.a.
const obj = { data: { a: 1}};Copy the code
Actually use object.defineProperty above
Object.defineProperty(obj, "a", {
get() {
// obj.data.a is the equivalent of storing variables
return obj.data.a;
},
set(newValue){ obj.data.a = newValue; }});/ / 1
console.log(obj.a);
Copy the code
Now look at the source code, for proxy implementation:
// src/core/instance/state.js
// const noop = function empty(){}
const sharedPropertyDefinition = {
enumerable: true.configurable: true.get: noop,
set: noop,
};
// Target = obj, sourceKey = data, key = a
X to vm.data.x
export function proxy(target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
Copy the code
Observe: Add an Observer instance
Observe is used to monitor data changes. Add an Observer to a non-VNode object. If an Observer has been added, return it.
// src/core/observer/index.js
export function observe(value: any, asRootData: ? boolean) :Observer | void {
if(! isObject(value) || valueinstanceof VNode) {
return;
}
let ob: Observer | void;
if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if( shouldObserve && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) && ! value._isVue ) { ob =new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob;
}
Copy the code
Observer class: Adds getters and setters for each property of an object
An Observer is a class that adds getters and setters to properties of an object for dependency collection and distribution of updates:
/** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. */
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor(value: any) {
this.value = value;
// Instantiate the Dep object
this.dep = new Dep();
this.vmCount = 0;
def(value, "__ob__".this);
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value); }}/** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */
walk(obj: Object) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); }}/** * Observe a list of Array items. */
observeArray(items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) { observe(items[i]); }}}Copy the code
The Observer constructor logic is simple:
- instantiation
Dep
Object, - By performing
def
The function adds an instance of itself to a data objectvalue
的__ob__
On the properties
Def is to give an object, define a property, set a property value, and by default that property is not traversable.
// the definition of def
// src/core/util/lang.js
export function def(obj: Object, key: string, val: any, enumerable? : boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable:!!!!! enumerable,writable: true.configurable: true}); }Copy the code
Def is a simple encapsulation of Object.defineProperty.
DefineReactive: Add getters and setters dynamically to a property of an object
The function of defineReactive is to dynamically add getters and setters to an object to make it reactive:
// src/core/observer/index.js
/** * Define a reactive property on an Object. */
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function, shallow? : boolean) {
// Initialize an instance of the Dep object
const dep = new Dep();
// Get the property descriptor for obj
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];
}
letchildOb = ! shallow && observe(val);Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
if (Dep.target) {
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();
}
if (setter) {
setter.call(obj, newVal);
} else{ val = newVal; } childOb = ! shallow && observe(newVal); dep.notify(); }}); }Copy the code
DefineReactive function:
- Initialize it initially
Dep
Object instance, - Then get
obj
Property descriptor of, - It then makes a recursive call to the child object
observe
methods
This ensures that no matter how complex obj’s structure is, all of its child properties can become responsive objects, so that we can access or modify a deeply nested property in OBJ and fire getters and setters.
Finally, use Object.defineProperty to add getters and setters to obj’s property key.
conclusion
The core of reactive objects is adding getters and setters to data using Object.defineProperty. This automatically performs some logic when accessing and writing data:
getter
The thing to do is rely on collectionsetter
What they do is they send out updates
reference
- Huang Yi teacher vuE2 source decryption course
- Vue. Js technology revealed