什么是双向绑定?
1.当一个对象(或变量)的属性改变,那么调用这个属性的地方显示也应该改变,模型到视图(model => view)
2.当调用属性的这个地方改变了这个属性(通常是一个表单元素),那么这个对象(或变量)的属性也会改为最新的值 ,即视图到模型(view => model)
那么vue利用es5的defineProperty特性实现双向绑定的原理是什么呢?
例如:
var person= {};
Object.defineProperty(person, "name", {
value: '张三'
})
console.log(person.name); // 张三
传参
-
第一个参数:要设置的目标对象(必填)
-
第二个参数:需要定义的属性或方法的名称(必填)
-
第三个参数:目标属性所拥有的特性。(descriptor)(必填)
三个参数都是必填项,重点介绍第三个参数 descriptor
descriptor
-
value:属性的值
-
writable:如果为false,属性的值就不能被重写, 只能为只读了
-
configurable:总开关,一旦为false,就不能再设置他的(value,writable,configurable)
-
enumerable:是否可枚举(是否能在for...in循环中遍历出来或在Object.keys中列举出来)
-
get:后面会重点讲解
-
set:后面会重点讲解
descriptor 默认值
var person = {};
Object.defineProperty(person ,"name",{
value: '张三',
writable :false,
enumerable: false,
configurable: false
});
console.log(person.name); // 张三
这里需要注意的是!!!!
configurable
总开关,第一次设置 false 之后,,第二次什么设置也不行了。
writable
如果设置为fasle,就变成只读了。
enumerable
属性特性 enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。
set 和 get
在 descriptor 中不能同时设置访问器(get 和 set)和 wriable 或 value,否则会错,就是说想用 get 和 set,就不能用 writable 或 value 中的任何一个。
var user = {};
var defaultName = "狂奔的蜗牛";
Object.defineProperty(user,"name",{
get:function(){
console.log("你是不是来获取值啦");
return defaultName;
},
set:function(value){
console.log("你是不是来设置值啦");
defaultName = value;
}
})
console.log(user.name);
user.name = "狂奔的萝卜";
console.log(user.name);
每当我获取user.name属性时,get方法被调用,get 方法对应的函数被执行,输出 你是不是来获取值啦;每当我设置user.name属性时,set方法对应的函数被执行,输出 你是不是来设置值啦 ; 是的,我们监控到了代码对user.name属性的存取。
模型到视图(model => view)的同步
说明 假设id="model" 的元素的 value 是user.name的值,既然我们可以在改变属性的执行日志输出(console.log("你是不是来设置值啦");),那么,我们在设置值的时候给id="model" 的元素设置下新值,不就实现了从模型到视图
<body>
手写一个简单双向绑定<br/>
<input type="text" id="model"><br/>
<div id="modelText"></div>
</body>
<script>
var user = {};
var defaultName = "狂奔的蜗牛";
document.querySelector("#model").value = defaultName;
document.querySelector("#modelText").textContent = defaultName;
//定义属性 监控改变
Object.defineProperty(user,"name",{
get:function(){
console.log("你是不是来获取值啦");
return defaultName;
},
set:function(newValue){
console.log("设置新值");
defaultName = newValue;
console.log("实现 模型 => 视图");
document.querySelector("#model").value = newValue;
document.querySelector("#modelText").textContent = newValue;
}
})
console.log("2s 后改变值");
setTimeout(() => {
//改变值
user.name = "狂奔的萝卜";
}, 2000);
</script>
视图到模型(view => model)的同步
问: 我们能捕捉到view对值更改吗?
答:可以!! id="model" 的input元素的 value 是user.name的值,填充在这个文本框里面,文本框有个“ keyup” 事件,当我们在文本框中输入文字的时候,文本框的值会跟着改变,并且会连续触发keyup事件,那么我们只需要监听这个事件,是不是就可以捕捉到view对值的更改了??既然文本框的值会跟着改变,我们获取最新的值再把新值更新到user.name属性,不就实现了视图到模型(view => model)的同步
<body>
手写一个简单双向绑定<br/>
<input type="text" id="model"><br/>
<div id="modelText"></div>
</body>
<script>
var user = {};
var defaultName = "狂奔的蜗牛";
var model = document.querySelector("#model");
var modelText = document.querySelector("#modelText");
model.value = defaultName;
modelText.textContent = defaultName;
//定义属性 监控改变
Object.defineProperty(user,"name",{
get:function(){
console.log("你是不是来获取值啦");
return defaultName;
},
set:function(newValue){
console.log("设置新值");
defaultName = newValue;
model.value = newValue;
modelText.textContent = newValue;
}
})
model.addEventListener("keyup", function () {
user.name = this.value;
console.log("实现 视图 => 模型");
}, false)
</script>
【最终源码】
在上述代码的基础上,加入了 用户输入中文的判断(用户输入中文时,频繁触发 keyup事件,但实际上输入并没有结束。)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>双向绑定</title>
</head>
<body>
手写一个简单双向绑定<br/>
<input type="text" id="model"><br/>
<div id="modelText"></div>
</body>
<script>
var model = document.querySelector("#model");
var modelText = document.querySelector("#modelText");
var defaultName = "defaultName";
var userInfo = {}
model.value = defaultName;
Object.defineProperty(userInfo, "name", {
get: function () {
return defaultName;
},
set: function (value) {
defaultName = value;
model.value = value;
console.log("-----value");
console.log(value);
modelText.textContent = value;
}
})
userInfo.name = "new value";
var isEnd = true;
model.addEventListener("keyup", function () {
if (isEnd) {
userInfo.name = this.value;
}
}, false)
//加入监听中文输入事件
model.addEventListener("compositionstart", function () {
console.log("开始输入中文");
isEnd = false;
})
model.addEventListener("compositionend", function () {
isEnd = true;
console.log("结束输入中文");
})
</script>
</html>
这样基于object.definePropety特性就完成了双向绑定。
下面我们再来看一下object.definePropety的用法及其他属性
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj要在其上定义属性的对象。prop要定义或修改的属性的名称。descriptor将被定义或修改的属性描述符。
返回值
被传递给函数的对象。
描述
该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for...in 或 Object.keys 方法), 这些属性的值可以被改变,也可以被删除。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的。
属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。
数据描述符和存取描述符均具有以下可选键值:
configurable- 当且仅当该属性的 configurable 为 true 时,该属性
描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。 enumerable- 当且仅当该属性的
enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
数据描述符同时具有以下可选键值:
value- 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为
undefined。 writable- 当且仅当该属性的
writable为true时,value才能被赋值运算符改变。默认为 false。
存取描述符同时具有以下可选键值:
get- 一个给属性提供 getter 的方法,如果没有 getter 则为
undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。 - 默认为
undefined。 set- 一个给属性提供 setter 的方法,如果没有 setter 则为
undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。 - 默认为
undefined。 - 如果一个描述符不具有value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。
-
记住,这些选项不一定是自身属性,如果是继承来的也要考虑。为了确认保留这些默认值,你可能要在这之前冻结
Object.prototype,明确指定所有的选项,或者通过Object.create(null)将__proto__属性指向null。
来源:https://www.cnblogs.com/Narkea/p/10554715.html