面向对象并不总是基于类,也有基于原型的方式。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 中类的概念是相当弱的,它仅仅是运行时的一个字符串属性。
Object.prototype.toString.call(new Number) // "[object Number]"Object.prototype.toString.call(new String) // "[object String]"Object.prototype.toString.call(new Boolean) // "[object Boolean]"Object.prototype.toString.call(new Date) // "[object Date]"var arg = function() { return arguments }();Object.prototype.toString.call(arg) // "[object Arguments]"Object.prototype.toString.call(new RegExp) // "[object RegExp]"Object.prototype.toString.call(new Function) // "[object Function]"Object.prototype.toString.call(new Array) // "[object Array]"Object.prototype.toString.call(new Error) // "[object Error]"
在 ES5 开始, [[class]] 私有属性被 Symbol.toStringTag 代替, Object.prototype.toString 的意义从命名上不再跟 class 相关。
ES6 中的类
ES6 中引入了 class 关键字,并且在标准中删除了所有 [[class]] 相关的私有属性描述,类的概念正式从属性升级成语言的基础设施,从此,基于类的编程方式成为了 JS 的官方编程范式。
Class 本质上是一个特别的函数
class Polygon {constructor (height, width) {this.name = 'Polygon';this.height = height;this.width = width;}sayName() {console.log('Hi, I am a ', this.name + '.');}}class Square extends Polygon {constructor(length) {super(length, length);this.name = 'Square';}get area() {return this.height * this.width;}set area(value) {this.area = value;}}var mySquare = new Square(4, 4);
原型的好处
原型对象上的所有属性和方法,都能被对应的构造函数所创建的实例对象共享。也就是说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
prototype 与 proto
每个函数都有一个 prototype 属性,prototype 是函数才有的属性。
函数的 prototype 属性指向一个对象,这个对象是调用该构造函数而创建的实例的原型。
每一个 JavaScript 对象(除了 null )都具有 __protp__ 属性,这个属性会指向该对象的原型。
constructor ,每个原型都有一个 constructor 属性指向关联的构造函数。
关系图:
function Person () {}Person.prototype.name = '1'var person = new Person()person.__proto__ === Person.prototype // truePerson === Person.prototype.constructor // trueperson.constructor === Person // trueperson.constructor === Person.prototype.constructor // true
当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到 constructor 时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性。
class F {}class C extends F {}C.__proto__ === F // trueC.prototype.__proto__ === F.prototype // true
作为一个对象,子类的原型是父类:即:C.__protptype === F 。
作为一个构造函数(C.prototype),子类的原型对象是父类的原型对象的实例。即:C.prototype.__proto__===F.prototype 。
原型链有什么作用
读取对象的某个属性时, JS 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的 Object.prototype 还是找不到,则返回 undefined 。
参考链接:
