阅读 1121

JS 的原型和原型链

JS 的原型和原型链,对于很多初学者来说是个难啃的骨头,因为它涉及到的专业名词也是挺多的,那么这篇文章从解析原型和原型链所涉及的专业名词开始,带你一步一步去理解原型和原型链。它其实并没有那么难。

普通对象和函数对象

在 JavaScript 中,万物皆对象!而对象也是有区别的,分为 普通对象和函数对象

image.png

凡是通过 new Function() 创建的对象都是函数对象,其它的则为普通对象

构造函数和实例

在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数;首字母一般大写。而通过 new 构造函数出来的则是实例

function Person(name) {
  this.name = name;
  this.say = function () {
    console.log(this.name);
  }
}
let person = new Person('张三');
person.say(); // 张三
console.log(person.constructor === Person); // true
复制代码

person 是构造函数 Person 的实例,它有一个 constructor 属性,指向构造函数 Person

需要记住:实例的构造函数属性(constructor)指向构造函数

原型

每个对象都自带有一个 __proto__ 属性,但只有函数对象才有 prototype 属性。每一个函数对象都有会有一个 prototype 属性(Function.prototype除外,它是函数对象,但是没有 prototype 属性),这个属性指向函数的原型对象。

这里把上面的例子改一下:

Person.prototype = {
  name: '张三',
  say: function () {
    console.log(this.name);
  },
}
console.log(typeof Person.prototype); // object
复制代码

这里原型对象就是 Person.prototype,一个普通对象。它有一个默认的 constructor 属性,这个属性(一个指针)指向 prototype 所在的函数(Person) ,即 Person.prototype.constructor === Person

在上面的例子中, personPerson 的实例,它的 constructor 属性指向 Person。而 Person.prototype 它也有一个 constructor 属性,也指向 Person;同理, Person.prototype 也是 Person的实例。

原型对象 Peson.prototype 是构造函数 Person 的一个实例

image.png

其实 原型对象就是一个普通对象Funciton.prototype 除外,因为 Funciton.prototype 是通过 new Function() 生成的,所以它是一个函数对象,但是它没有 prototype属性)。

image.png

原型对象主要是用来继承

__proto__

每个对象都内置有一个 __proto__ 属性,用来指向创建它的构造函数的原型对象。在 new 的过程中就是这样实现的。所以 person.__proto__ === Person.prototype

结合上面构造函数和原型对象的说明,可以得出:

// 实例的构造函数属性指向构造函数
person.constructor === Person;
// 构造函数的原型对象也是一个实例,它的构造函数属性同样指向构造函数
Person.prototype.constructor === Person;
// 内置的__proto属性指向构造函数的原型对象
person.__proto__ === Person.prototype;
复制代码

image.png

构造器

在 JavaScript 中我们经常使用字面量的方式创建对象:let obj = {}

它等同于: let obj = new Object()

obj 是构造函数 Object 的实例。所以以下这些等式都成立:

obj.constructor === Object;
obj.__proto__ === Object.prototype;
复制代码

Object 就是一个构造器函数,本质是一个函数对象,跟 Person 差不多。不过 Object 是 JS 里面内置的, Person 是我们自己定义的。

这样的内置构造器,在 JavaScript 中还有很多,如: FunctionDateArrayErrorNumberStringBooleanRegExp等,它们都是函数对象。

原型链

对象通过 __proto__ 属性将原型连接起来,形成了原型链

以实例 person(普通对象)为例:

// person的__proto__指向其构造函数的原型
person.__proto__ === Person.prototype;
// Person.prototype是一个普通对象,所以它的__proto__指向Object的原型
Person.prototype.__proto__ === Object.prototype;
// 而Object.prototype就去到原型链的起点了
Object.prototype.__proto__ === null;
复制代码

上面的 person 是普通对象,下面再以函数对象 Person 走一波:

// Person是函数对象,它的构造函数是Function
Person.__proto__ === Function.prototype;
// Function.prototype它的构造函数是Object
Function.prototype.__proto__ === Object.prototype;
// Object.prototype再走下去就是起点了
Object.prototype.__proto__ === null;
复制代码

构造器也是函数对象,所以它们的 __proto__ 是指向 Function.prototype 的:

image.png

所以我们平时创建对象明明没有创建对应的一些方法(如 toStringvalueOf 等方法 ),却可以使用,是因为对象它存在一条原型链,它的 查询机制是先查询自己有没有这个属性,有则直接使用;如果没有,则会通过 __proto__ 指向的原型对象里面去找这个属性,一路找下去,最后是找到 Object.prototype 这里,它的 __proto__null ,这也是对象没有的属性最后会返回 null 的原因。

总结

  • 对象分为普通对象和函数对象,通过 new Function() 创建是函数对象
  • 实例的构造函数属性(constructor)指向构造函数
  • 函数的 prototype 就是一个普通对象(Funciton.prototype 除外)
  • 对象的 __proto__ 属性指向原型, __proto__ 将对象和原型连接起来就组成了原型链
  • Object 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它
  • Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它
文章分类
前端
文章标签