您的位置:首页 > 新闻 > 热点要闻 > 网站开发技术实验教程_龙华网站建设公司_厦门人才网最新招聘信息_企业网络营销

网站开发技术实验教程_龙华网站建设公司_厦门人才网最新招聘信息_企业网络营销

2025/2/25 4:22:43 来源:https://blog.csdn.net/qq_58738794/article/details/145105405  浏览:    关键词:网站开发技术实验教程_龙华网站建设公司_厦门人才网最新招聘信息_企业网络营销
网站开发技术实验教程_龙华网站建设公司_厦门人才网最新招聘信息_企业网络营销

第二节 属性描述对象

1. 概述

JavaScript 提供了一个内部数据结构,用来描述对象的属性,控制它的行为,比如该属性是否可写、可遍历等等。这个内部数据结构称为“属性描述对象”(attributes object)。每个属性都有自己对应的属性描述对象,保存该属性的一些元信息。
下面是属性描述对象的一个例子。
{value: 123,writable: false,enumerable: true,configurable: false,get: undefined,set: undefined
}
属性描述对象提供6个元属性。
(1) value
value是该属性的属性值,默认为 undefined
(2) writable
writable是一个布尔值,表示属性值(value)是否可改变(即是否可写),默认为 true
(3) enumerable
enumerable是一个布尔值,表示该属性是否可遍历,默认为 true。如果设为 false,会使得某些操作(比如 for...in循环、 Object.keys())跳过该属性。
(4) configurable
configurable是一个布尔值,表示属性的可配置性,默认为 true。如果设为 false,将阻止某些操作改写属性描述对象,比如无法删除该属性,也不得改变各种元属性( value属性除外)。也就是说, configurable属性控制了属性描述对象的可写性。
(5) get
get是一个函数,表示该属性的取值函数(getter),默认为 undefined
(6) set
set是一个函数,表示该属性的存值函数(setter),默认为 undefined

2. Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor()方法可以获取属性描述对象。它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名。
var obj = { p: 'a' };Object.getOwnPropertyDescriptor(obj, 'p')
// Object { value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }
上面代码中, Object.getOwnPropertyDescriptor()方法获取 obj.p的属性描述对象。
注意, Object.getOwnPropertyDescriptor()方法只能用于对象自身的属性,不能用于继承的属性。
var obj = { p: 'a' };Object.getOwnPropertyDescriptor(obj, 'toString')
// undefined
上面代码中, toStringobj对象继承的属性, Object.getOwnPropertyDescriptor()无法获取。

3. Object.getOwnPropertyNames()

Object.getOwnPropertyNames方法返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历。
var obj = Object.defineProperties({}, {p1: { value: 1, enumerable: true },p2: { value: 2, enumerable: false }
});Object.getOwnPropertyNames(obj)
// ["p1", "p2"]
上面代码中, obj.p1是可遍历的, obj.p2是不可遍历的。 Object.getOwnPropertyNames会将它们都返回。
这跟 Object.keys的行为不同, Object.keys只返回对象自身的可遍历属性的全部属性名。
Object.keys([]) // []
Object.getOwnPropertyNames([]) // [ 'length' ]Object.keys(Object.prototype) // []
Object.getOwnPropertyNames(Object.prototype)
// ['hasOwnProperty',
//  'valueOf',
//  'constructor',
//  'toLocaleString',
//  'isPrototypeOf',
//  'propertyIsEnumerable',
//  'toString']
上面代码中,数组自身的 length属性是不可遍历的, Object.keys不会返回该属性。第二个例子的 Object.prototype也是一个对象,所有实例对象都会继承它,它自身的属性都是不可遍历的。

4. Object.defineProperty(),Object.defineProperties()

Object.defineProperty()方法允许通过属性描述对象,定义或修改一个属性,然后返回修改后的对象,它的用法如下。
Object.defineProperty(object, propertyName, attributesObject)
Object.defineProperty方法接受三个参数,依次如下。
  • object:属性所在的对象
  • propertyName:字符串,表示属性名
  • attributesObject:属性描述对象
