代码质量
在 Chrome 中调试
我们也可以使用 debugger 命令来暂停代码,像这样:
function hello(name) {
let phrase = `Hello, ${name}!`;
debugger; // <-- 调试器会在这停止
say(phrase);
}
Object(对象):基础知识
const 声明的对象
- 使用 const 声明的对象是可以被修改的。const 声明仅固定了 user 的值,而不是值(该对象)里面的内容。
属性命名没有限制。属性名可以是任何字符串或者 symbol(一种特殊的标志符类型,将在后面介绍)。其他类型会被自动地转换为字符串。
对象引用和复制
与原始类型相比,对象的根本区别之一是对象是“通过引用”被存储和复制的,与原始类型值相反:字符串,数字,布尔值等 —— 始终是以“整体值”的形式被复制的。
- 对象通过引用被赋值和拷贝。
- 我们可以将对象变量(例如 user)想象成一张带有地址的纸。当我们对对象执行操作时,例如获取一个属性 user.name,JavaScript 引擎将对该地址进行搜索,并在实际对象上执行操作。
- 当一个对象变量被复制 —— 引用则被复制,而该对象并没有被复制。
例如:
let user ={ name:”John”};
let admin = user; // 复制引用
现在我们有了两个变量,它们保存的都是对同一个对象的引用,仍然只有一个对象。我们可以通过其中任意一个变量来访问该对象并修改它的内容。这就像我们有个带两把钥匙的柜子,并使用其中一把钥匙(admin)来打开它。那么,我们如果之后用另外一把钥匙(user),则也能看到更改。
通过引用来比较
克隆与合并,Object.assign
想要复制一个对象,那该怎么做呢?
JavaScript 没有提供对此操作的内建的方法。实际上,也很少需要这样做。
- 但是,如果我们真的想要这样做,那么就需要创建一个新对象,并通过遍历现有属性的结构,在原始类型值的层面,将其复制到新对象,以复制已有对象的结构。 ```javascript 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
2. 我们也可以使用 Object.assign 方法来达成同样的效果。Object.assign(dest,[src1, src2, src3...])
例如,我们可以用它来合并多个对象.
```javascript
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 }
如果被拷贝的属性的属性名已经存在,那么它会被覆盖:
let user = { name: "John" };
Object.assign(user, { name: "Pete" });
alert(user.name); // 现在 user = { name: "Pete" }
用 Object.assign 代替 for..in 循环来进行简单克隆:
let user = {
name: "John",
age: 30
};
let clone = Object.assign({}, user);
//它将 user 中的所有属性拷贝到了一个空对象中,并返回这个新的对象。
深层克隆
到现在为止,我们都假设 user 的所有属性均为原始类型。但属性可以是对其他对象的引用。那应该怎样处理它们呢?例如:
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
alert( user.sizes.height ); // 182
现在这样拷贝 clone.sizes = user.sizes 已经不足够了,因为 user.sizes 是个对象,它会以引用形式被拷贝。因此 clone 和 user 会共用一个 sizes。
为了解决此问题,我们应该使用会检查每个 user[key] 的值的克隆循环,如果值是一个对象,那么也要复制它的结构。这就叫“深拷贝”。
有现成的实现,例如 JavaScript 库 lodash 中的 _.cloneDeep(obj)。
垃圾回收
- JavaScript 中主要的内存管理概念是可达性。
- 在 JavaScript 引擎中有一个被称作 垃圾回收器) 的东西在后台执行。它监控着所有对象的状态,并删除掉那些已经不可达的。
- 垃圾回收是自动完成的,我们不能强制执行或是阻止执行。
- 当对象是可达状态时,它一定是存在于内存中的。
- 被引用与可访问(从一个根)不同:一组相互连接的对象可能整体都不可达。(?
-
对象方法,”this”
方法中使用 this 关键字 访问该对象
```javascript let user = { name: “John”, age: 30,
sayHi() { // “this” 指的是“当前的对象” alert(this.name); }
};
user.sayHi(); // John
技术上讲,也可以在不使用 this 的情况下,通过外部变量名来引用它:
```javascript
let user = {
name: "John",
age: 30,
sayHi() {
alert(user.name); // "user" 替代 "this"
}
};
……但这样的代码是不可靠的。如果我们决定将 user 复制给另一个变量,例如 admin = user,并赋另外的值给 user,那么它将访问到错误的对象。下面这个示例证实了这一点:
let user = {
name: "John",
age: 30,
sayHi() {
alert( user.name ); // 导致错误
}
};
let admin = user;
user = null; // 重写让其更明显
admin.sayHi(); // TypeError: Cannot read property 'name' of null
“this” 不受限制
JavaScript 中的 this 可以用于任何函数,即使它不是对象的方法。
function sayHi() {
alert( this.name ); // this 的值是在代码运行时计算出来的,它取决于代码上下文。
}
在 JavaScript 中,this 是“自由”的,它的值是在调用时计算出来的,它的值并不取决于方法声明的位置,而是取决于在“点符号前”的是什么对象。
箭头函数没有自己的 “this”
箭头函数有些特别:它们没有自己的 this。
如果我们在这样的函数中引用 this,this 值取决于外部“正常的”函数。
举个例子,这里的 arrow() 使用的 this 来自于外部的 user.sayHi() 方法:
let user = {
firstName: "Ilya",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // Ilya
构造器和操作符 “new”
可选链 ?.
“不存在的属性”的问题
当我们尝试获取 user.address.street,而该用户恰好没提供地址信息,我们则会收到一个错误:
let user = {}; // 一个没有 "address" 属性的 user 对象
alert(user.address.street); // Error!
这是预期的结果。JavaScript 的工作原理就是这样的。因为 user.address 为 undefined,尝试读取 user.address.street 会失败,并收到一个错误。
但是在很多实际场景中,我们更希望得到的是 undefined(表示没有 street 属性)而不是一个错误。
可选链
例如 value?.prop:
- 如果 value 存在,则结果与 value.prop 相同,
- 否则(当 value 为 undefined/null 时)则返回 undefined。
下面这是一种使用 ?. 安全地访问 user.address.street 的方式:
let user = {}; // user 没有 address 属性
alert( user?.address?.street ); // undefined(不报错)
即使 对象 user 不存在,使用 user?.address 来读取地址也没问题:
let user = null;
alert( user?.address ); // undefined
alert( user?.address.street ); // undefined
Symbol 类型
- 根据规范,对象的属性键只能是字符串类型或者 Symbol 类型。不是 Number,也不是 Boolean,只有字符串或 Symbol 这两种类型。JavaScript 中的大多数值都支持字符串的隐式转换。例如,我们可以 alert 任何值,都可以生效。Symbol 比较特殊,Symbol 不会被自动转换为字符串。
- “Symbol” 值表示唯一的标识符。
- Symbol 属性不参与 for..in 循环。 ```javascript // id 是描述为 “id” 的 Symbol let id = Symbol(“id”);
//Symbol 保证是唯一的。即使我们创建了许多具有相同描述的 Symbol,它们的值也是不同。描述只是一个标签,不影响任何东西。 let id1 = Symbol(“id”); let id2 = Symbol(“id”);
alert(id1 == id2); // false ```
Symbol 有两个主要的使用场景:(?
“string”(对于 alert 和其他需要字符串的操作)
- “number”(对于数学运算)
- “default”(少数运算符)
转换算法是:
- 调用 objSymbol.toPrimitive 如果这个方法存在,
- 否则,如果 hint 是 “string”
- 尝试 obj.toString() 和 obj.valueOf(),无论哪个存在。
- 否则,如果 hint 是 “number” 或者 “default”
- 尝试 obj.valueOf() 和 obj.toString(),无论哪个存在。