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 不可访问
总结类的几点优势
类中声明的方法,会自动放到原型对象中,;简化了原型的操作
类的原型对象中的属性不可变量,属性特征默认为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);