举例来说,定义 obj.p可以写成下面这样。
var obj = Object.defineProperty({}, 'p', {value: 123,writable: false,enumerable: true,configurable: false
});obj.p // 123obj.p = 246;
obj.p // 123
上面代码中, Object.defineProperty()方法定义了 obj.p属性。由于属性描述对象的 writable属性为 false,所以 obj.p属性不可写。注意,这里的 Object.defineProperty方法的第一个参数是 {}(一个新建的空对象), p属性直接定义在这个空对象上面,然后返回这个对象,这是 Object.defineProperty()的常见用法。
如果属性已经存在, Object.defineProperty()方法相当于更新该属性的属性描述对象。
如果一次性定义或修改多个属性,可以使用 Object.defineProperties()方法。
var obj = Object.defineProperties({}, {p1: { value: 123, enumerable: true },p2: { value: 'abc', enumerable: true },p3: { get: function () { return this.p1 + this.p2 },enumerable:true,configurable:true}
});obj.p1 // 123
obj.p2 // "abc"
obj.p3 // "123abc"
上面代码中, Object.defineProperties()同时定义了 obj对象的三个属性。其中, p3属性定义了取值函数 get,即每次读取该属性,都会调用这个取值函数。
注意,一旦定义了取值函数 get(或存值函数 set),就不能将 writable属性设为 true,或者同时定义 value属性,否则会报错。
var obj = {};Object.defineProperty(obj, 'p', {value: 123,get: function() { return 456; }
});
// TypeError: Invalid property.
// A property cannot both have accessors and be writable or have a valueObject.defineProperty(obj, 'p', {writable: true,get: function() { return 456; }
});
// TypeError: Invalid property descriptor.
// Cannot both specify accessors and a value or writable attribute
上面代码中,同时定义了 get属性和 value属性,以及将 writable属性设为 true,就会报错。
Object.defineProperty()Object.defineProperties()参数里面的属性描述对象, writableconfigurableenumerable这三个属性的默认值都为 false
var obj = {};
Object.defineProperty(obj, 'foo', {});
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
//   value: undefined,
//   writable: false,
//   enumerable: false,
//   configurable: false
// }
上面代码中,定义 obj.foo时用了一个空的属性描述对象,就可以看到各个元属性的默认值。

5. Object.prototype.propertyIsEnumerable()

实例对象的 propertyIsEnumerable()方法返回一个布尔值,用来判断某个属性是否可遍历。注意,这个方法只能用于判断对象自身的属性,对于继承的属性一律返回 false
var obj = {};
obj.p = 123;obj.propertyIsEnumerable('p') // true
obj.propertyIsEnumerable('toString') // false
上面代码中, obj.p是可遍历的,而 obj.toString是继承的属性。

6. 元属性

属性描述对象的各个属性称为“元属性”,因为它们可以看作是控制属性的属性。

6.1 value

value属性是目标属性的值。
var obj = {};
obj.p = 123;Object.getOwnPropertyDescriptor(obj, 'p').value
// 123Object.defineProperty(obj, 'p', { value: 246 });
obj.p // 246

上面代码是通过value属性,读取或改写obj.p的例子。

6.2 writable

writable属性是一个布尔值,决定了目标属性的值(value)是否可以被改变。
var obj = {};Object.defineProperty(obj, 'a', {value: 37,writable: false
});obj.a // 37
obj.a = 25;
obj.a // 37

上面代码中,obj.awritable属性是false。然后,改变obj.a的值,不会有任何效果。

注意,正常模式下,对 writablefalse的属性赋值不会报错,只会默默失败。但是,严格模式下会报错,即使对 a属性重新赋予一个同样的值。
'use strict';
var obj = {};Object.defineProperty(obj, 'a', {value: 37,writable: false
});obj.a = 37;
// Uncaught TypeError: Cannot assign to read only property 'a' of object

上面代码是严格模式,对obj.a任何赋值行为都会报错。

如果原型对象的某个属性的 writablefalse,那么子对象将无法自定义这个属性。
var proto = Object.defineProperty({}, 'foo', {value: 'a',writable: false
});var obj = Object.create(proto);obj.foo = 'b';
obj.foo // 'a'

