在 JavaScript 中,创建对象有两种普遍的方式
通过字面值直接创建
const literalObject = {};
通过模版创建
function Template = {};
const templateObject = new Template();
实际上,上面所说的「模版」,一般在 Java 等语言中,是使用 class
来进行标记。
在 ES6 及以后,也可以使用 class
关键字来进行标记,但其实现的原理并没有变,在 JavaScript 中,「模版」其实就是一个普通的 JavaScript 函数,但由于在 JavaScript 中,函数是一等公民,这就使得函数并不是那么普通了
这是从 《JavaScript 高级程序设计》中截取的原型对象的图,结合这个图,我们可以来了解当我们创建一个函数时发生了什么事
Template
函数时,引擎同时会为函数 Template
创建一个属性,名叫 prototype
, 而这个属性指向的目标就是函数的原型对象
prototype
属性,同时,引擎根据这个函数以及一定的规则,创建一个学名为「原型对象」的对象,并且 prototype
指向这个「原型对象」Template.prototype
时,Template.prototype
默认会有且只有一个属性 constructor
, 学名就叫做「构造函数」。 而这个 constructor
, 实际上是指回了 Template
这个函数,于是,形成一个闭环。由于「原型对象」也是一个对象,所以也会附带了 Object
中的属性
然后,再了解下关于图中 [[Prototype]]
这个属性
[[Prototype]]
, 但是没有没有制定标准的访问形式[[Prototype]]
没有标准的访问形式,但是主流的几个浏览器都实现了通过 __proto__
属性来进行访问,因此在浏览器上,打印一个对象时,一般都会看到 __proto__
这个属性根据上述的理解,我们可以得出下面的结论
prototype
属性,只会在函数对象中出现。因为 prototype
指向的对象必须有一个 constructor
对象,而这个 constructor
又指向了自己
// 根据这条结论,于是就会有
literalObject.prototype // undefined 没有值
templateObject.prototype // undefined 没有值
Template.prototype // {constructor: ƒ} 有值
Template.prototype.constructor === Template // true
[[Prototype]]
a.k.a __proto__
是所有对象都有
// 根据这条结论,于是有
literalObject.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
templateObject.__proto__ // {constructor: ƒ}
Template.__proto__ // ƒ () { [native code] }
[[Prototype]]
是指向其构造函数的原型,即 anObject.[[Prototype]] === anObject.constructor.prototype
// 根据这条结论,于是有
literalObject.constructor
// ƒ Object() { [native code] }
// literalObject 的构造函数为 Object
literalObject.__proto__ === Object.prototype // true
templateObject.constructor
// ƒ Template() {}
// templateObject 的构造函数为 Template
templateObject.__proto__ === Template.prototype // true
Template.constructor
// ƒ Function() { [native code] }
// Template 函数本身就是函数类型的一个实例对象,所以它也有构造函数
Template.__proto__ === Function.prototype // true
其他的一些认识
constructor
, name
, age
等属性。共享的意思是:如果我通过 Person 创建了 person1, person2, 如果我直接更改原型对象中的值,那么 person1, person2 访问相应的属性时,是会访问到修改后的值name
, person2 中也可以有 name
, 且 Person.prototype.name
, person1.name
, person2.name
之间相互独立,只有当 person1 或 person2 中不存在 name
属性时,才会去读取 Person.prototype.name
的值在 JavaScript 中,实现继承的机制与 Java 等语言不同。Java 等语言实现的继承是复制式的继承,即父类和子类中的数据其实是隔离的。而 JavaScript 中实现的继承是引用式的继承,即所谓的父类和子类中的部分数据是通过引用关系来处理的,少有不慎,就会导致数据的不一致
在 JavaScript 中实现继承的核心在于,通过修改对象的原型对象类来实现一条原型链
以下例子是来自 《JavaScript 高级程序设计》
通过第一部分可以知道,在 JavaScript 中,所谓的类,其基本也只是一个函数而已
function SuperType() {
this.property = true;
}
SuperType
的类型new
关键字新建一个实例 A 时,A 会含有一个属性 property
, 默认值为 true
this
的意思就是新建出来的对象,绑定在了这个上下文中SuperType.prototype.getSuperValue = function () {
return this.property;
};
SuperType
的实例对象可以访问这个方法this
这个上下文也是绑定在了调用方法的实例对象上function SubType() {
this.subProperty = false;
}
SubType.prototype = new SuperType();
SubType
继承 SuperType
SubType
函数,这里也可以称之为类了。根据第一部分的描述,创建 SubType
函数的同时,会创建 SubType
的原型对象 SubType.prototype
, 现在,我又先把这个对象成为 A. 由于 SubType.prototype
这个属性,本质上只是一个引用,其引用值时可以变化的。这里,我是把 SubType.prototype
所指向的那个对象成为 A.new
创建一个实例 B 时,B 会含有一个属性 subProperty
, 默认值为 false
SubType
的原型。新创建一个 SuperType
的实例对象 A1, 并将其赋值到 SubType.prototype
. 此时,上面说的 A 就变为了 A1, 虽然 SubType.prototype
看起来还是那个 SubType.prototype
, 但是其具体内容,指向已经变化了SubType.prototype.getSubValue = function () {
return this.subProperty;
};
SubType
的原型添加了一个方法最后,再将代码打印出来
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
}
function SubType() {
this.subProperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subProperty;
}
const superInstance = new SuperType();
const subInstance = new SubType();
console.log(superInstance.getSuperValue()); // true
console.log(superInstance.getSubValue()); // TypeError: ...
console.log(subInstance.getSuperValue()); // true
console.log(subInstance.getSubValue()); // false
console.log(SuperType.prototype); // SuperType { getSuperValue: [Function] }
console.log(SubType.prototype); // SuperType { property: true, getSubValue: [Function] }
console.log(superInstance.constructor); // [Function: SuperType]
console.log(subInstance.constructor); // [Function: SuperType]
可以看到,实现继承后
SubType
的原型对象变为了 SuperType
的一个实例对象,原型对象还有 property
这个 SuperType
的实例属性SuperType
的实例对象中,其原型对象都会有一个 constructor
的函数,而这个 constructor
又是指向 SuperType
函数。实现继承后,SubType
的实例对象中原型对象指向了 superInstance
, 而 superInstance
的原型对象又是指向 SuperType
的原型对象,因此,SubType
的实例对象的原型对象的 constructor
从此也就指向了 SuperType
函数