项目入口和初始化
我们知道,Vue虽然复杂,但本质上仍旧是一个函数。所以第一步,我们先创建一个Vue函数。先在根目录下创建index.js,在里边创建Vue函数。
然后,我们需要获取到传递给Vue函数的参数,本质是一个对象,我们命名为options,包含Vue的如component、data、template等一系列配置。这个操作也应当抽离(init.js),再在主函数中调用。
因为这个配置比较常用,所以将它挂在到Vue的实例下。我们知道Vue实例上的属性函数等都会带上$符号,此处亦然。
1 2 3 4 5 6 7 8 9 10 11
| import { initMixin } from './init'
function Vue(options){ this._init(options) }
initMixin(Vue); initLifecycle(Vue)
export default Vue
|
1 2 3 4 5 6 7 8 9 10 11
| import { initState } from './state'
export function initMixin(Vue){ Vue.prototype._init = function(options){ const vm = this; vm.$options = options;
} }
|
处理data数据
目前我们已经把data挂载到Vue实例上。不过我们平时使用data里的数据都是this.a进行访问。此处this即为Vue实例,即直接使用Vue实例进行访问data中的数据。那么我们需要将data做一个备份,将这个备份代理到vm上,即可直接访问。
同样这个功能应当抽出为一个独立js文件(state.js)。
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
|
export function initState(vm){ const opts = vm.$options;
if(opts.data){ initData(vm); } }
function proxy(vm,target,key){ Object.defineProperty(vm,key,{ get(){ return vm[target][key]; }, set(newVal){ vm[target][key] = newVal; } }); }
function initData(vm){ let data = vm.$options.data;
typeof data === 'function'? data = data.call(vm) : data;
vm._data = data; observe(data);
for(let key in data){ proxy(vm,'_data',key); } }
|
这里写的initState方法会在_init中调用,执行结果即将 _data代理到vm上。
实现基本响应式
响应式逻辑比较核心,后续源码实现多处也会再次调用此处,所以抽为一个模块:新建observe文件夹,里边新建index.js。
vue2响应式主要依赖Object.definePoperty,监测对象的set和get方法,进行重新渲染来实现。
这里我们首先将data中的每个属性挂上set和get方法。
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
|
import Dep from './dep';
class Observer { constructor(data) {
data.__ob__ = this; Object.defineProperties({ value: this, enumerables: false, }) if(Array.isArray(data)){ data.__proto__ = newArrayProto; this.observeArray(data); }else{ this.walk(data); } }
walk(data) {
Object.keys(data).forEach(key => { defineReactive(data, key, data[key]); }); }
observeArray(data){ data.forEach(item => { observe(item); }) } }
function dependArray(value){ for(let i=0;i<value.length;i++){ let current = value[i]; current?.dep.depend(); if(Array.isArray(current)){ dependArray(current); } } }
export function defineReactive(target, key, value) { let childob = observe(key);
let dep = new Dep(); Object.defineProperty(target, key, { get() { if(Dep.target){ dep.depend(); if(childob){ childob.dep.depend(); if(Array.isArray(value)){ dependArray(value); } } } return value; }, set(newValue) { if(newValue === value) return; observe(newValue); value = newValue; dep.notify(); }, }) }
export function observe(data) {
if (typeof data!== 'object' || data === null) { return; }
if (data.__ob__ instanceof Observer) { return data.__ob__; }
return new Observer(data); }
|
数组响应式的特殊处理
通过循环对象绑定set和get对数组的某些情况无法处理到。比如数组的push、unshift等钩子,为数组增加的元素不会被绑定到,所以我们需要重写数组的方法,单独给数组内新增的元素绑定响应式。
在observe下新建array.js。
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
|
let oldArrayProto = Array.prototype
let newArrayProto = Object.create(oldArrayProto)
let methods = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse', ]
methods.forEach(method => { newArrayProto[method] = function (...args) { const result = oldArrayProto[method].call(this,...args)
let inserted; let ob = this.__ob__; switch (method) { case 'push': case 'unshift': inserted = args; break; case 'splice': inserted = args.slice(2); break; case'sort': case'reverse': default: break; }
if (inserted) { ob.observeArray(inserted) }
return result } })
export default newArrayProto
|