Skip to content

第十二章 类

类是封装继承和多态的脚手架。拥有更加方便的功能,避免造原始的轮子。

其内部使用原型和继承的方式,本质上是函数,拥有函数的特性。

相对于函数来说,更加清晰。

类的类型为函数

js
声明:
class XXX{}
let obj = new lass();
js
添加方法
class obj{
    func1(){}
	func2(){}
    func3(){}
}
let sm = new obj();
sm.func1();
sm.func2();
sm.func3();
js
初始化
class obj{
    constructor(){

    }
}
constructor会被自动执行。
js
练手
    {
        class My {
            constructor() {
                this.name = "yang";
                this.age = 19;
                this.spirit = "right";
            }
            get() {
                console.log(this.name, this.age)
            }
        }
        let my = new My();
        my.get();
    }

工作机制

js
1、继承机制
class obj{
    show(){}
}
obj.prototype.show = function(){}
两种本质上是一样的。
类方法更加清晰

2、初始化机制
class obj{
    constructor(name){
        this.name = name
    }
}
function obj(name){
    this..name = name
}

属性定义

加上this指的是对对象的声明,而不是class本身的声明

js
class obj{
    func1 = xxx
    constructor(){}
}
前者定义是固定定义,在constructor里定义是接受参数定义

特征声明

使用class时会自动设置好特征。

在函数中使用forin循环会默认遍历出原型的方法。而这不是我们想要的,本质上是因为函数在创建的时候特征里的enumerable为true。

class声明时,会自动修改为不可遍历。所以遍历出来的为对象的属性,而不会遍历出原型的方法。


严格模式

class自带严格模式。

class中添加方法时,this默认为undefined,实际上就是严格模式

js
    class User{
        constructor(){
            
        }
        show(){
            function test(){
                console.log(this);
            }
            test();
        }
    }
    let user = new User();
    user.show();

静态属性

通过prototype为示例添加的为普通方法, 通过对对象/class/函数本身/原型添加的为静态方法

分配给构造函数的为静态属性。区分静态属性和对象属性。

静态属性属于class类。

使用场景:设置静态属性会给所有的对象使用。

js
class obj = {
    static xxx = xxx;
    static xxx = xxx;
    static xxx = xxx;
}

通过构造函数创建的对象自动指向原型

函数有三种用途:1、普通函数 2、构造函数 3、对象


静态方法实现原理

js
    {
        class User {
            show() {
                console.log("对象调用的普通方法");
            }
            static show() {
                console.log("类调用的静态方法")
            }
        }
        User.show();
        let usr = new User();
        usr.show();
    }


    {
        class Usr {
            constructor(name, age, gender) {
                this.name = name;
                this.age = age;
                this.gender = gender;
            }
            static create(...args) {
                return new this(...args);
            }
        }
        console.log(Usr.create("yang", 18, "男"));
    }
js
使用静态方法的多数据处理

	const data = [
        { name: "js", price: 100 },
        { name: "mysql", price: 212 },
        { name: "vue.js", price: 98 }
    ];
    class lesson {
        constructor(data) {
            this.model = data;
        }
        name (){
            return this.model.name;
        }
        price() {
            return this.model.price;
        }
        static sum(data){
            return data.reduce((a,b)=>{ return a + b.price()},0)
        }
        static createBatch(data) {
            return data.map(item => new lesson(item))
        }
        static maxPrice(data) {
            return data.sort((a, b) => b.price() - a.price())[0];
        }
    }
    let all = lesson.createBatch(data);
    console.log(lesson.maxPrice(all).name());
    console.log(lesson.sum(all));

访问器(类)

对属性的控制通过函数控制。

js
    class Request {
        constructor() {
            this.data = {};
            this.host = {};
        }
        set host(url) {
            if (!/^https?:\/\//i.test(url)) {
                throw new Error("address error");
            }
            this.data.host = url;
        }
        get host() {
            return this.data["host"];
        }
    }
    let user = new Request("https://www.baidu.com");
    console.log(user.host);

属性保护

开放属性可修改,有时候我们不希望发生这种情况。

js
名义约定:
以下划线开始命名为私有属性

    class User{
        _url = "https://www.baidu.com";
        constructor(name){
            this.name = name;
        }
        set url(url){
            if(!/^https?:/i.test(url)){ throw new Error("address abnormal")}
            this._url = url;
        }
    }
    let usr = new User("yyyyyyyyyyy")
    usr.name ="yang";
    usr.url = "https:"
    console.log(usr)
js
Symbol()约定   (子类可访问)
    const protectes = Symbol();
    class Common {
        constructor() {
            this[protectes] = {};
            this[protectes].host = "https://www.baidu.com"
        }
    }
    class User extends Common {
        constructor(name) {
            super();
            this.name = name;
        }
        set url(url) {
            if (!/^https?:/i.test(url)) { throw new Error("address abnormal") }
            this[protectes].host = url;
        }
        get url(){
            return this[protectes].host;
        }
    }
    let usr = new User("yyyyyyyyyyy")
    usr.name = "yang";
    usr.url = "https://"
    console.log(usr[Symbol()]);
js
WeakMap设置 (子类可访问)
	const protecteds = new WeakMap();
    class Common {
        constructor() {
            protecteds.set(this, {
                host: "https://www.baidu.com"
            });
        }
        set url(url) {
            if (!/^https?:/i.test(url)) { throw new Error("address abnormal") }
            protecteds.set(this, { ...protecteds.get(this), url });
        }
        get url() {
            return protecteds.get(this)["host"];
        }
    }
    class User extends Common {
        constructor(name) {
            super();
            this.name = name;
        }
        set name(name) {
            protecteds.set(this, { ...protecteds.get(this), name });
        }
        get name(){
            return protecteds.get(this)["name"];
        }
    }
    let usr = new User("yyyyyyyyyyy");
    usr.name = "ya"
    console.log(usr.name);
    console.log(usr);
    let usr2 = new User("zzzzzzzzz");
    usr.protecteds = "https://jlsakjdl"
    console.log(usr.protecteds);
    console.log(usr2.protecteds);
