原型链恐惧消除器

51 阅读5分钟

前言

JavaScript 中的原型链是理解继承和对象行为的关键概念。本篇文章通过大量题目,帮助你逐步消除对原型链的恐惧,深入理解其原理和应用。题目难度交错,适合初学者和有一定基础的开发者。目标是读完并答对全部问题,像闯关一样逐步攻克原型链难题。

题目解析

题目 1

function Parent() {}
const p1 = new Parent();
console.log(p1.__proto__ === Parent.prototype); // true

解析p1 是通过 Parent 构造函数创建的实例,p1.__proto__ 指向 Parent.prototype

题目 2

function Parent() {}
const p1 = new Parent();
console.log(Parent.prototype.__proto__ === Object.prototype); // true

解析Parent.prototype 是一个对象,它的 __proto__ 指向 Object.prototype

题目 3

function Parent() {}
const p1 = new Parent();
console.log(p1.constructor === Parent); // true

解析p1 的构造函数是 Parentp1.constructor 指向 Parent

题目 4

function Parent() {}
Parent.prototype.name = "jjq";
const p1 = new Parent();
console.log(p1.name); // jjq

解析p1 可以访问 Parent.prototype 上的属性 name

题目 5

function Parent() {}
Parent.prototype.name = "jjq";
const p1 = new Parent();
p1.name = "jiangjianqing";
console.log(p1.name); // jiangjianqing

解析p1 自身的属性 name 会覆盖原型链上的同名属性。

题目 6

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.getName()); // jjq

解析p1 可以访问 Parent.prototype 上的方法 getName

题目 7

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
p1.getName = function () {
  return "jiangjianqing";
};
console.log(p1.getName()); // jiangjianqing

解析p1 自身的方法 getName 会覆盖原型链上的同名方法。

题目 8

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.getName === Parent.prototype.getName); // true

解析p1.getNameParent.prototype.getName 指向同一个方法。

题目 9

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
p1.getName = function () {
  return "jiangjianqing";
};
console.log(p1.getName === Parent.prototype.getName); // false

解析p1.getNameParent.prototype.getName 指向不同的方法。

题目 10

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.hasOwnProperty("getName")); // false

解析p1 自身没有 getName 属性,hasOwnProperty 返回 false

题目 11

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
p1.getName = function () {
  return "jiangjianqing";
};
console.log(p1.hasOwnProperty("getName")); // true

解析p1 自身有 getName 属性,hasOwnProperty 返回 true

题目 12

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.__proto__.getName === Parent.prototype.getName); // true

解析p1.__proto__ 指向 Parent.prototypep1.__proto__.getNameParent.prototype.getName 指向同一个方法。

题目 13

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.__proto__.constructor === Parent); // true

解析p1.__proto__ 指向 Parent.prototypeParent.prototype.constructor 指向 Parent

题目 14

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.__proto__.constructor === Function); // false

解析p1.__proto__.constructor 指向 Parent,而不是 Function

题目 15

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.__proto__.__proto__ === Object.prototype); // true

解析p1.__proto__ 指向 Parent.prototypeParent.prototype.__proto__ 指向 Object.prototype

题目 16

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.__proto__.__proto__.constructor === Function); // false

解析p1.__proto__.__proto__.constructor 指向 Object,而 Object !== Function,因此结果为 false

题目 17

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.__proto__.__proto__.__proto__); // null

解析Object.prototype.__proto__null,表示原型链的终点。

题目 18

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};

function Child() {}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const c1 = new Child();
console.log(c1.__proto__.hasOwnProperty("getName")); // false
console.log(c1.__proto__.__proto__.hasOwnProperty("getName")); // true

解析c1.__proto__ 指向 Child.prototype,而 Child.prototype 是通过 Object.create(Parent.prototype) 创建的,它自身并没有 getName 属性,所以 c1.__proto__.hasOwnProperty("getName") 返回 false

但是 c1.__proto__.__proto__ 指向 Parent.prototype,而 Parent.prototype 自身有 getName 属性,所以 c1.__proto__.__proto__.hasOwnProperty("getName") 返回 true

题目 19

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.__proto__.hasOwnProperty("constructor")); // true

解析p1.__proto__ 指向 Parent.prototypeParent.prototype 自身有 constructor 属性。

题目 20

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.__proto__.__proto__.hasOwnProperty("toString")); // true

解析p1.__proto__.__proto__ 指向 Object.prototypeObject.prototype 自身有 toString 方法。

题目 21

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.toString()); // [object Object]

解析p1 没有自身的 toString 方法,会沿着原型链找到 Object.prototype.toString

题目 22

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.__proto__.__proto__.toString === Object.prototype.toString); // true

