一、对象
创建对象
我们可以用下面两种语法中的任一种来创建一个空的对象(“空柜子”):
let user = new Object(); // “构造函数” 的语法
let user = {}; // “字面量” 的语法
文本和属性
let user = { // 一个对象
name: "John", // 键 "name",值 "John"
age: 30, // 键 "age",值 30
"likes birds": true // 多词属性名必须加引号
};
单属性名
// 读取文件的属性:
alert( user.name ); // John
alert( user.age ); // 30
//添加文本的属性
user.isAdmin = true;
//删除文本的属性
delete user.age;
多属性名
// 设置
user["likes birds"] = true;
// 读取
alert(user["likes birds"]); // true
// 删除
delete user["likes birds"];
计算属性
let fruit = 'apple';
let bag = {
[fruit + 'Computers']: 5 // bag.appleComputers = 5
};
属性名的简写
function makeUser(name, age) {
return {
//name: name,
name, // 与 name: name 相同,我们把name:name简写成这种形式
age: age,
// ……其他的属性
};
}
let user = makeUser("John", 30);
alert(user.name); // John
属性存在性测试,“in” 操作符
语法是
"key" in object
let user = { name: "John", age: 30 };
alert( "age" in user ); // true,user.age 存在
alert( "blabla" in user ); // false,user.blabla 不存在。
for…in 循环
语法
for (key in object) {
// 对此对象属性中的每个键执行的代码
}
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// keys
alert( key ); // name, age, isAdmin
// 属性键的值
alert( user[key] ); // John, 30, true
}
this
方法的示例
let user = {
name: "John",
age: 30
};
user.sayHi = function() {
alert("Hello!");
};
user.sayHi(); // Hello!
let user = {
name: "John",
age: 30
};
// 首先,声明函数
function sayHi() {
alert("Hello!");
}
// 然后将其作为一个方法添加
user.sayHi = sayHi;
user.sayHi(); // Hello!
方法的简写
在对象字面量中,有一种更短的(声明)方法的语法:
// 这些对象作用一样
user = {
sayHi: function() {
alert("Hello");
}
};
// 方法简写看起来更好,对吧?
let user = {
sayHi() { // 与 "sayHi: function(){...}" 一样
alert("Hello");
}
};
方法中的this
let user = {
name: "John",
age: 30,
sayHi() {
// "this" 指的是“当前的对象”
alert(this.name);
}
};
user.sayHi(); // John
二、对象的引用和复制
引用
与原始类型相比,对象的根本区别之一是对象是“通过引用”被存储和复制的,与原始类型值相反:字符串,数字,布尔值等 —— 始终是以“整体值”的形式被复制的。
let message = "Hello!";
let phrase = message;
let user = { name: "John" };
let admin = user; // 复制引用
我们可以通过其中任意一个变量来访问该对象并修改它的内容:
let user = { name: 'John' };
let admin = user;
admin.name = 'Pete'; // 通过 "admin" 引用来修改
alert(user.name); // 'Pete',修改能通过 "user" 引用看到
克隆
浅克隆
let user = {
name: "John",
age: 30
};
let clone = {}; // 新的空对象
// 将 user 中所有的属性拷贝到其中
for (let key in user) {
clone[key] = user[key];
}
// 现在 clone 是带有相同内容的完全独立的对象
clone.name = "Pete"; // 改变了其中的数据
alert( user.name ); // 原来的对象中的 name 属性依然是 John
Object.assign 方法来达成同样的效果。
Object.assign(dest, [src1, src2, src3...])
//第一个参数 dest 是指目标对象。
//更后面的参数 src1, ..., srcN(可按需传递多个参数)是源对象。
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
// 将 permissions1 和 permissions2 中的所有属性都拷贝到 user 中
Object.assign(user, permissions1, permissions2);
// 现在 user = { name: "John", canView: true, canEdit: true }
三、构造函数
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
四、symbol类型型
根据规范,只有两种原始类型可以用作对象属性键:
- 字符串类型
- symbol 类型
否则,如果使用另一种类型,例如数字,它会被自动转换为字符串。所以 obj[1] 与 obj[“1”] 相同,而 obj[true] 与 obj[“true”] 相同。
symbol
“symbol” 值表示唯一的标识符。
可以使用 Symbol() 来创建这种类型的值:
let id = Symbol();
创建时,我们可以给 symbol 一个描述(也称为 symbol 名),这在代码调试时非常有用:
// id 是描述为 "id" 的 symbol
let id = Symbol("id");
symbol 保证是唯一的。即使我们创建了许多具有相同描述的 symbol,它们的值也是不同
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
“隐藏”属性
symbol 允许我们创建对象的“隐藏”属性,代码的任何其他部分都不能意外访问或重写这些属性。
例如,如果我们使用的是属于第三方代码的 user 对象,我们想要给它们添加一些标识符。
我们可以给它们使用 symbol 键:
let user = { // 属于另一个代码
name: "John"
};
let id = Symbol("id");
user[id] = 1;
alert( user[id] ); // 我们可以使用 symbol 作为键来访问数据
使用 Symbol(“id”) 作为键,比起用字符串 “id” 来有什么好处呢?
因为 user 对象属于其他的代码,那些代码也会使用这个对象,所以我们不应该在它上面直接添加任何字段,这样很不安全。但是你添加的 symbol 属性不会被意外访问到,第三方代码根本不会看到它,所以使用 symbol 基本上不会有问题。
对象字面量中的 symbol
如果我们要在对象字面量 {…} 中使用 symbol,则需要使用方括号把它括起来。
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // 而不是 "id":123
};
这是因为我们需要变量 id 的值作为键,而不是字符串 “id”。
symbol 在 for…in 中会被跳过
symbol 属性不参与 for..in 循环。
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
for (let key in user) alert(key); // name, age(没有 symbol)
// 使用 symbol 任务直接访问
alert( "Direct: " + user[id] );
全局symbol
应用程序的不同部分想要访问的 symbol “id” 指的是完全相同的属性。
为了实现这一点,这里有一个 全局 symbol 注册表。我们可以在其中创建 symbol 并在稍后访问它们,它可以确保每次访问相同名字的 symbol 时,返回的都是相同的 symbol。
要从注册表中读取(不存在则创建)symbol,请使用 Symbol.for(key)。
// 从全局注册表中读取
let id = Symbol.for("id"); // 如果该 symbol 不存在,则创建它
// 再次读取(可能是在代码中的另一个位置)
let idAgain = Symbol.for("id");
// 相同的 symbol
alert( id === idAgain ); // true
Symbol.keyFor
Symbol.keyFor(sym)通过全局 symbol 返回一个名字。
// 通过 name 获取 symbol
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// 通过 symbol 获取 name
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
Symbol.keyFor 内部使用全局 symbol 注册表来查找 symbol 的键。所以它不适用于非全局 symbol。如果 symbol 不是全局的,它将无法找到它并返回 undefined。
也就是说,任何 symbol 都具有 description 属性。
let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");
alert( Symbol.keyFor(globalSymbol) ); // name,全局 symbol
alert( Symbol.keyFor(localSymbol) ); // undefined,非全局
alert( localSymbol.description ); // name
系统 symbol
JavaScript 内部有很多“系统” symbol,我们可以使用它们来微调对象的各个方面。
它们都被列在了 众所周知的 symbol 表的规范中:
- Symbol.hasInstance
- Symbol.isConcatSpreadable
- Symbol.iterator
- Symbol.toPrimitive
- ……等等。
例如,Symbol.toPrimitive 允许我们将对象描述为原始值转换。我们很快就会看到它的使用。
当我们研究相应的语言特征时,我们对其他的 symbol 也会慢慢熟悉起来。