JavaScript的语法糖 —— 类的实现

类的语法

class关键字声明,内部使用constructor初始化实例化对象的属性。

        class User {
            // constructor方法是对属性进行初始化的 接收参数,同样在实例化对象后,对象会自动执行
            constructor(name) {
                this.name = name;
            }
            //方法
            getName() {
                return this.name;
            }
        }
        console.log(typeof User); // function
        
        // 既然是函数的话,我们还有另外一种定义的方式
        let F = class {
            constructor(name) {
                this.name = name;
            }
            getName() {
                return this.name;
            }
        };
        let user = new User("sunny");

类的内部实现原理

类的本质还是原生js的构造函数,通过constructor构造方法声明的属性是对象的属性,类中定义的其他方法自动存在类的原型中。

对比一下类和构造函数实现的对象:

        // 语法糖的结构:结构清晰
        // 内部实现与构造函数一样
        class User {
            // 初始化对象的属性
            constructor(name) {
                this.name = name;
            }
            // 在类里面添加方法的方法,会自动添加到类的原型中,并且这些方法不会被遍历,默认特征为false
            show() {
                console.log(this.name);
            }
        }
        let u = new User("user");
        console.log(u); // 与f结构一样
        // 类也有自己的原型对象,也有原型对象的属性
        console.log(User.prototype.constructor == User);
        
        function Fun(name) {
            this.name = name;
        }
        // 构造函数呢,是我们手动添加的
        Fun.prototype.show = function () {
            console.log(this.name);
        }
        let f = new Fun("fun");
        console.log(f);

从显示中也可以看出,类方法默认添加在类原型当中并且方法是不可遍历的。
在这里插入图片描述
类和构造函数实例化出的对象以及二者的原型均是一样的:

		console.log(Object.getOwnPropertyNames(User.prototype));
        console.log(Object.getOwnPropertyNames(u));
		
        console.log(Object.getOwnPropertyNames(Fun.prototype));
        console.log(Object.getOwnPropertyNames(f));

在这里插入图片描述

在类中设置对象的属性和方法

对象属性和方法都有两种设置的形式。
属性:属性的声明可以在constructor方法内也可在方法外,通常我们设置在构造方法内
方法:类方法通常在构造方法外声明,也可以在构造方法内声明。

        class User {
            site = "www.baidu.com";
            constructor(name) {
                this.name = name;
                this.only = function onlyMe() {
                    console.log("对象自己的方法");
                }
            }
            changeSite(value) {
                this.site = value;
            }
        }
        let ff = new User("xiaomin");
        console.log(ff);
        ff.site = "bai.com";
        console.log(ff.name + "的size是:" + ff.site); // 可更改

        let ff2 = new User("xiaofeng");
        console.log(ff2);
        ff2.site = "修改后的size";
        console.log(ff2.name + "的size是:" + ff2.site); // 不受其他对象改变属性的影响

定义类的静态属性和方法

类的静态方法和属性通过 static关键字声明。
定义类的静态属性和方法,只能本类及其子类能访问,不对实例化对象开放,如果想让对象访问,在类中定义方法即可。
类的静态属性和方法:适合批量操作实例化对象

        class Vip {
            // static 定义 静态属性 保存在类中
            static host = "www.baidu.com";
            constructor(name) {
                this.name = name;
            }
            // 静态方法 static关键字
            static getHost(url) {
                // this指向Vip类 getHost
                return this.host + `/${url}`;
            }

            static create(...args) {
                return new this(...args);
            }
            show() {
                console.log(this.name);
            }
        }

        let v = new Vip("vip");
        console.log(v);
        console.log(Vip.getHost("index"));
        console.dir(Vip.prototype.constructor == Vip); //true

        // 通过create方法创建对象 核心还是new 类的操作
        let obj = Vip.create("tt", 18);
        console.dir(obj);

在这里插入图片描述

静态方法的使用

