一、什么是对象🧐
对象是由属性或方法构成,每个属性或方法都由一个名称来标识,这个名称映射到一个值,值可以是数据或者函数。对象就是一组没有特定顺序的值
创建对象的两种形式✨
1.构造函数🍦
左边属性名,右边属性值。由于属性名和变量名是一样的,简写属性名语法出现了。简写属性名只要使用变量名(不用再 写冒号)就会自动被解释为同名的属性键
let person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function() { console.log(this.name); };
2.字面量(与上面等价)🍜
let name = "Nicholas";
let person = {
name, //简写!!!
age: 29,
job: "Software Engineer",
sayName() { console.log(this.name); } };
访问对象属性或者很简单,对象名.属性或对象名.方法名() 进行调用即可,比如
person.name、person.sayName()
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
Object.defineProperty(obj, prop, descriptor)
-
obj要定义属性的对象。
-
prop要定义或修改的属性的名称或
Symbol。 -
descriptor要定义或修改的属性描述符。
[返回值]
被传递给函数的对象。
内部属性🍰
JavaScript 中使用一些内部特性来描述属性的特征,比如描述属性是否可以枚举,是否可以删除修改等等,我们无法访直接问属性的这些特征,但是可以通过[[]]的方式来将某个特性标识为内部属性。这些内部属性分为数据属性和访问器属性。
1.数据属性🍲
[[Configurable]]:表示属性是否可以通过delete 删除并 重新定义,是否可以修改它的特性,以及是否可以把它改为访问 器属性(下文会讲到)。默认情况下(指的是使用字面量的形式),所有直接定义在对象上的属性的这个特性都是true ,如前面的例子所示。[[Enumerable]]:表示属性是否可以通过for-in 循环返回。默认情况下是 true[[Writable]]:表示属性的值是否可以被修改。默认情况下是true[[Value]]:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性的默认值为undefined 。
注意:数据属性的值一般都为ture或false,但是也可以写为1、0、null、undefined等等,但是为了代码规范,尽量写成true/false,感兴趣可的小伙伴可自行测试呐
// 'use strict'
let person = { name: "Nicholas" };
Object.defineProperty(person,'age',{
value:19,
enumerable:1,//true 内部转为了布尔类型
configurable:null, //false
})
console.log(Object.getOwnPropertyDescriptor(person,'age'));
delete person.age; //严格模式下直接报错,非严格模式不报错,并且继续编译
console.log(person.age);//并没有删除属性
console.log(Object.keys(person));
for (let i in person) {
console.log(person[i]);
}
例如🍕
let person = { name: "Nicholas" };
创建了一个名为person的对象,内部有个属性叫name,值为"Nicholas",意味着[Value]被设置为"Nicholas" ,读取和写入都在这个位置。该属性可通过delete person.name;将属性删除也就是说:Configurable:true,可重新赋值进行修改,意为可写Writable:true,当然也可以直接用for ... in遍历出来,意为可枚举Enumerable:true,这也恰好印证了默认情况下所有的数据属性都为true
要修改属性的默认特性,就必须使用Object.defineProperty()方法。这个方法接收3个参数:要给其添加属性的对象、属性的名称和 一个描述符对象。最后一个参数,即描述符对象上的属性可以包含: configurable 、enumerable 、writable 和value ,跟相关特性的名称一一对应。根据要修改的特性,可以设置其中一个或多个值。
[[Writable]]🍥 此时的name属性赋予了一个只读的属性,以后不能再修改了,严格模式下,抛出错误
[[Configurable]]
'use strict'//注意是严格模式下!!!
let person = { name: "Nicholas" };
Object.defineProperty(person, 'age', {
value: 19,
configurable: false,
// writable:true,
// enumerable:true
});
// 第一种情况
// delete person.age; //控制台报错,不可删除
//第二种情况 报错err: Cannot redefine property: age
// Object.defineProperty(person, 'age', {
// value: 19,
// configurable:true
// });
//第三种情况 如果writable:true,意味着值可更改,当`configurable:false,writable:true`
//[[value]]可以改变并且修改任何非writable属性会导致错误。
// 但是仅`configurable: false` [[value]]不可配置,并且其余的属性只要与第一次配置的属性不一致都会报错
Object.defineProperty(person, 'age', {
value: 19,
// writable:false,
// enumerable:true,
// configurable:true
});
console.log(person.age);
[[Enumerable]]🍥
①当enumerable:false时
②当enumerable:true
在调用
Object.defineProperty()时,configurable、enumerable和writable的值如果不指定,则都默认为false 。 多数情况下,可能都不需要Object.defineProperty()提供的这 些强大的设置,但要理解JavaScript对象,就要理解这些概念。
-
[[value]]设置属性的值,读取和写入都在这个位置,🍥-
当属性为不可配置的时候不能更改
[[value]], -
不可配置可写,[[value]]可以改变并且修改任何非writable属性会导致错误,上面的例子已经演示过了哦
-
2.访问器属性🍟
访问器属性不包含数据值。相反,它们包含一个获取(getter)函数 和一个设置(setter)函数,不过这两个函数不是必需的。在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。在写入访问器属性时,会调用设置函数并传入新值,这个函数必 须决定对数据做出什么修改。访问器属性有4个特性描述它们的行为。
[[Configurable]]:表示属性是否可以通过delete 删除并 重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认情况下true 。[[Enumerable]]:表示属性是否可以通过for-in 循环返回。默认为true 。[[Get]]:获取函数,在读取属性时调用。默认值为 undefined 。[[Set]]:设置函数,在写入属性时调用。默认值为 undefined
访问器属性是不能直接定义,必须使用 Object.defineProperty()获取函数和设置函数不一定都要定义。只定义获取函数意味着属性是只读的,尝试修改属性会被忽略。在严格模式下,尝试写入只定义了 获取函数的属性会抛出错误。类似地,只有一个设置函数的属性是不 能读取的,非严格模式下读取会返回undefined ,严格模式下会抛出错误。
let book = {
year_: 2017,
edition: 1
};
Object.defineProperty(book, "year", {
get() {
return this.year_;
},
set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
});
book.year = 2018;
console.log(book.edition); // 2
访问到对象year的属性,比如输出
book.year就会调用get方法。并且返回一个值。book.year = 2018;就会走set方法,newValue就是接收到的值2018。需要注意的是,在这里很容易忽略Configurable和Enumerable这两个属性和上面的数据属性类似,如果在defineProperty中不定义为true,默认也是为false。
思考:如果用for in时 year属性是否能够遍历出来呢
答案是不能,在defineProperty中year默认不可枚举
初学者的坑🌚🌚
初学者大多数都会犯的一个错误,就是搞不懂为什么要用_year来模仿私有成员,现在我们把_year删掉用year代替
大家觉得这段代码对么?😇
let book = {
// year_: 2017,
edition: 1
};
Object.defineProperty(book, "year", {
get() {
return this.year;
}
});
console.log(book.year);
控制台的错误是
出现这样的错误很简单,当调用book.year的时候调用了get方法,返回的值又是去找get方法,成了一个死循环,解决办法就是要么定义一个伪私有属性,或者直接返回一个值。
敲重点🧐:不能同时指定访问器和值或可写属性
Object.defineProperty(book, "year", {
get() {
return 1;
},
// writable:true //不能与上面的get共存
// value:2 //不能与上面的get共存
});
普通属性和有访问器属性的一些区别💕
3.定义多个属性与读取属性的特征
使用Object.getOwnPropertyDescriptor() 方法可以取得指定属性 的属性描述符。这个方法接收两个参数:属性所在的对象和要取得其描述 符的属性名。返回值是一个对象,对于访问器属性包含configurable 、 enumerable 、get 和set 属性,对于数据属性包含configurable 、 enumerable 、writable 和value 属性。
let book = {};
Object.defineProperties(book, {
year_: {
value: 2017
},
edition: {
value: 1
},
year: {
get: function () {
return this.year_;
},
set: function (newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
}
});
console.log(Object.getOwnPropertyDescriptor(book, "year_"));
// {value: 2017, writable: false, enumerable: false, configurable: false}
console.log(Object.getOwnPropertyDescriptor(book, "year"));
// {enumerable: false, configurable: false, get: ƒ, set: ƒ}
唯一的区别是所有属性都是同时定义,用Object.getOwnPropertyDescriptor() 方法可以取得指定属性 的属性描述符。这个方法接收两个参数:属性所在的对象和要取得其描述 符的属性名。返回值是一个对象,访问器属性与数据属性返回值不一样,请自行观察Object.getOwnPropertyDescriptors() 静 态方法。这个方法实际上会在每个自有属性上调用 Object.getOwnPropertyDescriptor() 并在一个新对象中返回它们
console.log(Object.getOwnPropertyDescriptors(book));
// { edition: {
// configurable: false,
// enumerable: false,
// value: 1,
// writable: false
// },
// year: {
// configurable: false,
// enumerable: false,
// get: f(),
// set: f(newValue),
// },
// year_: {
// configurable: false,
// enumerable: false,
// value: 2017,
// writable: false
// } }