上面代码中,proto是原型对象,它的foo属性不可写。obj对象继承proto,也不可以再自定义这个属性了。如果是严格模式,这样做还会抛出一个错误。

但是,有一个规避方法,就是通过覆盖属性描述对象,绕过这个限制。原因是这种情况下,原型链会被完全忽视。
var proto = Object.defineProperty({}, 'foo', {value: 'a',writable: false
});var obj = Object.create(proto);
Object.defineProperty(obj, 'foo', {value: 'b'
});obj.foo // "b"

6.3 enumerable

enumerable(可遍历性)返回一个布尔值,表示目标属性是否可遍历。
JavaScript 的早期版本, for...in循环是基于 in运算符的。我们知道, in运算符不管某个属性是对象自身的还是继承的,都会返回 true
var obj = {};
'toString' in obj // true
上面代码中, toString不是 obj对象自身的属性,但是 in运算符也返回 true,这导致了 toString属性也会被 for...in循环遍历。
这显然不太合理,后来就引入了“可遍历性”这个概念。只有可遍历的属性,才会被 for...in循环遍历,同时还规定 toString这一类实例对象继承的原生属性,都是不可遍历的,这样就保证了 for...in循环的可用性。
具体来说,如果一个属性的 enumerablefalse,下面三个操作不会取到该属性。
  • for..in 循环
  • Object.keys 方法
  • JSON.stringify 方法
因此, enumerable可以用来设置“秘密”属性。
var obj = {};Object.defineProperty(obj, 'x', {value: 123,enumerable: false
});obj.x // 123for (var key in obj) {console.log(key);
}
// undefinedObject.keys(obj)  // []
JSON.stringify(obj) // "{}"
上面代码中, obj.x属性的 enumerablefalse,所以一般的遍历操作都无法获取该属性,使得它有点像“秘密”属性,但不是真正的私有属性,还是可以直接获取它的值。
注意, for...in循环包括继承的属性, Object.keys方法不包括继承的属性。如果需要获取对象自身的所有属性,不管是否可遍历,可以使用 Object.getOwnPropertyNames方法。
另外, JSON.stringify方法会排除 enumerablefalse的属性,有时可以利用这一点。如果对象的 JSON 格式输出要排除某些属性,就可以把这些属性的 enumerable设为 false

6.4 configurable

configurable(可配置性)返回一个布尔值,决定了是否可以修改属性描述对象。也就是说, configurablefalse时, writableenumerableconfigurable都不能被修改了。
var obj = Object.defineProperty({}, 'p', {value: 1,writable: false,enumerable: false,configurable: false
});Object.defineProperty(obj, 'p', {writable: true})
// TypeError: Cannot redefine property: pObject.defineProperty(obj, 'p', {enumerable: true})
// TypeError: Cannot redefine property: pObject.defineProperty(obj, 'p', {configurable: true})
// TypeError: Cannot redefine property: pObject.defineProperty(obj, 'p', {value: 2})
// TypeError: Cannot redefine property: p
上面代码中, obj.pconfigurable属性为 false。然后,改动 writableenumerableconfigurable,结果都报错。
注意, writable属性只有在 false改为 true时会报错, true改为 false是允许的。
var obj = Object.defineProperty({}, 'p', {writable: true,configurable: false
});Object.defineProperty(obj, 'p', {writable: false})
// 修改成功
value属性的情况比较特殊。只要 writableconfigurable有一个为 true,就允许改动 value
var o1 = Object.defineProperty({}, 'p', {value: 1,writable: true,configurable: false
});Object.defineProperty(o1, 'p', {value: 2})
// 修改成功var o2 = Object.defineProperty({}, 'p', {value: 1,writable: false,configurable: true
});Object.defineProperty(o2, 'p', {value: 2})
// 修改成功
另外, writablefalse时,直接对目标属性赋值,不报错,但不会成功。
var obj = Object.defineProperty({}, 'p', {value: 1,writable: false,configurable: false
});obj.p = 2;
obj.p // 1
上面代码中, obj.pwritablefalse,对 obj.p直接赋值不会生效。如果是严格模式,还会报错。
可配置性决定了目标属性是否可以被删除(delete)。
var obj = Object.defineProperties({}, {p1: { value: 1, configurable: true },p2: { value: 2, configurable: false }
});delete obj.p1 // true
delete obj.p2 // falseobj.p1 // undefined
obj.p2 // 2