案例:课程类
批量操作对象,统计、筛选等操作。

        const lesson = [{
            name: "js",
            price: 198
        }, {
            name: "css",
            price: 82
        }, {
            name: "html",
            price: 100
        }, {
            name: "jquery",
            price: 150
        }, {
            name: "mysql",
            price: 300
        }];
        // 定义一个课程类
        class Lesson {
            constructor(date) {
                this.module = date;
            }
            // 设置获取name和price属性
            get name() {
                return this.module.name;
            }
            get price() {
                return this.module.price;
            }
            // 对所有对象批量操作:使用静态方法
            // 为每个对象创建实例化对象
            static create(date) {
                // 因为是数组类型,所以传入的数据是数组,遍历分别创建对象
                return date.map(item => {
                    return new this(item);
                })
            }
            // 选出价格最高的课程
            static maxPrice(date) {
                // 排序,降序取第一个
                return date.sort((a, b) => {
                    return b.price - a.price;
                })[0];
            }
            // 计算课程总额
            static totalPrice(date) {
                // 参数1:归并的结果
                // 参数2:遍历时当前的元素
                return date.reduce((total, cur) => {
                    return total += cur.price;
                }, 0)
            }
            // 筛选价格高于一个价格的课程
            static filterPrice(date, putPrice) {
                return date.filter(item => {
                    return item.price > putPrice;
                }).map(item => {
                    return `课程是:${item.name},价格是${item.price}`;
                });
            }
        }
        //通过静态方法实例化对象
        let lessones = Lesson.create(lesson);
        console.log(lessones[0].name);
        //console.log(lessones);
        //获取价格最高的课程
        let maxPrice = Lesson.maxPrice(lesson).price;
        console.log(`价格最高的课程是:${Lesson.maxPrice(lesson).name},价格是:${maxPrice}`);

		//计算课程总价
        let sumPrice = Lesson.totalPrice(lesson);
        console.log(sumPrice);
		
		//筛选符合价格条件的课程
        console.log(Lesson.filterPrice(lesson, 100));

类的继承

上面说到了类本质就是构造函数,继承的实现同样是原型的继承,不过在类中继承不像之前那么麻烦了,只需要extends关键字就能实现类的继承。
A extends B :A类 继承 B类。
B类的构造方法中必须调用super(),同时super()还必须放在this之前,否则会报错。

        class Father {
            static father = "father.attr";
            constructor() {
                this.name = "name";
                this.age = "age";
            }
            get msg() {
                return `名字是:${this.name},年龄${this.age}岁`;
            }
            show() {
                //方法自动挂在Father的原型上
            }
        }
        class Son extends Father {
            host = "private";
            constructor() {
                // 继承父类中的对象的所有属性
                // 必须在子类构造方法的第一行,也就是this之前调用
                super();
                this.only = "son.attr";
            }
        }
        let son = new Son;
        console.log(son);
        console.dir(Son);
        console.log(Son.prototype.__proto__ == Father.prototype); // true

super原理

super是通过call、apply、bind方法实现,但是在多继承的时候又比它们好用,也正是super解决了原生多继承的问题。
两个对象的继承
先看下用原生的对象实现继承:

        let user = {
            name: "user.name",
            // person要执行commen对象的show方法
            show: function () {
                console.log(this.name + " user对象");
            }
        };
        let person = {
            name: "person.name",
            // person继承user对象
            __proto__: user,
            show: function () {
                // person对象执行父级show方法
                // this.__proto__.show(); // 这个是打印的是user的name
                // 这才是本对象调用了父级方法  这样就实现了clas中super的作用
                this.__proto__.show.call(this);
            }
        };
        person.show();

定义了两个对象,user和person,让person继承user对象,这样我们需要设置person的__proto__指向user。person调用show方法时想使用父级的show方法,这时候我们需要让show方法的this指向person对象,则需要call或apply等方法。

            show: function () {
                //这个是user执行的,打印的是user的name,因为this.__proto__指向了user
                // this.__proto__.show(); 
                // 这才是本对象调用了父级方法  这样就实现了clas中super的作用
                this.__proto__.show.call(this);
            }

两个对象以上的继承
此时是没有发现问题,当我们再加一层,让user对象继承commen对象;
定义commen对象:

        let commen = {
            name: "commen.name",
            show: function () {
                console.log(this.name + " commen对象");
            }
        }

只需让user的__proto__ 属性指向 commen。

        let user = {
            name: "user.name",
            // // user继承commen
            __proto__: commen,
            // person要执行commen对象的show方法
            show: function () {
                console.log(this.name + "  user对象");
                //此时this指向person,
               	//则this.__proto__指向user
               	//所以就变成了 user.show.call(person)
                // 会不断的调用自己,陷入死循环的状态
                this.__proto__.show.call(this);
                // 这就体现了super的作用了
            }
        };
        person.show();

这就需要用到类的super了,既方便又好用,不会出现多继承出现的这种问题。

私有属性的设置

在类中,我们人为的规定私有属性使用 _开头定义对象私有属性。

        class User {
            // 这种命名方法,就是认为的告诉用户这个属性是不对外开放的,不可访问的
            // 但本质上还是能访问和修改的,这种方式知识命名方式的保护属性
            _size = "http://baidu.com";
            // 如果想开放,设置接口
            set url(url) {
                if (!/^http?:/i.test(url)) {
                    throw new Error("地址异常");
                }
                this._size = url;
            }
            get url() {
                return this._size;
            }
        }
        let user = new User;
        console.log(user);
        user._size = "http://js.com"; // 能设置 并没有真正意义上实现保护
        console.log(user._size); // 能获取
        user.url = "http://baidu.com";
        console.log(user.url);

