前端数据双向绑定原理:Object.defineProperty()
Object.definedProperty方法可以在一个对象上直接定义一个新的属性、或修改一个对象已经存在的属性,最终返回这个对象。
Object.defineProperty(obj, prop, descriptor)
参数:
obj:被定义或修改属性的对象;
prop :要定义或修改的属性名称;
descriptor :对属性的描述;
返回值: obj
描述符(descriptor)说明
该方法允许开发者精确的对对象属性的定义和修改。通过正常赋值进行属性添加而构建的属性会被枚举器方法(如for..in循环或Object.keys方法)获取,从而导致属性值被外部方法改变或删除。而Object.defineProperty()可以避免以上描述的情况,通过Object.defineProperty()添加的属性是默认不可改变的。
属性描述参数(descriptor)主要由两部分构成:数据描述符(data descriptor)和访问器描述符(accessor descriptor)。数据描述符就是一个包含属性的值,并说明这个值可读或不可读的对象;访问器描述符就是包含该属性的一对getter-setter方法的对象。一个完整的属性描述(descriptor)必须是这两者之一,并且不可以两者都有。
数据描述符和访问器描述符各自都是对象,他们必须包含以下键值对:
configurable
仅当设置的属性的描述符需要被修改或需要通过delete来删除该属性时,configurable属性设置为true。默认为false。
enumerable
仅当设置的属性需要被枚举器(如for..in)访问时设置为true。默认为false。
数据描述符可以包含以下可选键值对:
value
设置属性的值,可以是任何JavaScript值类型(number,object,function等类型)。默认为undefined。
writable
仅当属性的值可以被赋值操作修改时设置为true。默认为false。
访问器描述符可以包含以下可选键值对:
get
属性的getter方法,若属性没有getter方法则为undefined。该方法的返回为属性的值。默认为undefined。
set
属性的setter方法,若属性没有setter方法则为undefined。该方法接收唯一的参数,作为属性的新值。默认为undefined。
请牢记,这些描述符的属性并不是必须的,从原型链继承而来的属性也可填充。为了保证这些描述符属性被填充为默认值,你可能会使用形如预先冻结Object.prototype、明确设置每个描述符属性的值、使用Object.create(null)来获取空对象等方式。
一个简单的例子:
var obj = {};
var descriptor = Object.create(null); // no inherited properties //所有描述符的属性被设置为默认值
descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor); //明确设置每个描述符的属性
Object.defineProperty(obj, 'key', {
enumerable: false,
configurable: false,
writable: false,
value: 'static'
}); //重用同一个对象作为描述符
function withValue(value) {
var d = withValue.d || (
withValue.d = {
enumerable: false,
writable: false,
configurable: false,
value: null
}
);
d.value = value;
return d;
}
Object.defineProperty(obj, 'key', withValue('static')); //如果Object.freeze方法可用,则使用它来防止对对象属性的修改
(Object.freeze || Object)(Object.prototype);
使用示例
创建一个属性
如果当前对象不存在我们要设置的属性,Object.defineProperty()会根据方法设置为对象创建一个新的属性。如果描述符参数缺失,则会被设置为默认值。所有布尔型描述符属性会被默认设置为false。而value,get,set会被默认设置为undefined。一个未设置get/set/value/writable的属性被称为一个“原生属性(generic)”,并且他的描述符(descriptor)会被“归类”为一个数据描述符(data descriptor)
var o = {}; //创建一个对象 //使用数据描述符来为对象添加属性
Object.defineProperty(o, 'a', {
value: 37,
writable: true,
enumerable: true,
configurable: true
});
//属性”a”被设置到对象o上,并且值为37 //使用访问器描述符来为对象添加属性
var bValue = 38;
Object.defineProperty(o, 'b', {
get: function() { return bValue; },
set: function(newValue) { bValue = newValue; },
enumerable: true,
configurable: true
});
o.b; //
//属性”b”被设置到对象o上,并且值为38。
//现在o.b的值指向bValue变量,除非o.b被重新定义 //你不能尝试混合数据、访问器两种描述符
Object.defineProperty(o, 'conflict', {
value: 0x9f91102,
get: function() { return 0xdeadbeef; }
});
//抛出一个类型错误: value appears only in data descriptors, get appears only in accessor descriptors(value只出现在数据描述符中,get只出现在访问器描述符中)
修改一个属性
当某个属性已经存在了,Object.defineProperty()会根据对象的属性配置(configuration)和新设置的值来尝试修改该属性。如果该属性的configurable被设置为false,则该属性无法被修改(这种情况下有个特殊情况:如果之前的writable设置为true,则我们仍可以将writable设置为false,一旦这么做之后,任何描述符属性将变得不可设置)。如果属性的configurable设置为false,则我们无法将属性的描述符在数据描述符和访问器描述符之间转换。
如果新设置的属性和该属性不同,并且该属性的configurable被设置为false,则一个类型错误(TypeError)会被抛出(除了上一段文字中说的特殊情况)。若新旧属性完全相同,则什么都不会发生。
可写特性-writable
当一个属性的writable被设置为false,这个属性就成为“不可写的(non-writable)”。该属性不可被重新赋值。
var o = {}; //创建一个对象 Object.defineProperty(o, 'a', {
value: 37,
writable: false
}); console.log(o.a); //
o.a = 25; //没有错误抛出
//在严格模式下会抛出错误
console.log(o.a); //仍然是37,赋值操作无效
正如上述代码所述,尝试重写一个“不可写(non-writable)”属性不会发生任何改变,也不会抛出错误。
可枚举特性-enumerable
属性的enumerable值定义对象的属性是否会出现在枚举器(for..in循环和Object.keys())中。
var o = {};
Object.defineProperty(o, 'a', {
value: 1,
enumerable: true
});
Object.defineProperty(o, 'b', {
value: 2,
enumerable: false
});
Object.defineProperty(o, 'c', {
value: 3
}); //enumerable默认设置为false
o.d = 4; //通过直接设置属性的方式,enumerable将被设置为true for (var i in o) {
console.log(i);
}
//打印出’a’和’d’ Object.keys(o); // ['a', 'd'] o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false
可配置特性-configurable
属性的configurable值控制一个对象的属性可否被delete删除,同时也控制该属性描述符的配置可否改变(除了前文所述在configurable为false时,若writable为true,则仍可以进行一次修改将writable改变为false)。
var o = {};
Object.defineProperty(o, 'a', {
get: function() { return 1; },
configurable: false
}); Object.defineProperty(o, 'a', {
configurable: true
}); //抛出错误
Object.defineProperty(o, 'a', {
enumerable: true
}); //抛出错误
Object.defineProperty(o, 'a', {
set: function() {}
}); //抛出错误(set之前被设置为undefined)
Object.defineProperty(o, 'a', {
get: function() { return 1; }
}); //抛出错误(即使新的get做的是相同的事,但方法的前后引用不相同)
Object.defineProperty(o, 'a', {
value: 12
}); //抛出错误 console.log(o.a); //
delete o.a; //什么都不发生
console.log(o.a); //
如果o.a属性的configurable为true,就不会有任何错误抛出,并且o.a在最后的delete操作中会被删除。
添加属性时的默认值
考虑描述符特性的默认值如何被应用是非常重要的。正如下面示例所示,简单的使用”.”符号来设置一个属性和使用Object.defineProperty()是有很大区别的。
var o = {}; o.a = 1;
//等同于:
Object.defineProperty(o, 'a', {
value: 1,
writable: true,
configurable: true,
enumerable: true
}); //另一方面,
Object.defineProperty(o, 'a', { value: 1 });
//等同于:
Object.defineProperty(o, 'a', {
value: 1,
writable: false,
configurable: false,
enumerable: false
});
定制的Setters和Getters
下面的示例展示了如何实现一个“自存档(self-archiving)”的对象。当temperature属性被设置时,archive数组就会添加一个日志记录。
function Archiver() {
var temperature = null;
var archive = []; Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
}
}); this.getArchive = function() { return archive; };
} var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
或者下面这样写也是同样的效果:
var pattern = {
get: function () {
return 'I always return this string, whatever you have assigned';
},
set: function () {
this.myname = 'this is my name string';
}
}; function TestDefineSetAndGet() {
Object.defineProperty(this, 'myproperty', pattern);
} var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';
console.log(instance.myproperty);
// I always return this string, whatever you have assigned console.log(instance.myname); // this is my name string
最后放上一个利用Object.defineProperty()实现的简单的双向绑定的例子
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="Generator" content="EditPlus®">
<meta name="Author" content="">
<meta name="Keywords" content="">
<meta name="Description" content="">
<title>Document</title>
</head>
<body>
<input type="text" id="aa"/>
<span id="bb">{{hello}}</span>
<script>
var obj = {};
Object.defineProperty(obj,'hello',{
set:function(val){
document.querySelector('#bb').innerHTML = val;
document.querySelector('#aa').value = val;
}
});
document.querySelector('#aa').oninput = function(e){
obj.hello = e.target.value;
};
obj.hello = "";
obj.hello = "abc";
</script>
</body>
</html>
前端数据双向绑定原理:Object.defineProperty()的更多相关文章
- Vue的数据双向绑定和Object.defineProperty()
Vue是前端三大框架之一,也被很多人指责抄袭,说他的两个核心功能,一个数据双向绑定,一个组件化分别抄袭angular的数据双向绑定和react的组件化思想,咱们今天就不谈这种大是大非,当然我也没到达那 ...
- 关于简单的数据双向绑定原理,defineProperty 和Proxy演示
双向绑定,也就是说js中的数据传到页面,页面中的内容到js,实现同步更新,简单的演示可以直接复制下放HTML代码运行. 在这个例子中,我们使用defineProperty ,Object.define ...
- 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分
最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...
- vue数据双向绑定原理
vue的数据双向绑定的小例子: .html <!DOCTYPE html> <html> <head> <meta charset=utf-> < ...
- 对象的属性类型 和 VUE的数据双向绑定原理
如[[Configurable]] 被两对儿中括号 括起来的表示 不可直接访问他们 修改属性类型:使用Object.defineProperty() //IE9+ 和标准浏览器 支持 查看属性的 ...
- vuejs数据双向绑定原理(get & set)
前端的数据双向绑定指的是view(视图)和model(数据)两者之间的关系:view层是页面上展示给用户看的信息,model层一般是指通过http请求从后台返回的数据.view到model的绑定都是通 ...
- Vue数据双向绑定原理及简单实现
嘿,Goodgirl and GoodBoy,点进来了就看完点个赞再go. Vue这个框架就不简单介绍了,它最大的特性就是数据的双向绑定以及虚拟dom.核心就是用数据来驱动视图层的改变.先看一段代码. ...
- Vue数据双向绑定原理(vue2向vue3的过渡)
众所周知,Vue的两大重要概念: 数据驱动 组件系统 1 2 接下来我们浅析数据双向绑定的原理 一.vue2 1.认识defineProperty vue2中的双向绑定是基于definePropert ...
- [JS] 数据双向绑定原理
通常在前端开发过程中,经常遇到需要绑定两个甚至多个元素之间的值,比如将input的值绑定到一个h1上,改变input的值,h1的文字也自动更新. <h1 id="title" ...
随机推荐
- 让Redis突破内存大小的限制
Redis虽然可以实现持久化存储,也是基于数据内存模型的基础之上,单机内存大小限制着Redis存储的数据量,有没有一种替代方案呢?本文介绍一款笔者使用的采用New BSD License 许可协议的软 ...
- PYTHONIOENCODING = UTF-8 引发的血案
血案: 我就是想在Jenkins上运行一段自动化python代码,就是最简单的本地控制台输出, 我就不懂了它为什么一直是去找 cp1252.py 编码???目前, 确定 pycharm 运行脚本很OK ...
- cogs 2320. [HZOI 2015]聪聪的世界题解
2320. [HZOI 2015]聪聪的世界 时间限制:6 s 内存限制:512 MB [题目描述] 背景: 聪聪的性取向有问题. 题目描述: 聪聪遇到了一个难题: 给出一个序列a1…an,完成以 ...
- 《C Primer Plus(第6版)中文版》勘误
搬运自己2016年11月28日发布于SegmentFault的文章.链接:https://segmentfault.com/a/1190000007626460 本勘误由本人整理并发布,仅针对下方列出 ...
- html css 布局小细节
学了两个月的html和css每天都重复一样的生活,敲着大同小异的代码,这样的生活枯燥无味.我腻了,我也累了!小米首页算是我写的第三个静态页面,写了好久,很多细节都把握不好,下面的这个简单的布局细节是我 ...
- 个人永久性免费-Excel催化剂功能第70波-工作薄外部链接维护管理
Excel在数据领域万物互联的特性,其中一个使用场景是连接非本工作薄的外部性文件内容,如其他Excel工作薄文件里的内容或直接用OLE对象的方式嵌入一个文件链接,使其在不离开Excel环境,也可提供类 ...
- 个人永久性免费-Excel催化剂功能第18波-在Excel上也能玩上词云图
这年头数据可视化日新月异,在Excel上做数据分析,最后一步,难免要搞个图表输出高大上一回,微软也深知此道,在Excel2016上更新了一大波图表功能,市场上很耀眼的词云图还是没加进来,虽然在各大的在 ...
- Java中常见的异常类型
一. Java中常见的异常类 异常类 说明 ClassCastException 类型准换异常 ClassNotFoundException 未找到相应类异常 ArithmeticException ...
- CentOS 下编译安装PHP
1. 去php官网下载源码 http://www.php.net/downloads.php ,我下载使用的版本是(php-5.4.8.tar.gz) 2. 安装环境 yum install ...
- Flutter学习笔记(11)--文本组件、图标及按钮组件
如需转载,请注明出处:Flutter学习笔记(10)--容器组件.图片组件 文本组件 文本组件(text)负责显示文本和定义显示样式,下表为text常见属性 Text组件属性及描述 属性名 类型 默认 ...