原型与原型链详解


构造函数

与new运算符一起使用在创建对象的语句中的函数。

在ES6之前,我们面向对象都是通过构造函数实现的。我们把对象的公共属性和方法放在构造函数中。

// 创建一个构造函数
function Foo(name, age) {
  this.name = name;
  this.age = age;
  this.say = function() {
    console.log('我的名字叫' + this.name + ',今年' + this.age + '岁');
  }
}

实例对象

使用new运算符创建的对象。

构造函数虽然好用,但是这样做存在浪费内存的问题。如下例,构造函数中有say方法,函数属于复杂数据类型,它会单独在内存中开辟空间。下例创建了两个实例对象,就开辟了两个内存空间来存放同一个函数,造成了内存浪费的问题。

// 创建一个构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.say = function() {
    console.log('我的名字叫' + this.name + ',今年' + this.age + '岁');
  }
}

// 创建实例对象
var person1 = new Person('cxk', '22');
var person2 = new Person('lyf', '23');

person1.say(); // 我的名字叫cxk,今年22岁
person2.say(); // 我的名字叫lyf,今年23岁

原型

将构造函数中的函数存放在原型对象(prototype)中,就可以解决内存浪费的问题。

  • prototype(显式原型):每个对象(除了null)都具有的属性,该属性指向该对象的原型
  • _proto_(隐式原型):只有函数(实例)对象才有的属性,该属性指向该函数的原型对象
// 创建一个构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.say = function() {
  console.log('我的名字叫' + this.name + ',今年' + this.age + '岁');
}

// 创建实例对象
var person= new Person('cxk', '22');

// 构造函数的显式原型(prototype) 和 实例对象的隐式原型(__proto__) 指向的是同一个原型对象
console.log(Person.prototype); // {say: ƒ, constructor: ƒ}
console.log(person.__proto__); // {say: ƒ, constructor: ƒ}
console.log(Person.prototype === person.__proto__); // true

image

constructor属性

原型对象上都存在一个constructor属性,它指向构造函数本身。

// 创建一个构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

console.log(Person.prototype); // {constructor: ƒ}
console.log(Person.prototype.constructor);
/*
  运行结果:
    ƒ Person(name, age) {
      this.name = name;
      this.age = age;
    }
*/

console.log(Person.prototype.constructor === Person); // true

image

原型链

个人理解,原型链就是实例对象查找属性和方法的规则:

可以看出,person实例对象上并没有say方法,say方法是设置在原型对象(prototype)上的,但是使用person.say()却可以成功调用。

这是因为,person在自身对象上没有找到say方法,就会顺着__proto__属性去查找原型上是否存在say方法,如果有则调用,如果没有则继续顺着原型对象的__proto__属性去原型的原型上查找,一直找到最顶层为止。

这样查找变量的过程,就形成了一条原型链。

也因为原型链的存在,所有的实例对象都可以共享使用say方法。

// 创建一个构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.say = function() {
  console.log('我的名字叫' + this.name + ',今年' + this.age + '岁');
}

// 创建实例对象
var person= new Person('cxk', '22');
person.say(); // 我的名字叫cxk,今年22岁

原型的原型

上述原型链的查找规则描述:person在自身对象上没有找到say方法,就会顺着__proto__属性去查找原型上是否存在say方法,如果有则调用,如果没有则继续顺着原型对象的__proto__属性去原型的原型上查找,一直找到最顶层为止。

那么原型的原型是什么呢?原型链的尽头又是哪里?

// 创建一个构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

console.log(Person.prototype); // {constructor: ƒ}
console.log(Person.prototype.__proto__); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Person.prototype.__proto__.__proto__); // null
console.log(Person.prototype.__proto__.constructor); // ƒ Object() { [native code] }

// 证明原型链的查找规则是否成立:
// 将say方法放在原型的原型上,看看实例对象person是否可以查找到
Person.prototype.__proto__.say = function() {
  console.log('我的名字叫' + this.name + ',今年' + this.age + '岁');
}

var person = new Person('cxk', '22');
person.say(); // 我的名字叫cxk,今年22岁

image

posted @ 2022-09-14 13:29  笔下洛璃  阅读(65)  评论(0编辑  收藏  举报