生成实例对象的传统方法是通过构造函数
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5 的构造函数Point,对应 ES6 的Point类的构造方法。
Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。
class Bar {
doStuff() {
console.log('stuff');
}
}
var b = new Bar();
b.doStuff() // "stuff"
在类的实例上面调用方法,其实就是调用原型上的方法。
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。
class Point {
constructor() {
// ...
}
}
Object.assign(Point.prototype, {
toString() {console.log("创建成功")},
toValue() { }
});
//添加之后执行
var b = new Point();
b.toString();
constructor
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
class Point {
}
// 等同于
class Point {
constructor() {}
}
constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
class Foo {
constructor() {
}
}
new Foo() instanceof Foo;//true
class Foo {
constructor() {
return Object.create(null);//改变了返回的对象
}
}
new Foo() instanceof Foo;// false
举个栗子
class A{
constructor(x){
//当前类本身
//this:当前实例
this.x = x;//增加私有属性
//return 的时基本数据类型队实力没有影响,如果是引用数据类型,会改变实例
}
}
let a = new A(10);
console.log(a);
new A();//必须使用new执行,不可以作为普通函数执行,但他还是函数
name 属性
name属性总是返回紧跟在class关键字后面的类名。
let A = class AA{
//AA 只能在类里面使用 ,类的name都是AA
constructor(){
console.log(AA.name);
}
getA(){
console.log(AA.name);
}
};
//let a = new AA();//外面不可以使用AA,报错
let a = new A();
console.log(A.name);//"AA"
a.getA();
采用 Class 表达式,可以写出立即执行的 Class。
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');//立即执行
person.sayName(); // "张三"
提升问题
类不存在变量提升(hoist),这一点与 ES5 完全不同。
new BB();//Cannot access 'BB' before initialization
class BB{
constructor(){
this.a="aa";
console.log("没有变量提升");
}
}
this指向
类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。
解决方法
在构造方法中绑定this,这样就不会找不到print方法了。
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod();
// TypeError: foo.classMethod is not a function
上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
如果静态方法包含this关键字,这个this指的是类,而不是实例。
class Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.bar();// hello
上面代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
父类的静态方法,可以被子类继承。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
静态方法也是可以从super对象上调用的。
super用来调用父类上的方法
class F{
static getF(){
console.log("我是F的getF");
}
};
class G extends F{
//extends关键字用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。
//语法:class ChildClass extends ParentClass { ... } 继承的.prototype必须是一个Object 或者 null。
static getF(){
super.getF();
}
}
G.getF();
原型上的方法都是不可枚举的
class FF{
constructor(){
this.x="enum";
}
getF(){};
}
let ff=new FF();
for(let key in ff){
console.log(key);//x
}
console.log(Object.getOwnPropertyDescriptor(ff.__proto__,"getF"));//不可枚举的
console.log(Object.getOwnPropertyDescriptor(FF.prototype,"getF"));//不可枚举的