vue源码解析:深入理解vue底层原理

vue工作机制

在这里插入图片描述

Vue响应式的原理defineProperty

class Vue {
 constructor(options) { 
 	this._data = options.data;
 	this.observer(this._data);
 }
 observer(value) { 
 	if (!value || typeof value !== "object") {
 	 return;
 }
 Object.keys(value).forEach(key => {
 	this.defineReactive(value, key, value[key]); 
 	}); 
 }
 defineReactive(obj, key, val) { 
 	Object.defineProperty(obj, key, { 
 		enumerable: true /* 属性可枚举 */, 
 		configurable: true /* 属性可被修改或删除 */, 
 		get() { 
 			return val; 
 		},
 		set(newVal) { 
 			if (newVal === val) return;
 			this.cb(newVal); 
 		} 
 	});
 }
 cb(val) {
 	console.log("更新数据了", val); 
 } 
}
let o = new Vue({ 
	data: { test: "I am test." } 
}); 
o._data.test = "hello,world";

  
    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

依赖收集与追踪

new Vue({ 
	template: `
{{text1}} {{text2}}
`, data: { text1: 'name1' }, created(){ this.text1="练习" } }); text1被修改,所以视图更新,但是text2视图没用到,所以不需要更新,如何实现呢,就需要我们的依赖收集
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
// 依赖收集小朋友 
class Dep { 
	constructor () {
	 	// 存数所有的依赖 
	 	this.deps = []
	}
	// 在deps中添加一个监听器对象 
	addDep (dep) {
	    this.deps.push(dep) 
	}
	// 通知所有监听器去更新视图
	notify () {
		this.deps.forEach((dep) => {
			dep.update() 
		 }) 
	} 
}

  
    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
class Watcher {
	constructor () {
	 	// 在new一个监听器对象时将该对象赋值给Dep.target,在get中会用到
		Dep.target = this 
	}
		// 更新视图的方法 
	update () {
		console.log("视图更新啦~") 
	} 
}

  
    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这里插入图片描述
我们在增加了一个 Dep 类的对象,用来收集 Watcher 对象。读数据的时候,会触发 reactiveGetter 函数把当前的Watcher 对象(存放在 Dep.target 中)收集到 Dep 类中去。
写数据的时候,则会触发 reactiveSetter 方法,通知Dep 类调用 notify 来触发所有 watcher 对象的update 方法更新对应视图

 constructor(options){
        this._data = options.data
        this.observer(this._data)
        // 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
        new Watcher();
        // 在这里模拟render的过程,为了触发test属性的get函数
        console.log('模拟render,触发test的getter', this._data.test);
    }
    defineReactive(obj, key, val){
        const dep = new Dep()
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: function reactiveGetter() {
                // 将Dep.target(即当前的Watcher对象存入Dep的deps中)
                dep.addDep(Dep.target)
                return val
            },
            set: function reactiveSetter(newVal) {
                if (newVal === val) return
                // 在set的时候触发dep的notify来通知所有的Watcher对象更新视图
                dep.notify()
            }
        })
    }

  
    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

编译compile

核心逻辑 获取dom,遍历dom,获取{{}}、k-和@开头的 ,设置响应式
在这里插入图片描述

目标功能

<body>
<div id="app"><p>{{name}}</p>
    <p k-text="name"></p>
    <p>{{age}}</p>
    <p>{{doubleAge}} </p> <input type="text" k-model="name">
    <button @click="changeName">呵呵</button>
    <div k-html="html"></div>
</div>
<script src='./compile.js'></script>
<script src='./k-vue.js'></script>
<script>
    let kVue = new Vue({
        el: '#app',
        data: {
            name: "I am test.",
            age: 12,
            html: ''},
        created() {
            console.log('开始啦')
            setTimeout(() => {
                this.name = '我是蜗牛'
            }, 1500)
        },
        methods: {
            changeName() {
                this.name = '哈喽'
                this.age = 1
                this.id = 'xx'
                console.log(1, this)
            }
        }
    })
</script>
</body>

  
    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

