JavaScript的语法糖 —— 类的实现

    技术2022-07-11  73

    类的语法

    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 不可访问

    总结类的几点优势

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

    类的综合案例:滑动栏

    有点类似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);

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

    Processed: 0.012, SQL: 9