“This is the 9th day of my participation in the November Gwen Challenge.The final text challenge in 2021”.

preface

Today, I read an article about the vUE responsive principle, and my knowledge is more in-depth. Although it took a long time, I still have a slight understanding of some of the principles. I would like to write an article to share my understanding, which may be more common and suitable for beginners to watch.

What is the responsive principle

Data models are just plain old JavaScript objects. And when you modify them, the view is updated. This is the explanation in the official document, which is really too official. Let’s simply understand that when a data changes, the data related to it will also change immediately. Then this chapter will talk about the underlying logic of the responsive principle, that is, how it is implemented specifically.

Responsive design patterns

Responsive design pattern is based on the observer pattern, the observer pattern is simply, do you want to go to a store to buy something, but you don’t know when the store will have, normally you need to go over and ask, but you think a good idea, you put the call to the boss, until the purchase is made, to call you, to inform you, So you don’t have to ask it over and over again, and it saves you a lot of time, and the time you save here is equivalent to the performance savings,

You can switch stations if you want to know moreDesign patterns — Observer patterns

Vue contains files



A basic VUE needs to include these files, and I’ll explain what they do next

1. vue.js

Vue. Js has a _proxyData() method that hijkes all the data in the data.

2. compiler.js

A collection of processing files for parsing instructions, differential expressions, and so on

3.dep.js

Vue responsiveness combines the observer mode, which starts to show that there is a storage container in the DEP, a way to add an observer, and a way to notify an observer, which is the observed in the observer mode. The store function in my previous example is to collect dependencies, which is watcher.

4.observer.js

Don’t let his name mislead you into thinking that he is an observer, but he is not. He has an internal walk method, which traverses data and adds get and set to each data in data

5.watcher.js

This is the observer in observer mode, which contains the method that receives update notifications, where the data data is called

Specific code

It is recommended that you read all of them before understanding them one by one. Follow the instructions in the article

vue.js

