ES6学习笔记(三)-- class(类)

233 阅读4分钟
文章有点长,边听课边整理的笔记,如有疏漏不当之处,还请指教,立即更改。

类是一个函数,基层依旧是原型继承,class语法糖使类的声明和使用、继承更加的简洁。

类的定义方式

类的定义可以分为两种,一种是声明类,一种是赋值类。

//声明类
class User{
    // constructor起到了传递对象的初始参数的作用,不是必须定义声明的。在new的收,会执行constructor方法。
    constructor(name){
        this.name = name;
        // show 是构造函数的方法。
        this.show = function(){ console.log( 'show' )}
    };
    // getName是原型上的方法。
    getName(){
        return this.name
    }
}
//赋值类
let class = class{
    constructor(name){
        this.name = name;
    }
};

var chen = new User('陈');
chen.getName();
chen.show();
typeof User;   // function

constructor

类如果不设置constructor,则类会默认设置如下类型:在子构造器调用完super后才可以使用this

constructor( args ){
    super( ...args )
}

类和构造函数

  • 类就是一个函数,是一种语法糖。下式代码中,两者所起到的作用是一样的。
  • 在使用in遍历的时候,通过方式一class定义的,原型上的方法getName不能遍历到,而方式二构造函数追加到原型上的方法getName则可以遍历到。
  • class默认使用严格模式(use strict)执行。
// 定义类
class User{
    // site name 为对象上的两个属性
    site = 'www.chen.com';
    constructor(name){
        this.name = name;
        // show 是构造函数的方法。
        this.show = function(){ console.log( 'show' )}
    };
    // 原型上的方法
    getName(){
        console.log( 'getName' );
    }
}

let chen = new User('陈');

for (const key in chen) {
    console.log(key);  // site name show
}

// 定义构造函数
function User(name){
    this.site = 'www.chen.com';
    this.name = name;
    this.show =function(){
        console.log( 'show' );
    };
};
// 为构造函数原型添加方法
User.prototype.getName = function(){
    console.log( 'getName' );
}

let chen = new User('陈');

for (const key in chen) {
    console.log(key);  // site name show  getName
}

静态属性和静态方法(static)

  • 可以把为所有对象使用的值定义为静态属性。
  • 一般来讲方法不需要对象属性参与计算就可以定义为静态方法。
  • 静态方法和静态属性只能通过类来访问,继承的类也可以访问到,但是不能通过实例来获取。
// 静态访问实现原理
function User(){
    this.show = function(){
        console.log('inside show');
    }
};
User.show = function(){
    console.log( 'outside show' );
};
User._name = 'Miracle';

let chen = new User();

User.show();  // outside show
console.log( User._name );  // Miracle

chen.show();  // inside show
console.log( chen.name );  // undfined
// 类中静态方法和静态属性使用方法
class User{
    constructor(fn){
        this.fn = fn;
    }
	// 静态属性
    static  LAST_NAME = 'chen';
    // 静态方法
    static getFullName (fn) {
        console.log( this );
        return '大家好,我是' + fn + ' ' + User.LAST_NAME;
    }
    query(fn){
        return fn + ' ' + User.LAST_NAME;
    };
    sayWord(){
        return User.getFullName();
    }
};
let chen = new User();
chen.query('miracle');
User.getFullName('xiaowu');

属性的访问控制(访问器)

在函数前加上 get/set修饰,防止属性被随意的更改。

class User{
    constructor(name, age){
        this.data = {
            name: name,
            age: age
        }
    };
    get name(){
        return this.data.name;
    };
    set name(value){
        if( value.length > 10 ){
            throw new Error( 'name长度过长' );
        };
        this.data.name = value;
    }
};
let chen = new User('chen', 18);
chen.name = 'miracle';
chen.name;

私有属性

命名保护

  1. Symbol定义私有属性
