代码质量

在 Chrome 中调试

我们也可以使用 debugger 命令来暂停代码,像这样:

  1. function hello(name) {
  2. let phrase = `Hello, ${name}!`;
  3. debugger; // <-- 调试器会在这停止
  4. say(phrase);
  5. }

Object(对象):基础知识

const 声明的对象

  • 使用 const 声明的对象是可以被修改的。const 声明仅固定了 user 的值,而不是值(该对象)里面的内容。
  • 属性命名没有限制。属性名可以是任何字符串或者 symbol(一种特殊的标志符类型,将在后面介绍)。其他类型会被自动地转换为字符串。

    对象引用和复制

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

  • 对象通过引用被赋值和拷贝。
  • 我们可以将对象变量(例如 user)想象成一张带有地址的纸。当我们对对象执行操作时,例如获取一个属性 user.name,JavaScript 引擎将对该地址进行搜索,并在实际对象上执行操作。
  • 当一个对象变量被复制 —— 引用则被复制,而该对象并没有被复制。

例如:
let user ={ name:”John”};
let admin = user; // 复制引用
现在我们有了两个变量,它们保存的都是对同一个对象的引用,仍然只有一个对象。我们可以通过其中任意一个变量来访问该对象并修改它的内容。这就像我们有个带两把钥匙的柜子,并使用其中一把钥匙(admin)来打开它。那么,我们如果之后用另外一把钥匙(user),则也能看到更改。

通过引用来比较

仅当两个对象为同一对象时,两者才相等。

克隆与合并,Object.assign

想要复制一个对象,那该怎么做呢?
JavaScript 没有提供对此操作的内建的方法。实际上,也很少需要这样做。

  1. 但是,如果我们真的想要这样做,那么就需要创建一个新对象,并通过遍历现有属性的结构,在原始类型值的层面,将其复制到新对象,以复制已有对象的结构。 ```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

  1. 2. 我们也可以使用 Object.assign 方法来达成同样的效果。Object.assign(dest,[src1, src2, src3...])
  2. 例如,我们可以用它来合并多个对象.
  3. ```javascript
  4. let user = { name: "John" };
  5. let permissions1 = { canView: true };
  6. let permissions2 = { canEdit: true };
  7. // 将 permissions1 和 permissions2 中的所有属性都拷贝到 user 中
  8. Object.assign(user, permissions1, permissions2);
  9. // 现在 user = { name: "John", canView: true, canEdit: true }

如果被拷贝的属性的属性名已经存在,那么它会被覆盖:

  1. let user = { name: "John" };
  2. Object.assign(user, { name: "Pete" });
  3. 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 有两个主要的使用场景:(?

    • “隐藏” 对象属性。 防止被意外使用或重写。
    • JavaScript 使用了许多系统 Symbol,这些 Symbol 可以作为 Symbol.* 访问。我们可以使用它们来改变一些内置行为。

      对象 — 原始值转换

      对象到原始值的转换,是由许多期望以原始值作为值的内建函数和运算符自动调用的。
      这里有三种类型(hint):
  • “string”(对于 alert 和其他需要字符串的操作)

  • “number”(对于数学运算)
  • “default”(少数运算符)

转换算法是:

  1. 调用 objSymbol.toPrimitive 如果这个方法存在,
  2. 否则,如果 hint 是 “string”
    • 尝试 obj.toString() 和 obj.valueOf(),无论哪个存在。
  3. 否则,如果 hint 是 “number” 或者 “default”
    • 尝试 obj.valueOf() 和 obj.toString(),无论哪个存在。