随着web应用的发展,直接操作dom的应用已渐行渐远,取而代之的是时下越来越流行的MVVM框架,dom操作几乎绝迹,这里面自然是框架底层封装的结果。MVVM框架的双向数据绑定使开发效率大大提高;然后在实现这些双向数据绑定时,使用ES7原生的Object.observe方法则是完美解决方案,但是遗憾的是该方法目前还是ES7的草案阶段,各浏览器还不支持,目前chrome 36+支持该方法。

  既然Object.observe不被支持,但是其替代方案是ECMAScript 262v5带来的新东西Object.defineProperty和Object.defineProperties方法也可以很优雅的实现双向数据绑定。主要是利用在定义一个对象的属性时,可以设置其set和get方法,用于对对象的该属性进行设置或者读取时的“拦截”操作,类似于"钩子",即在一定的情况下执行特定的逻辑;目前使用该方案实现的MVVM框架有avalon、vue等,但是目前比较流行的angularjs框架缺使用为人诟病的"脏检查"机制实现,这也是其性能遭人吐槽低的一个原因,据说目前angularjs2会改造这一实现机制。

好了,言归正传,接下来看看defineProperty的使用

1、用法

语法: Object.defineProperty(obj, attr, descriptor)

从语法可以看出,该方法接受三个参数:

  obj 为属性attr所属的对象;

  attr 为obj对象新定义或者修改的属性名;

  descriptor 为该对象属性的描述符,其中其有6个配置项:

  • value: 属性的值,默认undefined
  • configurable: 默认为false,true表示当前属性是否可以被改变或者删除,其中”改变“是指属性的descriptor的配置项configurable、enumerable和writable的修改
  • enumerable:默认为false,true表示当前属性能否被for...in或者Objectk.keys语句枚举
  • writable:默认为false,true表示当前属性的值可以被赋值重写
  • get:默认undefined,获取目标属性时执行的回调方法,该函数的返回值作为该属性的值
  • set:默认undefined,目标属性的值被重写时执行的回调

从上面的用法可以知道:可以通过设置get和set方法对属性的读取和修改进行拦截,通过将实现数据和视图同步的逻辑置于这两个方法中,从而实现数据变更视图也可以跟着同步,反之则需要配合事件了;

var obj = {};

Object.defineProperty(obj, 'name', {
"value": "wonyun",
"configurable": false
}); Object.getOwnPropertyDescriptor(obj, 'name') //Object {value: "wonyun", writable: false, enumerable: false, configurable: false}

2、注意事项

  上面介绍了Object.defineProperty的用法,但是其使用还是有很多注意的地方,一不留心就会出错。

  • 不允许同一个属性存在两个及以上的存取访问器配置,所以writable或者value不能与get/set同时配置,否则报错

    var obj = {};
    
    Object.defineProperty(obj, 'name', {
    "writable": false,
    "get": function(){},
    "set": function(){}
    });

    执行上面代码,会发现控制台报错”Uncaught TypeError: Invalid property.  A property cannot both have accessors and be writable or have a value“,

  • configurable和writable设置为false的区别是:前者不能在对目标属性的configurable、enumerable和writable进行修改,修改也是无效;
    后者不能对目标属性重写赋值,赋值也无效;

  • 在使用Obejct.defineProperty方法定义对象属性时,若是属性描述符没有配置的,configurable、enumerable和writable默认为false,value默认为undefined,而get/set则不会配置;而在使用非Object.defineProperty方法定义对象属性,其configurable、enumerable和writable均为true,value为赋的值,get/setb不会配置;

3、兼容性

  就如本文开头说的,Obejct.defineProperty方法ECMAScript 262v5带来的新特性,支持ES5的浏览器才可使用该方法;当然这就涉及到其使用的浏览器兼容性问题;目前非IE浏览器的标准浏览器都支持该特性了,当然在天朝,你懂得还有些不少ie6-8的用户,这些低版本的IE浏览器是不支持Object.defineProperty的。话说回来,随着微软自己放弃维护这些低版本的ie浏览器,ie6-8的使用占比是越来越低,这也是当前一些前端框架不在支持ie6、7、8浏览器的一大原因,像angularjs、vuejs都不支持低版本ie浏览器,看来低版本的ie6也快走到尽头。

Feature Firefox (Gecko) Chrome Internet Explorer Opera Safari
Basic support 4.0 5 11.60 5.1