毕竟是人为规定的,实际还是能操作的。
我们可以使用Symbol数据类型来设置真正的私有属性。

        // []存储Symbol的值
        const date = Symbol();
        class User {
            // 设置属性  当前类及其子类可使用,换句话说就是有其他类继承本类也可使用这个私有属性
            // [HOST] = "http://www.baidu.com";
            // 当私有变量多的时候,使用对象把这些变量都存储起来
            constructor(name) {
                this[date] = {};
                this[date].host = "http://www.baidu.com";
                // this.name = name;
                // 设置name为私有属性
                this[date].name = name;
            }

            // 若想操作私有属性时,开放接口来操作
            // 通过host属性来访问
            set host(url) {
            	//正则验证地址是否合法
                if (!(/^http(s?):/i).test(url)) {
                    throw new Error("地址异常");
                }
                this[date].host = url;
            }
            get host() {
                return this[date].host;
            }
            // 设置接口开放name属性
            get name() {
                return this[date].name;
            }
        }
        let u1 = new User("u1");
        let u1 = new User("u1");
        console.log(u1[Symbol()]); // 获取不到 实现属性的保护作用
        console.log(u1.host); // this[date].host
        console.log(u1.name); // undefined 不可访问

总结类的几点优势

  1. 类中声明的方法,会自动放到原型对象中,;简化了原型的操作
  2. 类的原型对象中的属性不可变量,属性特征默认为false;
  3. 类默认在严格模式下运行,使得编写得代码更加健壮

类的综合案例:滑动栏

有点类似tab栏切换,就是一列选项,点击当前选项显示其内容,其余的隐藏,进行切换效果。
效果图:
在这里插入图片描述
整个区域:事件的处理;
存在面板:面板做的动画;
存在动画:显示、隐藏的效果;

        // 动画类
        class Animation {
            // 传递一个参数:做动画的部分
            constructor(ele) {
                this.ele = ele;
                this.defaultHeight = this.height;
                // 默认为显示
                this.isShow = true;
                // 滑动的速度
                this.speed = 1;
                // 定时器的间隔时间
                this.cutTime = 5;
            }
            // 隐藏方法
            hide(callback) {
                this.isShow = false;

                let timer = setInterval(() => {
                    if (this.height <= 0) {
                        clearInterval(timer);
                        callback && callback();
                        return;
                    }
                    this.height = this.height - this.speed;
                }, this.cutTime);
            }
            // 显示方法
            show(callback) {
                let timer = setInterval(() => {
                    if (this.height >= this.defaultHeight) {
                        clearInterval(timer); // 到达效果只会移除定时器
                        callback && callback();
                        return;
                    }
                    this.height = this.height + this.speed;
                }, this.cutTime);
            }
            get height() {
                // 去除px单位 去掉后两位 变为数值类型
                return parseInt(window.getComputedStyle(this.ele).height.slice(0, -2));
            }
            set height(height) {
                this.ele.style.height = height + "px";
            }
        }
        //测试动画效果
        // let box = document.querySelector(".box");
        // let an = new Animation(box);
        // console.log(an);
        // an.hide(() => {
        //     console.log("隐藏完了");
        //     // an.show(() => {
        //     //     console.log("显示完了");

        //     // })
        // });

        // 区域类
        class Slider {
            constructor(ele) {
                this.ele = document.querySelector(ele);
                this.links = this.ele.querySelectorAll("dt");
                // 使用类创建对象
                this.panles = [...this.ele.querySelectorAll("dd")].map(item => {
                    // 继承了动画类 , 需要一个元素
                    return new Panle(item);
                });
                this.bind(); // 添加事件
            }
            bind() {
                this.links.forEach((element, index) => {
                    element.addEventListener("click", item => {
                        this.handler(index);
                    })
                });
            }
            // 事件中的处理方法
            handler(index) {
                // 隐藏当前之外的元素
                // console.log(Panle.filter(this.panles, index));
                // 解决多动画抖动
                Panle.hideAll(Panle.filter(this.panles, index), () => {
                    // 显示自己
                    this.panles[index].show();
                });
            }
        }

        // 这个面板区域, 做动画操作 
        class Panle extends Animation {
            // 检测动画
            static flag = 0;
            // 批量操作对象
            static hideAll(panle, callback) {
                // 解决多动画
                if (Panle.flag > 0) return;
                panle.forEach(item => {
                    Panle.flag++;
                    item.hide(() => {
                        Panle.flag--;
                    });
                })
                callback && callback();
            }
            static filter(panle, index) {
                // 过滤掉点击的对象
                return panle.filter((item, i) => i != index);
            }
        }
        let s = new Slider(".slider");
        console.log(s);

适合敲三遍以上,主要是整理逻辑。类这个东西我都看了快两天了。
加油加油!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值