须要的环境以下:vue
须要的知识储备:typescript
该实践的整体结构是以一个Watcher
实现类为载体,模拟Vue
的方式,将须要进行响应的数据(data
)、渲染函数(render
)、挂载的dom
节点输入进来。而后对传参送进来的data
的属性进行改变的时候,会触发render
函数的调用(前提是这个修改的数据有在渲染函数中被使用到)。数组
Watcher
类的结构bash
class Watcher {
// 渲染函数数组,一个数据可能不止存在于一个渲染函数当中,可能会有多个渲染函数调用
renderList: Array<Function>;
// 数据
data: any;
// 挂载的el元素
el: String | HTMLElement;
}
复制代码
上面是Watcher
类的结构,将数据、渲染函数、dom
元素传进来后,就会进行自动进行监测。babel
代理工具实现dom
notifySet
,是一个Set
。这个集合存放着哪些属性被观察到,若是被观察到的,对其进行setter
调用的时候,会触发渲染函数进行渲染。flag
来实现是否深度观察。代理思路函数
getter
和setter
进行改写,在getter
的时候进行依赖的肯定(由于在render
函数使用到了,因此这个依赖应当被监控),在setter
的时候对渲染函数进行调用(当值改变的时候,须要对相应的渲染内容进行更新,这也就是本文章的目的)notifySet
进行添加属性的时候,只须要将被观察到的属性放进这个set
中,无关的属性则不放进去。本项目的实现思路是以下:
项目结构工具
-DataBind
--core
|- Proxy.ts // 代理工具
--utils
|- Utils.ts // 通用工具
Watcher.ts
复制代码
Watcher
类的具体实现ui
构造器this
interface WatcherOption {
el: String | HTMLElement; // 绑定现有的dom对象
data: any; // 数据对象
render: Function; // 渲染函数
}
constructor(options: WatcherOptions) {
if (typeof options.el === 'string') {
this.el = document.getElementById(options.el);
} else {
// @ts-ignore
this.el = options.el;
}
this.data = makeProxy.call(this, options.data); // 先将整一个数据对象深度遍历构建代理层
this.addRender(options.render); // 将渲染函数添加到渲染函数数组中
}
复制代码
构造器传进来配置(options
),配置有三个重要的属性:挂载对象、数据对象、渲染函数。具体流程以下:
data
属性渲染函数管理
/** * @description 为渲染函数所调用到的对象查询须要代理的对象 * @param fn */
public addRender(fn: Function): void {
Watcher.target = this; // 开启代理模式,这个target对象是Watcher类的静态变量,在proxy函数里面会使用到
this.renderList.push(fn);
this.notify();
Watcher.target = null; // 关闭代理模式
}
复制代码
Watcher.target
是Watcher
的静态属性,这个属性的做用是记录当前进行观测的对象。这个对象会在代理的时候用到。使用这个缘由是:在添加依赖的时候,先当前的Watcher
设置为Watcher.target
,而后调用渲染函数,渲染函数会调用响应的属性的getter
,从而触发代理层进行添加依赖(后面从新渲染的时候,是不会进行依赖的重复添加,由于Watcher.target
为空。这个在makeProxy函数里面能够查看到。
因此这个函数是先将记录当前的Watcher
实例,而后将渲染函数推动数组中,再进行调用渲染函数。此时会进行依赖的添加,而后将target
设为空。
代理层的实现
/** * @description 这个是咱们本文章的核心代码,由于我没有设置watch、computed属性,因此也就不须要筐来存放watcher。也就不会有Dep这个类 * @param object * @param this Wacther对象 */
export function makeProxy(this: Watcher, object: any): any {
object.__proxy__ = {};
// @ts-ignore
object.__proxy__.notifySet = new Set<string | number | symbol>();
object.__watcher__ = this;
// @ts-ignore
let proxy = new Proxy(object, {
get(target: any, p: string | number | symbol, receiver: any): any {
if (Watcher.target != null) {
Watcher.addDep(object, p); // 添加依赖
}
return target[p];
},
set(target: any, p: string | number | symbol, value: any, receiver: any): boolean {
if (target[p] !== value) {
// 两个值不一样的时候才须要去渲染视图层
target[p] = value;
if (target.__proxy__.notifySet.has(p)) {
target.__watcher__.notify();
}
}
return true;
}
});
// 获取对象的全部子属性,而且对子属性进行递归代理以便实现深度观察
let propertyNames = Object.getOwnPropertyNames(object);
for (let i = 0; i < propertyNames.length; i++) {
// @ts-ignore
if (isPlainObject(object[propertyNames[i]]) && (!propertyNames[i].startsWith('__') && !propertyNames[i].endsWith('__'))) {
object[propertyNames[i]] = makeProxy.call(this, object[propertyNames[i]]);
}
}
return proxy;
}
复制代码
此功能有两个特别注意的点,第一个是对object属性的添加、第二个是代理对象的细节。
object
属性的添加:
__proxy__.notifySet
:这是存放set
实例的属性,这个set
实例是进行记录哪一个属性被监听到,若是该属性被监听,那么会放到这个集合中,方便得知监听哪一个属性__watcher__
:这个是指向当前的wacher
实例对象。代理对象的生成:
new Proxy(object, {
get(target: any, p: string | number | symbol, receiver: any): any {
if (Watcher.target != null) {
Watcher.addDep(object, p); // 添加依赖
}
return target[p];
},
set(target: any, p: string | number | symbol, value: any, receiver: any): boolean {
if (target[p] !== value) {
// 两个值不一样的时候才须要去渲染视图层
target[p] = value;
if (target.__proxy__.notifySet.has(p)) {
// 仅仅当notifySet拥有这个属性的时候,才进行渲染函数的执行
target.__watcher__.notify();
}
}
return true;
}
});
复制代码
getter
:要特别主要到一个判断语句:if (Watcher.target != null) {
Watcher.addDep(object, p); // 添加依赖
}
复制代码
还记得在添加渲染函数的时候,修改Watcher.target
吗?这个条件不为空的时候就是在添加渲染函数的时候,将对象的属性添加进notifySet
中,方便调用该属性的时候执行回调函数
setter
:这个已经代码已经解释得很清楚了,就是判断这个属性有没有被渲染函数被添加至集合中,若是有的话,就进行调用渲染函数。Watcher
// @ts-ignore
import {makeProxy} from "./core/Proxy";
interface WatcherOption {
el: String | HTMLElement; // 绑定现有的dom对象
data: any; // 数据对象
render: Function; // 渲染函数
}
/** * @description 观察者对象,因为咱们目的是用代理模式来进行模拟vue数据响应系统,那么就从简设计这个类 */
export class Watcher {
// 全局使用到watcher实例,指向当前的watcher对象,方便proxy使用
public static target: any;
data: any = {};
el: HTMLElement;
renderList: Array<Function> = new Array<Function>();
constructor(options: WatcherOption) {
if (typeof options.el === 'string') {
this.el = document.getElementById(options.el);
} else {
// @ts-ignore
this.el = options.el;
}
this.data = makeProxy.call(this, options.data); // 先将整一个数据对象深度遍历构建代理层
this.addRender(options.render); // 将渲染函数添加到渲染函数数组中
}
// 响应而且调用观察者对象
notify(): void {
for (let item of this.renderList) {
item.call(this.data, this.createElement);
}
}
/** * @description 为渲染函数所调用到的对象查询须要代理的对象 * @param fn */
public addRender(fn: Function): void {
Watcher.target = this; // 进行添加依赖的时候,要肯定给哪一个
this.renderList.push(fn);
this.notify();
Watcher.target = null;
}
/** * @description 为每一个数据对象添加代理层的须要观察的观察者列表 * @param object * @param property */
static addDep(object, property): void {
object.__proxy__.notifySet.add(property);
}
static removeDep(object, property): void {
object.__proxy___.notifySet.remove(property);
}
private createElement(innerHTML: string) {
_createElement(this.el, innerHTML);
}
}
const _createElement = (dom: HTMLElement, innerHtml: string) => {
dom.innerHTML = innerHtml;
};
复制代码
Proxy
/** * 在对象上添加一个属性 __proxy__ * 这个属性表明着这个对象的代理层所存放的东西 */
import {isPlainObject} from "../utils/Utils";
import {Watcher} from "../Watcher";
/** * @description 这个是咱们本文章的核心代码,由于我没有设置watch、computed属性,因此也就不须要筐来存放watcher。也就不会有Dep这个类 * @param object * @param this Wacther对象 */
export function makeProxy(this: Watcher, object: any): any {
object.__proxy__ = {};
// @ts-ignore
object.__proxy__.notifySet = new Set<string | number | symbol>();
object.__watcher__ = this;
// @ts-ignore
let proxy = new Proxy(object, {
get(target: any, p: string | number | symbol, receiver: any): any {
if (Watcher.target != null) {
Watcher.addDep(object, p); // 添加依赖
}
return target[p];
},
set(target: any, p: string | number | symbol, value: any, receiver: any): boolean {
if (target[p] !== value) {
// 两个值不一样的时候才须要去渲染视图层
target[p] = value;
if (target.__proxy__.notifySet.has(p)) {
// 仅仅当notifySet拥有这个属性的时候,才进行渲染函数的执行
target.__watcher__.notify();
}
}
return true;
}
});
// 获取对象的全部子属性,而且对子属性进行递归代理以便实现深度观察
let propertyNames = Object.getOwnPropertyNames(object);
for (let i = 0; i < propertyNames.length; i++) {
// @ts-ignore
if (isPlainObject(object[propertyNames[i]]) && (!propertyNames[i].startsWith('__') && !propertyNames[i].endsWith('__'))) {
object[propertyNames[i]] = makeProxy.call(this, object[propertyNames[i]]);
}
}
return proxy;
}
复制代码
utils
const _toString = Object.prototype.toString
/** * @description 用于普通的函数,提取函数的内部代码块 * @param func */
export function getFunctionValue(func: Function): string {
let funcString: string = func.toLocaleString();
let start: number = 0;
for (let i = 0; i < funcString.length; i++) {
if (funcString[i] == '{') {
start = i + 1;
break;
}
}
return funcString.slice(start, funcString.length - 1);
}
export function isPlainObject (obj: any): boolean {
return _toString.call(obj) === '[object Object]'
}
复制代码