JavaScript基础回顾(二):对象、原型、类

84 阅读4分钟

对象

  • 在 JavaScript 中,几乎所有的东西都是对象,包括函数、数组、日期等。
  • 对象可以包含属性(键值对),其中键是字符串(或者符号),值可以是任何数据结构。

浅拷贝与深拷贝

浅拷贝(Shadow Copy)

浅拷贝指的是创建一个新对象,其字段值与原对象相同。对于字段是基本数据类型的,拷贝的是基本数据类型的值,而对于字段是对象的引用,拷贝的是引用,而不是引用的对象的本身。

  • Object.assign():方法可以将所有可枚举属性的值从一个或多个源对象复制到目标对象。
  • 展开运算符(...):在数组或对象字面量中使用展开运算符可以创建一个新对象或数组,复制其结构。

深拷贝(Deep Copy)

深拷贝不仅复制对象的第一层属性,还递归的复制了所有子对象。这意味着如果原始对象中有一个对象作为属性,深拷贝会创建这个对象的一个全新副本。

  • JSON 方法:通过 JSON.stringify() 将对象转为字符串,然后使用 JSON.parse() 将字符串解析为新的对象。但这种方法不能复制函数、undefined、循环引用等。
  • 第三方库:例如 lodash 库中的 cloneDeep() 方法,可以很方便的进行深拷贝。
  • 递归方法:自定义一个函数,递归的遍历对象的所有属性,对于每一个属性,如果是对象,则递归复制。

自己实现深拷贝

function deepCopy(obj) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  let copy = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnproperty(key)) {
      copy[key] = deepCopy(obj[key])
    }
  }
  return copy;
}

构造函数

当你使用 new 关键字创建一个对象时,你实际上是在调用一个构造函数。构造函数是一个特殊的函数,它用于创建和初始化一个对象。使用构造函数创建对象时,会执行以下步骤:

  1. 创建一个空对象。
  2. 将这个空对象的原型指向构造函数的 prototype 属性。
  3. 执行这个构造函数体内的代码,构造函数体内的 this 指向新创建的对象。
  4. 如果构造函数返回了一个对象,则返回该对象;否则,返回步骤 1 创建的新对象。

原型与原型链

在 JavaScript 中,每个对象都有一个内部属性,称为原型(prototype),它指向另外一个对象。这个原型对象包含了一组属性和方法,当前对象可以继承这些属性和方法。这种机制称为原型链。

需要理解的概念

原型对象

  • 每个 JavaScript 对象都有 prototype 属性,指向它的原型。
  • 原型对象也是一个普通的 JavaScript 对象,它也有自己的原型,这样形成一个链式结构。

构造函数的原型

  • 函数在 JavaScript 中也是对象,每个对象都有一个 prototype 属性,指向一个对象,这个对象包含了可以由通过构造函数创建的所有实例集成的属性和方法。

原型链

  • 当你尝试访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末尾(即 Object.prototype 的 null)。

容易混淆的 prototype、proto、[[prototype]]

  • [[]] 表示内部属性,不是直接暴露给 JavaScript 代码的,不能直接访问。
  • [[prototype]] 表示对象的原型,它存在于所有对象中,除了 null。
  • proto 是一个非标准的特性,是大多数浏览器用于访问和设置 [[prototype]]。
  • Object.prototype 表示原型链的末端,也可以指构造函数的 prototype,而对于实例来说就是一个属性,实例的原型需要用 [[prototype]]。
  • 正确的访问和设置 [[prototype]] 是 Object.getPrototypeOf()Object.setPrototypeOf()

看看原型链的最末尾

console.log(Object.getPrototypeOf(Object));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object)));
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object))));
console.log(Object.prototype);
console.log(Object.prototype.prototype);
console.log(Object.__proto__);
console.log(Object.__proto__.__proto__);
console.log(Object.__proto__.__proto__.__proto__);
console.log(Object.prototype.__proto__);
console.log(Object.__proto__.prototype);

上面的代码在 Chrome 中打印的结果会是什么呢?

  1. ƒ () { [native code] }。打印的是 Object 构造函数的原型,是一个引擎内置的原生对象,包含原生的 JavaScript 代码。
  2. {constructor: ƒ, defineGetter: ƒ, defineSetter: ƒ, hasOwnProperty: ƒ, lookupGetter: ƒ, …}。打印的是上述原生对象的原型,返回到了 Object.prototype。
  3. null。即 Object.prototype 是原型链的最末尾,之后就是 null。
  4. {constructor: ƒ, defineGetter: ƒ, defineSetter: ƒ, hasOwnProperty: ƒ, lookupGetter: ƒ, …}。这就是所有原型链的末尾。
  5. undefined。这行代码首先是找到 Object.prototype,然后访问它的 prototype 属性,所以是 undefined。
  6. 同 1。
  7. 同 2。
  8. 同 3。
  9. 同 3。
  10. 同 5。

类(class)是在 ES6 中加入的心的基础性语法糖结构,背后使用的任然是原型和构造函数的概念。

类与函数

  1. 实现:类是函数的语法糖,底层还是使用了函数和原型链的机制。
  2. 语法:类有 extends、super、static 等关键字,能更方便的实现面向对象编程。
  3. 变量提升:函数声明可以被提升,但类生命不能提升。
  4. 作用域:函数受函数作用域限制,类受块作用域限制。