Vue is now in version 3, so let’s review its development step by step. And analyze the core principles.
If you don’t know much about ES6, you can read the es6 introduction tutorial written by Ruan Yifeng
There is no virtual DOM in Vue1, and in this version we will only focus on the corresponding. The thing that implements bidirectional binding of arrays in VUE1 is Object.defineProperty. So let’s just do a little demo and create a new index.js
function defineReactive(obj, key, val){
Object.defineProperty(obj, key, {
get (){
console.log('get',val)
return val
},
set (newVal){
if(newVal ! == val){console.log('set',newVal)
val = newVal
}
}
})
}
var obj = {bar:123}
defineReactive(obj, bar, obj.bar)
obj.bar // The console prints get 123
obj.bar = 456 // The console prints set 456
Copy the code
I see that that triggers the setter and getter. But what if objects nested objects? Obviously our method is not sufficient for such a case. Should we see what its children are before we call it? Let’s see if that solves the problem.
function defineReactive (obj, key, val) {
observer(val)
// If it is an object type, the re-execution listens at a deeper level
Object.defineProperty(obj, key, {
get () {
console.log('get', val)
return val
},
set (newVal) {
if(newVal ! == val) {console.log('set', newVal)
val = newVal
observer(val)
// If an item in the object is changed to an array, listen again}}})}function observer (obj) {
if (typeofobj ! = ='object'&& obj ! =null) {
return
} // If the object passed in is not an object, the return is not executed
Object.keys(obj).forEach(key= > {
defineReactive(obj, key, obj[key])
})
}
var obj = { bar: 123.bar1: { a: 123 } }
observer(obj)
obj.bar1.a // get 123
obj.bar1.a = 465 // set 465
Copy the code
Data correspondence
Now let’s see how do we use vue
new Vue({
el:'#app'.data(){
return{... }},methods: {... }})Copy the code
We can see from calling this code that we first need a vue class (constructor) and then pass an object that has these properties el, data, methods. So these are sort of the configurations that we need to use this class, and some optional configurations. Next, let’s simply implement a vue class
class Vue{
constructor(options){ // The parameters passed in
this.$options = options // Save the parameters
initData(this) // Execute the initialization function}}function initData(vm){
let {data} = vm.$options // Separate the data
// Check whether data exists
if(! data){ vm_data={}// Create an empty object if the data option does not exist
} else {
vm_data = typeof= = ='function' ? data() : data
Data (){return {}} = data(){return {}} = data(){return {}} If it is a method it executes one and returns an object.
}
// The important thing about this method is that we loop through _data and then conveniently hang up the agent so that we can access it directly instead of this.data. The variable is directly this. Variable
Object.keys(vm._data).forEach(key= > {
proxy(vm, '_data', key)
})
observe(vm._data)
}
/ / access to the target. The key is, in fact, return target [sourceKey] [key] here is equivalent to this. A visit is actually enclosing _data while forming. With a need to modify and change the target value
function proxy(target, sourceKey, key){
Object.defineProperty(target, key,{
get() {
return target[sourceKey][key]
},
set(newVal){
target[sourceKey][key] = newVal
}
})
}
function observe(value){
if(typeofvalue ! = ='object'&& value ! =null) {return
}
if(value.__ob__){
return value.__ob__
}
new Observer(value)
}
class Observer{
constructor(value){
Object.defineProperty(value,'__ob__', {value:this.enumerable: false.writable: true.configurable: true
})
this.walk(value)
}
walk(obj){
Object.keys(obj).forEach(key= > {
defineReactive(obj, key, obj[key])
})
}
}
function defineReactive(obj, key, val) {
observe(val)
Object.defineProperty(obj, key, {
get() {
return val
},
set(newVal) {
if(newVal ! == val) { val = newVal observe(val) } } }) }Copy the code
Now we can implement a vUE that doesn’t focus on the view, and we can do the same thing with initData to execute the agent methods, props, and so on. DefineProperty can only proxy objects, so let’s do the same for arrays.
class Observer{
constructor(value){
Object.defineProperty(value, '__ob__', {
value: this.enumerable: false.writable: true.configurable: true
})
if(Array.isArray(value)){
// Process the array and reserve the location first
} else {
this.walk(value)
}
}
}
Copy the code
Page updates
So we’re almost done with the data form, and now we’re going to combine it with the page.There are a few things we need to know before we do that
Vue: the framework constructor
Observer: Performs data correspondence (analyzes whether the data is an object or an array)
Complie: compile templates, initialize attempts, collect dependencies (update functions, watcher create)
Watcher: Perform the update function (DOM update)
Dep: Manage watcher, batch update
Compiling templates – Complie compiles vue special syntax in templates, initializes views, and updates functions
class Compile{
constructor(el, vm){
this.$vm = vm
this.$el = document.queryselector(el)
if(this.$el){
this.compile(this.$el)
}
}
compile(el){
el.childNodes.forEach(node= >{
if(this.isElement(node)){
this.compileElement(node)
} else if(this.isInter(node)){
this.complieText(node)
}
if(node.childNodes){
this.compile(node)
}
})
}
isElement(node){
return node.nodeType === 1
}
isInter(node){
return node.nodeType === 3 && / \ {\ {(. *) \} \} /.test(node.textContent)
}
complieText(node){
this.update(node, RegExp. $1,'text')}compileElement(){
// Get the node attributes
const nodeAttrs = node.attributes
Array.from(nodeAttrs).forEach(attr= >{
const attrName = attr.name
const exp = attr.value
if(this.isDirective(attrName)){
const dir = attrName.substring(2) // Extract the vUE directive text HTML model
this[dir] && this[dir](node,exp)
}
if(this.isEvent(attrName)){
const dir = attrName.substring(1)
this.eventHandler(node, exp, dri)
}
})
}
eventHandler(node, exp, dir){
const cb = this.$vm.$options.methods && this.$vm.$options.methods[exp]
node.addEventListener(dir, fn.bind(this.$vm))
}
text(node, exp){
this.update(node, exp, 'text')}html(node, exp){
this.update(node, exp, 'html')}model(node, exp){
this.update(node, exp, 'model')
const { tagName, type } = node
tagName = tagName.toLowerCase()
if(tagName == 'input' && type == 'text') {// Assign an initial value if an initial value is bound
node.value = this.$vm[exp]
node.addEventListener('input'.() = >{
this.$vm[exp] = e.target.value
// Listen for input events to change the binding value})}else if(tagName == 'input' && type == 'checkbox') {
node.value = this.$vm[exp]
node.addEventListener('change'.() = >{
this.$vm[exp] = e.target.checked
// Listen for the change event of the checkbox to change the binding value})}else if(tagName == 'select'){
node.value = this.$vm[exp]
node.addEventListener('input'.() = >{
this.$vm[exp] = e.target.value
// Listen for input events to change the binding value}}})// All bindings need to bind the update function and the corresponding Wathcer instance
update(node, exp, dir){
const cb = this[dir + 'Updater']
cb && cb(node, this.$vm(exp))
new Watcher(this.$vm, exp, function(val){
cb && cb(node, val)
})
}
textUpdater(node, value){
node.textContent = value
}
htmlUpdater(node, value){
node.innerHTML = value
}
modelUpadter(node, value){
node.value = value
}
}
Copy the code
In VUE1, key and DEP are one-to-one correspondence. The object returned in data corresponds to a DEP, and each key in the object corresponds to this DEP
class Dep{
constructor(){
this.watchers = []
}
static target = null
depend(){
if (this.watchers.includes(Dep.target)){
return
}
this.watchers.push(Dep.target)
}
notify(){
this.watchers.forEach(watcher= >{
watcher.update()
})
}
}
Copy the code
A dependency on a page corresponds to a watcher. In vue1, dep and watcher have a 1:N relationship
class watcher{
constructor(vm, key, updateFn){
this.vm = vm
this.key = key
this.updateFn = updateFn
// Read the data once, triggering get() in definrReaective
Dep.target = this
this.vm[this.key]
Dep.target = null
}
update(){
// Pass the latest value of the parent to the update function
this.updateFn.call(this.vm, this.vm[this.key])
}
}
Copy the code
This basically creates a corresponding page, but we didn’t come close to overwriting the Array method, we just copied the method and didn’t notify the update. Let’s continue to refine this method.
const orignalProto = Array.prototype;
const arrayProto = Object.create(orignalProto);
// Only these 7 methods will change the array, so let's override these 7 methods
['push'.'pop'.'shift'.'unshift'.'splice'.'reverse'.'sort'].forEach(method= >{
orignalProto[method].apply(this.arguments)
let inserted = []
switch(method) {
case 'push':
case 'unshift':
inserted = arguments
break;
case 'splice':
inserted = arguments.slice(2)
break;
}
if(inserted.length > 0){
inserted.forEach(key= >{
observe(key)
})
}
})
Copy the code
A simple vUE is done, we only do the simplest analysis code, no compatibility processing. Don’t take it seriously.
conclusion
At the beginning of the design, VUE1 was designed to solve our daily tedious tasks of obtaining DOM, obtaining values, listening to DOM changes, updating DOM data, etc. He put all of these together into an automated framework that would reduce our cumbersome operations. Of course, vue1 also has disadvantages. The absence of virtual DOM and Diff in VUe1 will consume a lot of resources when the project is large and rich in content. Every corresponding data on a page generates a watcher, and a large page with a lot of data also generates a lot of Watcher. Total resources.