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:

  • withvm._dataPoint to theoptions.data
  • willvm._dataAll of the above properties are proxied tovmon
  • 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

  • traverseoptions.props
  • Each prop becomes responsive (SET/GET), and each prop is also synchronized tovm._props.xx
  • throughproxythevm._props.xxxAccess 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:

  • traversedataObject, every one of themvm._data.xxthroughproxyThe agent tovm._data
  • callobserveTo observe thedataChange, 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:

  • instantiationDepObject,
  • By performingdefThe 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 initiallyDepObject instance,
  • Then getobjProperty descriptor of,
  • It then makes a recursive call to the child objectobservemethods

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:

  • getterThe thing to do is rely on collection
  • setterWhat they do is they send out updates

reference

  • Huang Yi teacher vuE2 source decryption course
  • Vue. Js technology revealed