let protecteds = Symbol();
class User {
    constructor(host) {
        this[protecteds] = {};
        this[protecteds].host = host;
    };
    set host(url) {
        if (!/^https?:/i.test(url)) {
            throw new Error("url格式有误");
        }
        this[protecteds].host = url;
    }
    get host() {
        return this[protecteds].host;
    }
}
class Admin extends User {
    constructor(name) {
        super();
        this[protecteds].name = name;
    }
    get name() {
        return this[protecteds].name;
    }
}
let hd = new Admin("zhangsan");
hd.host = "https://www.zhangsan.com";
  1. 不成文的规定:已_开头的属性和方法,为私有属性和私有方法。

private

  • 为属性或方法名前加 # 为声明为私有属性
  • 只有在声明的类里使用,在继承的类中也访问不到。

下边代码中定义了私有属性host以及私有方法check

class User{
    // 私有属性
    #host = '#host';
    constructor(name){
        this.name = name;
        this.#check(name);
    };
    // 私有方法
    #check(value){
        if( value.length < 5 ){
            throw new Error('用户名长度不能少于5位');
        }
    }
    get host(){
        return this.#host;
    };
    set host(url){
        if(!/^https?:/.test( url)){
            throw new Error( 'url格式有误' );
        };
        return true;
    };
};
let hd = new User('zhangsan');
// hd.#host;  //SyntaxError: Private field '#host' must be declared in an enclosing class

属性保护

保护属性并使用访问器控制

const protecteds = Symbol("protected");
class User {
    constructor(name) {
        this[protecteds] = { name };
    }
    get name() {
        return this[protecteds].name;
    }
    set name(value) {
        if (value.trim() == "") throw new Error("invalid params");
        this[protecteds].name = value;
    }
}
let hd = new User("zhangsan");
hd.name = "lisi";
console.log(hd.name);
console.log(Object.keys(hd));

继承的原理分析

属性继承

属性继承的原型如下代码所示:

function User(name) {
  this.name = name;
}
function Admin(name) {
  User.call(this, name);  // 相当于类继承中的super(name)
}
let hd = new Admin("后盾人");
console.log(hd);

方法继承

在类中,可以采用如下代码来在子类中调用父类中的方法(通过super关键字)

// 调用父类的show方法
class User{
    show(){
        console.log( 'User.show', this.name );
    }
};

class Admin extends User{
    constructor(name){
        super();
        this.name = name;
    };

    show(){
        super.show();
    }
};

let admin = new Admin('admin');

admin.show();

我们也可以使用以下对象和__proto__的方式来实现类似的功能:

let user = {
    name: 'user',
    show(){
        console.log( 'user show', this.name );
    }
};

let admin = {
    name: 'admin',
    __proto__: user,
    show(){
        // 调用原型上的show方法,将其this指向admin。类似于类中的 super
        this.__proto__.show.call(this);
    }
}
admin.show();

但是这个代码有个缺陷,就是一旦层级超过二级,就会出现死循环。如:

let common = {
    name: 'common',
    show(){
        console.log( 'common show' );
    }
}
let user = {
    name: 'user',
    __proto__: common,
    show(){
        this.__proto__.show.call( this ); // 此时this指向的是admin对象,在此处调用的一直都是user对象的show方法,走进死循环。
    }
};

let admin = {
    name: 'admin',
    __proto__: user,
    show(){
        // 调用原型上的show方法,将其this指向admin。类似于类中的 super
        this.__proto__.show.call(this);
    }
}
admin.show();

为了解决上述的死循环问题,可以采用super关键字。

let common = {
    name: 'common',
    show(){
        console.log( 'common show', this.name );
    }
}
let user = {
    name: 'user',
    __proto__: common,
    show(){
        super.show();
    }
};

let admin = {
    name: 'admin',
    __proto__: user,
    show(){
        super.show();
    }
}
admin.show();

