vue.js响应式源码

114 阅读1分钟
  • 创建index.html
<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Vue基本结构</title></head><body>    <div id="app">        <h1 v-on:click="myClick">点击事件</h1>        <h1>插值表达式</h1>        <h3>{{msg}}</h3>        <h3>{{count}}</h3>        <h1 v-html="html">v-html</h1>        <h1>v-text</h1>        <div v-text="msg"></div>        <h1>v-model</h1>        <input type="text" v-model="msg">        <input type="text" v-model="count">    </div>    <script src="./js/Watcher.js"></script>    <script src="./js/Dep.js"></script>    <script src="./js/compile.js"></script>    <script src="./js/observer.js"></script>    <script src="./js/vue.js"></script>    <script>        let vm = new Vue({            el: '#app',            data: {                msg: 'hello vue',                count: 200,                content: {                    foo: 12                },                html: '<button style="color: red; font-size: 26px">v-html</button>'            },            methods: {                myClick() {                    alert('点击事件')                },            }        })    </script></body></html>
  • 创建js/vue.js
class Vue {    constructor (options){        //1.保存选项的数据        this.$options = options || {}        this.$el =typeof options.el ==='string' ? document.querySelector(options.el):options.el        this.$data = options.data || {}        this.$methods = options.methods || {}        //2.负责把data中的成员转换成getter和setter,注入到Vue实例        this._proxyData(this.$data)        //把methods中的成员注入到vue实例中        this._proxyMethods(this.$methods)        //3.负责调用observer实现数据劫持        new Observer(this.$data)        //4.负责调用Compiler解析指令/插值表达式等        new Compiler(this)    }        _proxyData (data) {        //遍历data的所有属性        Object.keys(data).forEach(key => {            //把data的属性注入到vue实例中            Object.defineProperty(this, key, {                enumerable:true,                configurable:true,                get () {                    return data[key]                },                set (newValue) {                    if(newValue === data[key]){                        return                    }                    data[key] = newValue                }            })        })    }    _proxyMethods(methods) {        Object.keys(methods).forEach(key => {            //把methods中的成员注入到vue实例中            this[key] = methods[key]        })    }}
  • 创建js/observer.js

    //负责数据劫持//把$data中的成员转换成getter/setterclass Observer { constructor (data) { this.walk(data) } //1.判断data是否是对象,如果不是对象返回 //2.如果是对象,遍历对象的所有属性,设置为getter/setter walk (data) { if(!data || typeof data !== 'object'){ return } //遍历data的所有成员 Object.keys(data).forEach(key => { this.defineReactive(data,key,data[key]) }) } //定义响应式成员 defineReactive(data,key,val) { let that = this //创建dep对象收集依赖 const dep = new Dep() //如果val是对象,继续设置它下面的成员成为响应式数据 this.walk(val) Object.defineProperty(data,key, { enumerable:true, configurable:true, get () { //get的过程中收集依赖 Dep.target && dep.addSub(Dep.target) return val }, set(newValue) { if(newValue === val){ return } val = newValue //如果newValue是对象,设置newValue的成员成为响应式 that.walk(newValue) //当数据变化时,发送通知 dep.notify() } }) }}

  • 创建js/compile.js