js
私有属性					(子类不可访问,仅当前类中访问)
	语法:#
    场景:属性私有化,方法私有化
    
    class Usr {
        #host = "https://www.baidu.com"
        constructor(name) {
            this.name = name
            this.check(name);
        }
        set(url) {
            if (!/^http?:/i.test(url)) { throw new Error("wrong") }
            this.#host = url;
        }
        check (){
            if(this.name.length < 5){throw new Error ("your name is too short")};
        }
    }
    let user = new Usr("aaaaaa");

继承

js
*extend方法

普通继承:
	1、原型继承
        function User(name){
        this.name = name;
    }
    function Admin(name){
        this.name = name;
    }
    Admin.prototype = Object.create(User.prototype);
    console.dir(Admin);
	2、属性继承
        function User(name){
        this.name = name;
    }
    function Admin(name){
        this.name = name;
        User.call(this,name);
    }
    Admin.prototype = Object.create(User.prototype);
    console.dir(Admin);
	3、方法继承(对象的原型上设置方法达成继承)
        function User(name){
        this.name = name;
    }
    function Admin(name){
        User.call(this,name);
    }
    Admin.prototype = Object.create(User.prototype);
    Admin.prototype.show = function(){}
    console.dir(Admin);

class继承
        class User{
            constructor(name){
                console.log("run")
                this.name = name;
            }
        }
        class Admin extends User{
            constructor(name){
                super(name);
            }
        }
        let adm = new Admin("demo_name")
        console.dir(adm)
被继承对象调用super方法,使用extends继承父类,父类constructor自动调入。
js
函数继承:
    function user() { }
    user.prototype.show = function () {
        console.log("user's show")
    }
    function admin() { }
    admin.prototype = Object.create(user.prototype);
    let yz = new admin();
    yz.show();
语法糖继承:
    class usr{
        show(){
            console.log("usr's show")
        }
    }
    class admin extends usr{}
    let yz = new admin();
    yz.show();
本质上是一样的
class不过是简写的自带便捷继承的function函数罢了。

补充:call方法在对象继承的中的应用
    let yz = {
        name:"yz___",
        show() {
            console.log("yz's show");
            console.log(this.name)
        }
    };
    let zh = {
        name:"zh____",
        __proto__: yz,
        show() {
            this.__proto__.show()
            console.log("zh's show");
        }
    };
    zh.show()
本来的意愿是使用zh里的数据调用yz的方法。因为yz里的show方法永远是yz调用的,所以this永远指向yz。结果是调用的数据来源是yz的数据(name)。
解决方法:call 改变this指向
    let yz = {
        name:"yz___",
        show() {
            console.log("yz's show");
            console.log(this.name)
        }
    };
    let zh = {
        name:"zh____",
        __proto__: yz,
        show() {
            this.__proto__.show.call(this,name);
            console.log("zh's show");
        }
    };
    zh.show()

所以super是调用父级的方法,而使用当前的数据(this指向自己)。
js
上述的对象继承只适用于两层。
 this.__proto__.show.call(this)对多继承将发生死循环
 
 this还是指对象,super只是做原型攀升与方法调用
super可以解决多重继承的问题,在此super仅做为原型攀升的功能。

在function(){}
js
使用super方法必须放在constuctor的首位书写,逻辑上优先级需要是自身>父类,所以需要先调用父类方法然后再进行覆盖以达到优先级的排序。

静态继承原理

属性和方法在皮上没有不同,只是值不同。

class内部实现原理就是原型。

prototype 是函数作为构造函数的原型

__proto__是函数做为对象的原型。

js
class中的static属性是为class(本质上是函数)作为对象时写入的属性/方法。
class中的非static属性是class作为构造函数时为实例化对象添加的方法。

继承判断

js
a为原型对象:
a.isPrototypeOf(b)   a是否能实现b   b是否被a实现

a为对象:
a instanceOf b 		a的原型链上(对象为__proto__)是否存在b的prototype

内置类继承

js
    class number extends Array {
        constructor(...args) {
            super(...args)
        }
        max() {
            return this.sort((a, b) => b - a)[0];
        }
        add(item) {
            return this.push(item);
        }
        remove(value) {
            let post = this.findIndex(item => item == value);
            this.splice(post,1);
        }
    }
    let demo = new number(5,61,65,16,13,546,123,321);
    demo.add(555)
    console.log(demo.max());
    demo.remove(555);
    console.log(demo.max());

多继承mixn

js
通过Object.assign方法添加多个属性

	const data = [
        {click:156,name:"abc"},
        {click:6541,name:"english"},
        {click:520,name:"law"}
    ]
    class Lesson{
        constructor(lessons){
            this.lessons = lessons;
        }
        get data(){
            return this.lessons
        }
    }
    let tool = {
        max(key){
            return this.data.sort((a,b)=>b[key]-a[key])[0];
        }
    }
    let arr = {
        count(key){
            return this.data.reduce((a,b)=> a + b[key],0)
        }
    }
    Object.assign(Lesson.prototype,tool,arr);
    let demo = new Lesson(data);
    console.dir(Lesson)
    console.log(demo.count("click"));