版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://cloud.tencent.com/developer/article/1347519
在看Vue的API时,里面提到修改Model层,会实时更新View视图,底层原理利用的是ES5的getter和setter方法,通过 Object.defineProperty 把实例属性全部转为 getter/setter。故温故一遍getter和setter定义属性的方法。
有个注意的地方,get与set的函数体都不能再定义本身该属性,否则执行的时候会陷入死循环,抛出栈溢出。
var o = {
set current (str) {
return this.log[this.log.length] = str;
},
log: []
}
此处current的值为undefined
var o = {
set current (str) {
this.current = str
return this.log[this.log.length] = str;
},
log: []
};
o.current = 'a' //抛出错误,进入死循环
//Uncaught RangeError: Maximum call stack size exceeded
var o = { a:0 }
Object.defineProperty(o, "b", { get: function () { return this.a + 1; } });
console.log(o.b) // Runs the getter, which yields a + 1 (which is 1)
下面这个代码会报错TypeError
var o = {a:0}
Object.defineProperty(o, "b", {
set: function (val) {
this.a = val + 2
},
get: function () {
return this.a + 1;
},
writable: true,
configurable: true,
enumerable: true
});
//执行报错:Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
//删除writable属性就可以了
双向数据绑定底层的思想非常的基本,它可以被压缩成为三个步骤:
1.我们需要一个方法来识别哪个UI元素被绑定了相应的属性
2.我们需要监视属性和UI元素的变化
3.我们需要将所有变化传播到绑定的对象和元素
方法一:利用发布订阅模式,订阅数据变更
html代码
<div class="app">
<input type="text" data-bind-id="demo">
<p data-id="demo"></p>
</div>
javascript代码
function DataBinder(object_id) {
//创建一个简单地PubSub对象
var pubSub = {
callbacks: {},
on: function (msg, callback) {
this.callbacks[msg] = this.callbacks[msg] || [];
this.callbacks[msg].push(callback);
},
publish: function (msg) {
this.callbacks[msg] = this.callbacks[msg] || [];
for (var i = 0, len = this.callbacks[msg].length; i < len; i++) {
this.callbacks[msg][i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
};
var data_attr = "data-bind-" + object_id,
view_attr = 'data-' + object_id,
message = object_id + ":change";
var changeHandler = function (e) {
var target = e.target || e.srcElemnt, //IE8兼容
prop_name = target.getAttribute(data_attr);
if (prop_name && prop_name !== "") {
pubSub.publish(message, target, prop_name, target.value);
}
};
//监听变化事件并代理到PubSub
if (document.addEventListener) {
document.addEventListener("keyup", changeHandler, false);
} else {
//IE8使用attachEvent而不是addEventListener
document.attachEvent("keyup", changeHandler);
}
//PubSub将变化传播到所有绑定元素
pubSub.on(message, function (event, prop_name, new_val) {
var elements = document.querySelectorAll("[" + view_attr + "=" + prop_name + "]"), tag_name;
for (var i = 0, len = elements.length; i < len; i++) {
tag_name = elements[i].tagName.toLowerCase();
if (tag_name === "input" || tag_name === "textarea" || tag_name === "select") {
elements[i].value = new_val;
} else {
elements[i].innerHTML = new_val;
}
}
});
return pubSub;
}
DataBinder('id');
方法二:数据劫持
html代码
<div class="app">
<input type="demo" h-model="demo" spellcheck="true">
<p h-text="demo"></p>
</div>
javascript代码
(function(window, undefined) {
var addEvent = (function () {
if(window.addEventListener) {
return function (el, type, fn) {
if(el && el.nodeName || el === window) {
el.addEventListener(type, fn, false)
}else if(el.length) {
for(var item of el) {
addEvent(item, type, fn);
}
}
}
}else {
return function (el, type, fn) {
if(el && el.nodeName || el === window) {
el.attachEvent('on' + type, function () {
return fn.call(el, event);
})
}else if(el.length) {
for(var item of el) {
addEvent(item, type, fn);
}
}
}
}
})();
var Hue = function(opt) {
var el = document.querySelector(opt.el);
var data = opt.data || {};
this.el = el;
this.data = data;
this.bindText();
this.bindModel();
return this;
};
Hue.prototype = {
constructor: Hue,
// **前端数据劫持**
defineObj: function(obj, prop, value) {
var _value = value || '', _this = this;
try {
Object.defineProperty(obj, prop, {
get: function() {
return _value;
},
set: function(newVal) {
_value = newVal;
_this.bindText();
},
enumerable: true,
configurable: true
});
} catch (error) {
// IE8+ 才开始支持defineProperty,这也是Vue.js不支持IE8的原因
console.log("Browser must be IE8+ !");
}
},
bindModel: function() {
var modelDOMs = this.el.querySelectorAll('[h-model]'), lenModel = modelDOMs.length;
var _this = this, i, propModel;
for (i = 0; i < lenModel; i++) {
propModel = modelDOMs[i].getAttribute('h-model');
//init value
modelDOMs[i].value = this.data[propModel] || '';
// 前端数据劫持
this.defineObj(this.data, propModel);
}
addEvent(modelDOMs, 'keyup', function (e) {
_this.data[propModel] = e.target.value;
})
},
bindText: function() {
var textDOMs = this.el.querySelectorAll('[h-text]'),
lenText = textDOMs.length,
prppText,
j;
for (j = 0; j < lenText; j++) {
propText = textDOMs[j].getAttribute('h-text');
textDOMs[j].innerHTML = this.data[propText] || '';
}
}
};
window.Hue = Hue;
})(window);
// test...
new Hue({
el: '.app',
data: {
demo: 'Kenny'
}
});
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有