总结:
之前的代码有误,忘记在
defineProperty
之后给属性赋值,会导致所有属性值相同缺点:
$watch
只能监测data
的第一层属性,主要原因是在循环defineProperty
时无法获取当前属性的全部引用链,比如data.name.firstName
在循环中只能取到'firstName'
,目前没有想出办法拿到'data_name_firstName'
-
- 主要利用
CustomEvent
,其中dispatchEvent
没太搞懂,为什么放在$watch
中就无法运行回调?放在setter
中就可以。 - 目前的理解是
dispatchEvent
是脚本内部的触发,放在setter
中就是每次设置值时触发,而放在$watch
中就相当于只有执行$watch
的时候触发
- 主要利用
代码:
<!DOCTYPE html>
<html>
<head>
<title>dymanic_data_binding_02</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>如果传入参数对象时一个“比较深”的对象(也就是其属性值也可能是对象),那该怎么办?</h3>
<pre><code>
let obj = new Observer({
a: 1,
b: 2,
c: {
d: 3,
e: 4
}
});
obj.data.c.d // 你访问了 d
obj.data.c.e = 100; // 你设置了 e,新的值为 100
obj.data.a // 你访问了 a
obj.data.b = 'science'; // 你设置了 b,新的值为 science
</code></pre>
</li>
<li>
<h3>如果设置新的值是一个对象的话,新设置的对象的属性是否能继续响应getter和setter?</h3>
<pre><code>
let app1 = new Observer({
name: 'youngwind',
age: 25
});
app1.data.name = {
lastName: 'liang',
firstName: 'shaofeng'
}
app1.data.name.lastName // 你访问了 lastName
app1.data.name.firstName = 'lalala'; // 你设置了 firstName,新的值为 lalala
</code></pre>
</li>
<li>
<h3>考虑传递回调函数。在实际应用中,当特定数据发生改变的时候,我们是希望做出一些特定的事情的,而不是每一次都只能打印出一些信息。所以,我们需要支持传入回调函数的功能。</h3>
<pre><code>
let app1 = new Observer({
name: 'youngwind',
age: 25
});
app1.$watch('age', function (age) {
console.log(`我的年纪变了,现在已经是:${age}岁了`);
});
app1.data.age = 100; // 我的年纪变了,现在已经是:100岁了
</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(data[v]) === 'object') {
_this.defineProperty(obj[key], v, data[v]);
} else {
if (_getType(obj[key]) !== 'object') {
delete obj[key];
obj[key] = {};
}
Object.defineProperty(obj[key], v, {
get: function () {
// console.log(`你访问了 ${v}`);
return dataVal;
},
set: function (value) {
if (_getType(value) === 'object') {
_this.defineProperty(obj[key], v, value);
} else {
let oldValue = obj[key][v];
console.log(`你设置了 ${v},新的值为 ${value}`);
dataVal = value;
_this.watchEvents[v] = _polyfillCustomEvent(`$watch_${v}`, {
detail: {
value: value,
oldValue: oldValue
}
});
document.dispatchEvent(_this.watchEvents[v]);
}
},
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);
})
};
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>