compile.js

 class Compile {
        constructor(el, vm) {
            this.$vm = vm
            this.$el = document.querySelector(el)
            if(this.$el){
                this.$fragment = this.node2Fragment(this.$el)
                this.compileElement(this.$fragment)
                this.$el.appendChild(this.$fragment)
            }
        }
        node2Fragment(el) {
            // 新建文档碎片 dom接口
            let fragment = document.createDocumentFragment()
            let child
            // 将原生节点拷贝到fragment
            while (child = el.firstChild) {
                fragment.appendChild(child)
            }
            return fragment
        }
        compileElement(el) {
            let childNodes = el.childNodes
            Array.from(childNodes).forEach((node) => {
                let text = node.textContent
                // 表达式文本
                // 就是识别{{}}中的数据
                let reg = /\{\{(.*)\}\}/
                // 按元素节点方式编译
                if (this.isElementNode(node)) {
                    this.compile(node)
                }else if (this.isTextNode(node) && reg.test(text)) {
                    // 文本 并且有{{}}
                    this.compileText(node, RegExp.$1)
                }
                // 遍历编译子节点
                if (node.childNodes && node.childNodes.length) {
                    this.compileElement(node)
                }
            })
        }
        compile(node) {
            let nodeAttrs = node.attributes
            Array.from(nodeAttrs).forEach( (attr)=>{
                // 规定:指令以 v-xxx 命名
                // 如  中指令为 v-text
                let attrName = attr.name // v-text
                let exp = attr.value // content
                if (this.isDirective(attrName)) {
                    let dir = attrName.substring(2) // text
                    // 普通指令
                    this[dir] && this[dir](node, this.$vm, exp)
                }
                if(this.isEventDirective(attrName)){
                    let dir = attrName.substring(1)// text
                    this.eventHandler(node, this.$vm, exp, dir)
                }
            })
        }
        compileText(node, exp) {
            this.text(node, this.$vm, exp)
        }
        isDirective(attr) {
            return attr.indexOf('k-') == 0
        }
        isEventDirective(dir) {
            return dir.indexOf('@') === 0
        }
        isElementNode(node) {
            return node.nodeType == 1
        }
        isTextNode(node) {
            return node.nodeType == 3
        }
        text(node, vm, exp) {
            this.update(node, vm, exp, 'text')
        }
        html(node, vm, exp) {
            this.update(node, vm, exp, 'html')
        }
        model(node, vm, exp) {
            this.update(node, vm, exp, 'model')
            let val = vm.exp
            node.addEventListener('input', (e) => {
                let newValue = e.target.value
                vm[exp] = newValue
                val = newValue
            })
        }
        update(node, vm, exp, dir) {
            let updaterFn = this[dir + 'Updater']
            updaterFn && updaterFn(node, vm[exp])
            new Watcher(vm, exp, function (value) {
                updaterFn && updaterFn(node, value)
            })
        }
        // 事件处理
        eventHandler(node, vm, exp, dir) {
            let fn = vm.$options.methods && vm.$options.methods[exp]
            if (dir && fn) {
                node.addEventListener(dir, fn.bind(vm), false)
            }
        }
        textUpdater(node, value) {
            node.textContent = value
        }
        htmlUpdater(node, value) {
            node.innerHTML = value
        }
        modelUpdater(node, value) {
            node.value = value
        }
    }

  
    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112

入口文件

 class Vue {
        constructor(options) {
            this.$data = options.data
            this.$options = options
            this.observer(this.$data)
            // 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
            // new Watcher()
            // 在这里模拟render的过程,为了触发test属性的get函数
            console.log('模拟render,触发test的getter', this.$data)
            if(options.created){
                options.created.call(this)
            }
            this.$compile = new Compile(options.el, this)
        }
        observer(value) {
            if (!value || (typeof value !== 'object')) {
                return
            }
            Object.keys(value).forEach((key) => {
                this.proxyData(key)
                this.defineReactive(value, key, value[key])
            })
        }
        defineReactive(obj, key, val) {
            const dep = new Dep()
            Object.defineProperty(obj, key, {
                enumerable: true,
                configurable: true,
                get() {
                    // 将Dep.target(即当前的Watcher对象存入Dep的deps中)
                    Dep.target && dep.addDep(Dep.target)
                    return val
                },
                set(newVal) {
                    if (newVal === val) return
                    val = newVal
                    // 在set的时候触发dep的notify来通知所有的Watcher对象更新视图
                    dep.notify()
                }
            })
        }
        proxyData(key) {
            Object.defineProperty(this, key, {
                configurable: false,
                enumerable: true,
                get() {
                    return this.$data[key]
                },
                set(newVal) {
                    this.$data[key] = newVal
                }
            })
        }
    }

  
    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

依赖收集Dep

class Dep {
        constructor() {
            // 存数所有的依赖
            this.deps = []
        }
        // 在deps中添加一个监听器对象
        addDep(dep) {
            this.deps.push(dep)
        }
        depend() {
            Dep.target.addDep(this)
        }
        //通知所有监听器去更新视图
        notify() {
            this.deps.forEach((dep) => {
                dep.update()
            })
        }
    }

  
    
    
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

监听器

// 监听器
    class Watcher {
        constructor(vm, key, cb) {
            // 在new一个监听器对象时将该对象赋值给Dep.target,在get中会用到
            // 将 Dep.target 指向自己
            // 然后触发属性的 getter 添加监听
            // 最后将 Dep.target 置空
            this.cb = cb
            this.vm = vm
            this.key = key
            this.value = this.get()
        }
        get() {
            Dep.target = this
            let value = this.vm[this.key]
            return value
        }
        // 更新视图的方法
        update() {
            this.value = this.get()
            this.cb.call(this.vm, this.value)
        }
    }

你可能感兴趣的:(vue)