实现一个简单版本的vue及源码解析

Vue.js的响应式原理依赖于Object.defineProperty,尤大大在Vue.js文档中就已经提到过,这也是Vue.js不支持IE8 以及更低版本浏览器的原因。Vue通过设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

Let data to observable

首先假定一种最简单的情况,不去考虑其他情况。在initData中会调用observe这个函数将Vue的数据设置成observable的。当_data数据发生改变的时候就会触发set,对订阅者进行回调(在这里是render)。

function observe(value, cb) {
 Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
}
function defineReactive (obj, key, val, cb) {
 Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: ()=>{
 /*....依赖收集等....*/
 /*Github:https://github.com/answershuto*/
 return val
 },
 set:newVal=> {
 val = newVal;
 cb();/*订阅者收到消息的回调*/
 }
 })
}

为了操作方便,我们需要将 _data 上的数据代理到vm实例上.

function proxy (data) {
 const that = this;
 Object.keys(data).forEach(key => {
 Object.defineProperty(that, key, {
 configurable: true,
 enumerable: true,
 get: function proxyGetter () {
 return that._data[key];
 },
 set: function proxySetter (val) {
 that._data[key] = val;
 }
 })
 });
}

依赖收集

依赖收集的原因

按照上面的方法进行绑定会出现一个问题——实际模板中未使用的数据被更改后也会进行重新渲染,而这样无疑会消耗性能,因此需要依赖收集来保证只渲染实际模板中使用到的数据。

Dep

当对data上的对象进行修改值的时候会触发它的setter,那么取值的时候自然就会触发getter事件,所以我们只要在最开始进行一次render,那么所有被渲染所依赖的data中的数据就会被getter收集到Dep的subs中去。在对data中的数据进行修改的时候setter只会触发Dep的subs的函数.

Dep.prototype.depend 方法是将观察者Watcher实例赋值给全局的Dep.target,然后触发render操作只有被Dep.target标记过的才会进行依赖收集。有Dep.target的对象会将Watcher的实例push到subs中,在对象被修改触发setter操作的时候dep会调用subs中的Watcher实例的update方法来重新获取数据生成虚拟节点,再由服务端将虚拟节点渲染成真实DOM。

src/oberver/dep.js

var uid = 0;
//dep构造函数
export default function Dep(argument) {
	this.id = uid++
	this.subs = []
}
//添加一个观察者对象
Dep.prototype.addSub = function(sub) {
	this.subs.push(sub)
}
//移除一个观察者对象
Dep.prototype.removeSub = function(sub) {
	remove(this.subs, sub)
}
//依赖收集
Dep.prototype.depend = function() {
	if(Dep.target) {
		Dep.target.addDep(this)
	} 全栈学习交流;582735936

}
//通知所有订阅者
Dep.prototype.notify = function() {
	var subs = this.subs.slice()
	for(var i = 0, l = subs.length; i < l; i++){
		subs[i].update()
	}
}
Dep.target = null
function remove (arr, item) {
 if (arr.length) {
 const index = arr.indexOf(item)
 if (index > -1) {
 return arr.splice(index, 1)
 }
}

实现

上面讲述响应式原理和依赖收集的原因,接下来就来简单实现一下。开始正式编程前,照常先写测试用例。

t est/observer/observer.spec.js

import {
 Observer,
 observe
} from "../../src/observer/index"
import Dep from '../../src/observer/dep'
describe('Observer test', function() {
 it('observing object prop change', function() {
 	const obj = { a:1, b:{a:1}, c:NaN}
 observe(obj)
 // mock a watcher!
 const watcher = {
 deps: [], 全栈学习交流;582735936

 addDep (dep) {
 this.deps.push(dep)
 dep.addSub(this)
 },
 update: jasmine.createSpy()
 }
 // observing primitive value
 Dep.target = watcher
 obj.a
 Dep.target = null
 expect(watcher.deps.length).toBe(1) // obj.a
 });
});

接下来正式实现数据绑定,其中 observe 的作用是返回一个 observer 实例,而 observer 则负责实现数据绑定.

src/observer/index.js

import {
 def, //new
 hasOwn,
 isObject
}
from '../util/index'
export function Observer(value) {
 this.value = value
 this.dep = new Dep()
 this.walk(value)
 def(value, '__ob__', this)
}
export function observe (value){
 if (!isObject(value)) {
 return
 }
 var ob
 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
 ob = value.__ob__
 } else {
 ob = new Observer(value)
 } 全栈学习交流;582735936
 return ob
}
Observer.prototype.walk = function(obj) {
 var keys = Object.keys(obj)
 for (var i = 0; i < keys.length; i++) {
 defineReactive(obj, keys[i], obj[keys[i]])
 }
}
export function defineReactive (obj, key, val) {
 var dep = new Dep()
 Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function reactiveGetter () {
 var value = val
 if (Dep.target) {
 dep.depend()
 }
 return value
 },
 set: function reactiveSetter (newVal) {
 var value = val
 if (newVal === value || (newVal !== newVal && value !== value)) {
 return
 }
	 val = newVal
 dep.notify()
 }
 })
}

在上面的代码中我们用到了一些工具函数,下面我们就把这些工具函数在单独的文件中实现,方便之后其他组件的调用。

src/util/index.js

const hasOwnProperty = Object.prototype.hasOwnProperty
//必须对传入的参数进行判断,不然obj为null时会报错
export function hasOwn(obj, key) {
 if (!isObject(obj) && !Array.isArray(obj)) {
 return
 }
 return hasOwnProperty.call(obj, key)
}
export function isObject(obj) {
	return obj !== null && typeof obj === 'object'
}
//给要观察的对象的_ob_属性存放Observer对象,标记已观察
export function def(obj, key, val, enumerable) {
	Object.defineProperty(obj, key, {
		value: val,
		enumerable: !!enumerable,
		writable: true,
		configurable: true
	})
}

全栈学习交流

上面已经简单实现了数据绑定,接下来不妨使用 npm run test 命令来测试下项目吧。

猜你喜欢

转载自blog.csdn.net/li420520/article/details/83379972