4、Object.defineProperties(obj, descriptors)

  Object.defineProperty方法只能定义单个对象的属性,当然也就有批量定义对象属性的方法,Object.defineProperties方法就是干这个的:在一个对象添加或者修改一个或者多个属性。它与Object.defineProperty不同的地方是它只有两个参数,其中第二参数为一个配置对象,其键值为对象的属性名,其值为属性名的配置项,如:

var obj = {}

Object.defineProperties(obj, {
name: {
value: "wonyun",
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 20,
writable: true
}
})

5、Object.defineProperty在avalon中的运用

  avalon中的利用Object.defineProperty,为其定义一个accessor来同步视图,每次改变或者读取vmodel对象中的属性时候,会调用accessor方法用于同步视图和其他操作。

 function modelFactory($scope, $special, $model) {

       //省略初始化代码
...
...
var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤
$model = $model || {} //vmodels.$model属性
var $events = {} //vmodel.$events属性
var watchedProperties = {} //监控属性
var computedProperties = [] //计算属性
for (var i in $scope) {
(function(name, val) {
$model[name] = val
... //省略其他无关代码 //总共产生三种accessor
var accessor
var valueType = avalon.type(val)
$events[name] = []
//总共产生三种accessor
if (valueType === "object" && isFunction(val.get) && Object.keys(val).length <= 2) {
var setter = val.set
var getter = val.get
//第1种对应计算属性, 因变量,通过其他监控属性触发其改变
accessor = function(newValue) {
var $events = $vmodel.$events
var oldValue = $model[name]
if (arguments.length) {
if (stopRepeatAssign) {
return
}
if (isFunction(setter)) {
var backup = $events[name]
$events[name] = [] //清空回调,防止内部冒泡而触发多次$fire
setter.call($vmodel, newValue)
$events[name] = backup
}
} else {
if (avalon.openComputedCollect) { // 收集视图刷新函数
collectSubscribers($events[name])
}
}
newValue = $model[name] = getter.call($vmodel) //同步$model
if (!isEqual(oldValue, newValue)) {
withProxyCount && updateWithProxy($vmodel.$id, name, newValue) //同步循环绑定中的代理VM
notifySubscribers($events[name]) //同步视图
safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
}
return newValue
} .... // 省略无关代码 } else if (rcomplexType.test(valueType)) {
//第2种对应子ViewModel或监控数组
accessor = function(newValue) {
var childVmodel = accessor.child
var oldValue = $model[name]
if (arguments.length) {
if (stopRepeatAssign) {
return
}
if (!isEqual(oldValue, newValue)) {
childVmodel = accessor.child = updateChild($vmodel, name, newValue, valueType)
newValue = $model[name] = childVmodel.$model //同步$model
var fn = rebindings[childVmodel.$id]
fn && fn() //同步视图
safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
}
} else {
collectSubscribers($events[name]) //收集视图函数
return childVmodel
}
}
var childVmodel = accessor.child = modelFactory(val, 0, $model[name])
childVmodel.$events[subscribers] = $events[name]
} else {
//第3种对应简单的数据类型,自变量,监控属性
accessor = function(newValue) {
var oldValue = $model[name]
if (arguments.length) {
if (!isEqual(oldValue, newValue)) {
$model[name] = newValue //同步$model
withProxyCount && updateWithProxy($vmodel.$id, name, newValue) //同步代理VM
notifySubscribers($events[name]) //同步视图
safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
}
} else {
collectSubscribers($events[name])
return oldValue
}
}
}
watchedProperties[name] = accessor
})(i, $scope[i])
} ... //省略无关代码 $vmodel = defineProperties($vmodel, descriptorFactory(watchedProperties), $scope) //生成一个空的ViewModel ... //省略无关代码
return $vmodel
} //生成avalon的vmodel的Object.defineProperty方法 var descriptorFactory = W3C ? function(obj) {
var descriptors = {}
for (var i in obj) {
descriptors[i] = {
get: obj[i],
set: obj[i],
enumerable: true,
configurable: true
}
}
return descriptors
} : function(a) {
return a
}

参考文献:

