摘要:调用父类的方法类在我们上一步已经实现。我们先实现的绑定,因为是要被监听,所以要进行进一步的处理。调用父类的方法方法绑定完事,其实就这么简单。
看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。
前言激动人心的时候即将来临,之前我们做的 8 步,其实都在为这一步打基础,这一步,我们来简单实现一个 Vue 对象,还没有看过之前代码的同学,请确认看过之前的文章。
主要实现内容我们从测试代码入手,来看我们这个 Vue 实现了什么,然后在根据要实现的内容来编写这个 Vue 对象:
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 源码中并不是通过这个方式实现的事件,有兴趣的可以自己去了解下,但是在我看来这样是最容易理解的方式。
class Vue extends Event { constructor() { // 调用父类的 constructor 方法 super() ... } ... }
Event 类在我们上一步已经实现。
接着我们来处理第二点。为了方便代码的管理,我们在类下定义一个 _init 方法,来实现 Vue 的初始化。
我们先实现 methods 的绑定,因为 data 是要被监听,所以要进行进一步的处理。
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 的整个实现并没有什么黑魔法,有的是精心的结构和处理,耐心点看下去,我相信我的收获会很大。
点击查看相关代码
系列文章地址VUE - MVVM - part1 - defineProperty
VUE - MVVM - part2 - Dep
VUE - MVVM - part3 - Watcher
VUE - MVVM - part4 - 优化Watcher
VUE - MVVM - part5 - Observe
VUE - MVVM - part6 - Array
VUE - MVVM - part7 - Event
VUE - MVVM - part8 - 优化Event
VUE - MVVM - part9 - Vue
VUE - MVVM - part10 - Computed
VUE - MVVM - part11 - Extend
VUE - MVVM - part12 - props
VUE - MVVM - part13 - inject & 总结
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/94631.html
摘要:在中关于如何实现在网上可以搜出不少,在看了部分源码后,梳理一下内容。换个说法,当我们取值的时候,函数自动帮我们添加了针对当前值的依赖,当这个值发生变化的时候,处理了这些依赖,比如说节点的变化。 在 VUE 中关于如何实现在网上可以搜出不少,在看了部分源码后,梳理一下内容。 首先,我们需要了解一下 js 中的一个 API :Object.defineProperty(obj, prop,...
摘要:事件是什么在标准浏览器中,我们经常使用来为一个添加一个事件等。仔细看这些情况,归结到代码中,无非就是一个行为或情况的名称,和一些列的动作,而在中动作就是,一系列的动作就是一个函数的集合。 看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。 事件是什么? 在标准浏览器中,我们经常使用:addEventListener 来为一个 DOM 添加一个事件(click、mouse...
摘要:看这篇之前,如果没看过先移步看实现中。同样的,在取值时收集依赖,在设置值当值发生变化时触发依赖。中实现了一个的类来处理以上两个问题,之后再说。以下语法下的,源码中差不多就这样点击查看相关代码系列文章地址优化优化总结 看这篇之前,如果没看过 step1 先移步看 实现 VUE 中 MVVM - step1 - defineProperty。 在上一篇我们大概实现了,Vue 中的依赖收集和...
摘要:所以方法,是对默认进行扩展,从而实现扩展。这里我用了这个库提供的合并方法,用来合并两个对象,并不会修改原对象的内容。测试符合我们的预期,方法也就实现了,下一步,实现父子组件。系列文章地址优化优化总结 看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。 组件的扩展 在 Vue 中有 extend 方法可以扩展 Vue 的实例,在上一步中,有一些实现是必须要通过子父组件才...
摘要:通过装作这些变化,我们实现了从而到达了数据变化触发函数的过程。于此同时,我们还实现了来扩展这个可响应的结构,让这个对象拥有了触发和响应事件的能力。最后,根据我们的实现,这是最终的产出,一个框架,了解一下系列文章地址优化优化总结 看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。 provide / inject 在上一步我们实现了,父子组件,和 props 一样 pr...
阅读 1265·2023-04-26 00:35
阅读 2689·2023-04-25 18:32
阅读 3315·2021-11-24 11:14
阅读 758·2021-11-22 15:24
阅读 1362·2021-11-18 10:07
阅读 6311·2021-09-22 10:57
阅读 2755·2021-09-07 09:58
阅读 3547·2019-08-30 15:54