一、对象

创建对象

我们可以用下面两种语法中的任一种来创建一个空的对象(“空柜子”):

  1. let user = new Object(); // “构造函数” 的语法
  2. let user = {}; // “字面量” 的语法

文本和属性

  1. let user = { // 一个对象
  2. name: "John", // 键 "name",值 "John"
  3. age: 30, // 键 "age",值 30
  4. "likes birds": true // 多词属性名必须加引号
  5. };

单属性名

  1. // 读取文件的属性:
  2. alert( user.name ); // John
  3. alert( user.age ); // 30
  4. //添加文本的属性
  5. user.isAdmin = true;
  6. //删除文本的属性
  7. delete user.age;

多属性名

  1. // 设置
  2. user["likes birds"] = true;
  3. // 读取
  4. alert(user["likes birds"]); // true
  5. // 删除
  6. delete user["likes birds"];

计算属性

  1. let fruit = 'apple';
  2. let bag = {
  3. [fruit + 'Computers']: 5 // bag.appleComputers = 5
  4. };

属性名的简写

  1. function makeUser(name, age) {
  2. return {
  3. //name: name,
  4. name, // 与 name: name 相同,我们把name:name简写成这种形式
  5. age: age,
  6. // ……其他的属性
  7. };
  8. }
  9. let user = makeUser("John", 30);
  10. alert(user.name); // John

属性存在性测试,“in” 操作符

语法是

  1. "key" in object
  1. let user = { name: "John", age: 30 };
  2. alert( "age" in user ); // true,user.age 存在
  3. alert( "blabla" in user ); // false,user.blabla 不存在。

for…in 循环

语法

  1. for (key in object) {
  2. // 对此对象属性中的每个键执行的代码
  3. }
  1. let user = {
  2. name: "John",
  3. age: 30,
  4. isAdmin: true
  5. };
  6. for (let key in user) {
  7. // keys
  8. alert( key ); // name, age, isAdmin
  9. // 属性键的值
  10. alert( user[key] ); // John, 30, true
  11. }

this

方法的示例

  1. let user = {
  2. name: "John",
  3. age: 30
  4. };
  5. user.sayHi = function() {
  6. alert("Hello!");
  7. };
  8. user.sayHi(); // Hello!
  1. let user = {
  2. name: "John",
  3. age: 30
  4. };
  5. // 首先,声明函数
  6. function sayHi() {
  7. alert("Hello!");
  8. }
  9. // 然后将其作为一个方法添加
  10. user.sayHi = sayHi;
  11. user.sayHi(); // Hello!

方法的简写

在对象字面量中,有一种更短的(声明)方法的语法:

  1. // 这些对象作用一样
  2. user = {
  3. sayHi: function() {
  4. alert("Hello");
  5. }
  6. };
  7. // 方法简写看起来更好,对吧?
  8. let user = {
  9. sayHi() { // 与 "sayHi: function(){...}" 一样
  10. alert("Hello");
  11. }
  12. };

方法中的this

  1. let user = {
  2. name: "John",
  3. age: 30,
  4. sayHi() {
  5. // "this" 指的是“当前的对象”
  6. alert(this.name);
  7. }
  8. };
  9. user.sayHi(); // John

二、对象的引用和复制

引用

与原始类型相比,对象的根本区别之一是对象是“通过引用”被存储和复制的,与原始类型值相反:字符串,数字,布尔值等 —— 始终是以“整体值”的形式被复制的。

  1. let message = "Hello!";
  2. let phrase = message;

image.png

  1. let user = { name: "John" };
  2. let admin = user; // 复制引用

image.png
我们可以通过其中任意一个变量来访问该对象并修改它的内容:

  1. let user = { name: 'John' };
  2. let admin = user;
  3. admin.name = 'Pete'; // 通过 "admin" 引用来修改
  4. alert(user.name); // 'Pete',修改能通过 "user" 引用看到

克隆

浅克隆

  1. let user = {
  2. name: "John",
  3. age: 30
  4. };
  5. let clone = {}; // 新的空对象
  6. // 将 user 中所有的属性拷贝到其中
  7. for (let key in user) {
  8. clone[key] = user[key];
  9. }
  10. // 现在 clone 是带有相同内容的完全独立的对象
  11. clone.name = "Pete"; // 改变了其中的数据
  12. alert( user.name ); // 原来的对象中的 name 属性依然是 John

Object.assign 方法来达成同样的效果。

  1. Object.assign(dest, [src1, src2, src3...])
  2. //第一个参数 dest 是指目标对象。
  3. //更后面的参数 src1, ..., srcN(可按需传递多个参数)是源对象。
  1. let user = { name: "John" };
  2. let permissions1 = { canView: true };
  3. let permissions2 = { canEdit: true };
  4. // 将 permissions1 和 permissions2 中的所有属性都拷贝到 user 中
  5. Object.assign(user, permissions1, permissions2);
  6. // 现在 user = { name: "John", canView: true, canEdit: true }

