DefineProperty allows you to define a property on an Object or modify the property, the first argument. For detailed usage of defineProperty, please refer to the MDN manual.

var obj = {}

Object.defineProperty(obj, 'name', {
  get () {
    console.log('getter',value)
    return value
  },
  set (newValue) {
    console.log('setter',newValue)
    value = newValue
  },
  enumerable: true,
  configurable: true}) // Then, accessing the obj.name property or modifying the value of obj.name will trigger get() orset().Copy the code

On this basis, all the properties of the object are iterated, and a beggar version of data hijacking can be implemented.

<input type="text" id ='input' onchange="input(event)">    
<span id="span"></span>
Copy the code
var obj = {}

Object.defineProperty(obj,'name', {get() {
            console.log('getter')},set(newValue) {
            console.log('setter',newValue)
            document.getElementById('input').value=newValue
            document.getElementById('span').innerHTML=newValue
        }
    })
function input (event){
    obj.name =event.target.value
}

Copy the code

But the code above, data, functions, and DOM are tightly coupled, so you need to decouple. Looking back at the beginning of learning VUE, the recommended way to use VUE was to introduce the vue.js file. So, next, let me write it first. The first thing to do is to parse out the template to better understand two-way binding of data.


<div id="app">
    <input type="text" v-model="user.name" />
    <div>
        <div>{{user.name}}-{{user.age}}</div>
    </div>
</div>
 
<script type="text/javascript">
    const app = new MineVue({
        el: '#app',
        data: {
            user: {
                name: 'Tom', age: Class MineVue {constructor(options) {this.el = options.el this.data = options.data this.init()}init() {
            if(this.el) { this.vm = document.querySelector(this.el) new Compiler(this.el, This)}}} // Constructor (el, vm) { this.el = document.querySelector(el) this.vm = vm this.init(this.el) } init(el) { const fragment = This.nodetofragment (el) this.compile(fragment) el.appendChild(fragment)} So to save the DOM in memory nodeToFragment (node) {const fragments = document. CreateDocumentFragment ()let firstChild = node.firstChild
            while (firstChild) {
                fragment.appendChild(firstChild)
                firstChild = node.firstChild
            }
            returnFragment} // Compile (node) {const childNodes = [...node.childNodes] // compile childNodes. ForEach (childNode) = > {if (childNode.nodeType === 1) {
                    this.compileElement(childNode)
                    this.compile(childNode)
                } else{this.piletext (childNode)}})} // Fetch v-model compileElement(node) {const attributes = [...node.attributes] attributes.forEach(attr => { const {name,value: expression} = attr const reg = /^v-model/if(reg.test(name)) {// Put the value into the node node.value = getValue(this.vm, Compile expression)}})} / / {{}} compileText (node) {const reg = / \ {\ {(. +?) \}\}/g const expression = node.textContentif(reg.test(expression)) { const value = expression.replace(reg, (... args) => {return getValue(this.vm, args[1])
                })
                node.textContent = value
                }
            }
        }
    
</script>
Copy the code

Once the template is compiled, a publish-subscribe pattern is introduced, starting with an Observer that listens for every change in data. In this way, the app data in minewvue.html is monitored.

// Modify MineVue, call Observer, Constructor (options) {this.el = options.el this.data = options.data this.init()} class MineVue {constructor(options) {this.el = options.el this.data = options.data this.init()}init() {
        if(this.el) {this.vm = document.querySelector(this.el) new Observer(this.data) new Compiler(this.el, this)}} // Observer, Intercepts all attributes in data. Class Observer {constructor(data) {this. Observer (data)}if(! data || typeof data ! = ='object') returnObject.keys(data).forEach(key => { this.defineReactive(data, key, // Set setters and getters for all attributes defineReactive(data, key, DefineProperty (data, key, {64x: 64x) {// Block Object. DefineProperty (data, key, {64x: 64x)true,
      enumerable: true.get() {
        return value
      },
      set(newValue) {
        if(value ! == newValue) {console.log(' detected${value}= >${newValue}`)
          value = newValue
        }
      }
    })
  }
}
Copy the code

Now that the data is being monitored, the next thing to do is to be a subscriber, subscribe to the data, and maintain a subscriber list of all the subscribers, and if the data is updated, update it from the subscriber list.

class Watcher { constructor(expression, Vm) {this.expression = expression this.vm = vm // Get the old value this.value = this.get()}get() {
        Deposit.target = this
        const value = getValue(this.expression, this.vm)
        Deposit.target = null
        returnValue} // The function triggered when data is updatedupdata() {
        const newValue = getValue(this.expression, this.vm)
        if(this.value ! == newValue) { this.value = newValue } } } class Deposit {constructorAddWatcher (Watcher) {this.watchers. Push (Watcher)} // Notify all subscribers of data updatesnotify() {
        this.watchers.forEach(watcher => {
            watcher.update()
        })
    }
}

Copy the code

This completes a subscriber and subscription publishing center. The next thing to do is relate. First, make changes in the Observer, either by subscribing to the publishing center Deposit to access or update the data.

class Observer {
    constructor(data) {
        this.observer(data)
    }
    observer(data) {
        if(! data || typeof data ! = ='object') returnObject.keys(data).forEach(key => { this.defineReactive(data, key, data[key]) }) } defineReactive(data, key, Const deposit = new deposit () object.defineProperty (data, key, {64x: 64x) {new deposit () const deposit = new deposit () Object.true,
            enumerable: true.get() {// Add attributes to the subscriber central Depositif (Deposit.target) deposit.addWatcher(Deposit.target)
                return value
            },
            set(newValue) {
                if (value === newValue) returnValue = newValue // Notifies the subscriber center of data updates. Notify ()}})}}Copy the code

So the data is associated with the subscriber center, except for the last one, which is bound to Watcher during template compilation, so modify the Compilder.

class Compiler {
    constructor(el, vm) {
        this.el = document.querySelector(el)
        this.vm = vm
        this.init(this.el)
    }
    init(el) {
        const fragment = this.nodeToFragment(el)
        this.compile(fragment)
        el.appendChild(fragment)

    }
    nodeToFragment(node) {
        const fragment = document.createDocumentFragment()
        let firstChild = node.firstChild
        while (firstChild) {
            fragment.appendChild(firstChild)
            firstChild = node.firstChild
        }
        return fragment
    }
    compile(node) {
        const childNodes = [...node.childNodes]
        childNodes.forEach(node => {
            if (node.nodeType === 1) {
                this.compileElement(node)
                this.compile(node)
            } else {
                this.compileText(node)
            }
        })
    }
    compileElement(node) {
        const attributes = [...node.attributes]
        attributes.forEach(attr => {
            const { name, value: expression } = attr
            if(name ! = ='v-model') returnConst value = getValue(expression, this.vm) // Add observer new Watcher(expression, this.vm, (newValue) => { node.value = newValue }) node.addEventListener('input', e => {
                const value = e.target.value
                setValue(expression, this.vm, value) }) node.value = value }) } compileText(node) { const reg = /\{\{(.+?) \}\}/g const content = node.textContentif(reg.test(content)) { const value = content.replace(reg, (... Args) = > {/ / every {{}} expression to add new observer Watcher (args [1], this. Vm, () = > {/ / because DOM template may be {{a}} {{b}} such form, So return the whole string node.textContent = this.getCompiletExtValue (content, this.vm)})returngetValue(args[1], this.vm) }) node.textContent = value } } getCompileTextValue(expression, vm) { const reg = /\{\{(.+?) \}\}/g const value = expression.replace(reg, (... args) => { const value = getValue(args[1], vm)return value
        })
        return value
    }
}

Copy the code