看这篇以前,若是没有看过以前的文章,可拉到文章末尾查看以前的文章。vue
激动人心的时候即未来临,以前咱们作的 8
步,其实都在为这一步打基础,这一步,咱们来简单实现一个 Vue
对象,尚未看过以前代码的同窗,请确认看过以前的文章。git
咱们从测试代码入手,来看咱们这个 Vue
实现了什么,而后在根据要实现的内容来编写这个 Vue
对象:github
let test = new Vue({ data() { return { baseTest: 'baseTest', objTest: { stringA: 'stringA', stringB: 'stringB' } } }, methods: { methodTest() { console.log('methodTest') this.$emit('eventTest', '事件测试') } }, watch: { 'baseTest'(newValue, oldValue) { console.log(`baseTest change ${oldValue} => ${newValue}`) }, 'objTest.stringA'(newValue, oldValue) { console.log(`objTest.stringA change ${oldValue} => ${newValue}`) } } }) test.$on('eventTest', function (event) { console.log(event) }) test.methodTest() test.baseTest
主要实现的内容有:函数
Watcher
data/methods
数据的代理(直接使用 this.xxx
就能访问到具体的属性/方法)$on/$emit
咱们根据实现的难易程度来实现上面 3
点。测试
实现第 3
点,只要继承 Event
这个类便可:优化
注: 在 Vue
源码中并非经过这个方式实现的事件,有兴趣的能够本身去了解下,可是在我看来这样是最容易理解的方式。ui
class Vue extends Event { constructor() { // 调用父类的 constructor 方法 super() ... } ... }
Event
类在咱们上一步已经实现。this
接着咱们来处理第二点。为了方便代码的管理,咱们在类下定义一个 _init
方法,来实现 Vue
的初始化。代理
咱们先实现 methods
的绑定,由于 data
是要被监听,因此要进行进一步的处理。code
class Vue extends Event { constructor(options) { // 调用父类的 constructor 方法 super() this._init(options) } _init(options) { let vm = this if (options.methods) { for (let key in options.methods) { vm[key] = options.methods[key].bind(vm) } } } }
ok methods
方法绑定完事,其实就这么简单。
接下来咱们来处理 data
,因为 data
是须要被变换成可监听结构,因此咱们先处理一下,而后代理到 this
对象下,若是直接赋值而不代理的话 data
的可监听结构就会被破坏,咱们须要一个完整的对象,这个可监听结构才能完整。
这里先实现一下代理的方法:
export function proxy(target, sourceKey, key) { const sharedPropertyDefinition = { enumerable: true, configurable: true, get() { }, set() { } } sharedPropertyDefinition.get = function proxyGetter() { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter(val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }
原理仍是经过 Object.defineProperty
方法来实现,当访问(get
) target
下的某个属性的时候,就会去找 target[sourceKey]
下的同名属性,设置(set
) target
下的某个属性,就会让设置 target[sourceKey]
下的同名属性。这就实现了代理。
ok 代理实现,咱们继续为 _init
添加方法,具体的步骤看代码中的注释
class Vue extends Event { constructor(options) { // 调用父类的 constructor 方法 super() this._init(options) } _init(options) { let vm = this if (options.methods) { for (let key in options.methods) { // 绑定 this 指向 vm[key] = options.methods[key].bind(vm) } } // 因为 data 是个函数,因此须要调用,并绑定上下文环境 vm._data = options.data.call(vm) // 将 vm._data 变成可监听结构,实现 watcher 的添加 observe(vm._data) // 代理属性,这保证了监听结构是一个完成的对象 for (let key in vm._data) { proxy(vm, '_data', key) } } }
最后一步,添加 watcher
,仔细分析咱们在实例化时写的 watcher
:
watch: { 'baseTest'(newValue, oldValue) { console.log(`baseTest change ${oldValue} => ${newValue}`) }, 'objTest.stringA'(newValue, oldValue) { console.log(`objTest.stringA change ${oldValue} => ${newValue}`) } }
key
为须要监听的属性的路径,value
为触发监听时的回调。
ok 咱们来实现它
class Vue extends Event { constructor(options) { super() this._init(options) } _init(options) { ... // 循环取出 key/value for (let key in options.watch) { // 用咱们以前实现的 Watcher 来注册监听 // 参一:watcher 的运行环境 // 参二:获取注册该 watcher 属性 // 参三:触发监听时的回调 new Watcher(vm, () => { // 须要监听的值,eg: 'objTest.stringA' ==> vm.objTest.stringA return key.split('.').reduce((obj, name) => obj[name], vm) }, options.watch[key]) } } }
ok watcher
也已经实现,如下就是完整的代码:
export function proxy(target, sourceKey, key) { const sharedPropertyDefinition = { enumerable: true, configurable: true, get() { }, set() { } } sharedPropertyDefinition.get = function proxyGetter() { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter(val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) } let uid = 0 export class Vue extends Event { constructor(options) { super() this._init(options) } _init(options) { let vm = this vm.uid = uid++ if (options.methods) { for (let key in options.methods) { vm[key] = options.methods[key].bind(vm) } } vm._data = options.data.call(vm) observe(vm._data) for (let key in vm._data) { proxy(vm, '_data', key) } for (let key in options.watch) { new Watcher(vm, () => { return key.split('.').reduce((obj, name) => obj[name], vm) }, options.watch[key]) } } }
接下来,咱们来测试一下
let test = new Vue({ data() { return { baseTest: 'baseTest', objTest: { stringA: 'stringA', stringB: 'stringB' } } }, methods: { methodTest() { console.log('methodTest') this.$emit('eventTest', '事件测试') } }, watch: { 'baseTest'(newValue, oldValue) { console.log(`baseTest change ${oldValue} => ${newValue}`) }, 'objTest.stringA'(newValue, oldValue) { console.log(`objTest.stringA change ${oldValue} => ${newValue}`) } } }) test.$on('eventTest', function (event) { console.log(event) }) test.methodTest() // methodTest // 事件测试 test.baseTest = 'baseTestChange' // baseTest change baseTest => baseTestChange test.objTest.stringA = 'stringAChange' // objTest.stringA change stringA => stringAChange
刚开始使用 Vue
的时候,感受代码里面都是些黑魔法,在看了源码以后惊觉:其实 Vue
的整个实现并无什么黑魔法,有的是精心的结构和处理,耐心点看下去,我相信个人收获会很大。