function Animal() {
}
let cat = new Animal()
console.log(cat.__proto__ === Animal.prototype) // true
上述例子中,cat 就是对象, Animal就是构造函数,通过构造函数创建的对象,他们之间必然有一道与之关联的桥梁(我们姑且这么描述),我们称之为原型链
每个对象都有一个私有属性(称之为 [[Prototype]]),它持有一个连接到另一个称为其 prototype 对象(原型对象)的链接。该 prototype 对象又具有一个自己的原型,层层向上直到一个对象的原型为 null。(译者注:Object.getPrototypeOf(Object.prototype) === null; // true)根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
JavaScript 中几乎所有的对象都是位于原型链顶端的Object的实例。
原型继承经常被视为 JavaScript 的一个弱点,但事实上,原型继承模型比经典的继承模型更加强大。例如,在一个原型模型之上构建一个经典模型是相当容易的。
遵循ECMAScript标准,
someObject.[[Prototype]]
符号是用于指向someObject
的原型。从 ECMAScript 6 开始,[[Prototype]]
可以用Object.getPrototypeOf()
和Object.setPrototypeOf()
访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性__proto__
。
它不应该与函数(function)的
func.prototype
属性相混淆,func.prototype
的作用是使用new func()
创建的对象的实例的[[Prototype]]
。Object.prototype
属性表示Object的原型对象。
每个函数都有一个 prototype 属性,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例原型,也就是下面的 cat 对象的原型
这里所说的对象,也就是通过构造函数创建的实例,当读取实例的属性时,如果找不到,就会通过原型链查找与实例关联的原型中的属性,如果没有,继续向上查找原型的原型,直至顶层 null 为止,如果找不到则返回 undefined。
function People() {
}
let a = new People()
People.prototype.name = 'Gushaohua'
a.name = 'Douzi'
// 此时访问的是a的自身属性,这时候原型上的name访问不到,也称之为属性遮蔽(property shadowing)
console.log(a.name) // Douzi
// 删除a的自身属性,此时访问的则是原型People上的name属性
delete a.name
console.log(a.name) // Gushaohua
// a和原型People上都不存在sex属性,返回undefined
console.log(a.sex) // undefined
对象上还有一个constructor属性,自身并没有这个属性,它指向其实就是构造器,也就是创建这个实例对象的构造函数,所以如下
a.constructor === People.prototype.constructor
在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。
ECMAScript 5 中引入了一个新方法:Object.create()
。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype
ECMAScript 6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括 class, constructor,static,extends 和 super。
"use strict";
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);
在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。如早期遍历对象的for ... in
方法
遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从Object.prototype继承的 hasOwnProperty 方法。下面给出一个具体的例子来说明它:
let parent = { a: 1, b: 2, c: 3 };
let child = { d: 4, e: 5, [Symbol()]: 6 };
//设置child对象中的d属性为不可枚举
Object.defineProperty(child, "d", { enumerable: false });
parent.__proto__ = child
for ( let k in parent ) {
console.log(k) // a,b, c, e
}
for (var i in parent) {
if (parent.hasOwnProperty(i)) {
console.log(i); // a, b, c
}
}
Object.keys(parent).forEach( key => {
console.log(key) // a, b, c
})