解析p1.__proto__.__proto__ 指向 Object.prototypeObject.prototype.toStringtoString 方法的来源。

题目 23

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.__proto__.__proto__.hasOwnProperty("hasOwnProperty")); // true

解析p1.__proto__.__proto__ 指向 Object.prototypeObject.prototype 自身有 hasOwnProperty 方法。

题目 24

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};

function Child() {}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.getName = function () {
  return "child";
};

const c1 = new Child();
console.log(c1.getName()); // "child"
console.log(c1.__proto__.hasOwnProperty("getName")); // true
console.log(c1.__proto__.__proto__.hasOwnProperty("getName")); // true

解析

  • c1.getName() 调用时,会先在 c1 自身查找 getName 属性,没有找到,然后沿着原型链查找,先找到 Child.prototype 上的 getName 方法,所以返回 "child"
  • c1.__proto__.hasOwnProperty("getName") 返回 true,因为 c1.__proto__ 指向 Child.prototype,而 Child.prototype 自身有 getName 属性。
  • c1.__proto__.__proto__.hasOwnProperty("getName") 也返回 true,因为 c1.__proto__.__proto__ 指向 Parent.prototype,而 Parent.prototype 自身也有 getName 属性。

题目 25

function Parent() {}
Parent.prototype.name = "Parent";

function Child() {}
Child.prototype = Object.create(Parent.prototype);

const c1 = new Child();
console.log(c1.constructor === Parent); // true
console.log(c1.constructor === Child); // false

解析:因为 Child.prototype 是通过 Object.create(Parent.prototype) 创建的,所以它继承了 Parent.prototype 的属性,包括 constructor,因此 c1.constructor 默认指向 Parent,而不是 Child

题目 26

function Parent() {}
const p1 = new Parent();
console.log(Parent.prototype.__proto__ === Object.prototype); // true

解析Parent.prototype 是一个对象,它的 __proto__ 指向 Object.prototype

题目 27

function Parent() {}
const p1 = new Parent();
console.log(p1.constructor === Parent); // true

解析p1 的构造函数是 Parentp1.constructor 指向 Parent。 相信自己的判断即可,不去思考题目的难易

题目 28

function Parent() {}
Parent.prototype.name = "jjq";
const p1 = new Parent();
p1.name = "jiangjianqing";
console.log(p1.name); // jiangjianqing

解析p1 自身的属性 name 会覆盖原型链上的同名属性。 相信自己的判断即可,不去思考题目的难易

题目 29

function Parent() {}
Parent.prototype.getName = function () {
  return "jjq";
};
const p1 = new Parent();
console.log(p1.getName()); // jjq

解析p1 可以访问 Parent.prototype 上的方法 getName

题目 30

function Parent() {
  this.name = "Parent";
}
Parent.prototype.getName = function () {
  return this.name;
};

function Child() {
  this.name = "Child";
}
Child.prototype = new Parent();

const child = new Child();
console.log(child.getName()); // 输出结果1
console.log(child.name); // 输出结果2

Child.prototype.name = "Prototype";
console.log(child.getName()); // 输出结果3
console.log(child.name); // 输出结果4

解析

  1. 创建 Parent 和 Child 构造函数

    • Parent 构造函数有一个属性 name 和一个方法 getName
    • Child 构造函数有一个属性 name
    • Child.prototype 被设置为 new Parent(),这意味着 Child 的原型对象是一个 Parent 实例。
  2. 创建 child 实例

    • child 是通过 new Child() 创建的实例。

    • child 自身有一个属性 name,值为 "Child"

    • child 的原型链是:

      • child -> Child.prototype(一个 Parent 实例)-> Parent.prototype -> Object.prototype -> null
  3. 第一次调用 child.getName()

    • child.getName() 会先在 child 自身查找 getName 方法,没有找到。
    • 然后沿着原型链查找,在 Child.prototype(一个 Parent 实例)中找到了 getName 方法。
    • getName 方法内部返回 this.name,此时 this 指向 child,所以返回 child.name,即 "Child"
  4. 第一次调用 child.name

    • child.name 直接在 child 自身找到了属性 name,值为 "Child"
  5. 修改 Child.prototype.name

    • Child.prototype.name 被设置为 "Prototype"
    • 这个修改不会影响 child 自身的属性,因为 child 自身已经有一个 name 属性。
  6. 第二次调用 child.getName()

    • 仍然沿着原型链查找 getName 方法,找到的是 Child.prototype 中的 getName
    • getName 方法内部返回 this.name,此时 this 仍然指向 child,所以返回 child.name,即 "Child"
  7. 第二次调用 child.name

    • child.name 仍然直接在 child 自身找到,值为 "Child"

最后

收摊!!