前端学习笔记--ES6类

273 阅读5分钟

一、基本语法

1.简介

传统JS中创建对象是通过**「构造函数」**,例如:

  // 通过构造函数创建对象
    function Person(name,age){
        this.name=name;
        this.age = age;
    }
    // 创建实例
    let p1 = new Person("小红",18);
    console.log("p1:",p1)
    // Person的prototype和p1的__proto__指向同一个对象
    console.log(Person.prototype === p1.__proto__// true

基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

用class的语法改写如下:

class PersonCopy{
        constructor(name,age){
            this.name=name;
            this.age=age;
        }
        run(){
            console.log(this.name +" is running")
        }
    }
    let p2 = new PersonCopy("小明",12);
    console.log("p2:",p2)
    console.log(typeof PersonCopy); // function 
    console.log(PersonCopy.prototype.constructor===PersonCopy//true
    p2.run();
    
    

可以得知:

  • 类的数据类型就是函数
  • 类本身就指向构造函数

而且:

  • 实例属性是添加到对象上的
  • 方法是定义到原型对象prototype上的,各个实例共用同一个方法
{
    "name""小明",
    "age"12
    
    [[prototype]]:{
     constructor:f()  class PersonCopy,
     run: f run()
    }
}

相当于:

 function PersonCopy(name,age){
        this.name = name;
        this.age =age;
    }
    // 方法加到原型对象上,所有实例共用。这样就只会存在一个run函数对象
    // 而不会为每个对象都创建一个run函数对象
    PersonCopy.prototype.run = function(){
        console.log(this.name +" is running")
    }
    let p3 = new PersonCopy("小王",13)
    p3.run() // 小王 is running

由于类的方法都是定义在类的prototype上的,所以可以通过Object.assign()方法为类一次性添加多个方法:

Object.assign(PersonCopy.prototype,{
        toString(){},
        toValue(){}
    })

2.constructor

constructor 方法是的默认方法,通过 new 命令生成对象实例时,自动调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加。

类必须使用 new 调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new 也可以执行。

与 ES5 一样,实例的属性除非显式定义在其本身(即定义在 this 对象上),否则都是定义在原型上(即定义在 class 上)。

3.getter和setter

  class Man{
        constructor(){} // 可不写

        // 会给prototype上添加一个name属性
        get name(){
            return this.nickname;
        }
        set name(name){
            this.nickname = name;
        }
    }
    let m = new Man();
    console.log(m);
    m.name="hello";
    console.log(m.name//hello
    console.log(m.nickname); //hello
    m.hasOwnProperty('name'// false
    m.hasOwnProperty('nickname')  //true
    Man.prototype.hasOwnProperty('name'// true

4.属性表达式

类的属性名,可以采用表达式。属性和方法都可以用表达式。

    // 属性表达式
    let prop1 = "name";
    let prop2 = "run";
    class Course{
        constructor(name){
            this[prop1] = name;
        }
        [prop2](){
            console.log(prop2)
        }
    }
    let c = new Course("基础");
    console.log(c)
    console.log(c.name)
    c.run()

5.class表达式

与函数一样,类也可以使用表达式的形式定义。


    const A = class B{
        getClassDeclareName(){
            return B.name + A.name// A.name也可以
        }
    }
    let a = new A();
   console.log( a.getClassDeclareName())
   console.log(A.name// B
   console.log(B.name)  // error

这个类的名字是B,但是只能在类内部使用,在外部只能使用A。A在内部和外部都可以使用。

6.静态方法

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

    class A{
        constructor(name){
            this.name = name;
        }

        run(){
            console.log(this.name +" is running")
        }
        static step(){
            console.log(this.name +" is step"// 此处this指向类A
        }
    }
    let a1 = new A("小红");
    
    console.log("a1",a1)
    a1.run()
    A.step()
    a1.step(); //error 实例没有该方法
  • 实例调用静态方法会报错
  • 如果静态方法包含 this 关键字,这个 this 指的是类,而不是实例
  • 静态方法和非静态方法可以重名,因为一个是实例的,一个是类的。如下:调用a1.step()就不会报错了,调用的是非静态方法step
    class A{
        constructor(name){
            this.name = name;
        }
        run(){
            console.log(this.name +" is running")
        }
        step(){
            console.log("实例方法step")
        }
        static step(){
            console.log(this.name +" is step"// 此处this指向类A
        }
    }
    let a1 = new A("小红");
    
    console.log("a1",a1)
    a1.run()
    A.step()
    a1.step(); // 实例方法step

7.实例属性的新写法

实例属性除了定义在constructor()方法里面的 this 上面,也可以定义在的最顶层。

  class B{
        name;
        age;
        getName(){
            return this.name;
        }
        setName(value){
            this.name = value;
        }
    }
    let b =new B();
    b.setName("hello")
    console.log(b)
    console.log(b.getName())

8.静态属性

静态属性指的是Class本身的属性,即 Class.propName,而不是定义在实例对象( this )上的属性。

 // 写法1
    class A{

    }
    A.foo=1;
    let a = new A();
    console.log(A.foo);  //1 
    console.log(a.foo// undefined

    // 写法2
   class B{
       static foo = 1;
   }
   let b = new B();
   console.log(B.foo//1 
   console.log(b.foo// undifined

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

写法2只是提案。

9.私有方法和私有属性

10.new.target

二、继承

1.简介

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

        class A{
            constructor(username,password){
                this.username = username;
                this.password = password;
                console.log("A constructor")
            }
            static run(){
                console.log("run")
            }
        }
        class B extends A{
            constructor(sex,username,password){ 
                console.log("B constructor")
                super(username,password);
                this.sex = sex;
            }
        }
        let b = new B("男","admin","1234")
        console.log(b)
        B.run() // 继承父类的静态方法

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

  • 子类必须调用父类的构造方法
  • 父类的构造方法必须在使用this关键字之前调用
  • 父类的静态方法,也会被子类继承

2.Object.getPrototypeOf()

Object.getPrototypeOf 方法可以用来从子类上获取父类。

console.log(Object.getPrototypeOf(B) === A) // true

3.super关键字

  • super()调用父类构造器
  • 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类