JavaScript中对像的属性

在JavaScript中,对象是属性的无序集合,通过对JS属性的理解可以更好地了解JS对象。

属性的构成与分类

在JS中,对像的属性是由名字(key)和一组特性(attribute)构成,其中:

  • key是一个字符串(包括空字符串),
  • attribute是一些与之相关的值,主要是这4个:
    • 值(value),可以是任意的JS值,也可以是一个getter或setter函数(或两者都有)。
      当value为getter或setter时,那么我们称这个属性为“存取器属性(accessor property)”,其他时候则称作“数据属性(data property)”
    • 可写性(writable attribute),表时属性的值是否可以设置
    • 可枚举性(enumerable attribute),表时属性是否可能通过for/in循环返回
    • 可配置性(configurable attribute),表明属性是否可以删除或者修改

实际上,当属性是存取器属性时,是没有可写属性的,只有可枚举和可配置这两个属性。因此,可以这么说,对于一个数据属性,那么它有4个特性:值、可写性、可枚举性和可配置性,对于一个存取器属性,也有4个特性:get、set、可写性和可配置性。

对于存取器属性的值是否可以设置,通过getter和setter来决定的:当只设置了getter函数时,那么这个属性中是只读的;当设置了setter时,那么属性是可写的;两个都设置那么属性是既可读也能写的。

下面是一个具有存取器属性对象的定义:

var person = {
    name: "Kaidi Yang",
    get familyName() {
        return this.name.split(" ")[1];
    },
    set familyName(val) {
        this.name = this.name.split(" ")[0] + " " + val;
    }
};
console.log(person.familyName);  //输出: Yang
person.familyName = "XXXX";
console.log(person.name);   //输出: Kaidi XXXX
var person2 = {
    name: "Kaidi Yang",
    get familyName() {
        return this.name.split(" ")[1];
    }
};
console.log(person2.familyName);  //输出: Yang
person2.familyName = "Wang";
console.log(person2.name);   //输出: Kaidi Yang</pre>  

对于person这个对象中的familyName,它是同时具有getter和setter两个特性的,因此可以设置与改变。而person2这个对象中的familyName,它不具有setter,因此它是一个只读属性,给他设置新的值的话则会失败。不过值得注意的是,给这样的只读属性赋值的操作不会引发错误,这是JS的一个bug,在严格模式中已得到修复。

ES5中也提供一个新的方法Object.defineProperty用来定义对象的属性:

var xxx = {};
Object.defineProperty(xxx, "k", {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
});
console.log(xxx.k); // 1
var zzz = {};
Object.defineProperty(zzz, "t", {
    get: function() { return 2 },
    set: function(val) {
        console.log("get new val: " + val);
    },
    enumerable: true,
    configurable: true
});
console.log(zzz.t);  // 2

属性的访问

JS是一门基于原型继承的语言,当在访问对对象的属性时,会从自身开始一直检索整个原型链。

首先,一个JS对象具有“自有属性(own property)”,同时也有一些属性是从原型对象中继承来的。当我们访问一个对象xxx的属性k时,如果xxx中有k这个自有属性,那么,就会返回这个属性;如果对象xxx中没有属性k,那么就会继续在他的原型对象中查找属性k。如果原型对象中也没有,那么就会在这个原型对象的原型对象中查找,直到找到属性k或者查找到一个原型是null。

同样,当我们给对象xxx的属性k赋值时,如果xxx中已经有属性x(自有的)了,那么这个赋值操作就只改变这个自有属性的值;如果xxx中没有这个的自有属性k,那么赋值操作会给xxx加上一个新的自有属性k(如果xxx的的原型对象中有k,那么这个新加上的自有属性会覆盖原来的继承属性)。

上面是两是JS对象属性访问基本的规则,下面指出一些特殊的情况:

  • 不能给只读属性赋值,除非用defineProperty方法把可配置属性变成可写的
  • 在覆盖原型对象中的同名属性时,如果此属性是只读的,那么覆盖会失败。比如下面的代码
console.log(Object.getOwnPropertyNames(navigator))  // []  
navigator.userAgent = "hello";  
console.log(navigator.userAgent);  //Mozilla/5.0 (Windows NT 10.0; WO....

这里我们先输出了navigator的所有自有属性,可以看出是一个空数组,因此可以知道navigator的所有属性值都是从原型对象中继承来的;于是我们就试图在navigator中加入一个userAgent,以期望它能覆盖原型对象中的userAgent;然而从后面的输出中可以看到userAgent没有发生变化,即覆盖失败,这就是因为userAgent中原型对象中是只读的。

  • 在覆盖原型对象中的同名属性时,如果此属性是存取属性且定义了setter方法,那么不会发生覆盖而是当前对象会执行这个setter方法,下面是一个例子
var z = Object.create(zzz);
z.t = 13;  // get new val: 13
console.log(z.t);  // 2 

属性的特性(attribute)

一个属性从构成来看有4个特性,这一点我们可以用过ES5提供的Object.getOwnPropertyDescriptor()方法来查看。比如下面的:

var xxx = {"k": 1};
console.log(Object.getOwnPropertyDescriptor(xxx, "k"));
console.log(Object.getOwnPropertyDescriptor(person, "familyName"));
//下面是输出
{ value: 1, writable: true, enumerable: true, configurable: true }
{ 
    get: [Function: familyName],
    set: [Function: familyName],
    enumerable: true, 
    configurable: true 
}

从上面的输出来看,对于一个新创建的对象,它默认的writable、enumerable和configurable都是true。如果我们想要改变的话,那么通过Object.defineProperty()方法:

Object.defineProperty(person2, "familyName", {
    writable: true,
    enumerable: false,
    configurable: false
});
person2.familyName = "XXXX";
console.log(person2.familyName);  
console.log(Object.getOwnPropertyDescriptor(person2, "familyName"));
//下面是输出
XXXX
{ 
    value: 'XXXX',
    writable: true,
    enumerable: false,
    configurable: false 
}
//试着再定义这个属性
Object.defineProperty(person2, "familyName", {
    writable: true,
    enumerable: true,
    configurable: false
});  //这里报错了

上面的代码,把person这个对象的familyName属性从存取器变成了数据性,enumerable和configurable变成了false。这样,familyName这个属性就变成了一个可写的、不可枚举的、不可配置的数据属性。我们可以设置它为新的值,也能正常访问,不过,却不能用for/in来遍历得到。
但是,当我们再次执行defineProperty,试图改变其枚举性时,JS执行却报错了,这是因为它的configurable已经设置了false。也就是说,一个属性的configurable一旦被设置成false,那么:

  • 它的枚举性和可配性就不能发生变化,
  • 也不能从数据性变成存取性,或从存取性变成数据性,或者修改setter和getter
  • 还不能将要可写性变从false变成true
  • 只能把可写性从true变成false

再回到前面试图修改浏览器userAgent的情况,我们先用getOwnPropertyDescriptor输出navigator.__proto__的中userAgent的情况,可以看到它是一个没有setter函数但是configurable为true的只读属性,因此完全是可以重写getter方法让这返回我们想的数据

Object.defineProperty(navigator.__proto__, "userAgent", {
    get: function() {
        return "hello"
    }
});
console.log(navigator.userAgent); //hello
//或者直接给navigator定义一个数据属性的
Object.defineProperty(navigator, "userAgent", {
    value: "hello"
});
console.log(navigator.userAgent); //hello
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容