一、对象
创建对象
我们可以用下面两种语法中的任一种来创建一个空的对象(“空柜子”):
let user = new Object(); // “构造函数” 的语法let user = {}; // “字面量” 的语法
文本和属性
let user = { // 一个对象name: "John", // 键 "name",值 "John"age: 30, // 键 "age",值 30"likes birds": true // 多词属性名必须加引号};
单属性名
// 读取文件的属性:alert( user.name ); // Johnalert( 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) {// keysalert( 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); // Jackalert(user.isAdmin); // false
四、symbol类型型
根据规范,只有两种原始类型可以用作对象属性键:
- 字符串类型
- symbol 类型
否则,如果使用另一种类型,例如数字,它会被自动转换为字符串。所以 obj[1] 与 obj[“1”] 相同,而 obj[true] 与 obj[“true”] 相同。
symbol
“symbol” 值表示唯一的标识符。
可以使用 Symbol() 来创建这种类型的值:
let id = Symbol();
创建时,我们可以给 symbol 一个描述(也称为 symbol 名),这在代码调试时非常有用:
// id 是描述为 "id" 的 symbollet 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");// 相同的 symbolalert( id === idAgain ); // true
Symbol.keyFor
Symbol.keyFor(sym)通过全局 symbol 返回一个名字。
// 通过 name 获取 symbollet sym = Symbol.for("name");let sym2 = Symbol.for("id");// 通过 symbol 获取 namealert( Symbol.keyFor(sym) ); // namealert( 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,全局 symbolalert( Symbol.keyFor(localSymbol) ); // undefined,非全局alert( localSymbol.description ); // name
系统 symbol
JavaScript 内部有很多“系统” symbol,我们可以使用它们来微调对象的各个方面。
它们都被列在了 众所周知的 symbol 表的规范中:
- Symbol.hasInstance
- Symbol.isConcatSpreadable
- Symbol.iterator
- Symbol.toPrimitive
- ……等等。
例如,Symbol.toPrimitive 允许我们将对象描述为原始值转换。我们很快就会看到它的使用。
当我们研究相应的语言特征时,我们对其他的 symbol 也会慢慢熟悉起来。
