理解defineProperty以及getter、setter

我们常听说vue是用getter与setter实现数据监控的,那么getter与setter到底是什么东西,它与defineProperty是什么关系,平时有哪些用处呢?本文将为大家一一道来。

对象的属性

按照一贯的“由浅到深”行文原则,我们先温习一下对象的属性。我们知道对象有自身的属性以及原型上的属性,它们都可以通过obj.key这样的方式访问到。

要设置/修改对象的属性也是很简单的,只需obj.key=‘value‘即可。要注意的是,如果key位于原型上,那么此时会在对象自身设置该值,而不是修改原型上的。

另外需要注意的是,原型上的属性有时候会被for in给“不小心”遍历出来,例如下面的代码:

var arr = [1,2,3];
arr.__proto__.test = 4;
for(i in arr){
    console.log(arr[i]);
}
//输出:1234

所以我们一般在用for in的时候都要加上hasOwnProperty判断,或者是抛弃for in,用forEach.

认识defineProperty

defineProperty是挂载在Object上的一个方法,作用是:为对象定义一个属性,或是修改已有属性的值,并设置该属性的描述符。该方法返回修改后的对象。

如果没有后半句作用的话,那它与obj.key = ‘value‘这种赋值语句没什么两样。他的完整语法是这样:Object.defineProperty(obj, prop, descriptor)

obj: 目标对象
prop: 属性名称
descriptor: 属性描述符

前两个就不必讲了,需要重点理解的是第三参数。属性描述符用于定义该属性的一些特性。具体来讲分了两类:数据描述符(data descriptor)、访问描述符(accessor descriptor).

这两类描述符有两个必选项:

  1. configurable
    从字面意思看它表示“可配置”,含义是:当它为true时,该属性的描述符可被修改,并且该属性可被delete删除。同理,当它为false时,我们无法再次调用defineProperty去修改描述符,也不可通过delete删除。
  2. enumerable
    从字面意思看它表示“可枚举”,含义是:当它为true时,该属性可被迭代器枚举出来。比如使用for in或者是Object.keys。

接下来就是数据描述符(data descriptor)了,有两个:

  1. value
    这个就是该属性的值啦,即通过obj.key访问时返回。任何js数据类型都可以使用(number,string,object,function等)。
  2. writable
    这个也很好理解,表示改属性是否可写。当它为false时,属性不可被任何赋值语句重写。想要通过调用defineProperty修改value的方式也是不行的。

剩下的就是访问描述符啦,先卖个关子讲两个注意事项。

描述符的原型与默认值

一般情况,我们会创建一个descriptor对象,然后传给defineProperty方法。如下:

var descriptor = {
    writable: false
}
Object.defineProperty(obj, 'key', descriptor);

这种情况是有风险的,如果descriptor的原型上面有相关特性,也会通过原型链被访问到,算入在对key的定义中。比如:

descriptor.__proto__.enumerable = true;
Object.defineProperty(obj, 'key', descriptor);
Object.getOwnPropertyDescriptor(obj,'key'); //返回的enumerable为true

为了避免发生这样的意外情况,官方建议使用Object.freeze冻结对象,或者是使用Object.create(null)创建一个纯净的对象(不含原型)来使用。

接下来的注意点是默认值,首先我们会想普通的赋值语句会生成怎样的描述符,如obj.key="value"

可以使用Object.getOwnPropertyDescriptor来返回一个属性的描述符:

obj = {};
obj.key = "value";
Object.getOwnPropertyDescriptor(obj, 'key');
/*输出
{
    configurable:true,
    enumerable:true,
    value:"value",
    writable:true,
}
*/

这也是复合我们预期的,通过赋值语句添加的属性,相关描述符都为true,可写可配置可枚举。但是使用defineProperty定义的属性,默认值就不是这样了,其规则是这样的:
configurable: false
enumerable: false
writable: false
value: undefined

所以这里还是要注意下的,使用的时候把描述符写全,免得默认都成false了。

getter与setter

所谓getter与setter其实是两个概念,并没有这样的属性。与之对应的是两个访问描述符(access descriptor):

  1. get
    它是一个函数,访问该属性时会自动调用,函数的返回值即为该属性的value。默认为undefined。

你可能会想,既有value又有get函数,那么属性的值是什么呢?那你就想多了,这种情况在定义的时候就直接报错了,本身逻辑就矛盾嘛。

  1. set
    它是一个函数,为该属性赋值时会自动调用,并且新值会被当做参数传入。

看到这里你可能就眼前一亮了,为属性赋值的时候会自动执行一个函数,那岂不是就能监控到数据的变化,从而实现mvvm的双向绑定?其实vue的数据监控用到的核心原理也就是这个啦。如果你用过knockout可能感受会更深,knockout能做到在IE6都支持双向绑定,就是强制让属性值为函数类型,必须手动执行函数才能拿到值。

还好现在有了浏览器的默认支持,ES5开始就支持gettter、setter了,现在移动端基本完全可用,pc端需要IE9+。

实际应用

这么好用的方法,我们平时好像也不怎么用呀?写业务代码可能用到的确实少,但是当你要写一个公共模块乃至写一个框架时,就可能用到啦。

比如你写一个公共模块,会往window上挂一些全局属性,并且你不希望别人在其他地方不小心覆盖这个属性,那就可以用defineProperty让该属性不可写、不可配置。贴一个我们项目中的代码:

