龙空技术网

谈谈你对ES6中Class类的理解

尚硅谷教育 151

前言:

现时朋友们对“js加class属性”都比较珍视,姐妹们都需要知道一些“js加class属性”的相关资讯。那么小编也在网络上网罗了一些对于“js加class属性””的相关内容,希望朋友们能喜欢,小伙伴们快快来学习一下吧!

我们都知道ES6有了面向对象的概念,而class(类)就是ES6中新的基础性语法糖结构,虽然ES6 class表面上看起来可以支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念,让对象原型的写法更加清晰、更像面向对象编程的语法。

一、为什么要有class

在JS语言中,以前我们生成实例对象的方法是通过构造函数创建。

function Person(name,age) {

this.name = name;

this.age = age;

}

Person.prototype.call= function () {

console.log(`我叫${this.name}, 今年${this.age}岁`);

};

var p1 = new Person('Tom', 18);

上面这种写法跟传统的面向对象语言(比如C++和Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

因此ES6提供了更接近传统语言的写法,引入了class这个概念,作为对象的模板。通过class关键字,就可以直接定义类。

class Person {

}

var p2 = new Person();

上面就是class的基本语法。它的绝大部分功能我们都可以通过ES5的构造函数以及原型做到,class写法只是让对象原型的写法更加清晰,更接近面向对象编程的语法。

下面我们来讲讲class的常用方法以及如何去使用。

二、class的方法

constructor()

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法,默认返回实例对象(即this)。一个类必须有constructor方法,如果没有手动定义,JS引擎会自动为它添加一个空的constructor方法。

class Person {

// 构造

constructor(name, age) {

this.name = name;

this.age = age;

}

}

var p3 = new Person('Tom', 18);

通过上面的案例我们可以看到,Person类可以通过constructor方法给this,也就是实例对象身上添加对应的属性,除了这种方法,我们也可以直接将这些属性定义在类的最外层:

class Person {

brand = "Tom";

price = 18;

// 构造

constructor() {}

}

var p4 = new Person();

通过这种方式定义出来的属性我们把它称为实例属性,我们可以直接将其定义在类的顶部,这样一眼就能看出来这个类有哪些实例属性。

实例方法

和ES5不同,类中的方法不需要加function关键字就可以直接生成,同时多个方法之间也不需要用逗号进行分隔,否则会报错,实例方法调用时同样可以传递参数。

class Person {

// 构造

constructor(name, age) {

this.name = name;

this.age = age;

}

call() {

console.log(`我叫${this.name}, 今年${this.age}岁`);

}

play(ball) {

console.log(`我爱玩${ball}`);

}

}

var p5 = new Person("Tom", 18);

p5.call(); // 我叫Tom, 今年18岁

p5.play("足球"); // 我爱玩足球

静态属性

实例对象身上有自身的属性,那class本身也会有自己的属性,也就是静态属性:

Person.prop = 1;

console.log(Person.prop) // 1

目前,只有这种写法可行,因为ES6明确规定,class内部只有静态方法,没有静态属性。

static静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就是class的静态方法。

class Person {

constructor() {}

call() {

console.log(`我是实例方法`);

}

static staticFn() {

console.log(`我是静态方法`,this);

}

}

var p6 = new Person("Tom", 18);

p6.call();

Person.staticFn();

p6.staticFn();

我们可以看到静态方法中的this指向的是类,而不是实例对象;

静态方法可以与非静态方法重名。实例与类调用时会调用对应的方法;

父类的静态方法,可以被子类继承。

extends 类的继承

class之间可以通过extends关键字实现继承(继承父类的所有属性和方法)。

// 父类

class Person {

constructor(name, age) {

this.name = name;

this.age = age;

}

call() {

console.log(`我叫${this.name}, 今年${this.age}岁`);

}

play(ball) {

console.log(`我喜欢玩${ball}`);

}

}

// 子类

class Student extends Person {

constructor(name, age, score) {

super(name, age);

this.score = score;

}

// 重写父类的方法

call() {

console.log(

`我叫${this.name},今年${this.age}岁, 考了${this.score}分`

);

}

}

var s1 = new Student("Tom", 18, 99);

s1.play("篮球"); // 我喜欢玩篮球

s1.call(); // 我叫Tom,今年18岁, 考了99分

从上面的案例中我们可以看到,当子类中出现与父类同名的方法时,会将父类的方法进行重写,从而使得子类实例能获取到最新的结果。

super

从上一个继承案例中我们可以发现有一个super关键字,子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

ES5的继承是先创造子类的实例对象this,再将父类的方法添加到this上Person.apply(this),和ES5的机制不同,ES6先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。父类身上的静态方法同样会被子类继承。

取值函数(getter)和存值函数(setter)

这两个方法其实就是对对象的属性进行方法的绑定,当对某一个属性进行获取时,就会执行getter,当对某一个属性进行设置时,则执行setter。

class Person {

constructor(score) {

this.score = score;

}

get myScore() {

console.log("分数被读取了");

return this.score;

}

set myScore(newval) {

console.log("分数被修改了");

this.score = newval;

}

}

// 实例化对象

let p7 = new Person(99);

console.log(p7.myScore);

p7.myScore = 100;

console.log(p7.myScore);

上面的类中,我们自定义了score的读写方法,当读取p7的score属性时,就会调用get myScore方法,并且函数的返回值就是我们想要的score值;当对score进行重新赋值时,就会调用set myScore方法,在方法内部就会对this.score进行修改,这里要注意setter一定要传入修改后的参数,不然是接收不到新的值的。

三、class注意事项

不能重复声明

class Person { }

class Person { }

// 或

let Person = class {}

class Start { }

以上两种写法都会报错:语法错误:“Person”已经被声明。

不能直接调用(必须使用new关键字)

class Person {

constructor() {}

}

console.log(Person());

以上会报错:类型错:没有“new”不能调用类构造函数Person。

不存在提升(存在暂时性死区)

类不存在变量提升,这一点与 ES5 完全不同。

console.log(new Person());

class Person {

constructor() {}

}

以上就会报错:引用错误:初始化前无法访问“Person”。

因此子类必须在父类声明之后定义,实例的声明也是一样。

四、class的实例

ES6 class生成类的实例的写法,与 ES5 完全一样,也是使用new命令。前面说过,如果忘记加上new,像函数那样调用class(),就将会报错。

类的属性和方法,除非直接定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

class Person {

constructor(name, age) {

this.name = name;

this.age = age;

}

call() {

console.log(`我叫${this.name}, 今年${this.age}岁`);

}

}

var p8 = new Person("Tom", 18);

console.log(p8.hasOwnProperty("name")); // true

console.log(p8.hasOwnProperty("age")); // true

console.log(p8.hasOwnProperty("call")); // false

console.log(p8.__proto__.hasOwnProperty("call")); // true

上面代码中,name和age都是实例对象p8(this)自身的属性,所以hasOwnProperty方法返回true,而call是原型对象的属性(因为直接定义在Person类上),所以hasOwnProperty方法返回false。这些都与ES5保持一致。并且类的所有实例共享一个原型对象。

var p8 = new Person("Tom", 18);

var p9 = new Person("Jack", 22);

console.log(p8.__proto__ === p9.__proto__) // true

上面代码中,p8和p9都是Person的实例,它们的原型都是Person.prototype,所以__proto__属性是全等的。

这也意味着,可以通过实例的__proto__属性为class添加方法。

__proto__ 并不是语言本身的特性,虽然目前很多浏览器的JS引擎中都提供了这个私有属性,它本质上是一个内部属性,而不是一个正式的对外的 API,只是由于浏览器广泛支持,才被加入了 ES6。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。所以不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf() 方法来获取实例对象的原型,然后再来为原型添加方法/属性。

var p8 = new Person("Tom", 18);

var p9 = new Person("Jack", 22);

p8.__proto__.play = function (ball) {

console.log(`我喜欢玩${ball}`);

};

p8.play("足球"); // 我喜欢玩足球

p9.play("篮球"); // 我喜欢玩篮球

var p10 = new Person("Jerry", 20);

p10.play("乒乓球"); // 我喜欢玩乒乓球

上面代码在p8的原型上添加了一个play方法,由于p8的原型就是p9的原型,因此p9也可以调用这个方法。而且,此后新建的实例p10也可以调用这个方法。这意味着,使用实例的__proto__属性改写原型,必须相当谨慎,不推荐使用,因为这会改变class的初始定义方法,影响到所有实例。

总结

class类是基于原型的继承的语法糖;class的出现使得代码更加优雅,代码量减少;class类能清晰的指定构造函数和抽象方法;class类在声明时就可以设定属性和方法,不需要再往函数的原型中添加方法;使用class类时必须实例化;使用extends关键字实现继承。

标签: #js加class属性 #js给class属性赋值