上面代码中, obj.p1configurabletrue,所以可以被删除, obj.p2就无法删除。

7. 存取器(存值函数setter和取值函数getter)

除了直接定义以外,属性还可以用存取器(accessor)定义。其中,存值函数称为 setter,使用属性描述对象的 set属性;取值函数称为 getter,使用属性描述对象的 get属性。
一旦对目标属性定义了存取器,那么存取的时候,都将执行对应的函数。利用这个功能,可以实现许多高级特性,比如定制属性的读取和赋值行为。
var obj = Object.defineProperty({}, 'p', {get: function () {return 'getter';},set: function (value) {console.log('setter: ' + value);}
});obj.p // "getter"
obj.p = 123 // "setter: 123"
上面代码中, obj.p定义了 getset属性。 obj.p取值时,就会调用 get;赋值时,就会调用 set
JavaScript 还提供了存取器的另一种写法。
// 写法二
var obj = {get p() {return 'getter';},set p(value) {console.log('setter: ' + value);}
};
上面两种写法,虽然属性 p的读取和赋值行为是一样的,但是有一些细微的区别。第一种写法,属性 pconfigurableenumerable都为 false,从而导致属性 p是不可遍历的;第二种写法,属性 pconfigurableenumerable都为 true,因此属性 p是可遍历的。实际开发中,写法二更常用。
注意,取值函数 get不能接受参数,存值函数 set只能接受一个参数(即属性的值)。
存取器往往用于,属性的值依赖对象内部数据的场合。
var obj ={$n : 5,get next() { return this.$n++ },set next(n) {if (n >= this.$n) this.$n = n;else throw new Error('新的值必须大于当前值');}
};obj.next // 5obj.next = 10;
obj.next // 10obj.next = 5;
// Uncaught Error: 新的值必须大于当前值
上面代码中, next属性的存值函数和取值函数,都依赖于内部属性 $n

8. 对象的拷贝

有时,我们需要将一个对象的所有属性,拷贝到另一个对象,可以用下面的方法实现。
var extend = function (to, from) {for (var property in from) {to[property] = from[property];}return to;
}extend({}, {a: 1
})
// {a: 1}
上面这个方法的问题在于,如果遇到存取器定义的属性,会只拷贝值。
extend({}, {get a() { return 1 }
})
// {a: 1}
为了解决这个问题,我们可以通过 Object.defineProperty方法来拷贝属性。
var extend = function (to, from) {for (var property in from) {if (!from.hasOwnProperty(property)) continue;Object.defineProperty(to,property,Object.getOwnPropertyDescriptor(from, property));}return to;
}extend({}, { get a(){ return 1 } })
// { get a(){ return 1 } })
上面代码中, hasOwnProperty那一行用来过滤掉继承的属性,否则可能会报错,因为 Object.getOwnPropertyDescriptor读不到继承属性的属性描述对象。

9. 控制对象状态

有时需要冻结对象的读写状态,防止对象被改变。JavaScript 提供了三种冻结方法,最弱的一种是 Object.preventExtensions,其次是 Object.seal,最强的是 Object.freeze

9.1 Object.preventExtensions()

Object.preventExtensions方法可以使得一个对象无法再添加新的属性。
var obj = new Object();
Object.preventExtensions(obj);Object.defineProperty(obj, 'p', {value: 'hello'
});
// TypeError: Cannot define property:p, object is not extensible.obj.p = 1;
obj.p // undefined
上面代码中, obj对象经过 Object.preventExtensions以后,就无法添加新属性了。

9.2 Object.isExtensible()

Object.isExtensible方法用于检查一个对象是否使用了 Object.preventExtensions方法。也就是说,检查是否可以为一个对象添加属性。
var obj = new Object();Object.isExtensible(obj) // true
Object.preventExtensions(obj);
Object.isExtensible(obj) // false
上面代码中,对 obj对象使用 Object.preventExtensions方法以后,再使用 Object.isExtensible方法,返回 false,表示已经不能添加新属性了。