class Vue { constructor (options) { this.$options = options || {} // save options this.$el = typeof options.el === 'string' ? document.querySelector(options.el) :options.el // get dom this.$data = options.data // get data this.$methods = Options.methods // 1.data All data hijacked proxy this._proxyData(this.$data) // 2. New Observer(this.$data) // 3 Calling the Compiler object, New Compiler(this)} _proxyData (data) {// Traverses all data object.keys (data).foreach (key => {// DefineProperty (this, key, {enumerable: true, signals: true, get () { return data[key] }, set (newValue) { if (data[key] === newValue) { return } data[key] = newValue } }) }) } }Copy the code

When I entered the first file vue.js, I encountered a problem at that time. I spent a lot of effort to check MDN and understand the method inside, which was relatively strange in _proxyData method. Keys and Object.defineProperty will be explained next, starting with the Observer file

observer.js

Class Observer {constructor(data) {this.walk(data)} walk(data) {// loop execute data if (! data || typeof data ! == 'object') { return } Object.keys(data).forEach(key => { this.defineReactive(data, key, Data [key])})} defineReactive (obj, key, val) {let that = this. Walk (val) DefineProperty (obj, key, {64x: disables any additional information, and works without any additional information. true, enumerable: True, get() {dep.target && dep.addSub(dep.target) // Collect dependent return val // if obj[key] is used, Set (newValue) {if (newValue === val) {return} val = newValue that. Walk (newValue) // Send notification}})}}Copy the code

!!!!!!!!! The walk () method is used to add get and set to each set of data in the observer. The walk () method calls object.keys (). If you look at the episode in the directory on the right, there’s an explanation of what these two methods do and how to use them. If you don’t understand this, watch the episode first and then continue to look at the code

Analysis of the code

  1. Observer.js first uses recursion to disassemble the keys of all existing objects in data.
  2. Keys method is used to cast to an Object and store the key value, forEach traversal
  3. DefineReactive calls Object.defineProperty to add get and set to each data, which is used to listen for data changes. When the data is called, the get method is executed, and the set method is executed when the value is assigned.
  4. Let dep = new dep () create a deP instance for each data set. Target && dep.addSub(dep.target) will execute the addSub method in the Dep instance of the data, save the watcher that called the data,
  5. When something changes, the set method is called to notify watcher on the DEP instance of the data because the watcher associated with the data is already stored in the SUB array of the DEP

Why do WE generate an instance of DEP every time we traverse?

I didn’t look at it very carefully, so I don’t have to look at it. First of all, you need to know what deP stores, it stores dependent objects, not data, and you need to be clear that every data has its own dependent object, which is watcher, So each data needs a DEP instance to hold their respective dependent objects, so one instance is created for each data

dep.js

// Subscriber Dep, Constructor () {constructor() {this.subs = []} /* addSub (sub) {this.subs.push(sub)} // Notify all Watcher objects to update the view notify () {this.subs.foreach ((sub) => {sub.update() // Watcher's update method})}}Copy the code

The code analysis

  1. This is the observed in the observer mode
  2. Contains arrays for holding observers, methods for adding observers, and methods for notifying observers
  3. Dep instances are created in the Observer, and there is one deP instance per data. Why do we need one DEP instance per data instance? Every data has a dependency, which is the Watcher that needs to use that data, and that’s the dependency, and that’s what’s in the DEP

What are collection dependencies?

So if I’m Data, how do I collect dependencies, and who needs to use me, that’s dependent on me, and I collect it, and who’s dependent on me, and that’s Watcher down here, so it should be pretty clear, so the array in the DEP is watcher, not Data

watcher.js

class Watcher { constructor (vm, key, Cb) {this.vm = vm // Data attribute name this.key = key // the callback is responsible for updating the view this.cb = cb // Log the watcher object to the Dep static attribute target Dep. Target = This // triggers the get method, AddSub this.oldValue = VM [key] dep.target = null} update () {let newValue = this.vm[this.key] if (this.oldValue === newValue) { return } this.cb(newValue) } }Copy the code

The code analysis

  1. The watcher object assigns this, itself, to dep.target when the constructor is constructed,
  2. The presence of dep.target is then determined in the Observer and added to the data DEP instance of the response
  3. Watcher has the upData method, which updates the view

compiler.js

// Parse v-model modelUpdater (node, value, key) {node. Value = value new Watcher(this.vm, key, (newValue) => {// Create watcher, Node. value = newValue}) // Bidirectional binding node.addEventListener('input', () => {this.vm[key] = node.value})} compile (el) {let childNodes = el.childnodes Array.from(childNodes).foreach (node => {if (this.istextNode (node)) {// Handle text node this.piletext (node)} else This.iselementnode (node)) {this.iselementNode (node)) {this.iselementNode (node)} If (node.childnodes && node.childnodes.length > 0) {this.compile(node)}})} Log (node.attributes) if (node.attributes. Length) {fetch element (node) {// console.log(node.attributes) if (node.attributes. Array.from(node.attributes).foreach (attr => {// Traversing all element nodes let attrName = attr.name if (this.isdirective (attrName)) {// AttrName = attrname.indexof (':') > -1? Attrname.substr (5) : Attrname. substr(2) let key = attr.value this. Update (node, key, attrName)}})}} Dir (node) // console.dir => convert to object let reg = /\{\{(.+?)\}\}/ let Value = node.textContent if (reg.test(value)) {let key = RegExp.$1.trim(); Node.textcontent = value.replace(reg, this.vm[key]) new Watcher(this.vm, key, (newValue) => { Update view node.textContent = newValue})}}Copy the code

Summary:

Compile converts elements into data models, which are ordinary JavaScript objects, called vNode objects, > and then iterates over the vNode objects, classifying them into element nodes, text nodes, and data according to their identifiers, into different processing functions. And create a Watcher object, and then trigger get in the Watcher object to achieve responsiveness, synchronization will perform updata update data, conversion into the real DOM, complete page rendering, update and so on.





episode

Object.keys

Force a parameter to an object and extract the key from the object into an array

Here’s an example:

Let obj = {name: "orange", age: 20}Copy the code
Let arr = "1", "2"] / / an array - > {0: "1", 1: "2"} / / forced into objects take out the key value Object. The keys (obj) / / / "0", "1"Copy the code

That’s what Object.keys are for, associated documentation

Put the key in the data into the array and then forEach traversal, so that’s why forEach says key instead of item, and that’s why traversal calls defineReactive(). Next, let’s look at this function,

Call the walk () method again, this is recursion, why call it, because if the data key has objects, it needs to be split again, until if (! data || typeof data ! == ‘object’) {return} Let dep = new dep () where an instance of dep is new and a strange function called Object.defineProperty is called

Object.defineProperty

Function: Defines a new attribute on an object, or modifies an existing attribute on an object, and returns the object.

The parameters of the Object. DefineProperty

Object. DefineProperty (Obj, Prop, Descriptor) OBj ==> An Object prop ==> Name descriptor to be added or modified ==> Attribute descriptor to be defined or modified

Let’s look at the code, an example I wrote myself, to verify the meaning of the attribute descriptor in Object.defineProperty

Function Person(){let name = 'a' object.defineProperty (this,"name",{get(){return name + 1}, set(value){ name = value } }) } const person = new Person() console.log(person.name); //a1 person.name = "orange" console.log(person.name); //orange1Copy the code

Here’s my understanding of get and set:

This refers to the Person object, the function itself, because the first parameter is also an object, so it satisfies, and the second object is “name”. Person has a name attribute, so modify the name, and if it doesn’t have a name attribute, add it, and we’ll talk about that in a second, The third argument is an Object that has get and set methods in it, but is that what the Person instance calls? No, in Object.defineProperty the get and set methods are evaluated and assigned, respectively. When you value, you call get and when you assign, you call set. If I value and assign it to the second parameter which is the “name” property, when I first call console.log which is the first value, it calls get so it prints A1 instead of A, and when I assign it I call set, so the second time I value it, the name becomes orange, So I’m printing orange1, so I can understand what set and get do in Object.defineProperty. I won’t go into data descriptors and access descriptors here

Summary: The purpose of looping over data is to add get and set to each data in the data, so as to listen for changes to each data

Finish this chapter