JavaScript中对像的属性

2016-09-30  本文已影响0人  Hill1942

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

属性的构成与分类

在JS中,对像的属性是由名字(key)和一组特性(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对象属性访问基本的规则,下面指出一些特殊的情况:

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中原型对象中是只读的。

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,那么:

再回到前面试图修改浏览器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
上一篇 下一篇

猜你喜欢

热点阅读