回忆一下javascript
的原型链。
每个函数且只有函数有一个prototype
的属性,这个属性指向一个对象,即原型对象。默认情况下,原型对象又包含一个指向这个函数的constractor
属性。
每个对象都有一个__proto__
的属性,指向生成该对象的构造函数的原型对象,函数也是对象,所以它也有这个属性。
下面我们来看看ES6
中class
定义类及其原型的东西
定义一个类A
:
class A {
constructor(name){
this.name = name;
}
getName(){
return this.name
}
toString(){
return 'my name is ' + this.name
}
}
使用class
关键字申明一个类,并为其定义了两个方法getName
和toString
。注意,类的constructor
构造函数是必须的,不写时会javascript
会默认一个空的函数constructor() {}
,定义的方法之间不能用逗号,
(分号;
可以)。
接下来,我们用typeof
操作符来看看A
的类型:
console.log(typeof A) //function
如上,定义的类A
其实是一个函数,同时函数也是一个对象,对象都有__proto__
属性,指向其构造函数的原型对象。
console.log(Reflect.getPrototypeOf(A) === Function.prototype); //true
getPrototypeOf
即返回对象__proto__
指向的原型prototype
。
前面讲到“每一个函数且只有函数有一个prototype
属性”,那么我们来看看类A
是否也有。
console.log(Reflect.getOwnPropertyDescriptor(A,'prototype'))
返回如下结果:
{ value: A {},
writable: false,
enumerable: false,
configurable: false
}
Reflect.getOwnPropertyDescriptor
基本等同于Object.getOwnPropertyDescriptor
,用于得到指定属性的描述对象,没报错,说明类A
是有prototype
这个属性的。
下面我们再来看看A.prototype
这个属性的相关信息
console.log(Object.getOwnPropertyDescriptors(A.prototype))
打印出如下信息:
{
constructor:
{ value: [Function: A],
writable: true,
enumerable: false,
configurable: true },
getName:
{ value: [Function: getName],
writable: true,
enumerable: false,
configurable: true },
toString:
{ value: [Function: toString],
writable: true,
enumerable: false,
configurable: true
}
}
也就是表明,类A
的原型对象A.prototype
有一个constructor
属性指向A
,在类A
中定义的方法是其原型对象的方法。
使用new
运算符生成一个类A
的实例:
let a = new A('tensweets');
console.log(a.toString()) //my name is tensweets
我们测试一下实例a
的与类A
的原型关系
console.log(Reflect.getPrototypeOf(a) === A.prototype);//true
console.log(a.constructor === A);//true
console.log(a.constructor === A.prototype.constructor);//true
以上表明,用class
定义类其实就是使用构造函数定义的语法糖。
function A(name) {
this.name = name
}
A.prototype.getName = function () {
return this.name
};
A.prototype.toString = function () {
return 'my name is ' + this.name
};
前面我们说到“每个对象都有一个__proto__
的属性,指向生成该对象的构造函数的原型对象”,对象的__proto__
属性我们叫做原型,函数的prototype
指向的对象我们叫做原型对象。
函数的prototype
属性(原型对象)及对象的原型都是可以修改的,例如:
let o = {
name:"tensweets.com",
toString:function () {
return this.name
}
};
let obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype);//true
Object.setPrototypeOf(obj,o);
console.log(Object.getPrototypeOf(obj) === Object.prototype);//false
console.log(Object.getPrototypeOf(obj) === o);//false
上面,我们使用对象字面量建造一个obj
对象,其内部的构建是通过Object
函数来进行的,其原型(——proto——)自动指向了Object
的prototype
属性Object.prototype
。后面我们使用Object.setPrototypeOf
方法把obj
对象的原型指向了另一个对象o
。
我们再来看看修改原型对象例子:
function Animal(name) {
this.name = name;
}
let obj = {
say:function () {
console.log('I don\'t want to tell you')
}
};
Animal.prototype.say = function () {
console.log('my name is ' + this.name)
};
let dog = new Animal('Pluto');
dog.say();//my name is Pluto
Animal.prototype = obj;
let cat = new Animal('Tom')
dog.say();//my name is Pluto
cat.say();//I don't want to tell you
当我们把Animal
的原型对象指向另一个对象后,生成的实例cat
的原型(__proto__
)也指向了这个对象,但在未修改之前生成的实例dog
其原型仍然指向以前的。
在 ES5
里我们通过修改原型链实现继承,ES6
里使用关键字extends
来实现类的继承。
class A {
constructor(name){
this.name = name;
}
static fn(){
return '静态方法'
}
getName(){
return this.name
}
toString(){
return 'my name is ' + this.name
}
}
class B extends A {
constructor(name,age){
super(name);
this.age = age;
}
}
let a = new A('a-name');
let b = new B('b-name');
我们来看一下B
的类型:
console.log(typeof B)//function
类B
跟类A
一样,类型为函数,那么B
一定有一个prototype
属性指向其原型对象B.prototype
。同B
也是一个对象(函数对象),其__proto__
指向创建时其构造函数的prototype
属性,也就是说B.__proto__
应该等于Function.prototype
,测试一下
console.log(Object.getPrototypeOf(B) === Function.prototype);//false
然而这里并不相等,说明在extents
实现继承的时候,修改了其原型,类似于使用Object.setPrototypeOf(B,新原型)
。
那我们再来看看为B
赋予了那个新原型
console.log(Object.getOwnPropertyDescriptors(Reflect.getPrototypeOf(B)))
输出以下结果:
{ length:
{ value: 1,
writable: false,
enumerable: false,
configurable: true },
prototype:
{ value: A {},
writable: false,
enumerable: false,
configurable: false },
fn:
{ value: [Function: fn],
writable: true,
enumerable: false,
configurable: true },
name:
{ value: 'A',
writable: false,
enumerable: false,
configurable: true } }
仔细查看发现,B
的原型指向了A
,正如执行的Object.setPrototypeOf(B,A)
。也就是说当B
和A
作为对象:B.proto === A;//true
。
再来看看B
作为函数时其原型对象B.prototype
。
console.log(Object.getOwnPropertyDescriptors(Reflect.getPrototypeOf(B.prototype)))
输出的结果是:
{ constructor:
{ value: [Function: A],
writable: true,
enumerable: false,
configurable: true },
getName:
{ value: [Function: getName],
writable: true,
enumerable: false,
configurable: true },
toString:
{ value: [Function: toString],
writable: true,
enumerable: false,
configurable: true } }
明显,返回的结果是A
的prototype
指向的对象,也就是说B.prototype
这个对象的__proto__
指向A.prototype
,相当于执行了Object.setPrototypeOf(B.prototype,A.prototype)
,即B.prototype.__proto__ === A.prototype
。
作为一个对象,子类B
的原型(__proto__
属性)指向父类A
;作为一个构造函数,子类B
的原型对象(prototype
属性的原型(__proto__
属性)指向父类A
的prototype
属性。
总结,class
类的继承相当于如下代码的处理过程
Object.setPrototypeOf(B.prototype, A.prototype);
Object.setPrototypeOf(B, A);
所以,子类B
对象可以使用父类A
对象的静态属性,子类B
的实例可以使用父类A
的原型对象的属性。