用法
class跟let、const一样:不存在变量提升、不能重复声明…
es5面向对象写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
ES6 的class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
//通过构造函数的形式构造一个对象
function Person(name){
this.name = name;
}
//es5的构造函数他只有构造函数和原始模式的感念他没有类的概念
var person = new Person('huasheng');
//这种用构造函数构造出来的一个对象和别的语言创建的方式是不一样的,
//因为别的语言创建对象的方式是有一个类的概念的,而通过构造函数创建的对象是没有类的感念的
//es5
function Fn(x, y) {
this.x = x;
this.y = y;
}
Fn.prototype.add = function () {
return this.x + this.y;
};
//等价于
//es6
class Fn{
constructor(x,y){
this.x = x;
this.y = y;
}
add(){
return this.x + this.y;
}
}
var F = new Fn(1, 2);
console.log(F.add()) //3
构造函数的prototype
属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype
属性上面。
class Fn {
constructor() {
// ...
}
add() {
// ...
}
sub() {
// ...
}
}
// 等同于
Fn.prototype = {
constructor() {},
add() {},
sub() {},
};
类的内部所有定义的方法,都是不可枚举的(non-enumerable),这与es5不同。
//es5
var Fn = function (x, y) {
// ...
};
Point.prototype.add = function() {
// ...
};
Object.keys(Fn.prototype)
// ["toString"]
Object.getOwnPropertyNames(Fn.prototype)
// ["constructor","add"]
//es6
class Fn {
constructor(x, y) {
// ...
}
add() {
// ...
}
}
Object.keys(Fn.prototype)
// []
Object.getOwnPropertyNames(Fn.prototype)
// ["constructor","add"]
严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict
指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
constructor
onstructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。
class Fn {
}
// 等同于
class Fn {
constructor() {}
}
constructor
方法默认返回实例对象(即this
),完全可以指定返回另外一个对象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
//constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。
//class(函数式编程)
class Person{ //Person是类
//Person是一个类的名字 class是一个类
constructor(name,age){//construction是构造函数(是存放私有属性的)
//constructor是简洁写法(在class内部的函数必须使用函数的简洁写法)
this.name = name;
this.age = age;
}
sayName(){
console.log(this.name)
}
//你原型上面的所有方法都会写在Person的花括号里面
}//class Person后面的花括号是一个代码块,只能在这个类的代码块中写函数不能别的
//而且这些函数会自动挂在到Person.prototype上面
new Person('huasheng',28);//通过new启动Person
//这个类就会自动执行construction,而这个类是一个空对象
console.log(typeof Person)//function 通过class关键词定义了一个类(Person)
//而这个类的数据类型是function函数
// 只要通过new启动了类 就会自动执行constructor这个函数
// 使用class关键词可以定义一个类
// 类的数据类型是function
类必须使用new调用
类必须使用new
调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行
class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
Class 表达式
与函数一样,类也可以使用表达式的形式定义。
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass
而不是Me
,Me
只在 Class
的内部代码可用,指代当前类。
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
如果类的内部没用到的话,可以省略Me
,也就是可以写成下面的形式。
const MyClass = class { /* ... */ };
采用 Class 表达式,可以写出立即执行的 Class。
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // "张三"
上面代码中,person
是一个立即执行的类的实例。
私有方法和私有属性
私有方法/私有属性是常见需求,但 ES6 不提供,只能通过变通方法模拟实现。(以后会实现)
通常是在命名上加以区别。
// 公有方法
foo () {
//....
}
// 假装是私有方法(其实外部还是可以访问)
_bar() {
//....
}
}
原型的属性
class定义类时,只能在constructor里定义属性,在其他位置会报错。
如果需要在原型上定义方法可以使用:
- Fn.prototype.prop = value;
- Object.getPrototypeOf()获取原型,再来扩展
- Object.assign(Fn.prototype,{在这里面写扩展的属性或者方法}
Class 的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
ES6 明确规定,Class 内部只有静态方法,没有静态属性。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
//静态属性只能手动设置
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
class Person{ //Person是类
constructor(name,age){
//construction是构造函数(是存放私有属性的)定义你生成对象的私有属性的
this.name = name;
this.age = age;
}
//实例化对象可以调用的函数,这些方法叫做动态方法
//(挂在原型中的才能通过实例化对象来访问这些动态方法并且能执行)
sayName(){//动态方法 ,prototype原始模型
console.log(this.name)
}
sayAge(){//动态方法 ,prototype原始模型
console.log(this.age)
}
//static(静态的意思)是Person这个类中的关键词
//挂在到类上面的函数是静态方法
static hello(){
console.log('hello')
}
//都会挂在到原型当中 __proto__存放了类的原始模型
//如果一个函数前面加上static这样的一个函数就不会挂载到原型当中,Person类
//用static修饰的函数会自动挂在到Person这个类当中
}
var person = new Person('huasheng',28);
console.log(Person.hello)//用static静态方法调用属性挂在到类上
console.log(person.sayName.name);//通过实例化产品找到动态方法执行 挂在到原型中的动态方法
/*
* 使用class关键词可以定义一个类
* 类的数据类型是function
*
* 只要通过new启动了类 就会自动执行constructor这个函数
* */
class Person{ //类 只能写函数
// 而且这些函数(没有使用static关键词)会自动挂到Person.prototype上 这些方法成为动态方法
//有static 关键词修饰的函数 会挂载到类上 这些方法叫做静态方法
constructor(name,age){ //构造函数
this.name = name;
this.age = age
};
sayName(){
console.log(this.name)
}
sayAge(){
console.log(this.age)
}
static hello(){
console.log('hello')
}
}
var person = new Person('heaven',28)
get、set
存值函数和取值函数,不多说,看代码
class Fn{
constructor(){
this.arr = []
}
get bar(){
return this.arr;
}
set bar(value){
this.arr.push(value)
}
}
let obj = new Fn();
obj.menu = 1;
obj.menu = 2;
console.log(obj.menu)//[1,2]
console.log(obj.arr)//[1,2]
继承
用法
class Fn {
}
class Fn2 extends Fn {
}
注意
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类没有自己的this
对象,而是继承父类的this
对象,然后对其进行加工。如果不调用super
方法,子类就得不到this
对象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
super()//必须调用
}
}
let cp = new ColorPoint(); // ReferenceError
父类的静态方法也会被继承。
//父类
class Person{//类
constructor(name){//构造函数(存放一些私有属性)
this.name = name;
};
sayName(){
console.log(this.name);
}
}
// 要继承的子类 student类继承 Person
class Student extends Person{
constructor(name){
super(name);//在继承父类的子类并且手动写上constructor,super是指一个函数,
//而这个函数是指父类的constructor(就是把父类的constructor在执行一遍)
//在执行子类的方法的同时 系统自动继承父类的东西,
//使用super那么这个类中的方法和构造函数会自动挂在原型当中(class是使用了原型链的继承方式)
}//当前这个类他是一个继承的类是继承person的类
//在继承了被的类的类(不是原始的是继承的),那么你想人为写constructor属性就要写super属性否则就报错
sayName(){//在执行子类的方法的同时 还需要执行父类的同名方法
super.sayName();//还可以在普通函数中使用
//supe是sayName函数中是一个对象了,他会触发父类和子类的同名函数了(会继承父类的方法),
//这时super是作为一个对象来使用
console.log('子类的方法')
}
//即使你在这个花括号中没有写上constructor这个属性,但是JS会给你偷偷的给你写上的,
//你自己要是手动的写上了constructor这个属性那么你必须在这里面写super属性
};
var student = new Student('huasheng');
Object.getPrototypeOf()
Object.getPrototypeOf
方法可以用来从子类上获取父类。
Object.getPrototypeOf(Fn2) === Fn
// true
因此,可以使用这个方法判断,一个类是否继承了另一个类。
super 关键字
super
这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况,super
作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super
函数。
作为函数时,
super()
只能用在子类的构造函数之中,用在其他地方就会报错。
class A {}
class B extends A {
constructor() {
super();
}
}
上面代码中,子类B的构造函数之中的super()
,代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。
注意,super
虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super
内部的this
指的是B,因此super()
在这里相当于A.prototype.constructor.call(this)
。
第二种情况,super
作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代码中,子类B当中的super.p()
,就是将super
当作一个对象使用。这时,super
在普通方法之中,指向A.prototype
,所以super.p()
就相当于A.prototype.p()
。
由于this
指向子类,所以如果通过super
对某个属性赋值,这时super
就是this
,赋值的属性会变成子类实例的属性。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
上面代码中,super.x
赋值为3,这时等同于对this.x
赋值为3。而当读取super.x
的时候,读的是A.prototype.x
,所以返回undefined
。
class A{ //父类
constructor(){//构造函数
this.x = 1;//this指向的是实例化对象A
}
sayName(){//这个是方法(属性)
}
// x(){//这个是方法(属性)
// }
}
class B extends A { //子类
constructor(){
super();//在子类的constructor执行super函数,那么这个super的指向是父类的constructor构造函数
//(继承父类中constructor函数的私有属性)
this.x = 2;//现在this指向的是子类的实例化产品b
super.x = 3;//当前super作为一个对象来使用的并且还赋了值 如果通过super对某个属性赋值 这时super就是this
console.log(super.x);//undefined
//super是指父类的原型而父类的原型是没有x这个属性的
// 现在super是作为一个对象来使用这个对象是指向原型对象,而这个原型对象是父类的原型对象
//(而父类上面没有x这个属性结果是undefined)
console.log(this.x);//3
// 后面的super.x=3的赋值操作分割掉了 this.x=2这个值
}
}
let b = new B();
super
的指向
- 在子类的所有方法中,把
super
当做对象使用,则super
表示父类的原型对象 - 在子类的
constructor
中,当做函数使用,则super
就指向父类的constructor
- 在子类的方法中,把
super
当做对象使用 并且赋值,则super
指向this