对象
创建对象
有两种方式:
- 通过 new 操作符实例化一个对象,再添加属性。
let person = new Object();
person.name = "孤城浪人";
person.sayName = function() {
console.log(this.name);
};
构造函数,若不需要传参,那么在实例化时可以省略后边小括号。这种方式的缺点是每个实例的属性和方法都是新创建的,实例之间无法共享方法和属性。
function Person() {
this.name = '孤城浪人';
}
const person1 = new Person();
const person2 = new Person;
console.log(person1);//Person {name: '孤城浪人'}
console.log(person2);//Person {name: '孤城浪人'}
- 对象字面量。这种方式更方便。
let person = {
name: "孤城浪人",
sayName() {
console.log(this.name);
}
};
- 原型方式
将属性和方法定义在构造函数的原型对象上,那么任何实例都能够访问到该属性和方法,这样就实现了属性和方法的共享。
function Person() {}
Person.prototype.name = "孤城浪人";
Person.prototype.age = 29;
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
person1.sayName(); // "孤城浪人"
let person2 = new Person();
person2.sayName(); // "孤城浪人"
console.log(person1.sayName == person2.sayName); // true
属性
属性分为数据属性和访问器属性。JS 中可以在给对象添加属性时可以通过定义一个描述对象来限制属性的行为,我们不能直接拿到描述对象。
数据属性
数据属性就是一个保存数据值的“键”。如下例中name
就是属性孤城浪人
就是值。
let person = {
name: "孤城浪人"
};
描述对象的四个特性:
- [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改属性的描述对象,以及是否可以把它改为访问器属性。默认为 true,如前面的例子所示。
- [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认为 true。
- [[Writable]]:表示属性的值是否可以被修改。默认为 true。
- [[Value]]:属性实际的值。默认值为 undefined。
要修改属性的默认特性,就必须使用 Object.defineProperty()
方法。这个方法接收 3 个参数:
- 要给其添加属性的对象。
- 属性的名称和一个描述符对象。
- 描述符对象。
如下例子,person 对象的 name 属性既不能删除(严格模式下会抛出错误)也不能修改值。注意属性一旦被定义为不可配置之后,就不能再变回可配置
let person = {};
Object.defineProperty(person, "name", {
writable: false,
Configurable:false,
value: "孤城浪人"
});
console.log(person.name); // "孤城浪人"
person.name = "Greg";
console.log(person.name); // "孤城浪人"
delete person.name;
console.log(person.name); // "孤城浪人"
访问器属性
访问器属性只能是一个获取(getter)函数或一个设置(setter)函数,这两个函数不是必需的。在读取访问器属性值时,会调用获取函数返回一个有效的值。在写入访问器属性时,会调用设置函数将值设为新值。访问器属性也有 4 个特性描述它们的行为。
- [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改属性的描述对象,以及是否可以把它改为数据属性。默认为 true。
- [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认为 true。
- [[Get]]:获取函数,在读取属性时调用。默认值为 undefined。
- [[Set]]:设置函数,在写入属性时调用。默认值为 undefined。
访问器属性是不能直接定义的,必须使用Object.defineProperty
。下面是一个例子:
let person = { name_: '' };
Object.defineProperty(person, "name", {
get() {
console.log('读取函数');
return this.name_;
},
set(newValue) {
console.log('设置函数');
this.name_ = newValue;
}
});
person.name = '孤城浪人';
console.log(person.name_);
console.log(person.name);
// 设置函数
// 孤城浪人
// 读取函数
// 孤城浪人
上边例子中,person
对象中有一个不愿被外部方法访问的name_
属性,所以定义了访问器属性 name
,通过 name 来间接读取和设置 name_
。
获取函数和设置函数是可选的。只定义获取函数那么修改属性会被忽略。在严格模式下,尝试写入只定义了获取函数的属性会抛出错误。只定义设置函数的属性不能读取,非严格模式下读取返回 undefined,严格模式下会抛出错误。
同时添加多个属性
由于 Object.defineProperty
方法一次只能添加一个属性,当需要添加多个属性时就会变得很麻烦,因此有了 Object.defineProperties
方法,可以一次添加多个属性。它接收两个参数:
- 添加或修改属性的对象
- 描述符对象,其属性与要添加或修改的属性一一对应。
如下例:
let person = {};
Object.defineProperties(person, {
name_: {
value: 'WPF'
},
age: {
value: 22
},
name: {
get() {
console.log('读取函数');
return this.name_;
},
set(newValue) {
console.log('设置函数');
this.name_ = newValue;
}
}
})
person.name = '孤城浪人';
console.log(person.name_);
console.log(person.name);
console.log(person.age);
// 设置函数
// WPF
// 读取函数
// WPF
// 22
注意:数据属性的 configurable、enumerable 和 writable 特性值都是 false。 所以在上边例子中可以看到修改 person.name
并没有成功。
获取属性的描述对象
前边说过了我们不能直接在 JS 中拿到属性的描述对象,但是我们可以通过 JS 提供的Object.getOwnPropertyDescriptor
方法拿到属性的描述对象。
let person = {};
Object.defineProperty(person, 'name', {
writable: false,
value: '孤城浪人'
})
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
同时获取多个属性的描述对象
若想获取多个属性的描述对象Object.getOwnPropertyDescriptor
方法就没那么方便了,所以 ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors
方法,接收一个参数:需要查询的对象。这个方法实际上会在每个属性上调用Object.getOwnPropertyDescriptor
并在一个新对象中返回它们。如下例
let person = {};
Object.defineProperties(person, {
name_: {
value: 'WPF'
},
age: {
value: 22
},
})
console.log(Object.getOwnPropertyDescriptors(person));
合并对象
把源对象所有的本地属性一起复制到目标对象上,这种操作也叫“混入”(mixin)。
Object.assign
方法接收一个目标对象和一个或多个源对象作为参数,将每个源对象中可枚举属性和自有属性浅复制到目标对象。符合条件的属性,本方法会使用源对象上的 [[Get]] 取得属性的值,然后使用目标对象上的 [[Set]] 设置属性的值。
如果多个源对象有相同的属性,则后面的会覆盖前面的值。不能在两个对象间转移获取函数和设置函数。
如下例就会调用 person 的 get 访问器属性并会调用目标对象 person1 的同名 set 访问器属性。且通过打印结果可以看到目标对象上并没有叫 name 的 get 访问器属性。
const person1 = {
set name(val) {
console.log(`set执行,拿到 ${val}`);
}
};
const person = {
get name() {
console.log('get执行');
return 'foo';
}
};
Object.assign(person1, person);
console.log(person1)
注意:如果赋值期间出错,则操作会中止并退出,同时抛出错误。所以可能只会完成部分属性的复制。
const person1 = {};
const person = {
age: 22,
get name() {
throw Error('出错啦!')
},
sex: 'man'
};
try {
Object.assign(person1, person);
} catch (error) {
console.log(error)
}
console.log(person1)
对象语法增强
Object.is
接收两个参数,判断两个参数是否相同,很像 ===
。
console.log(Object.is(+0, -0)); // false
console.log(Object.is(+0, 0)); // true
console.log(Object.is(-0, 0)); // false
console.log(Object.is(NaN, NaN)); // true
属性值简写
属性名和变量名是一样可以,只要在对象中使用变量名即可,若未找到同名变量则会报错。
let name = '孤城浪人';
let age = 22;
let person = {
name,
age
}
console.log(person);
可计算属性
可计算属性可以在对象字面量中完成动态属性赋值,中括号中的内容会被当作 JavaScript 表达式求值。
注意:如果表达式抛出错误,那么之前完成的计算是不能回滚的
let myName = 'name';
function getAge() {
return 'age';
}
let person = {
[myName]: '孤城浪人',
[getAge()]: 22
}
console.log(person);//{name: '孤城浪人', age: 22}
in
in 操作符会在可以通过对象访问指定属性时返回 true,无论该属性是在实例上还是在原型上。
function Person() {
this.name = '孤城浪人';
}
Person.prototype.age = 22;
const person1 = new Person;
console.log("name" in person1);//true
console.log("age" in person1);//true
console.log(person1.hasOwnProperty('name'));//true
console.log(person1.hasOwnProperty('age'));//false
对象的迭代
Object.values & Object.entries
ECMAScript 2017 新增两个静态方法Object.values
和 Object.entries
接收一个对象作为参数,返回它们内容的数组。Object.values
返回对象值的数组,Object.entries
返回键/值对的数组。
function Person() {
this.name = '孤城浪人';
this.age = 22;
}
const person = new Person;
console.log(Object.values(person));
console.log(Object.entries(person));
注意,非字符串属性会被转换为字符串输出。并且这两个方法对对象属性是浅复制:
Object.keys & for in
Object.keys
接收一个对象作为参数,返回包含该对象所有可枚举属性名称的字符串数组,该字符串数组不包含原型对象上的可枚举属性。for in
迭代该对象和原型对象上的所有可枚举属性名称。
function Person() {
this.name = '孤城浪人';
this.age = 22;
}
Person.prototype.sex = 'man';
const person = new Person;
console.log(Object.keys(person));//['name', 'age']
for (let key in person) {
console.log(key + ':' + person[key]);
}
注意:for-in
循环和 Object.keys
的枚举顺序是不确定的,取决于 JS 引擎,可能因浏览器而异。
我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。