super关键字

  • super的作用是从当前对象的原型中执行方法,进行原型攀升。

  • 在class方法中,继承是使用extends关键字来实现继承的。

  • 子类必须在constructor()中调用super()方法,否则新建实例时会报错。报错的原因是,子类是没有自己的this对象的,它只能继承父类的this对象,然后对其进行加工,而super()就是将父类中的this对象继承给子类的。没有super,子类就得不到this对象。

  • super只能在类或者对象中使用,不能在方法中使用, 如:

    show: function(){
        return super.show(); // //Uncaught SyntaxError: 'super' keyword unexpected here
    }
    

constructor

  • super指向父类引用,在有继承关系的子类中,在构造函数中,必须先调用 super() , 因为防止父类变量方法覆盖子类变量方法。

    class Admin extends User{
        constructor(name){
            super();  // 其原理如: User.apply(this, args);
            this.name = name;
        }
    }
    

静态方法和静态属性

先来看看类的静态方法和静态属性用法:

class User {
    static name = 'herry';
    static showName() {
        return this.name;
    };
    sum(){};
};
class Admin extends User {};
let admin = new Admin();
console.log( admin );

代码原理分析:

在上述代码中,静态属性和静态方法都是被加在了类User的对象上,而不是在其prototype原型上,但是sum是加在了其prototype上。类Admin继承了类User,Admin.prototype.__proto__就指向了User.prototype,也就是 Admin.prototype.__proto__ === User.prototype。然后通过new 创建一个Admin实例 adminadmin.__proto__ 就指向了 Admin.prototype, 所以 admin.__proto__.__proto__ === User.prototype。在上述代码中, sum是在User.prototype上,所以 admin 可以访问到 sum, 但是访问不到 User 的 静态方法showName和静态属性name

下边通过构造函数来分析下其实现原理

function User(){};
User.name = 'herry';
User.showName = function(){ };
User.prototype.sum = function(){};
function Admin () {};
Admin.prototype.__proto__ = User.prototype;
let admin = new Admin();
console.dir( admin );

调用父类方法

  • super不接方法名,默认调用父类的constructor。
  • super后跟着方法名,则调用父类对应的方法。
  • 可以再子类中重写父类的方法。

代码如下所示:

let data = [
    { name: "js", price: 100 },
    { name: "mysql", price: 212 },
    { name: "vue.js", price: 98 }
];
class User {
    constructor(name) {
        this.name = name;
    };
    showName() {
        return this.name;
    };
    //匹配包含名字的课程
    filterListByName(name){
        return data.filter( i => i.name.includes( name ));
    };
    // 父类中的,计算所有课程价格的总和
    sum(){
        return data.reduce((acc, cur) => acc + cur.price, 0 );
    };
    // 获取课程中价格最大的
    max(){
        return data.map( i => i.price ).sort((a, b) => b - a )[0];
    }
};

class Admin extends User {
    constructor(name) {
        // super后不跟方法名,默认执行的是父类的constructor构造函数。
        super(name);
    };
    // 在子类重写父类的方法, 只获取对应课程的名字
    filterListByName(value){
        // super后跟着方法,是调用父类中对应的方法。
        return super.filterListByName(value).map( i => i.name )
    }
};
let hd = new Admin('admin');
hd.showName();
hd.sum();
hd.max();
hd.filterListByName('js');

mixin

JS不能实现多继承,如果要使用多个类的方法时可以使用mixin混合模式来完成。

  • mixin 类是一个包含许多供其它类使用的方法的类
  • mixin 类不用来继承做为其它类的父类
  • 其原理与mixin在原型中是一样的。

直接上代码:

const Tool = {
  max(key) {
    return this.data.sort((a, b) => b[key] - a[key])[0];
  }
};

class Lesson {
  constructor(lessons) {
    this.lessons = lessons;
  }
  get data() {
    return this.lessons;
  }
}
// 让Lessons的实例可以同时使用其原型与Tools对象中的方法。将Tools对象压到Lessons.prototype中。
Object.assign(Lesson.prototype, Tool);
const data = [
  { name: "js", price: 100 },
  { name: "mysql", price: 212 },
  { name: "vue.js", price: 98 }
];
let hd = new Lesson(data);
console.log(hd.max("price"));