9.3 Object.seal()

Object.seal方法使得一个对象既无法添加新属性,也无法删除旧属性。
var obj = { p: 'hello' };
Object.seal(obj);delete obj.p;
obj.p // "hello"obj.x = 'world';
obj.x // undefined
上面代码中, obj对象执行 Object.seal方法以后,就无法添加新属性和删除旧属性了。
Object.seal实质是把属性描述对象的 configurable属性设为 false,因此属性描述对象不再能改变了。
var obj = {p: 'a'
};// seal方法之前
Object.getOwnPropertyDescriptor(obj, 'p')
// Object {
//   value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }Object.seal(obj);// seal方法之后
Object.getOwnPropertyDescriptor(obj, 'p')
// Object {
//   value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: false
// }Object.defineProperty(obj, 'p', {enumerable: false
})
// TypeError: Cannot redefine property: p
上面代码中,使用 Object.seal方法之后,属性描述对象的 configurable属性就变成了 false,然后改变 enumerable属性就会报错。
Object.seal只是禁止新增或删除属性,并不影响修改某个属性的值。
var obj = { p: 'a' };
Object.seal(obj);
obj.p = 'b';
obj.p // 'b'
上面代码中, Object.seal方法对 p属性的 value无效,是因为此时 p属性的可写性由 writable决定。

9.4 Object.isSealed()

Object.isSealed方法用于检查一个对象是否使用了 Object.seal方法。
var obj = { p: 'a' };Object.seal(obj);
Object.isSealed(obj) // true
这时, Object.isExtensible方法也返回 false
var obj = { p: 'a' };Object.seal(obj);
Object.isExtensible(obj) // false

9.5 Object.freeze()

Object.freeze方法可以使得一个对象无法添加新属性、无法删除旧属性、也无法改变属性的值,使得这个对象实际上变成了常量。
var obj = {p: 'hello'
};Object.freeze(obj);obj.p = 'world';
obj.p // "hello"obj.t = 'hello';
obj.t // undefineddelete obj.p // false
obj.p // "hello"
上面代码中,对 obj对象进行 Object.freeze()以后,修改属性、新增属性、删除属性都无效了。这些操作并不报错,只是默默地失败。如果在严格模式下,则会报错。

9.6 Object.isFrozen()

Object.isFrozen方法用于检查一个对象是否使用了 Object.freeze方法。
var obj = {p: 'hello'
};Object.freeze(obj);
Object.isFrozen(obj) // true
使用 Object.freeze方法以后, Object.isSealed将会返回 trueObject.isExtensible返回 false
var obj = {p: 'hello'
};Object.freeze(obj);Object.isSealed(obj) // true
Object.isExtensible(obj) // false
Object.isFrozen的一个用途是,确认某个对象没有被冻结后,再对它的属性赋值。
var obj = {p: 'hello'
};Object.freeze(obj);if (!Object.isFrozen(obj)) {obj.p = 'world';
}
上面代码中,确认 obj没有被冻结后,再对它的属性赋值,就不会报错了。

9.7 局限性

上面的三个方法锁定对象的可写性有一个漏洞:可以通过改变原型对象,来为对象增加属性。
var obj = new Object();
Object.preventExtensions(obj);var proto = Object.getPrototypeOf(obj);
proto.t = 'hello';
obj.t
// hello
上面代码中,对象 obj本身不能新增属性,但是可以在它的原型对象上新增属性,就依然能够在 obj上读到。
一种解决方案是,把 obj的原型也冻结住。
var obj = new Object();
Object.preventExtensions(obj);var proto = Object.getPrototypeOf(obj);
Object.preventExtensions(proto);proto.t = 'hello';
obj.t // undefined
另外一个局限是,如果属性值是对象,上面这些方法只能冻结属性指向的对象,而不能冻结对象本身的内容。
var obj = {foo: 1,bar: ['a', 'b']
};
Object.freeze(obj);obj.bar.push('c');
obj.bar // ["a", "b", "c"]
上面代码中, obj.bar属性指向一个数组, obj对象被冻结以后,这个指向无法改变,即无法指向其他值,但是所指向的数组是可以改变的。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com