总结:
主要针对上一版的缺陷进行了优化
为每个属性增加了
propPath
属性,记录属性自身的path
,同时watchEvents
也利用path
设置 keydispatch
watchEvents
时,由path
向上冒泡在 dispatch
watchEvents
时,修正属性链上的value
及oldValue
值缺点:
CustomEvent
中的bubbles
目前是写死的
代码:
<!DOCTYPE html>
<html>
<head>
<title>dymanic_data_binding_03</title>
<meta charset="utf-8">
<style type="text/css">
</style>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/styles/default.min.css" />
</head>
<body>
<ol>要求:
<li>
<h3>firstName 和 lastName 作为 name 的属性,其中任意一个发生变化,都会得出以下结论:"name 发生了变化。"这种机制符合”事件传播“机制,方向是从底层往上逐层传播到顶层。</h3>
<pre><code>
var app2 = new Observer({
name: {
firstName: 'shaofeng',
lastName: 'liang'
},
age: 25
});
app2.$watch('name', function(newName) {
console.log('我的姓名发生了变化,可能是姓氏变了,也可能是名字变了。')
});
app2.data.name.firstName = 'hahaha'; // 输出:我的姓名发生了变化,可能是姓氏变了,也可能是名字变了。
app2.data.name.lastName = 'blablabla'; // 输出:我的姓名发生了变化,可能是姓氏变了,也可能是名字变了。
</code></pre>
<pre><code>
var app1 = new Observer({
name: {
firstName: 'shaofeng',
lastName: {
test: 1
}
},
age: 25
});
app1.$watch('name', function(newName) {
console.log('我的姓名发生了变化,可能是姓氏变了,也可能是名字变了。')
});
app1.data.name.lastName.test = 'fdfff';
app1.data.name = 2;
app1.data.name.firstName = 'hahaha'; // 此时不会报错,但是赋值也不会成功
</code></pre>
</li>
</ol>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/highlight.min.js"></script>
<script type="text/javascript">
hljs.initHighlightingOnLoad();
function Observer (data) {
this.watchEvents = {};
this.defineProperty(this, 'data', data);
}
Observer.prototype.defineProperty = function (obj, key, data) {
if (_getType(data) !== 'object') throw 'wrong data type';
let _this = this;
Object.keys(data).forEach(function (v, i) {
let dataVal;
if (_getType(obj[key]) !== 'object') {
// obj[key] && (delete obj[key]); // @tag1 处进行处理
obj[key] = {};
}
Object.defineProperty(obj[key], v, {
get: function () {
// console.log(`你访问了 ${v}`);
return dataVal;
},
set: function (value) {
_this._setPath(obj, key, v);
let oldValue = obj[key][v],
curPath = _getPath(obj[key], v);
if (_getType(value) === 'object') {
if (Object.keys(value).length > 0) {
_this.defineProperty(obj[key], v, value);
} else {
/* @tag: 1 */
console.log(`你设置了 ${curPath},新的值为 ${JSON.stringify(value)}`);
dataVal = value;
}
} else {
if(v !== 'propPath') {
console.log(`你设置了 ${curPath},新的值为 ${value}`);
}
dataVal = value;
}
_this.watchEvents[`${curPath}`] = _polyfillCustomEvent(`$watch_${v}`, {
bubbles: true, // 具体使用方式???
detail: {
value: _getJsonObj(value),
oldValue: _getJsonObj(oldValue)
}
});
_this._dispatchEvent(`${curPath}`);
},
enumerable: true,
configurable: true
});
obj[key][v] = data[v];
}, obj);
}
Observer.prototype.$watch = function (key, callback) {
let _this = this;
// $watch 仅对 data 的第一层属性有效
document.addEventListener(`$watch_${key}`, function (e) {
(e.detail.oldValue !== e.detail.value) && callback(e.detail.value);
})
};
Observer.prototype._dispatchEvent = function (eventPath) {
let _this = this,
event = this.watchEvents[eventPath],
targetPropName = eventPath.substr(eventPath.lastIndexOf('_') + 1),
parentPath = eventPath.substr(0, eventPath.lastIndexOf('_')),
parentEvent, parentLiveValue;
if (!event) return;
if (parentPath) {
parentEvent = this.watchEvents[parentPath];
if (parentEvent) {
parentLiveValue = JSON.parse(JSON.stringify(_this._getValueFromPath(parentPath)));
parentEvent.detail.value = JSON.stringify(parentLiveValue);
parentLiveValue[targetPropName] = event.detail.oldValue;
parentEvent.detail.oldValue = JSON.stringify(parentLiveValue);
}
}
document.dispatchEvent(event);
if (event.bubbles) {
this._dispatchEvent(parentPath);
}
}
Observer.prototype._setPath = function (obj, key) {
if (obj === this) return;
obj[key].propPath = _getPath(obj, key);
return obj[key].propPath;
}
Observer.prototype._getValueFromPath = function (path) {
let pathArr = path.split('_');
return pathArr.reduce((last, curVal, curIndex, arr) => {
return last[curVal];
}, this.data);
}
function _getJsonObj (value) {
return _getType(value) === 'object' ? JSON.stringify(value) : value;
}
function _getPath (obj, key) {
return `${obj.propPath ? `${obj.propPath}_` : ''}${key}`;
}
function _polyfillCustomEvent (event, params) {
if (!CustomEvent) {
function CustomEvent (event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
let evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
CustomEvent.prototype = window.Event.prototype;
}
return new CustomEvent(event, params);
}
function _getType (obj) {
return Object.prototype.toString.call(obj).match(/ .+(?=\])/)[0].trim().toLowerCase();
}
</script>
</body>
</html>