前言
本文从一个简单的双向绑定开始,逐步升级到由defineProperty和Proxy分别实现的响应式系统,注重入手思路,抓住关键细节,希望能对你有所帮助。
一、极简双向绑定
首先从最简单的双向绑定入手:
// html<input type="text" id="input"><span id="span"></span>
// jslet input = document.getElementById('input')let span = document.getElementById('span')input.addEventListener('keyup', function(e) { span.innerHTML = e.target.value})
以上似乎运行起来也没毛病,但我们要的是数据驱动,而不是直接操作dom:
// 操作obj数据来驱动更新let obj = {}let input = document.getElementById('input')let span = document.getElementById('span')Object.defineProperty(obj, 'text', { configurable: true, enumerable: true, get() { console.log('获取数据了') return obj.text }, set(newVal) { console.log('数据更新了') input.value = newVal span.innerHTML = newVal }})input.addEventListener('keyup', function(e) { obj.text = e.target.value})
以上就是一个简单的双向数据绑定,但显然是不足的,下面继续升级。
二、以defineProperty实现响应系统
在Vue3版本来临前以defineProperty实现的数据响应,基于发布订阅模式,其主要包含三部分:Observer、Dep、Watcher。
1. 一个思路例子
// 需要劫持的数据let data = { a: 1, b: { c: 3 }}// 劫持数据dataobserver(data)// 监听订阅数据data的属性new Watch('a', () => { alert(1)})new Watch('a', () => { alert(2)})new Watch('b.c', () => { alert(3)})
以上就是一个简单的劫持和监听流程,那对应的observer和Watch该如何实现?
2. Observer
observer的作用就是劫持数据,将数据属性转换为访问器属性,理一下实现思路:
①Observer需要将数据转化为响应式的,那它就应该是一个函数(类),能接收参数。
②为了将数据变成响应式,那需要使用Object.defineProperty。
③数据不止一种类型,这就需要递归遍历来判断。
// 定义一个类供传入监听数据class Observer { constructor(data) { let keys = Object.keys(data) for (let i = 0; i < keys.length; i++) { defineReactive(data, keys[i], data[keys[i]]) } }}// 使用Object.definePropertyfunction defineReactive (data, key, val) { // 每次设置访问器前都先验证值是否为对象,实现递归每个属性 observer(val) // 劫持数据属性 Object.defineProperty(data, key, { configurable: true, enumerable: true, get () { return val }, set (newVal) { if (newVal === val) { return } else { data[key] = newVal // 新值也要劫持 observer(newVal) } } })}// 递归判断function observer (data) { if (Object.prototype.toString.call(data) === '[object, Object]') { new Observer(data) } else { return }}// 监听objobserver(data)
新闻热点
疑难解答
图片精选