实现一个简单版本的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)
	}
}
//通知所有订阅者
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)
    }
}
复制代码

实现

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

test/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: [],
      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)
  }
  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命令来测试下项目吧。

转载于:https://juejin.im/post/5bcae323e51d4527ed0094f5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值