1.Class类的介绍
在ES6中新增了Class类的概念,让语法看起来更像是面向对象编程,其实这可以说是一个语法糖,ES5可以做到Class绝大部分功能,但也有一些不同。在ES6以前,可以通过构造函数来模拟类的概念,如下所示
function Student(name,age){
this.name = name;
this.age = age;
}
Student.prototype = {
constructor:Student,
show:function(){
console.log(this.name,this.age);
}
};
var xiaoming = new Student("xiaoming",20);
xiaoming.show();
xiaoming 20
在引入了Class关键字后,可以这样做,如下所示
class Person{
constructor(name,age){ //构造函数,默认返回this对象,如果强制返回其他对象也是被允许的
this.name = name;
this.age = age;
}
show(){
console.log(this.name,this.age);
}
}
var s = new Person('test',30);
s.show();
test 30
如上所示,便是ES6中类的定义,上面的constructor函数是类的构造函数,如果不写,则会自动创建一个constructor空构造函数,并且方法之间不允许有逗号,否则会报错,如下所示
class Person{
show(){
alert("show");
},
hide(){
alert("hide");
}
}
var s = new Person();
s.show();
SyntaxError: invalid property id
其实类的数据类型就是函数,类本身就指向构造函数,如下所示
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
show(){
console.log(this.name,this.age);
}
}
typeof Person
"function"
Person.prototype.constructor===Person
true
var p = new Person('test',20);
undefined
p.constructor
function Person()
p.constructor == Person
true
prototype属性在类上依旧存在,且类的方法都定义在类的prototype上面,如下所示
var person = new Person('person',100);
person.constructor === Person.prototype.constructor
true
因此可以在类的prototype上面定义的新方法为类扩展方法,如下所示
Object.assign(Person.prototype,{show(){console.log(this.name);}});
person.show();
var a = new Person('nihao',20);
a.show();
person
nihao
但是类内部定义的方法是不可枚举的,这和ES5的构造函数还是有很大的区别,如下所示
Object.getOwnPropertyNames(Person.prototype)
Array [ "constructor", "show" ]
Object.keys(Person.prototype)
Array [ ]
类的属性名可以使用表达式来定义,如下所示
var show = 'show';
class Test{
constructor(){
console.log("class test constructing...");
}
[show](){
console.log('show');
}
}
var test = new Test();
test[show]();
class test constructing...
show
在生成类的实例对象时,必须使用new关键字,如果直接像调用函数那样调用类,则会报错,如下所示
var t1 = Test();
TypeError: class constructors must be invoked with |new|
类的所有实例共享一个原型,实例对象可以通过__proto__属性来访问原型,因此可以通过该属性为原型添加方法,如下所示
test.__proto__.say = () => "hello prototype!";
test.say();
new Test().say();
class test constructing...
"hello prototype!"
test.say()
类Class也可以像Function一样,采用表达式形式来定义,如下所示
var Aclass = class Bclass{ constructor(){ console.log(Bclass.name);//只有在类的内部才能访问到Bclass } show(){ console.log('Aclass...'); } }; var a = new Aclass(); a.show(); Bclass Aclass...
因此我们可以写出类似立执行函数的立执行类,如下所示
var iifeInstance = new class{ constructor(text){ this.text = text; } show(){ console.log(this.text); } }('iife testing...'); iifeInstance.show(); iife testing...
类的定义不存在变量声明提升,这和ES5函数不同,如下图所示
var a = new Dclass(); class Dclass{ } ReferenceError: can't access lexical declaration `Dclass' before initialization
2.Class类的继承
Class类的继承通过关键字extends来实现,和ES5通过修改原型链实现继承不同,如下所示
class Person{ constructor(name,age){ this.name = name; this.age = age; } print(){ console.log(this.name,this.age); } } class Student extends Person{ constructor(name,age,sno){ super(name,age); //调用父类的构造函数,必须存在且需放在子类使用this对象之前,否则将报错 this.sno = sno; } print(){ super.print(); console.log(this.sno); } } var s = new Student('xiaoming',20,"21014130217"); s.print(); xiaoming 20 21014130217
值得注意的是在定义子类构造函数时,一定要调用父类的构造函数且必须在子类使用this对象之前,因为子类不存在this对象,需要使用super得到父类的this对象,然后对其进行改写加工。如果缺省构造函数,则会自动创建一个默认的构造函数并调用父类的构造函数
子类的原型prototype是父类的实例,有点像原型链继承,子类__proto__指向父类,这和函数的默认继承不同。如下所示
Student.prototype instanceof Person true Student.prototype.__proto__ === Person.prototype true s.__proto__ === Student.prototype true Student.__proto__ === Person true
可以使用Object.getPrototypeOf获取子类的父类,如下所示
Object.getPrototypeOf(Student) === Person true
3.原生构造函数的继承
在ES5中不能继承原生的构造函数,例如Array,Date,Object,Function等等,但是在ES6中可以实现对原生构造函数的继承,如下所示
class MyArray extends Array{ constructor(){ super(); this.sign = "myArray"; } } var myArr = new MyArray(); console.log(myArr); myArr.push(1,2,3); console.log(myArr); Array [ ] Array [ 1, 2, 3 ] console.log(myArr.sign); "myArray"
但是在继承Object时,有一点差异就是调用super给Object构造函数传参时会被忽略,这是ES6在实现时检测到不是以new Object形式调用构造函数,则忽略参数,如下所示
class MyObj extends Object{ constructor(arg){ super(arg); this.sign = "MyObj"; } } var myObj = new MyObj({x:1}); console.log(myObj.x); undefined
3.类的getter和setter函数,与ES5中的一样,都是对类属性存取行为的拦截,如下所示
class Xclass{ constructor(){ console.log('Xclass initializing...'); } set x(val){ console.log(val); } get x(){ return 2; } } var xIns = new Xclass(); xIns.x = 4; console.log(xIns.x); Xclass initializing... 4 2
4.类的静态方法与静态属性
静态方法与属性不能被实例对象调用,静态方法可以在类内部定义,但静态属性则不能,静态方法可以被子类继承,如下所示
class StaticTest{ static test(){ console.log('static...'); } } class SubStatic extends StaticTest{ show(){ console.log('substatic show...'); } } StaticTest.test(); SubStatic.test(); new SubStatic().show(); new StaticTest().test(); static... 2次
substatic show...
TypeError: (intermediate value).test is not a function
静态属性的定义则是直接使用ClassName.prop形式来定义,如下所示
class StaticTest{ static test(){ console.log('static...'); } } StaticTest.xxx = "hello class static variable"; StaticTest.xxx; "hello class static variable"
5.new.target
在ES6中为new命令添加了一个target属性,该属性在构造函数中返回new命令作用于的那个函数,如果不是以new命令调用构造函数则返回undefined,如下所示
class Target{ constructor(){ console.log(Object.is(new.target,Target)); } } var target = new Target(); true
有了这个属性,我们可以强制构造函数必须以new命令实例化,如下所示
function Animal(name){ if(!new.target) throw new Error('必须使用new实例化Animal'); this.name = name; } var duck1 = new Animal('duck1'); console.log(duck1.name); var duck2 = Animal('duck2'); duck1 Error: 必须使用new实例化Animal
也可以用来模仿抽象类,不能被实例化,只能被继承使用
class Car{ constructor(brand){ if(new.target === Car) throw new Error('Car类不能被实例化'); this.brand = brand; } } class Baoma extends Car{ constructor(brand){ super(brand); } showBrand(){ console.log(this.brand); } } var baoma = new Baoma('baoma'); baoma.showBrand(); baoma var car = new Car('car'); Error: Car类不能被实例化
在实例化子类时,父类的new.target指向的是子类。