    //负责解析指令/插值表达式class Compiler { constructor(vm) { this.vm = vm this.el = vm.el //编译模板 this.compile(this.el) } //编译模板 //处理文本节点和元素节点 compile (node) { //childNodes属性可返回指定节点的子节点的节点列表 let nodes = node.childNodes Array.from(nodes).forEach(node => { //处理文本节点 if(this.isTextNode(node)){ this.compileText(node) //处理元素节点 }else if(this.isElementNode(node)){ this.compileElement(node) } if(node.childNodes && node.childNodes.length) { //如果当前节点中还有子节点,递归编译 this.compile(node) } }) } //编译元素节点,处理指令 compileElement (node) { Array.from(node.attributes).forEach(attr => { //获取元素属性的名称 let attrName = attr.name //判断当前属性名称是否是指令 if(this.isDirective(attrName)){ //截取属性的名称 attrName = attrName.substr(2) let key = attr.value if(attrName.startsWith('on')){ const event = attrName.replace('on:','') //事件更新 this.eventUpdate(node, key, event) }else{ this.update(node,key,attrName) } } }) } eventUpdate(node,key,event) { this.onUpdater(node,key,event) } update (node, key, attrName) { let updateFn = this[attrName + 'Updater'] updateFn && updateFn.call(this, node,this.vm[key],key) } // 处理 v-on 指令 onUpdater(node, key, event) { node.addEventListener(event, (e) =>this.vm[key](e)) } //处理html指令 htmlUpdater (node ,value, key) { node.innerHTML = value new Watcher(this.vm, key, newValue => { node.innerHTML = newValue }) } //处理v-text指令 textUpdater (node, value, key) { node.textContent = value //每一个指令中创建一个watcher,观察数据的变化 new Watcher(this.vm, key, newValue => { node.textContent = newValue }) } //处理v-model指令 modelUpdater (node, value, key) { node.value = value //每一个指令中创建一个watcher,观察数据的变化 new Watcher(this.vm, key, newValue => { node.value = newValue }) //监听视图的变化,双向绑定 node.addEventListener('input', () => { this.vm[key] = node.value }) } //编译文本节点,处理插值表达式 compileText (node) { // console.dir(node); //js正则表达式的非贪婪模式 ? const reg = /\{\{(.+?)\}\}/ const value = node.textContent if(reg.test(value)){ //插值表达式中的值就是我们要的属性名称 //RegExp是js中的一个内置对象,为正则表达式 //RegExp.1是RegExp的一个属性,指的是与正则表达式匹配的第一个子匹配(以括号为标志)字符串 let key = RegExp.$1.trim() node.textContent = value.replace(reg, this.vm[key]) new Watcher(this.vm,key,newValue => { node.textContent = newValue }) } } //判断元素属性是否是指令 isDirective (attrName) { return attrName.startsWith('v-') } //判断节点是否是文本节点 isTextNode (node) { return node.nodeType === 3 } //判断节点是否是元素节点 isElementNode (node) { return node.nodeType === 1 }}

创建js/Dep.js

class Dep {    constructor () {        //存储所有的观察者        this.subs = []    }    //添加观察者    addSub (sub) {        if(sub && sub.update){            this.subs.push(sub)        }    }    //通知所有观察者    notify () {        this.subs.forEach(sub => {            sub.update()        })    }}
  • 创建js/Watcher.js

    class Watcher { constructor(vm, key, cb) { this.vm = vm //data中的属性名称 this.key = key //当数据变化的时候,调用cb更新视图 this.cb = cb //在Dep的静态属性上记录当前watcher对象,当访问数据的时候把watcher添加到dep的subs中 Dep.target = this //触发一次getter,让dep为当前key记录watcher this.oldValue = vm[key] //清空target Dep.target = null } update(){ const newValue = this.vm[this.key] if(this.oldValue === newValue) { return } this.cb(newValue) }}

  • v-html实现:

  • vue实例初始化时,调用new Compiler(this)解析指令和插值表达式

  • 在Compiler类中编译模板,处理文本节点和元素节点:this.compile(this.el)

  • 处理元素节点:this.compileElement(node)

  • compileElement(node)方法会遍历这个节点写下的属性,如果是指令则调用相对应的指令处理函数:

  • htmlUpdater:处理v-html指令

  • textUpdater:处理v-text指令

  • modelUpdater:处理v-model指令

  • onUpdater:处理v-on指令

  • v-on实现:

  • vue实例初始化时,调用new Compiler(this)解析指令和插值表达式

  • 把methods中的成员注入到Vue实例中,this._proxyMethods(this.$methods)

  • 在Compiler类中编译模板,处理文本节点和元素节点:this.compile(this.el)

  • 处理元素节点:this.compileElement(node)

  • 如果找到属性名是以on开头的,则调用this.eventUpdate(node,key,event)

  • eventUpdate调用onUpdater来处理v-on指令