Object.defineProperty和其在Vue.js中的应用

在js中我们可以通过下面这几种方法定义属性 :

someOne.name = 'cover';
//or use (2) 
someOne['name'] = 'cover';
// or use (3) defineProperty
Object.defineProperty(someOne, 'name', {
 value : 'cover'
})

从上面看,貌似使用Object.defineProperty很麻烦, 那为啥存在这样的方法呢?

我们来看下 Object.defineProperty的定义:

The Object.defineProperty() method defines a new property directly on an object, or modifies an exisiting property on an object, and returns the object.

从上面得知,我们可以通过Object.defineProperty这个方法,直接在一个对象上定义一个新的属性,或者是修改已存在的属性。最终这个方法会返回该对象。

语法

Object.defineProperty(object, propertyname, descriptor)

参数

  • object 必需。 要在其上添加或修改属性的对象。 这可能是一个本机 JavaScript对象(即用户定义的对象或内置对象)或 DOM 对象。

  • propertyname 必需。 一个包含属性名称的字符串。

  • descriptor 必需。 属性描述符。 它可以针对数据属性或访问器属性。

属性的状态设置

其中descriptor的参数值得我们关注下,该属性可设置的值有:

  • 【value】 属性的值,默认为 undefined。

  • 【writable】 该属性是否可写,如果设置成 false,则任何对该属性改写的操作都无效(但不会报错),对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。

Object.defineProperty(someOne, "name", {
 value:"coverguo" , //由于设定了writable属性为false 导致这个量不可以修改
 writable: false 
});  
console.log(someOne.name); // 输出 coverguo
someOne.name = "linkzhu";
console.log(someOne.name); // 输出coverguo
  • 【configurable]】如果为false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化,对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。
Object.defineProperty(someOne, "name", {
 value:"coverguo" ,
 configurable: false 
});  
delete someOne.name; 
console.log(someOne.name);// 输出 coverguo
someOne.name = "linkzhu";
console.log(someOne.name); // 输出coverguo
  • 【enumerable】 是否能在for-in循环中遍历出来或在Object.keys中列举出来。对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。

注意 在调用Object.defineProperty()方法时,如果不指定, configurable, enumerable, writable特性的默认值都是false,这跟之前所 说的对于像前面例子中直接在对象上定义的属性,这个特性默认值为为 true。并不冲突,如下代码所示:

//直接在对象上定义的属性,这个特性默认值为为 true
var someOne = { };
someOne.name = 'coverguo';
console.log(Object.getOwnPropertyDescriptor(someOne, 'name'));
//输出 Object {value: "coverguo", writable: true, enumerable: true, configurable: true}
​
//调用Object.defineProperty()方法时,如果不指定
var otherOne = {};
Object.defineProperty(otherOne, "name", {
 value:"coverguo" 
});  
console.log(Object.getOwnPropertyDescriptor(otherOne, 'name'));
//输出 Object {value: "coverguo", writable: false, enumerable: false, configurable: false}
  • 【get】一旦目标对象访问该属性,就会调用这个方法,并返回结果。默认为 undefined。

  • 【set】 一旦目标对象设置该属性,就会调用这个方法。默认为 undefined。

从上面,可以得知,我们可以通过使用Object.defineProperty,来定义和控制一些特殊的属性,如属性是否可读,属性是否可枚举,甚至修改属性的修改器(setter)和获取器(getter)

那什么场景和地方适合使用到特殊的属性呢?


实际运用

在一些框架,如vue、express、qjs等,经常会看到对Object.defineProperty的使用。那这些框架是如何使用呢?

MVVM中数据‘双向绑定’实现

如vue,qjs等大部分mvvm框架(angular用的是脏处理)都是通过Object.defineProperty来实现数据绑定的。

vue视图和数据变化绑定

对于一个html页面

<div>
 <p>你好,<span id='nickName'></span></p>
 <div id="introduce"></div>
</div>   

设置一个数据的属性的getter和setter

//视图控制器
var userInfo = {};
Object.defineProperty(userInfo, "nickName", {
 get: function(){
 return document.getElementById('nickName').innerHTML;
 },
 set: function(nick){
 document.getElementById('nickName').innerHTML = nick;
 }
});
Object.defineProperty(userInfo, "introduce", {
 get: function(){
 return document.getElementById('introduce').innerHTML;
 },
 set: function(introduce){
 document.getElementById('introduce').innerHTML = introduce;
 }
})

然后就是交互:

userInfo.nickName = "xxx";
userInfo.introduce = "我是xxx"
vue.js的数据变动

但是,这个例子只是数据和dom节点的绑定,而vue.js更为复杂一点,它在网页dom和accessor之间会有两层,一层是Wacher,一层是Directive,比如以下代码。

var a = { b: 1 }
var vm = new Vue({ 
 data: data
})

把一个普通对象(a={b:1})传给 Vue 实例作为它的 data 选项,Vue.js 将遍历它的属性,用Object.defineProperty 将它们转为 getter/setter,如图绿色的部分所示。 每次用户更改data里的数据的时候,比如a.b =1,setter就会重新通知Watcher进行变动,Watcher再通知Directive对dom节点进行更改。

官网图片
优化对象获取和修改属性方式

这个优化对象获取和修改属性方式,是什么意思呢? 过去我们在设置dom节点transform时是这样的。

//假如有一个目标节点, 我们想设置其位移时是这样的
var targetDom = document.getElementById('target');
var transformText = 'translateX(' + 10 + 'px)';
targetDom.style.webkitTransform = transformText;
targetDom.style.transform = transformText;</pre>

通过上面,可以看到如果页面是需要许多动画时,我们这样编写transform属性是十分蛋疼的。

但如果通过Object.defineProperty, 我们则可以:

//这里只是简单设置下translateX的属性,其他如scale等属性可自己去尝试
Object.defineProperty(dom, 'translateX', {
set: function(value) {
 var transformText = 'translateX(' + value + 'px)';
 dom.style.webkitTransform = transformText;
 dom.style.transform = transformText;
}
//这样再后面调用的时候, 十分简单
dom.translateX = 10;
dom.translateX = -10;
//甚至可以拓展设置如scale, originX, translateZ,等各个属性,达到下面的效果
dom.scale = 1.5;  //放大1.5倍
dom.originX = 5;  //设置中心点X
}

上面只是个简单的版本,并不是最合理的写法,但主要是为了说明具体的意图和方法

有兴趣了解更多可以看下面这个库:https://github.com/AlloyTeam/AlloyTouch/blob/master/transform.js

增加属性获取和修改时的信息

如在Express4.0中,该版本去除了一些旧版本的中间件,为了让用户能够更好地发现,其有下面这段代码,通过修改get属性方法,让用户调用废弃属性时抛错并带上自定义的错误信息。

[
 'json',
 'urlencoded',
 'bodyParser',
 'compress',
 'cookieSession',
 'session',
 'logger',
 'cookieParser',
 'favicon',
 'responseTime',
 'errorHandler',
 'timeout',
 'methodOverride',
 'vhost',
 'csrf',
 'directory',
 'limit',
 'multipart',
 'staticCache',
].forEach(function (name) {
 Object.defineProperty(exports, name, {
 get: function () {
 throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.');
 },
 configurable: true
 });
});

转自 :
http://imweb.io/topic/56d40adc0848801a4ba198ce
https://www.jianshu.com/p/07ba2b0c8fca

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容