搞懂JS原型和原型链

发布于 2021-03-01  569 次阅读 本文共2625个字


前边写过一篇文章叫做《JavaScript面向对象继承》,里边提到原型的知识点,通常原型和继承都是放在一块儿说的,所以这篇就对原型和原型链做一个全面说明。

构造函数

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayName = function() {
    console.log(this.name)
  };
};

let p1 = new Person('gjr', 16);
let p2 = new Person('kris', 18);

如上例所示,p1和p2都是Person的实例。这两个实例都有一个constructor属性,该属性指向Person。也就是:

console.log(p1.constructor == Person); // true
console.log(p2.constructor == Person); // true

所以记住:

  • p1 和 p2 都是new出来的构造函数Person的实例
  • 实例的构造函数属性(constructor)指向构造函数本身

原型对象(prototype)

每一个函数都有一个prototype属性,此属性指向函数的原型对象。

每一个JS对象(除null外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象也都会从原型继承属性。

function Person() {
  this.age = 18;
};
Person.prototype.name = 'gjr';
Person.prototype.sayName = function() {
  console.log(this.name)
};

let p1 = new Person();
let p2 = new Person();

p1.sayName(); // gjr
p2.sayName(); // gjr

console.log(p1.sayName == p2.sayName); // true

要记住:prototype是函数才会有的属性。

上边Person除了name、age、sayName四个属性外,其实它还有一个默认的constructor的属性。所有原型对象(prototype)都会有一个constructor(构造函数)属性,这个属性指向prototype属性所在的函数本身。用Person这个栗子来简单总结一下就是:

Person.prototype.constructor == Person;

上边的构造函数章节里我们也提到p1.constructor == Person,p1是Person的实例,那同理这里也可以看做Person.prototype也是Person的实例。

就例总结一下:

  • 原型对象(Person.prototype)是构造函数(Person)的一个实例
  • 除Function.prototype函数对象以外的原型对象其实都是普通对象,因为Function.prototype对象没有prototype属性,其他的都有

普通对象与函数对象

凡是通过new Function() 方式创建的对象都是函数对象,其他的都是普通对象。

原型指针(__proto__)

JS在创建对象(无论是普通还是函数对象,除null以外)的时候,都会有一个__proto__的内置属性,叫做原型指针,用于指向创建它的构造函数的原型对象。

也就是:p1.__proto__ == Person.prototype

Person.prototype.constructor === Person;
p1.__proto__ === Person.prototype;
p1.constructor === Person;

从上述关系中需要明白:__proto__的连接是实例和原型对象prototype之间的连接,不存在于实例与构造函数之间。

看个栗子:

function Person() {};
Person.prototype.name = 'gjr';

let p1 = new Person();
p1.name = 'kris';
let p2 = new Person();

console.log(p1.name); // kris
console.log(p2.name); // gjr

delete p1.name;
console.log(p1.name); // gjr

当我们给实例p1添加了自己的name属性,那打印p1.name的时候自然是找自己的私有属性,打印出了kris。而p2没有添加自己的私有name,所以就通过原型指针__proto__,也就是prototype找到了公有属性name,打印出了gjr。

当删除p1的name时,读取了p1的私有name给删除掉了,当我们再打印p1.name时,查找name就和p2的方式一样了,最后也打印出了gjr。

注意:__proto__最初是一个非标准属性,ES6已将其标准化,可用标准Object.getPrototypeOf() 代替。

原型链

前边我们总提到'除null以外',是因为:

  • null处于原型链的顶端,没有__proto__的属性。
  • Object.prototype的__proto__属性为null。

就像人类进化史,从现在的人类倒推到原始的类人猿,那类人猿从何而来呢?一直追溯下去的时候就是无。所以原型也是,一直追溯下去就是null了。"原型始于NULL"这个特点只需记住就可以了。

疑点解释:

Object.__proto__ === Function.prototype

解释:Object是函数对象,是通过new Function() 创建的,所以Object.__proto__指向Funtion.prototype

Function.__proto__ === Function.prototype

解释:Funtion 也是函数对象,通过new Function创建的,所以Function.__proto__指向Function.prototype。

Funtion.prototype.__proto__ === Object.prototype

按道理Funtion是函数对象,Function.__proto__指向Function.prototype。但是我们都知道一句话叫“万物皆对象”,所以Function.prototype.__proto__指向了Object.prototype,然后Object.prototype.__proto__为null,原型链结束。

附加:

原型链训练题目


努力,只为遇见更好的自己!