原型链

83 阅读4分钟

Hello 小伙伴们,如果觉得本文还不错,记得给个 star , 你们的 star 是我学习的动力!

一、牛刀小试

题目一:

var A = function () {};
  A.prototype.n = 1;
  var b = new A();
  A.prototype = { n: 2, m: 3 };
  var c = new A();

  console.log(b.n); 
  console.log(b.m); 
  console.log(c.n); 
  console.log(c.m); 

试着写出上面编程的输出结果吧!

题目二:

var F = function () {};
  var foo = new F();
  Object.prototype.a = 'value a';
  Function.prototype.b = 'value b';

  console.log(foo.a); 
  console.log(foo.b);
  console.log(F.a); 
  console.log(F.b); 

试着写出上面编程的输出结果吧!

二、答案

题目一答案:

 console.log(b.n); // 1
 console.log(b.m); // undefined
 console.log(c.n); // 2
 console.log(c.m); // 3

题目二答案:

 console.log(foo.a); // value a
 console.log(foo.b); // undefined
 console.log(F.a); // value a
 console.log(F.b); // value b
  • 如果小伙伴们查看答案后仍不明白,那就接着往下看,愉快的扩展我们的知识吧!!!

三、要想理解原型和原型链必读的三句话:

  • 每一个构造函数都有一个默认的prototype属性,指向自己的原型对象
  • 每一个实例(对象)都有一个__proto__的属性,指向所属类或构造函数的原型对象
  • 每一个默认的原型对象上都有一个constructor属性,指向对应构造函数本身

不理解没关系,那就让风暴来的更猛烈些吧!

四、请看下图:

image.png

  • 看完这张图的第一反应多数小伙伴肯定是一脸蒙蔽(我自己也是>_<),不过没关系,接下来就让我们一起来攀登这座高山吧。 命运对勇士低语:“你无法抵御风暴!”,勇士低声回应:“我就是风暴!”

五、原理及示例解释:

1、原型链的作用:

  • 在利用构造函数创建实例的时候,会给每个实例都添加自身所独有的属性和方法,可行,但是最后会导致大量的重复代码的编写以及内存的占用;所以就有了原型和原型链,把实例所共享的属性和方法都放在原型上,让实例在需要的时候自己沿原型链去寻找并使用。
  • 属性和方法的查找规则是,先在自身寻找,找到输出;未找到则沿着原型链向上寻找,层层向上,直到Object的原型(Object.prototype)还未找到就输出报错信息,因为Object.prototype的原型为null。

2、首先让我们来揭开原型的真面目吧!

  let F = function () {};
  let f = new F();
  /* 
  上述例子中:
  构造函数: F;
  构造函数 F的实例:f;
 */
  // 1.每一个构造函数都有一个默认的prototype属性,指向自己的原型对象,
  // 2.每一个实例(对象)都有一个__proto__的属性,指向所属类或构造函数的原型对象
  // 实例f是构造函数F 的实例,所以验证一下上面的两句话吧
  console.log(F.prototype); // {}  从结果来看,构造函数 F 上有一个属性prototype,且该属性指向一个对象
  console.log(f.__proto__); // {}  从结果来看,实例 f 上有一个属性__proto__,且该属性指向一个对象
  console.log(f.__proto__ === F.prototype); // true 这两个对象为同一个对象
  // 我们就把这个对象叫做构造函数的原型对象,也就是我们常说的原型

  // 3.每一个默认的原型对象上都有一个constructor属性,指向对应构造函数本身
  // 那我们再来验证一下第三句话,已知F为构造函数,且F.prototype属性指向构造函数的原型
  console.log(F.prototype.constructor === F); // true
  console.log(f.__proto__.constructor === F); // true

3、知道了什么是原型,那就轮到原型链了

  • 原型链和函数的作用域很相似,原型链查找和变量的作用域查找也很相似,好了废话不多说,直接上例子。 例子还是原先的那个,只是更深入的剖析 ^_^
let F = function () {};
let f = new F();
  /* 
  上述例子中:(记住这些,后面会用到)
  所有的构造函数或类有:
      1、Object;
      2、Function;
      3、F
  所有的实例:
  !!!F既是Function的实例,也是f的构造函数
  !!!所有的原型对象又都是Object的实例(没有被修改)
      1、Function.prototype 和 F.__proto__指向的是同一个实例对象(Object的实例)
      2、F(Function的实例)
      3、F.prototype 和 f.__proto__ 指向的是同一个实例对象(Object的实例)
      4、f(F的实例)
 */

  // 首先我们得知道Object是所有(除Object的原型外)原型对象的构造函数(未做出修改),
  // 那么肯定就会有小伙伴问啦,构造函数Object的原型(Object.prototype)又是谁的实例?
  console.log(Object.prototype.__proto__, '1'); // null  "1"
  // 结果很显然,Object的原型不是任何构造函数的实例,这也是为什么原型链的查找只到Object的原型就结束了

  // 原型链是什么? 原型链相当于一条线索,两端连接着实例和原型,而原型又连接着原型的原型,最终到Object的原型
  // !!! 要时刻记住,实例上的 __proto__ 属性 ;  构造函数和类上的prototype 属性 !!!

  // 原型链的起点为null
  // 然后是构造函数Object的原型
  console.log(null === Object.prototype.__proto__, '2'); // true  "2"
  // 再接着是构造函数Object的实例,也就是其他的原型(本例子中是构造函数Function和构造函数F的实例)
  console.log(Object.prototype === Function.prototype.__proto__, '3'); // true  "3"
  console.log(Object.prototype === F.prototype.__proto__, '4'); // true  "4"
  // 紧接着是构造函数Function的实例 构造函数F(F既是构造函数,也是实例); 和构造函数F的实例f
  console.log(Function.prototype === F.__proto__, '5'); // true  "5"
  console.log(f.__proto__ === F.prototype, '6'); // true  "6"
  • 上述关系如下图:

image.png

最后,再去看之前的那张图,是否有种我就是风暴的感觉!