前言
在ES6之前予权,準(zhǔn)確來(lái)說(shuō)JavaScript語(yǔ)言只有對(duì)象站刑,沒(méi)有類(lèi)的概念。生成實(shí)例對(duì)象的傳統(tǒng)方法是通過(guò)構(gòu)造函數(shù)敲街,與傳統(tǒng)的面向?qū)ο笳Z(yǔ)言(比如 C++ 和 Java)差異很大团搞,ES6 提供了更接近傳統(tǒng)語(yǔ)言的寫(xiě)法,引入了 class(類(lèi))這個(gè)概念多艇,作為對(duì)象的模板逻恐。通過(guò)class關(guān)鍵字,可以定義類(lèi)峻黍。但需要清楚的是ES6中class只是構(gòu)造函數(shù)的一種語(yǔ)法糖复隆,并非新鮮玩意,class能實(shí)現(xiàn)的奸披,我們通過(guò)ES5構(gòu)造函數(shù)同樣可以實(shí)現(xiàn)昏名。
一、構(gòu)造函數(shù)與class寫(xiě)法的部分區(qū)別
【1.1】構(gòu)造函數(shù)
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function () {
console.log("您好");
};
let person = new Person("dingFY", 20);
console.log(person.name) // dingFY
console.log(person.age) // 20
person.sayHi() // 您好
【1.2】類(lèi)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
};
sayHi() {
console.log("您好");
};
};
let person = new Person('dingFY', 20);
console.log(person.name) // dingFY
console.log(person.age) // 20
person.sayHi() // 您好
【1.3】 主要區(qū)別
- 構(gòu)造函數(shù)名Person在class寫(xiě)法時(shí)變成了類(lèi)名阵面,但調(diào)用方式不變轻局,依然通過(guò)new關(guān)鍵字創(chuàng)建實(shí)例
- 構(gòu)造函數(shù)中this相關(guān)操作,在class寫(xiě)法時(shí)歸納到了constructor方法中样刷,也就是說(shuō)ES5的構(gòu)造函數(shù)Person對(duì)應(yīng)ES6的Person類(lèi)中的constructor構(gòu)造方法
- ES5中原型上的方法sayHi在ES6 class寫(xiě)法中直接寫(xiě)在了內(nèi)部仑扑,同時(shí)省略了function關(guān)鍵字
// 對(duì)于方法添加,下面這兩種寫(xiě)法是等效的
// ES5
function Parent() {};
Parent.prototype = {
constructor: function () {},
sayName: function () {},
sayAge: function () {}
};
// ES6
class Parent {
constructor() {}
sayName() {}
sayAge() {}
};
// constructor()置鼻、sayName()镇饮、sayAge()這三個(gè)方法,其實(shí)都是定義在Parent.prototype上面
// 因此箕母,在類(lèi)的實(shí)例上面調(diào)用方法储藐,其實(shí)就是調(diào)用原型上的方法俱济。
二、類(lèi)的基礎(chǔ)用法
【2.1】類(lèi)定義
// 匿名類(lèi)
let Example = class {
constructor(a) {
this.a = a;
}
}
// 命名類(lèi)
let Example = class Example {
constructor(a) {
this.a = a;
}
}
【2.2】類(lèi)聲明
// 類(lèi)聲明
class Example {
constructor(a) {
this.a = a;
}
}
// 不可重復(fù)聲明
// class Example {}
// class Example {}
// 報(bào)錯(cuò):Uncaught SyntaxError: Identifier 'Example' has already been declared
// let Example = class {}
// class Example {}
// 報(bào)錯(cuò):Uncaught SyntaxError: Identifier 'Example' has already been declared
【2.3】注意
- class不存在變量提升钙勃,所以需要先定義再使用蛛碌。因?yàn)镋S6不會(huì)把類(lèi)的聲明提升到代碼頭部,但是ES5就不一樣辖源,ES5存在變量提升蔚携,可以先使用,然后再定義克饶。
//ES5可以先使用再定義,存在變量提升
new A();
function A(){}
//ES6不能先使用再定義,不存在變量提升 會(huì)報(bào)錯(cuò)
new B(); // Uncaught ReferenceError: Cannot access 'B' before initialization
class B{}
- 在類(lèi)中聲明方法的時(shí)候酝蜒,千萬(wàn)不要給該方法加上function關(guān)鍵字,方法之間也不要用逗號(hào)分隔矾湃,否則會(huì)報(bào)錯(cuò)
class Example {
constructor(a) {
this.a = a;
}
getName() {} // 不需要逗號(hào)
function getAge() // 報(bào)錯(cuò)
}
- 類(lèi)的內(nèi)部所有定義的方法亡脑,都是不可枚舉的,這與ES5構(gòu)造函數(shù)行為不一致
class Example {
constructor(x, y) {}
getName() {}
}
console.log(Object.keys(Example.prototype)) // []
console.log(Object.getOwnPropertyNames(Example.prototype)) // ["constructor","getName"]
- constructor()方法是類(lèi)的默認(rèn)方法洲尊,通過(guò)new命令生成對(duì)象實(shí)例時(shí)远豺,自動(dòng)調(diào)用該方法。一個(gè)類(lèi)必須有constructor()方法坞嘀,如果沒(méi)有顯式定義躯护,一個(gè)空的constructor()方法會(huì)被默認(rèn)添加。
class Example {}
// 等同于
class Example {
constructor() {}
}
- 通過(guò)class類(lèi)創(chuàng)建實(shí)例必須使用new關(guān)鍵字丽涩,不使用會(huì)報(bào)錯(cuò)棺滞,這點(diǎn)與構(gòu)造函數(shù)不同,在ES5中其實(shí)我們不使用new也能調(diào)用構(gòu)造函數(shù)創(chuàng)建實(shí)例矢渊,雖然這樣做不符合規(guī)范继准。
// ES6
class Person {
constructor() {}
};
let person = Person(); // 報(bào)錯(cuò)
// ES5
let Person = function (name) {
this.name = name;
};
let person = Person(); // 不符合規(guī)范
- 與 ES5 一樣,類(lèi)的所有實(shí)例共享一個(gè)原型對(duì)象
class Person {
constructor() {}
};
let p1 = new Person()
let p2 = new Person()
console.log(p1.__proto__ === p2.__proto__) // true
- ES6 的類(lèi)只是 ES5 的構(gòu)造函數(shù)的一層包裝矮男,所以函數(shù)的許多特性都被Class繼承移必,包括name屬性。name屬性總是返回緊跟在class關(guān)鍵字后面的類(lèi)名毡鉴。
class Person{}
Person.name // "Point"
- 類(lèi)和模塊的內(nèi)部崔泵,默認(rèn)就是嚴(yán)格模式,所以不需要使用use strict指定運(yùn)行模式
三猪瞬、取值函數(shù)(getter)和存值函數(shù)(setter)
與 ES5 一樣憎瘸,在“類(lèi)”的內(nèi)部可以使用get
和set
關(guān)鍵字,對(duì)某個(gè)屬性設(shè)置存值函數(shù)和取值函數(shù)陈瘦,攔截該屬性的存取行為
class Person {
constructor() {
// ...
}
get name() {
return 'getter';
}
set name(value) {
console.log('setter: ' + value);
}
}
let son = new Person();
son.name = 'dingFY' // setter: dingFY
console.log(son.name) // getter
四幌甘、靜態(tài)屬性和靜態(tài)方法
【4.1】靜態(tài)屬性
靜態(tài)屬性指的是 Class 本身的屬性,寫(xiě)法是在實(shí)例屬性的前面,加上static關(guān)鍵字
class Person {
static age = 18;
constructor() {
console.log(Person.age);
}
}
let person = new Person() // 18
【4.2】靜態(tài)方法
類(lèi)相當(dāng)于實(shí)例的原型锅风,所有在類(lèi)中定義的方法酥诽,都會(huì)被實(shí)例繼承。如果在一個(gè)方法前皱埠,加上static關(guān)鍵字盆均,就表示該方法不會(huì)被實(shí)例繼承,而是只能在類(lèi)內(nèi)部使用或者直接通過(guò)類(lèi)來(lái)調(diào)用漱逸,這就稱為“靜態(tài)方法”。如果靜態(tài)方法包含this
關(guān)鍵字游沿,這個(gè)this
指的是類(lèi)饰抒,而不是實(shí)例。父類(lèi)的靜態(tài)方法诀黍,可以被子類(lèi)繼承袋坑。
class Foo {
static classMethod() {
console.log('test')
}
}
// 類(lèi)調(diào)用靜態(tài)方法
Foo.classMethod() // 'test'
// 實(shí)例調(diào)用靜態(tài)方法
let foo = new Foo();
foo.classMethod() // TypeError: foo.classMethod is not a function
class Foo {
static classMethod() {
console.log('test')
}
}
// 父類(lèi)的靜態(tài)方法可以被子類(lèi)繼承
class Bar extends Foo {}
Bar.classMethod() // 'test'
五、Class 的繼承
- class 通過(guò) extends 關(guān)鍵字實(shí)現(xiàn)類(lèi)的繼承眯勾,子類(lèi)可以繼承父類(lèi)的所有屬性和方法枣宫。
class Person {}
class Son extends Person {}
- 子類(lèi) constructor 方法中必須有 super ,且必須出現(xiàn)在 this 之前吃环,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)
class Person {
constructor(age) {
this.age = age
}
sayHi() {
return '您好'
}
}
class Son extends Person {
constructor(age, name) {
super(age) // 子類(lèi)繼承必須要有super(), 且必須在this前面
this.name = name
}
sayHi() {
console.log(super.sayHi() + `也颤,我是${this.name}`)
}
}
let son = new Son(18, 'dingFY');
console.log(son.age) // 18
console.log(son.name) // dingFY
son.sayHi() // 您好,我是dingFY
六郁轻、實(shí)例
【6.1】封裝彈框組件
// dialog組件封裝
class Dialog {
constructor(options) {
// 合并默認(rèn)配置和用戶傳的配置參數(shù)翅娶;
this.opts = Object.assign({
width: "30%",
height: "200px",
title: "標(biāo)題",
content: "內(nèi)容",
dragable: false, //是否可拖拽
maskable: true, //是否有遮罩
isCancel: true, //是否有取消按鈕
success() {
console.log('點(diǎn)擊確定按鈕處理邏輯'); //
},
cancel() {
console.log('點(diǎn)擊取消按鈕處理邏輯');
}
}, options);
this.init(); // 自動(dòng)調(diào)用
}
init() {
// 渲染視圖
this.renderView();
// 捕獲彈框點(diǎn)擊事件
this.dialogHtml.onclick = e => {
switch (e.target.className) {
case 'k-close': // 關(guān)閉按鈕
this.close();
this.opts.cancel();
break;
case 'k-cancel': // 取消按鈕
this.close();
this.opts.cancel();
break;
case 'k-primary': // 確定按鈕
this.close();
this.confim();
break;
}
}
}
// 確定按鈕操作
confim(value) {
this.opts.success(value);
}
//關(guān)閉對(duì)話框;
close() {
this.dialogHtml.querySelector(".k-wrapper").style.display = "none";
this.dialogHtml.querySelector(".k-dialog").style.display = "none";
}
// 顯示對(duì)話框好唯;
open() {
// 是否顯示遮罩層
if (this.opts.maskable) {
this.dialogHtml.querySelector(".k-wrapper").style.display = "block";
}
// 是否可拖拽
if (this.opts.dragable) {
let dialog = this.dialogHtml.querySelector(".k-dialog")
let drag = new Drag(dialog);
}
this.dialogHtml.querySelector(".k-dialog").style.display = "block";
}
// 渲染視圖
renderView() {
this.dialogHtml = document.createElement("div");
this.dialogHtml.innerHTML = `<div class="k-wrapper"></div>
<div class="k-dialog" style="width:${this.opts.width};height:${this.opts.height}">
<div class="k-header">
<span class="k-title">${this.opts.title}</span><span class="k-close">X</span>
</div>
<div class="k-body">
<span>${this.opts.content}</span>
</div>
<div class="k-footer">
${this.opts.isCancel ? '<span class="k-cancel">取消</span>' : ''}
<span class="k-primary">確定</span>
</div>
</div>`;
document.querySelector("body").appendChild(this.dialogHtml);
}
}
【6.2】封裝拖拽方法
// 拖拽方法封裝
class Drag {
constructor(ele) {
this.ele = ele
this.downFn();
}
// 按下鼠標(biāo)
downFn() {
this.ele.onmousedown = e => {
let ev = e || window.event;
let x = ev.clientX - this.ele.offsetLeft;
let y = ev.clientY - this.ele.offsetTop;
this.moveFn(x, y);
this.upFn();
}
}
// 移動(dòng)鼠標(biāo)
moveFn(x, y) {
this.ele.onmousemove = e => {
let ev = e || window.event;
let xx = ev.clientX;
let yy = ev.clientY;
this.setStyle(xx - x, yy - y);
}
}
// 設(shè)置拖拽后元素的定位
setStyle(leftNum, topNum) {
leftNum = leftNum < 0 ? 0 : leftNum;
topNum = topNum < 0 ? 0 : topNum;
this.ele.style.left = leftNum + "px";
this.ele.style.top = topNum + "px";
}
// 鼠標(biāo)抬起
upFn() {
this.ele.onmouseup = () => {
this.ele.onmousemove = "";
}
}
}
【6.3】通過(guò)繼承封裝擴(kuò)展彈框組件
// 擴(kuò)展Dialog, 可輸入內(nèi)容
class ExtendsDialog extends Dialog {
constructor(options) {
super(options);
this.renderInput();
}
renderInput() {
let myInput = document.createElement("input");
myInput.type = "text";
myInput.classList.add("input-inner");
this.dialogHtml.querySelector(".k-body").appendChild(myInput);
}
confim() {
let value = this.dialogHtml.querySelector(".input-inner").value;
super.confim(value);
}
}
【6.4】完整代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.k-dialog {
width: 30%;
z-index: 2001;
display: block;
position: absolute;
background: #fff;
border-radius: 2px;
box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
margin: 0 auto;
top: 15vh;
left: 30%;
display: none;
}
.k-wrapper {
position: fixed;
left: 0px;
top: 0px;
bottom: 0px;
right: 0px;
background: black;
opacity: 0.4;
z-index: 2000;
display: none;
}
.k-header {
padding: 10px 20px 30px;
}
.k-header .k-title {
line-height: 24px;
font-size: 18px;
color: #303133;
float: left;
}
.k-body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.k-footer {
padding: 10px 20px 30px;
position: absolute;
bottom: 0;
right: 0;
}
.k-close {
color: #909399;
font-weight: 400;
float: right;
cursor: pointer;
}
.k-cancel {
color: #606266;
border: 1px solid #dcdfe6;
text-align: center;
cursor: pointer;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
font-weight: 500;
margin-right: 10px;
}
.k-cancel:hover {
color: #409eff;
background: #ecf5ff;
border-color: #c6e2ff;
}
.k-primary {
border: 1px solid #dcdfe6;
text-align: center;
cursor: pointer;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
font-weight: 500;
background: #409eff;
color: #fff;
margin-left: 10px;
}
.k-primary:hover {
background: #66b1ff;
}
.k-input {
width: 100%;
margin-left: 20px;
margin-bottom: 20px;
}
.input-inner {
-webkit-appearance: none;
background-color: #fff;
background-image: none;
border-radius: 4px;
border: 1px solid #dcdfe6;
box-sizing: border-box;
color: #606266;
display: inline-block;
font-size: inherit;
height: 40px;
line-height: 40px;
outline: none;
padding: 0 15px;
transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
width: 100%;
margin-top: 20px;
}
</style>
</head>
<body>
<button class="dialog1">dialog1</button>
<button class="dialog2">dialog2</button>
</body>
<script>
// 拖拽方法封裝
class Drag {
constructor(ele) {
this.ele = ele
this.downFn();
}
// 按下鼠標(biāo)
downFn() {
this.ele.onmousedown = e => {
let ev = e || window.event;
let x = ev.clientX - this.ele.offsetLeft;
let y = ev.clientY - this.ele.offsetTop;
this.moveFn(x, y);
this.upFn();
}
}
// 移動(dòng)鼠標(biāo)
moveFn(x, y) {
this.ele.onmousemove = e => {
let ev = e || window.event;
let xx = ev.clientX;
let yy = ev.clientY;
this.setStyle(xx - x, yy - y);
}
}
// 設(shè)置拖拽后元素的定位
setStyle(leftNum, topNum) {
leftNum = leftNum < 0 ? 0 : leftNum;
topNum = topNum < 0 ? 0 : topNum;
this.ele.style.left = leftNum + "px";
this.ele.style.top = topNum + "px";
}
// 鼠標(biāo)抬起
upFn() {
this.ele.onmouseup = () => {
this.ele.onmousemove = "";
}
}
}
// dialog組件封裝
class Dialog {
constructor(options) {
// 合并默認(rèn)配置和用戶傳的配置參數(shù)竭沫;
this.opts = Object.assign({
width: "30%",
height: "200px",
title: "標(biāo)題",
content: "內(nèi)容",
dragable: false, //是否可拖拽
maskable: true, //是否有遮罩
isCancel: true, //是否有取消按鈕
success() {
console.log('點(diǎn)擊確定按鈕處理邏輯'); //
},
cancel() {
console.log('點(diǎn)擊取消按鈕處理邏輯');
}
}, options);
this.init(); // 自動(dòng)調(diào)用
}
init() {
// 渲染視圖
this.renderView();
// 捕獲彈框點(diǎn)擊事件
this.dialogHtml.onclick = e => {
switch (e.target.className) {
case 'k-close': // 關(guān)閉按鈕
this.close();
this.opts.cancel();
break;
case 'k-cancel': // 取消按鈕
this.close();
this.opts.cancel();
break;
case 'k-primary': // 確定按鈕
this.close();
this.confim();
break;
}
}
}
// 確定按鈕操作
confim(value) {
this.opts.success(value);
}
//關(guān)閉對(duì)話框;
close() {
this.dialogHtml.querySelector(".k-wrapper").style.display = "none";
this.dialogHtml.querySelector(".k-dialog").style.display = "none";
}
// 顯示對(duì)話框骑篙;
open() {
// 是否顯示遮罩層
if (this.opts.maskable) {
this.dialogHtml.querySelector(".k-wrapper").style.display = "block";
}
// 是否可拖拽
if (this.opts.dragable) {
let dialog = this.dialogHtml.querySelector(".k-dialog")
let drag = new Drag(dialog);
}
this.dialogHtml.querySelector(".k-dialog").style.display = "block";
}
// 渲染視圖
renderView() {
this.dialogHtml = document.createElement("div");
this.dialogHtml.innerHTML = `<div class="k-wrapper"></div>
<div class="k-dialog" style="width:${this.opts.width};height:${this.opts.height}">
<div class="k-header">
<span class="k-title">${this.opts.title}</span><span class="k-close">X</span>
</div>
<div class="k-body">
<span>${this.opts.content}</span>
</div>
<div class="k-footer">
${this.opts.isCancel ? '<span class="k-cancel">取消</span>' : ''}
<span class="k-primary">確定</span>
</div>
</div>`;
document.querySelector("body").appendChild(this.dialogHtml);
}
}
// 當(dāng)用戶點(diǎn)擊dialog1按鈕創(chuàng)建彈框
document.querySelector(".dialog1").onclick = function () {
let dialog1 = new Dialog({
width: '30%',
height: "200px",
title: "彈框標(biāo)題",
content: "彈框內(nèi)容",
dragable: true,
isCancel: true,
maskable: true,
success() {
console.log('點(diǎn)擊確定按鈕處理邏輯');
},
cancel() {
console.log('點(diǎn)擊確定按鈕處理邏輯');
}
})
dialog1.open();
}
// 擴(kuò)展Dialog, 可輸入內(nèi)容
class ExtendsDialog extends Dialog {
constructor(options) {
super(options);
this.renderInput();
}
renderInput() {
let myInput = document.createElement("input");
myInput.type = "text";
myInput.classList.add("input-inner");
this.dialogHtml.querySelector(".k-body").appendChild(myInput);
}
confim() {
let value = this.dialogHtml.querySelector(".input-inner").value;
super.confim(value);
}
}
// 當(dāng)用戶點(diǎn)擊dialog2按鈕創(chuàng)建彈框
document.querySelector(".dialog2").onclick = function () {
let dialog2 = new ExtendsDialog({
width: "30%",
height: "250px",
title: "擴(kuò)展彈框",
content: "擴(kuò)展彈框內(nèi)容",
isCancel: true,
maskable: true,
success(val) {
console.log(val);
},
cancel() {
console.log('點(diǎn)擊取消');
}
})
dialog2.open();
}
</script>
</html>
【6.5】查看效果
文章每周持續(xù)更新蜕提,可以微信搜索「 前端大集錦 」第一時(shí)間閱讀,回復(fù)【視頻】【書(shū)籍】領(lǐng)取200G視頻資料和30本PDF書(shū)籍資料