//向全局挂载通用方法
for(let key in methods){
    if(methods.hasOwnProperty(key)){
        Object.defineProperty(WIN, key, {
            value        : methods[key]
        });
    }
}

另外一个用途呢,就是你自己想干坏事。覆盖别人写的代码,比如写chrome插件刷页面。或者说是想篡改浏览器的一些信息。

比如你想把浏览器的userAgent给改了,直接写navigator.userAgent = ‘iPhoneX‘.你再输出一下userAgent,发现并没有修改。这是为什么呢?我们用这行代码看一下:

Object.getOwnPropertyDescriptor(window, 'navigator');
//输出
{
    configurable:true,
    enumerable:true,
    get:? (),
    set:undefined
}

原因就找到了,navigator是有setter的,每次取值总会执行这个set函数来做返回。但是好消息是什么呢?configurable为true,那就意味这我们可以通过defineProperty来修改这个属性,代码就相当简单了:

Object.defineProperty(navigator, 'userAgent', {get: function(){return 'iphoneX'}})
console.log(navigator.userAgent); //输出iphoneX

喏,篡改浏览器userAgent的方法我教给你了。

时间: 2024-11-13 07:53:01

理解defineProperty以及getter、setter的相关文章

有关js 的 defineProperty函数 getter setter

ECMAScript 中有两种属性:数据属性和访问器属性.1. 数据属性数据属性包含一个数据值的位置.在这个位置可以读取和写入值.数据属性有4 个描述其行为的特性.? [[Configurable]]:表示能否通过delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性.像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true.? [[Enumerable]]:表示能否通过for-in 循环返回属性.像前面例子中那样直接在对象上定义的属性,它们的这

Java程序员的JavaScript学习笔记(4——闭包/getter/setter)

计划按如下顺序完成这篇笔记: 理念. 属性复制和继承. this/call/apply. 闭包/getter/setter. prototype. 面向对象模拟. jQuery基本机制. jQuery选择器. jQuery工具方法. jQuery-在"类"层面扩展. jQuery-在"对象"层面扩展. jQuery-扩展选择器. jQuery UI. 扩展jQuery UI. 这是笔记的第4篇,聊聊闭包/getter/setter,看看JavaScript中的变量作

Java程序猿JavaScript学习笔记(4——关闭/getter/setter)

计划和完成这个例子中,音符的顺序如下: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaScript学习笔记(3--this/call/apply) Java程序猿的JavaScript学习笔记(4--this/闭包/getter/setter) Java程序猿的JavaScript学习笔记(5--prototype) Java程序猿的JavaScript学习笔记(6--面向对象模拟) Ja

JS对象属性中get/set与getter/setter是什么

在js属性描述符这部分有几个较难理解的名词概念,本文旨在描述对它们的理解,主要包括:[[Get]]/[[Put]].get/set.getter/setter几个概念的阐述,数据属性和访问器属性. 属性 首先我们要搞清楚属性的概念,属性是存储在特定命名位置的值,是对象的内容,属性并不直接存储在对象容器内部.属性有两种类型:数据属性和访问器属性.属性具备了属性描述符,用来描述属性拥有的特性. 属性描述符 属性描述符用来描述属性特性的(只有在内部才能用的特性),配置属性是否可读,是否可写,是否可枚举

es6 getter setter

https://stackoverflow.com/questions/34517538/setting-an-es6-class-getter-to-enumerable 1. 我要 getter 没有要暴露 class A { get age() { return 11; } } console.log(Object.keys(new A())); // [] 翻译 es5 var A = /** @class */ (function () { function A() { } Objec

10.getter & setter

自定义 Person 类 class Person: NSObject { var name: String? var age: Int? } getter & setter var _name: String? var name: String? { get { return _name } set { _name = newValue } } 在 Swift 中以上形式的 getter & setter 很少用 didSet 在 OC 中,我们通常希望在给某一个变量赋值之后,去做一些额

Lombok(1.14.8) - @Getter, @Setter, @ToString, @EqualsAndHashCode & @Data

@Getter / @Setter @Getter 和 @Setter,分别实现了 Gette r和 Setter 方法. package com.huey.hello.bean; import java.util.Date; import lombok.Getter; import lombok.Setter; public class Student { @Getter @Setter private int studNo; @Getter @Setter private String st

iOS getter setter

getter setter 给成员变量起名字用的 setter方法 设置成员变量值 1. setter 方法一定是对象方法 不可能是类方法 2.一定没有返回值 3. 以set开头,并且set后面跟上需要设置的成员白能量的名称去掉下划线,并且首字母大写 4. 一定有参数 参数类型一定要和成员变量的类型一直 参数名称就是成员变量名称去掉下划线 getter方法用于返回成员变量的值 1. getter 一定是对象方法 2. 一定有返回值 返回值一定和获取的成员变量类型一致 3. 方法名称就是获取的成员

lombok注解为java类生成Getter/Setter方法

1. 先到lombok官网下载lombok.jar包 : http://projectlombok.org/ 2. 下载了之后的两种安装方法: 1. 双击下载下来的 JAR 包安装 lombok 我选择这种方式安装的时候提示没有发现任何 IDE,所以我没安装成功,我是手动安装的.如果你想以这种方式安装,请参考官网的视频. 2.eclipse / myeclipse 手动安装 lombok 1. 将 lombok.jar 复制到 myeclipse.ini / eclipse.ini 所在的文件夹