三、构造函数

  1. function User(name) {
  2. this.name = name;
  3. this.isAdmin = false;
  4. }
  5. let user = new User("Jack");
  6. alert(user.name); // Jack
  7. alert(user.isAdmin); // false

四、symbol类型型

根据规范,只有两种原始类型可以用作对象属性键:

  • 字符串类型
  • symbol 类型

否则,如果使用另一种类型,例如数字,它会被自动转换为字符串。所以 obj[1] 与 obj[“1”] 相同,而 obj[true] 与 obj[“true”] 相同。

symbol

“symbol” 值表示唯一的标识符。
可以使用 Symbol() 来创建这种类型的值:

  1. let id = Symbol();

创建时,我们可以给 symbol 一个描述(也称为 symbol 名),这在代码调试时非常有用:

  1. // id 是描述为 "id" 的 symbol
  2. let id = Symbol("id");

symbol 保证是唯一的。即使我们创建了许多具有相同描述的 symbol,它们的值也是不同

  1. let id1 = Symbol("id");
  2. let id2 = Symbol("id");
  3. alert(id1 == id2); // false

“隐藏”属性

symbol 允许我们创建对象的“隐藏”属性,代码的任何其他部分都不能意外访问或重写这些属性。
例如,如果我们使用的是属于第三方代码的 user 对象,我们想要给它们添加一些标识符。
我们可以给它们使用 symbol 键:

  1. let user = { // 属于另一个代码
  2. name: "John"
  3. };
  4. let id = Symbol("id");
  5. user[id] = 1;
  6. alert( user[id] ); // 我们可以使用 symbol 作为键来访问数据

使用 Symbol(“id”) 作为键,比起用字符串 “id” 来有什么好处呢?
因为 user 对象属于其他的代码,那些代码也会使用这个对象,所以我们不应该在它上面直接添加任何字段,这样很不安全。但是你添加的 symbol 属性不会被意外访问到,第三方代码根本不会看到它,所以使用 symbol 基本上不会有问题。

对象字面量中的 symbol

如果我们要在对象字面量 {…} 中使用 symbol,则需要使用方括号把它括起来。

  1. let id = Symbol("id");
  2. let user = {
  3. name: "John",
  4. [id]: 123 // 而不是 "id":123
  5. };

这是因为我们需要变量 id 的值作为键,而不是字符串 “id”。

symbol 在 for…in 中会被跳过

symbol 属性不参与 for..in 循环。

  1. let id = Symbol("id");
  2. let user = {
  3. name: "John",
  4. age: 30,
  5. [id]: 123
  6. };
  7. for (let key in user) alert(key); // name, age(没有 symbol)
  8. // 使用 symbol 任务直接访问
  9. alert( "Direct: " + user[id] );

全局symbol

应用程序的不同部分想要访问的 symbol “id” 指的是完全相同的属性。
为了实现这一点,这里有一个 全局 symbol 注册表。我们可以在其中创建 symbol 并在稍后访问它们,它可以确保每次访问相同名字的 symbol 时,返回的都是相同的 symbol。
要从注册表中读取(不存在则创建)symbol,请使用 Symbol.for(key)。

  1. // 从全局注册表中读取
  2. let id = Symbol.for("id"); // 如果该 symbol 不存在,则创建它
  3. // 再次读取(可能是在代码中的另一个位置)
  4. let idAgain = Symbol.for("id");
  5. // 相同的 symbol
  6. alert( id === idAgain ); // true

Symbol.keyFor

Symbol.keyFor(sym)通过全局 symbol 返回一个名字。

  1. // 通过 name 获取 symbol
  2. let sym = Symbol.for("name");
  3. let sym2 = Symbol.for("id");
  4. // 通过 symbol 获取 name
  5. alert( Symbol.keyFor(sym) ); // name
  6. alert( Symbol.keyFor(sym2) ); // id

Symbol.keyFor 内部使用全局 symbol 注册表来查找 symbol 的键。所以它不适用于非全局 symbol。如果 symbol 不是全局的,它将无法找到它并返回 undefined。
也就是说,任何 symbol 都具有 description 属性。

  1. let globalSymbol = Symbol.for("name");
  2. let localSymbol = Symbol("name");
  3. alert( Symbol.keyFor(globalSymbol) ); // name,全局 symbol
  4. alert( Symbol.keyFor(localSymbol) ); // undefined,非全局
  5. alert( localSymbol.description ); // name

系统 symbol

JavaScript 内部有很多“系统” symbol,我们可以使用它们来微调对象的各个方面。
它们都被列在了 众所周知的 symbol 表的规范中:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive
  • ……等等。

例如,Symbol.toPrimitive 允许我们将对象描述为原始值转换。我们很快就会看到它的使用。
当我们研究相应的语言特征时,我们对其他的 symbol 也会慢慢熟悉起来。