MVVM双向绑定实现之Object.defineProperty的更多相关文章

  1. 【Vue】-- 数据双向绑定的原理 --Object.defineProperty()

    Object.defineProperty()方法被许多现代前端框架(如Vue.js,React.js)用于数据双向绑定的实现,当我们在框架Model层设置data时,框架将会通过Object.def ...

  2. 双向绑定Proxy VS Object.defineProperty

    Vue3.0的双向绑定将使用Proxy代替Object.defineProperty,据尤大说,速度提升了1倍. 本文我们来探讨一下Proxy对比Object.defineProperty究竟有哪些优 ...

  3. 【.NET6+WPF】WPF使用prism框架+Unity IOC容器实现MVVM双向绑定和依赖注入

    前言:在C/S架构上,WPF无疑已经是"桌面一霸"了.在.NET生态环境中,很多小伙伴还在使用Winform开发C/S架构的桌面应用.但是WPF也有很多年的历史了,并且基于MVVM ...

  4. C#使用Xamarin开发可移植移动应用(3.进阶篇MVVM双向绑定和命令绑定)附源码

    前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github.com/l2999019/DemoApp 可以Star一下,随意 - - 说点什么.. 嗯..前面 ...

  5. C#使用Xamarin开发可移植移动应用(4.进阶篇MVVM双向绑定和命令绑定)附源码

    前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github.com/l2999019/DemoApp 可以Star一下,随意 - - 说点什么.. 嗯..前面 ...

  6. 【Maui正式版】创建可跨平台的Maui程序,以及有关依赖注入、MVVM双向绑定的实现和演示

    前言:Maui终于在昨天(2022年8月9日)推送出来了.今儿就迫不及待来把玩一下先. A.我本地已有VS2022,不过版本比较老,此处选择更新.工具 -> 获取功能和更新里面,可以获取到新版本 ...

  7. mvvm双向绑定机制的原理和代码实现

    mvvm框架的双向绑定,即当对象改变时,自动改变相关的dom元素的值,反之,当dom元素改变时,能自动更新对象的值,当然dom元素一般是指可输出的input元素. 1. 首先实现单向绑定,在指定对象的 ...

  8. vue双向绑定、Proxy、defineproperty

    本文原链接:https://www.jianshu.com/p/2df6dcddb0d7 前言 双向绑定其实已经是一个老掉牙的问题了,只要涉及到MVVM框架就不得不谈的知识点,但它毕竟是Vue的三要素 ...

  9. vue实现双向绑定的简单原理: defineProperty

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

随机推荐

  1. JDK8集合类源码解析 - HashSet

    HashSet 特点:不允许放入重复元素 查看源码,发现HashSet是基于HashMap来实现的,对HashMap做了一次“封装”. private transient HashMap<E,O ...

  2. Likecloud—吃、吃、吃(P1508)

    题目链接:Likecloud-吃.吃.吃 这题的状态非常的自然. 就是ans[i][j]表示从(i,j)出发,能得到的最大能量值. 那么对应每一个点,我们只要选出他能到达的点的最大值,加上自己就行了. ...

  3. java web实践

    语言:java.javascript 软件:eclipse.mysql 环境配置:下载jdk:配置jdk环境变量.相关教程:https://jingyan.baidu.com/article/db55 ...

  4. kbmmw 的HTTPSmartService入门

    前面介绍过kbmmw 中的smartservice. 这个既可以用于kbmmw 的客户端,也可以使用http 访问. 在新版的kbmmw里面,作者加强了http 的支持,我们可以只使用HTTPSmar ...

  5. Hadoop3集群搭建之——hive添加自定义函数UDTF (一行输入,多行输出)

    上篇: Hadoop3集群搭建之——虚拟机安装 Hadoop3集群搭建之——安装hadoop,配置环境 Hadoop3集群搭建之——配置ntp服务 Hadoop3集群搭建之——hive安装 Hadoo ...

  6. 2019.01.02 bzoj2467: [中山市选2010]生成树(矩阵树定理)

    传送门 矩阵树定理模板题. 题意简述:自己看题面吧太简单懒得写了 直接构建出这4n4n4n个点然后按照题面连边之后跑矩阵树即可. 代码: #include<bits/stdc++.h> # ...

  7. 关于拼接SQL语句sqlMap的使用方法

    1.为什么使用? 主要还是为了代码中获取到值,然后带入SQL语句中拼接查询 2.怎么使用? 1)bean继承了BaseEntity类,该类中有 /** * 自定义SQL(SQL标识,SQL内容) */ ...

  8. 第11章:MongoDB-CRUD操作--文档--查询

    ①语法 db.collection.find(query, projection) ②参数 query :可选,使用查询操作符指定查询条件 projection :可选,使用投影操作符指定返回的键.查 ...

  9. Struts2-result

    所有result-type <result-types> <result-type name="chain" class="com.opensympho ...

  10. Ubuntu 默认启动到命令行 12.04

    源文链接:http://my.oschina.net/jackguo/blog/85706 代码: sudo gedit /etc/default/grub 引用: GRUB_CMDLINE_LINU ...