这里为07中添加了响应式deepProperty函数
// ==========================响应式函数deepProperty===================================
// 要拦截的数组方法
let arrs = [
"push",
"pop",
"shift",
"unshift",
"reserve",
"splice",
"sort",
];
// 获取数组对象
let arrays = Object.create(Array.prototype);
// 在原型数组中添加拦截器
arrs.forEach((element) => {
//做一层proxy处理
arrays[element] = function (...args) {
//在这里对数据进行响应式化
// for (let index = 0; index < arguments.length; index++) {
// 不是对象,是字符,或数字???
// console.log(arguments[index], 122);
deepProperty.call(this, args);
// }
//返回响应Array原型
return Array.prototype[element].apply(this, args);
};
});
// 对象遍历
function deepProperty(o) {
let keys = Object.keys(o);
for (let index = 0; index < keys.length; index++) {
let key = keys[index];
let val = o[key];
if (Array.isArray(val)) {
// ======数值响应式start======
val.__proto__ = arrays;
// ======数值响应式end======
for (let j = 0; j < val.length; j++) {
deepProperty.call(this, val[j]);
}
} else {
deepCreateProperty.call(this, o, key, val, true);
}
}
}
/**
*@params target:any
*@params key:String
*@params val:null
*@params enumerable:boolean
*/
//将数据转换成响应式
function deepCreateProperty(target, key, val, enumerable) {
// 判断val 是否是对象
if (typeof val === "object" && val !== null && !Array.isArray(val)) {
// 是对象就递归
deepProperty(val);
}
Object.defineProperty(target, key, {
enumerable: !!enumerable, //默认不传参 undefinde false
get: () => {
console.log("得到val");
return val;
},
set: (newVal) => {
if (val === newVal) return;
this.mountComponet();
console.log("改变val");
val = newVal;
},
});
}
// ==========================combine函数 合并数据与带有{{}}虚拟的Domdom===================================
//解析data.xxx.xx
/**
*@params obj:Object data:{nama:{firstName:'小',lastName:'陈'}}
*@params path:String 'xxx.xxx'
*@return res:any
*/
function getValueByPath(data, path) {
let paths = path.split(".");
//[name, firstName];
let res = data;
//循环查找
for (let index = 0; index < paths.length; index++) {
res = res[paths[index]];
}
// res === '小'
return res;
}
//正则,用来匹配{{}}
let r = /\{\{(.+?)\}\}/g;
// 带坑的vnode和数据 合并 生成带数据vnode
/**
*@params vnode:VNode (带有{{}}的虚拟dom)
*@params data:Object(数据源)
*@return _vnode:VNode(带有数据的虚拟dom)
*/
function combine(vnode, data) {
let {
tag: _tag,
data: _data,
value: _value,
type: _type,
children: _children,
} = vnode;
let _vnode = null;
if (_type === 3) {
//文本节点 判断 是否有{{}}
_value = _value.replace(r, (_, g) => {
let key = g.trim();
let value = getValueByPath(data, key);
return value;
});
_vnode = new VNode(_tag, _data, _value, _type);
} else if (_type === 1) {
_vnode = new VNode(_tag, _data, _value, _type);
_children.forEach((element) => {
_vnode.appendChild(combine(element, data));
});
}
return _vnode;
}
/*
// ==========================paraseVnode函数 虚拟dom转真实dom===================================
*@params tag:String 'div||span||h1'
*@params data:Object '{id:'root,class:'theme'...}'
*@params value:String 'nodeValue || undefined'
*@params type:Number 'nodeType=1 || 2 ||3 '
*@params children:Array<Vnode>
@return dom:HtmlElement
*/
function createElement(tag, data, value, type, children) {
let dom; //定义储存dom变量
// 元素节点
if (type === 1) {
//创建元素
dom = document.createElement(tag);
//添加属性
for (const key in data) {
if (data.hasOwnProperty.call(data, key)) {
dom.setAttribute(key, data[key]);
}
}
// 存在子节点遍历循环递归
if (children) {
for (let index = 0; index < children.length; index++) {
let nodeTag = children[index].tag;
let nodeData = children[index].data;
let nodeValue = children[index].value;
let nodeType = children[index].type;
let nodeChildren = children[index].children;
dom.appendChild(
createElement(
nodeTag,
nodeData,
nodeValue,
nodeType,
nodeChildren
)
);
}
}
}
// 文本节点,创建文本节点
else if (type === 3) {
dom = document.createTextNode(value);
}
return dom;
}
/*
*@params vnode:Object
@return dom:HtmlElement
*/
function paraseVnode(vnode) {
return createElement(
vnode.tag,
vnode.data,
vnode.value,
vnode.type,
vnode.children
);
}
// ==========================getVNode函数 真实dom转虚拟dom===================================
/*
*@params tag:String 'div||span||h1'
*@params data:Object '{id:'root,class:'theme'...}'
*@params value:String 'nodeValue || undefined'
*@params type:Number 'nodeType=1 || 2 ||3 '
*/
class VNode {
constructor(tag, data, value, type) {
this.tag = tag && tag.toLocaleLowerCase();
this.data = data;
this.value = value;
this.type = type;
this.children = []; //子节点数组,默认[]
}
// 添加子节点
appendChild(childrenNode) {
this.children.push(childrenNode);
}
}
/*
*@params realDom:HtmlElement '真实的DOM对象'
*@return vNode:Object '虚拟的DOM对象'
*/
function getVNode(realDom) {
let nodeType = realDom.nodeType; //节点类型
let _vode = null;
// 元素节点
if (nodeType === 1) {
let tag = realDom.nodeName; //标签名
let attributes = realDom.attributes; //对象属性
let childNodes = realDom.childNodes; //子节点
// 获取属性对象
let data = {};
for (let i = 0; i < attributes.length; i++) {
let key = attributes[i].nodeName;
let value = attributes[i].value;
data[key] = value;
}
// 生成虚拟dom对象
_vode = new VNode(tag, data, undefined, nodeType);
//如果有子节点,添加子节点,递归遍历 ===本节难点!!!===
for (let i = 0; i < childNodes.length; i++) {
_vode.appendChild(getVNode(childNodes[i]));
}
}
// 文本节点
else if (nodeType === 3) {
_vode = new VNode(undefined, undefined, realDom.nodeValue, nodeType);
}
return _vode; //虚拟dom
}
// ==========================Vue构造函数===================================
function Vue(options) {
// 内部数据使用_ 开头 ,只读数据$开头
this._data = options.data;
this._el = options.el;
//准备工作(模板)
this.$el = this._templateNode = document.querySelector(this._el); //获取挂载的对象
this._parent = this._templateNode.parentNode; //父节点
//将数据全部转成响应式的
deepProperty.call(this, this._data);
//挂载
this.mount();
}
//挂载
Vue.prototype.mount = function () {
this.render = this.createRenderFn(); //生成带有data数据的vnode
//挂载dom
this.mountComponet();
};
// 函数颗粒化,缓存带有{{}}的vnode
Vue.prototype.createRenderFn = function () {
//带坑的虚拟dom
let ast = getVNode(this.$el);
return function () {
// 生成带数据vnode
let combineData = combine(ast, this._data);
return combineData;
};
};
//这里就是以后当数据发生变化watcher来进行调用 更新组件vue diff算法就在这里
Vue.prototype.mountComponet = function () {
this.updata(this.render());
};
// 更新dom 到页面
Vue.prototype.updata = function (vnode) {
//将带有数据的vnode转成真实的dom
let realDom = paraseVnode(vnode);
//这里我们直接操作dom替换了,后面再细讨论
this._parent.replaceChild(realDom, document.querySelector("#root"));
};
// 创建Vue的实例
const vm = new Vue({
el: "#root",
data: {
age: 1,
name: {
firstName: "小",
lastName: "陈",
},
date: [1, 2, 3, 4],
},
});
总结这里还是有点瑕疵,数组中的1,2,3,4不能响应式