面向对象并不总是基于类,也有基于原型的方式。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 // true
Person === Person.prototype.constructor // true
person.constructor === Person // true
person.constructor === Person.prototype.constructor // true
当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到 constructor 时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性。
class F {}
class C extends F {}
C.__proto__ === F // true
C.prototype.__proto__ === F.prototype // true
作为一个对象,子类的原型是父类:即:C.__protptype === F
。
作为一个构造函数(C.prototype),子类的原型对象是父类的原型对象的实例。即:C.prototype.__proto__===F.prototype
。
原型链有什么作用
读取对象的某个属性时, JS 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的 Object.prototype 还是找不到,则返回 undefined 。
参考链接: