面向对象并不总是基于类,也有基于原型的方式。JavaScript 一来是就是基于原型来描述对象的,在 ES6 之前都是采用构造函数的方式来模拟类,直到 ES6 才出现了真正的类。

什么是原型?

“基于类”:总是先有类,再从类去实例化一个对象。类与类之间又可能会形成继承、组合等关系。类又往往与语言的类型系统整合,形成一定编译时的能力。
“基于原型”:通过“复制”的方式来创建新对象。

JavaScript的原型

可用两条概括原型系统:

  • 如果所有对象都有私有字段 [[prototype]] ,就是对象的原型;
  • 读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。

ES6 提供了三个方法,让人们可以更为直接地操纵原型。

  • Object.create :根据指定的原型创建新对象,原型可以是 null ;
  • Object.getPrototypeOf :获得一个对象的原型,以前用 __proto__ 表示;
  • Object.setPrototypeOf :设置一个对象的原型。

js 原型是指为其他对象提供共享属性访问的对象。在创建对象时,每个对象都包含一个隐式引用(__protp__)指向它的原型对象或者 null 。
原型链:原型也是对象,因此它也有自己的原型。这样就构成了一个原型链。

早期版本中的类与原型

在早期版本的 JavaScript 中,“类”的定义是一个私有属性 [[class]] ,语言标准为内置类型诸如 Number、String、Date 等指定了 [[class]] 属性,以表示它们的类。语言使用者唯一可以访问 [[class]] 属性的方式是 Object.prototype.toString
因此,在 ES3 和之前的版本, JS 中类的概念是相当弱的,它仅仅是运行时的一个字符串属性。

  1. Object.prototype.toString.call(new Number) // "[object Number]"
  2. Object.prototype.toString.call(new String) // "[object String]"
  3. Object.prototype.toString.call(new Boolean) // "[object Boolean]"
  4. Object.prototype.toString.call(new Date) // "[object Date]"
  5. var arg = function() { return arguments }();
  6. Object.prototype.toString.call(arg) // "[object Arguments]"
  7. Object.prototype.toString.call(new RegExp) // "[object RegExp]"
  8. Object.prototype.toString.call(new Function) // "[object Function]"
  9. Object.prototype.toString.call(new Array) // "[object Array]"
  10. Object.prototype.toString.call(new Error) // "[object Error]"

在 ES5 开始, [[class]] 私有属性被 Symbol.toStringTag 代替, Object.prototype.toString 的意义从命名上不再跟 class 相关。

ES6 中的类

ES6 中引入了 class 关键字,并且在标准中删除了所有 [[class]] 相关的私有属性描述,类的概念正式从属性升级成语言的基础设施,从此,基于类的编程方式成为了 JS 的官方编程范式。

Class 本质上是一个特别的函数

  1. class Polygon {
  2. constructor (height, width) {
  3. this.name = 'Polygon';
  4. this.height = height;
  5. this.width = width;
  6. }
  7. sayName() {
  8. console.log('Hi, I am a ', this.name + '.');
  9. }
  10. }
  11. class Square extends Polygon {
  12. constructor(length) {
  13. super(length, length);
  14. this.name = 'Square';
  15. }
  16. get area() {
  17. return this.height * this.width;
  18. }
  19. set area(value) {
  20. this.area = value;
  21. }
  22. }
  23. var mySquare = new Square(4, 4);

image.png

原型的好处

原型对象上的所有属性和方法,都能被对应的构造函数所创建的实例对象共享。也就是说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。

prototype 与 proto

每个函数都有一个 prototype 属性,prototype 是函数才有的属性。
函数的 prototype 属性指向一个对象,这个对象是调用该构造函数而创建的实例的原型。

每一个 JavaScript 对象(除了 null )都具有 __protp__ 属性,这个属性会指向该对象的原型。

constructor ,每个原型都有一个 constructor 属性指向关联的构造函数。

关系图:
image.png

  1. function Person () {}
  2. Person.prototype.name = '1'
  3. var person = new Person()
  4. person.__proto__ === Person.prototype // true
  5. Person === Person.prototype.constructor // true
  6. person.constructor === Person // true
  7. person.constructor === Person.prototype.constructor // true

当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到 constructor 时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性。

  1. class F {}
  2. class C extends F {}
  3. C.__proto__ === F // true
  4. C.prototype.__proto__ === F.prototype // true

作为一个对象,子类的原型是父类:即:C.__protptype === F
作为一个构造函数(C.prototype),子类的原型对象是父类的原型对象的实例。即:C.prototype.__proto__===F.prototype

原型链有什么作用

读取对象的某个属性时, JS 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的 Object.prototype 还是找不到,则返回 undefined 。

参考链接: