什么是双向绑定?html
1.当一个对象(或变量)的属性改变,那么调用这个属性的地方显示也应该改变,模型到视图(model => view)vue
2.当调用属性的这个地方改变了这个属性(一般是一个表单元素),那么这个对象(或变量)的属性也会改成最新的值 ,即视图到模型(view => model)数组
那么vue利用es5的defineProperty特性实现双向绑定的原理是什么呢?ide
例如:函数
var person= {}; Object.defineProperty(person, "name", { value: '张三' }) console.log(person.name); // 张三
传参this
第一个参数:要设置的目标对象(必填)es5
第二个参数:须要定义的属性或方法的名称(必填)spa
第三个参数:目标属性所拥有的特性。(descriptor)(必填)prototype
三个参数都是必填项,重点介绍第三个参数 descriptor翻译
descriptor
value:属性的值
writable:若是为false,属性的值就不能被重写, 只能为只读了
configurable:总开关,一旦为false,就不能再设置他的(value,writable,configurable)
enumerable:是否可枚举(是否能在for...in循环中遍历出来或在Object.keys中列举出来)
get:后面会重点讲解
set:后面会重点讲解
var person = {}; Object.defineProperty(person ,"name",{ value: '张三', writable :false, enumerable: false, configurable: false }); console.log(person.name); // 张三
这里须要注意的是!!!!
总开关,第一次设置 false 以后,,第二次什么设置也不行了。
若是设置为fasle,就变成只读了。
属性特性 enumerable 定义了对象的属性是否能够在 for...in 循环和 Object.keys() 中被枚举。
在 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
描述符
才可以被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable
enumerable
为true
时,该属性才可以出如今对象的枚举属性中。默认为 false。
数据描述符同时具备如下可选键值:
value
undefined
。
writable
writable
为true
时,value
才能被赋值运算符改变。默认为 false。
存取描述符同时具备如下可选键值:
get
undefined
。当访问该属性时,该方法会被执行,方法执行时没有参数传入,可是会传入this
对象(因为继承关系,这里的this
并不必定是定义该属性的对象)。
undefined
。
set
undefined
。当属性值修改时,触发执行该方法。该方法将接受惟一参数,即该属性新的参数值。
undefined
。
记住,这些选项不必定是自身属性,若是是继承来的也要考虑。为了确认保留这些默认值,你可能要在这以前冻结 Object.prototype
,明确指定全部的选项,或者经过 Object.create(null)
将__proto__
属性指向null
。