一.Class 介紹+基本語法
(1).介紹
經過class關鍵字,能夠定義類。基本上,ES6 的class能夠看做只是一個語法糖,它的絕大部分功能,ES5 均可以作到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。html
(2).Class 的基本語法vue
//definedClasses.js
//定義類
class Person{ // 構造
constructor(x,y){ this.x = x; this.y = y; } //定義在類中的方法不須要添加function
toString(){ return (this.x + "的年齡是" +this.y+"歲"); } } export { Person }; // test.vue
<template>
<div>
<P id="testJs"></p>
</div>
</template>
<script> import {Person} from '@/assets/js/definedClasses'; export default { data() { return { } }, mounted(){ let text=document.getElementById("testJs"); //使用new的方式獲得一個實例對象
let person = new Person('張三',12); text.innerHTML=person.toString();//張三的年齡是12歲
console.log(typeof Person);//function
} } </script>
上面代碼定義了一個「類」,能夠看到裏面有一個constructor方法,這就是構造方法,而this關鍵字則表明實例對象。也就是說,ES5的構造函數Person,對應ES6的Person類的構造方法。從console.log(typeof Person);輸出function ,看出類的數據類型就是函數,類自己就指向構造函數。web
使用方式跟構造函數同樣,對類使用new命令,跟構造函數的用法徹底一致編程
let person = new Person('張三',12); 數組
person.toString();數據結構
構造函數的prototype屬性,在ES6的「類」上面繼續存在。事實上,類的全部方法都定義在類的prototype屬性上面,經過如下方式但是覆蓋類中的方法,固然定義類的時候也能夠經過這個方式添加方法。app
class Person { constructor() {// ... } toString() {// ...} toValue() {// ...} } // 等同於 Person.prototype = { constructor() {}, toString() {}, toValue() {}, }; 在類的實例上面調用方法,其實就是調用原型上的方法 console.log(person.constructor === Person.prototype.constructor);//true
Object.assign方法能夠給對象Person動態的增長方法,而Person.prototype = {}是覆蓋對象的方法,或者在初始化的時候添加方法。
//test.vue Object.assign(Person.prototype,{ getWidth(){ console.log('12'); }, getHeight(){ console.log('24'); } }); console.log(Person.prototype);//就能夠輸出Person內全部方法 包括getWidth() getHeight()
補充:ide
Object.keys(obj),返回一個數組,數組裏是該obj可被枚舉的全部屬性。Object.getOwnPropertyNames(obj),返回一個數組,數組裏是該obj上全部的實例屬性。函數
//ES5 console.log(Object.keys(Person.prototype));//["toString", "getWidth", "getHeight"] console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "toString", "getWidth", "getHeight"] //ES6 console.log(Object.keys(Person.prototype));//["getWidth", "getHeight"] console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "toString", "getWidth", "getHeight"]
(3).注意
①ES6類的constructor函數至關於ES5的構造函數
②經過class關鍵字,能夠定義類
③類中定義方法時,前面不用加function,後面不得加 , ,方法所有都是定義在類的prototype屬性中。
④類必須使用new調用,不然會報錯
let person = new Person('張三',12);
不能Person();
⑤ 類不存在變量提高,Foo類定義在前 使用在後,或者會報錯
⑥name屬性老是返回緊跟在class關鍵字後面的類名
class Point {}
Point.name // "Point"
⑦類的內部定義的全部方法都是不可枚舉的
⑧類和模塊內部默認採起嚴格模式
另外,下面這種寫法是無效的。class內部只容許定義方法,不容許定義屬性(包括靜態屬性)
class Foo{bar:2}測試
二.分析類class
1.constructor方法
是類的默認方法,經過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。
class Point { } // 等同於 class Point { constructor() {} }
constructor方法默認返回實例對象(即this),徹底能夠指定返回另一個對象.可是也能夠指定constructor方法返回一個全新的對象,讓返回的實例對象不是該類的實例。
2.類的實例對象
上面說了,不適用new實例會報錯。
實例的屬性除非顯式定義在其自己(即定義在this對象上),不然都是定義在原型上(即定義在class上)。hasOwnProperty()函數用於指示一個對象自身(不包括原型鏈)是否具備指定名稱的屬性。若是有,返回true,不然返回false。
class Person{ // 構造 constructor(x,y){ this.x = x; this.y = y; } toString(){ return (this.x + "的年齡是" +this.y+"歲"); } } let person = new Person('lis',8); console.log(person.toString()); console.log(person.hasOwnProperty('x'));//true console.log(person.hasOwnProperty('y'));//true console.log(person.hasOwnProperty('toString'));//false console.log(person.__proto__.hasOwnProperty('toString'));//true
上面結果說明對象上有x,y屬性,可是沒有toString屬性。也就是說x,y是定義在this對象上,toString定義在類上。
let person1 = new Person('張三',12); let person2 = new Person('李四',13); console.log(person1.__proto__ === person2.__proto__);//true
類的全部實例共享一個原型對象,person1和person2都是Person的實例,它們的原型都是Person.prototype,因此__proto__屬性是相等的。這也意味着,能夠經過實例的__proto__屬性爲Class添加方法。可是不建議使用,由於這會改變Class的原始定義,影響到全部實例。
3.class表達式
類也可使用表達式的形式定義。
const MyClass = class Me { getClassName() { return Me.name; } }; let inst = new MyClass(); inst.getClassName() // Me Me.name // ReferenceError: Me is not define
上面代碼使用表達式定義了一個類。須要注意的是,這個類的名字是MyClass而不是Me,Me只在 Class 的內部代碼可用,指代當前類。
若是類的內部沒用到的話,能夠省略Me,也就是能夠寫成下面的形式。
const MyClass = class { /* ... */ };
三.私有方法和私有屬性
(1).私有方法
1.一種作法是在命名上加以區別
// 私有方法
class Widget {
_bar(baz) {return this.snaf = baz;}
}
2.一種方法就是索性將私有方法移出模塊,由於模塊內部的全部方法都是對外可見的。
//PrivateMethod.js export default class PrivateMethod{ // 構造 constructor() { } getName(){ priName(); } } function priName(){ console.log('私有方法測試'); } //test.vue import PriMe from './PrivateMethod'; let prime = new PriMe(); prime.getName();
3.利用Symbol值的惟一性,將私有方法的名字命名爲一個Symbol值
const bar = Symbol('bar'); const snaf = Symbol('snaf'); export default class myClass{ // 公有方法 foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return this[snaf] = baz; } // ... };
(2).私有屬性
爲class加了私有屬性。方法是在屬性名以前,使用#表示。#x就表示私有屬性x,它也能夠用來寫私有方法
class Foo { #a=1;//私有屬性 #b;//私有屬性 #sum() { //私有方法 return #a + #b; } printSum() { console.log(#sum()); } constructor(a, b) { #a = a; #b = b; } }
私有屬性也能夠設置 getter 和 setter 方法
class Counter { #xValue = 0; get #x() { return #xValue; } set #x(value) { this.#xValue = value; } constructor() { super(); // ... } }
上面代碼中,#x是一個私有屬性,它的讀寫都經過get #x()和set #x()來完成
四.this指向
類的方法內部若是含有this,它默認指向類的實例。getName方法中的this,默認指向ThisStu類的實例。可是,若是將這個方法提取出來單獨使用,this會指向該方法運行時所在的環境,由於找不到name方法而致使報錯。
//ThisStu.js class ThisStu{ getName(){ return this.name(); } name(){ return '王五'; } } export {ThisStu}; //test.vue import {ThisStu} from './ThisStu'; let thisStu = new ThisStu(); console.log(thisStu.getName()); const {getName} = thisStu; getName(); //Cannot read property 'name' of undefined
一個比較簡單的解決方法是,在構造方法中綁定this,這樣就不會找不到name方法了。修改ThisStu.js文件以下:
//ThisStu.js class ThisStu{ // 構造 constructor() { this.getName = this.getName.bind(this); } getName(){ return this.name(); } name(){ return '王五'; } } export {ThisStu};
使用構造函數,直接給當前實例的getName賦值,修改修改ThisStu.js文件的構造函數以下:
// 構造 constructor() { //this.getName = this.getName.bind(this); this.getName = ()=>{ return this.name(); } }
五.方法
1.Class 的取值函數(getter)和存值函數(setter)
取值函數(getter)和存值函數(setter)能夠自定義賦值和取值行爲。當一個屬性只有getter沒有setter的時候,咱們是沒法進行賦值操做的,第一次初始化也不行。若是把變量定義爲私有的(定義在類的外面),就能夠只使用getter不使用setter。
//GetSet.js let data = {}; class GetSet{ // 構造 constructor() { this.width = 10; this.height = 20; } get width(){ console.log('獲取寬度'); return this._width; } set width(width){ console.log('設置寬度'); this._width = width; } //當一個屬性只有getter沒有setter的時候,咱們是沒法進行賦值操做的,第一次初始化也不行。 //bundle.js:8631 Uncaught TypeError: Cannot set property width of #<GetSet> which has only a getter get data(){ return data; } //若是把變量定義爲私有的,就能夠只使用getter不使用setter。 } let getSet = new GetSet(); console.log("=====GetSet====="); console.log(getSet.width); getSet.width = 100; console.log(getSet.width); console.log(getSet._width); console.log(getSet.data); //存值函數和取值函數是設置在屬性的descriptor對象上的。 var descriptor = Object.getOwnPropertyDescriptor( GetSet.prototype, "width"); console.log("get" in descriptor);//true console.log("set" in descriptor);//true
若是把上面的get和set改爲如下形式,不加下劃線報Uncaught RangeError: Maximum call stack size exceeded錯誤。
這是由於,在構造函數中執行this.name=name的時候,就會去調用set name,在set name方法中,咱們又執行this.name = name,進行無限遞歸,最後致使棧溢出(RangeError)。
get width(){ console.log('獲取寬度'); return this.width; } set width(width){ console.log('設置寬度'); this.width = width; }
以上width的getter和setter只是給width自定義存取值行爲,開發者仍是能夠經過_width繞過getter和setter獲取width的值。
補充:
Class的繼承(extends)和自定義存值(setter)取值(getter)的整個案例:
//ExtendStuParent.js const ExtendStuParent = class ExtendStuParent{ name;//定義name age;//定義age // 構造 constructor(name,age) { //用於寫不能實例化的父類 if (new.target === ExtendStuParent) { throw new Error('ExtendStuParent不能實例化,只能繼承使用。'); } this.name = name; this.age = age; } getName(){ return this.name; } getAge(){ return this.age; } }; ExtendStuParent.prototype.width = '30cm'; export default ExtendStuParent; //ExtendStu.js import ExtendStuParent from './ExtendStuParent'; export default class ExtendStu extends ExtendStuParent{ // 構造 constructor(name, age, height) { super(name,age); this.height = height; //父類和子類都有實例屬性name,可是在toString方法調用getName的時候, //返回的是子類的的數據 this.name = 'LiSi 子類'; // this.color = 'black'; super.color = 'white'; console.log(super.color);//undefined console.log(this.color);//black } toString(){ console.log(super.age);//undefined console.log(super.width);//30cm return "name:"+ super.getName() + " age:"+this.age + " height:"+this.height; } }
2.Class 的 Generator 方法
若是某個方法以前加上星號(*),就表示該方法是一個 Generator 函數
class Foo { constructor(...args) { this.args = args; } * [Symbol.iterator]() { for (let arg of this.args) { yield arg; } } } for (let x of new Foo('hello', 'world')) { console.log(x); } // hello // world
上面代碼中,Foo類的Symbol.iterator方法前有一個星號,表示該方法是一個 Generator 函數。Symbol.iterator方法返回一個Foo類的默認遍歷器,for...of循環會自動調用這個遍歷器。
3.new.target屬性
new是從構造函數生成實例的命令。ES6爲new命令引入了一個new.target屬性,(在構造函數中)返回new命令做用於的那個構造函數。若是構造函數不是經過new命令調用的,new.target會返回undefined,所以這個屬性能夠用來肯定構造函數是怎麼調用的。
class Targettu{
// 構造
constructor() {
if(new.target !== undefined){
this.height = 10;
}else{
throw new Error('必須經過new命令建立對象');
}
}
}
//或者(判斷是否是經過new命令建立的對象下面這種方式也是能夠的)
class Targettu{
// 構造
constructor() {
if(new.target === Targettu){
this.height = 10;
}else{
throw new Error('必須經過new命令建立對象');
}
}
}
let targettu = new Targettu();
//Uncaught Error: 必須經過new命令建立對象
//let targettuOne = Targettu.call(targettu);
須要注意的是,子類繼承父類時,new.target會返回子類。經過這個原理,能夠寫出不能獨立使用、必須繼承後才能使用的類。
// 構造
constructor(name,age) {
//用於寫不能實例化的父類
if (new.target === ExtendStuParent) {
throw new Error('ExtendStuParent不能實例化,只能繼承使用。');
}
this.name = name;
this.age = age;
}
六.class的靜態方法
類至關於實例的原型,全部在類中定義的方法,都會被實例繼承。若是在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲「靜態方法」。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()// TypeError: foo.classMethod is not a function
父類的靜態方法,能夠被子類繼承
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
注意:
1.若是靜態方法包含this關鍵字,這個this指的是類,而不是實例
2.靜態方法能夠與非靜態方法重名。
class Foo {
static bar () {
this.baz();
}
static baz () {
console.log('hello');
}
baz () {
console.log('world');
}
}
Foo.bar() // hello
靜態方法bar調用了this.baz,這裏的this指的是Foo類,而不是Foo的實例,等同於調用Foo.baz
3.靜態方法只能在靜態方法中調用,不能再實例方法中調用
七.Class靜態屬性和實例屬性
靜態屬性指的是 Class 自己的屬性,即Class.propName,而不是定義在實例對象(this)上的屬性。Class 內部只有靜態方法,沒有靜態屬性。
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
八.class的繼承
(1).基本用法+super關鍵字
Class 之間能夠經過extends關鍵字實現繼承, 這比 ES5 的經過修改原型鏈實現繼承, 要清晰和方便不少。
class Point {
}
class ColorPoint extends Point {
}
上面代碼定義了一個ColorPoint類, 該類經過extends關鍵字, 繼承了Point類的全部屬性和方法。 可是因爲沒有部署任何代碼, 因此這兩個類徹底同樣, 等於複製了一個Point類。 下面, 咱們在ColorPoint內部加上代碼。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 調用父類的 constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 調用父類的 toString()
}
}
上面代碼中, constructor方法和toString方法之中, 都出現了super關鍵字, 它在這裏表示父類的構造函數, 用來新建父類的this對象。
子類必須在constructor方法中調用super方法, 不然新建實例時會報錯。 這是由於子類沒有本身的this對象, 而是繼承父類的this對象, 而後對其進行加工。 若是不調用super方法, 子類就得不到this對象。
//ExtendStu.js
//正確 構造
constructor(name, age, height) {
super(name,age);
this.height = height;
}
//錯誤 構造
constructor(name, age, height) {
this.height = height;
super(name,age);
//SyntaxError: 'this' is not allowed before super()
}
上面代碼中,子類的constructor方法沒有調用super以前,就使用this關鍵字,結果報錯,而放在super方法以後就是正確的。
ES5的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。ES6的繼承機制徹底不一樣,實質是先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改this。若是子類沒有定義constructor方法,這個方法會被默認添加。
1.super做爲函數時,指向父類的構造函數。super()只能用在子類的構造函數之中,用在其餘地方就會報錯。
class ExtendStuParent {
constructor() {
console.log(new.target.name);
}
}
class ExtendStu extends ExtendStuParent {
constructor() {
super();
}
}
new ExtendStuParent() // ExtendStuParent
new ExtendStu() // ExtendStu
注意:new.target指向當前正在執行的函數。能夠看到, super雖然表明了父類ExtendStuParent的構造函數,可是返回的是子類ExtendStu的實例,即super內部的this指的是ExtendStu,所以super()在這裏至關於ExtendStuParent.prototype.constructor.call(this)。
2. super做爲對象時,指向父類的原型對象。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代碼中,子類B當中的super.p(),就是將super看成一個對象使用。這時,super在普通方法之中,指向A.prototype,因此super.p()就至關於A.prototype.p()。
這裏須要注意,因爲super指向父類的原型對象,因此定義在父類實例上的方法或屬性,是沒法經過super調用的。
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
上面代碼中,p是父類A實例的屬性,super.p就引用不到它。
屬性定義在父類的原型對象上,super就能夠取到。
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
上面代碼中,屬性x是定義在A.prototype上面的,因此super.x能夠取到它的值。
ES6 規定,在子類普通方法中經過super調用父類的方法時,方法內部的this指向當前的子類實例。
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print();
}
}
let b = new B();
b.m() // 2
上面代碼中,super.print()雖然調用的是A.prototype.print(),可是A.prototype.print()內部的this指向子類B的實例,致使輸出的是2,而不是1。也就是說,實際上執行的是super.print.call(this)。
因爲this指向子類實例,因此若是經過super對某個屬性賦值,這時super就是this,賦值的屬性會變成子類實例的屬性。
最後,因爲對象老是繼承其餘對象的,因此能夠在任意一個對象中,使用super關鍵字。
var obj = {
toString() {
return "MyObject: " + super.toString();
}
};
obj.toString(); // MyObject: [object Object]
(2).類的prototype屬性和__proto__屬性
Class 做爲構造函數的語法糖, 同時有prototype 屬性和__proto__屬性, 所以同時存在兩條繼承鏈。
( 1) 子類的__proto__屬性, 表示構造函數的繼承, 老是指向父類。
( 2) 子類prototype屬性的__proto__屬性, 表示方法的繼承, 老是指向父類的prototype屬性。
class A {}
class B extends A {}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
子類B的__proto__屬性指向父類A, 子類B的prototype屬性的__proto__屬性指向父類A的prototype屬性。
這樣的結果是由於, 類的繼承是按照下面的模式實現的。
class A {}
class B {}
// B 的實例繼承 A 的實例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 繼承 A 的靜態屬性
Object.setPrototypeOf(B, A);
這兩條繼承鏈,能夠這樣理解:做爲一個對象,子類(B)的原型(__proto__屬性)是父類(A);做爲一個構造函數,子類(B)的原型對象(prototype屬性)是父類的原型對象(prototype屬性)的實例。
Object.create(A.prototype);
// 等同於
B.prototype.__proto__ = A.prototype;
(3).Extends 的繼承目標
extends關鍵字後面能夠跟多種類型的值。
class B extends A {}
討論三種特殊狀況:
1.子類繼承 Object 類
class A extends Object {}
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
這種狀況下, A其實就是構造函數Object的複製, A的實例就是Object的實例。
2.不存在任何繼承
class A {}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
這種狀況下, A 做爲一個基類( 即不存在任何繼承), 就是一個普通函數, 因此直接繼承Funciton.prototype。 可是, A調用後返回一個空對象( 即Object實例), 因此A.prototype.__proto__指向構造函數( Object) 的prototype屬性。
3.子類繼承null
class A extends null {}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true
這種狀況與第二種狀況很是像。 A也是一個普通函數, 因此直接繼承Funciton.prototype。 可是, A 調用後返回的對象不繼承任何方法, 因此它的__proto__指向Function.prototype, 即實質上執行了下面的代碼。
class C extends null {
constructor() {
return Object.create(null);
}
}
(4).Object.getPrototypeOf()
Object.getPrototypeOf方法能夠用來從子類上獲取父類。
Object.getPrototypeOf(ColorPoint) === Point // true
所以, 可使用這個方法判斷, 一個類是否繼承了另外一個類。
(5).實例的 __proto__ 屬性
子類實例的 __proto__ 屬性的 __proto__ 屬性, 指向父類實例的 __proto__ 屬性。 也就是說, 子類的原型的原型, 是父類的原型。
class A{}
class B extends A{}
let a = new A();
let b = new B();
console.log(b.__proto__ === a.__proto__);//false
console.log(b.__proto__.__proto__ === a.__proto__);//true
所以,經過子類實例的__proto__.__proto__屬性,能夠修改父類實例的行爲。
b.__proto__.__proto__.getName = ()=>{
console.log('給父類添加一個函數');
};
b.getName();//給父類添加一個函數
a.getName();//給父類添加一個函數
使用子類的實例給父類添加getName方法,因爲B繼承了A,因此B和A中都添加了getName方法
(6).原生構造函數的繼承
原生構造函數是指語言內置的構造函數, 一般用來生成數據結構。 ECMAScript 的原生構造函數大體有下面這些。
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
之前, 這些原生構造函數是沒法繼承的, 好比, 不能本身定義一個Array的子類。
function MyArray() {
Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
});
上面代碼定義了一個繼承Array的MyArray類。可是,這個類的行爲與Array徹底不一致。
var colors = new MyArray();
colors[0] = "red";
colors.length // 0
colors.length = 0;
colors[0] // "red"
之因此會發生這種狀況,是由於子類沒法得到原生構造函數的內部屬性,經過Array.apply()或者分配給原型對象都不行。原生構造函數會忽略apply方法傳入的this,也就是說,原生構造函數的this沒法綁定,致使拿不到內部屬性。ES5是先新建子類的實例對象this,再將父類的屬性添加到子類上,因爲父類的內部屬性沒法獲取,致使沒法繼承原生的構造函數。好比,Array構造函數有一個內部屬性[[DefineOwnProperty]],用來定義新屬性時,更新length屬性,這個內部屬性沒法在子類獲取,致使子類的length屬性行爲不正常。
ES6容許繼承原生構造函數定義子類,由於ES6是先新建父類的實例對象this,而後再用子類的構造函數修飾this,使得父類的全部行爲均可以繼承。下面是一個繼承Array的例子。
//ExtendsArray.js
class ExtendsArray extends Array{}
let extendsArray = new ExtendsArray();
console.log("=====ExtendsArray=====");
extendsArray[0] = '數據1';
console.log(extendsArray.length);
上面代碼定義了一個ExtendsArray類,繼承了Array構造函數,所以就能夠從ExtendsArray生成數組的實例。這意味着,ES6能夠自定義原生數據結構(好比Array、String等)的子類,這是ES5沒法作到的。上面這個例子也說明,extends關鍵字不只能夠用來繼承類,還能夠用來繼承原生的構造函數。所以能夠在原生數據結構的基礎上,定義本身的數據結構。
(7).Mixin模式的實現
Mixin模式指的是,將多個類的接口「混入」(mix in)另外一個類。它在ES6的實現以下。
/MixinStu.js
export default function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== "constructor"
&& key !== "prototype"
&& key !== "name"
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
上面代碼的mix函數,能夠將多個對象合成爲一個類。使用的時候,只要繼承這個類便可。
class MixinAll extends MixinStu(B,Serializable){
// 構造
constructor(x,y,z) {
super(x,y);
this.z = z;
}
}
九.引用
1.隊列類 應用 var q=new Queue([1,2,3,4]); q.shift();刪除
class Queue{
constructor(content=[]){
this._queue=[...content];
}
//刪除
shift(){
const value=this._queue[0];
this._queue.shift();
return value;
}
//增長
push(n){
this._queue.push(n);
return this._queue.length;
}
}
var q=new Queue([1,2,3,4]);
q.shift();
q.push(5);
console.log(q._queue);
2.簡單的選項卡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>選項卡</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<style>
.on{ background: #f60; color: #fff}
.box div{
width:200px;
height:200px;
border: 1px solid #000;
display: none;
}
</style>
<script>
//選項卡
class Tab{
constructor(id){
this.oBox=document.getElementById(id);
this.aBtn=this.oBox.getElementsByTagName('input');
this.aDiv=this.oBox.getElementsByTagName('div');
this.init();
}
init(){
for(let i=0; i<this.aBtn.length; i++){
this.aBtn[i].onclick=function(){
this.iNow=i;
this.hide();
this.show(i);
}.bind(this);//從新綁定this的指向
}
}
hide(){
for(let i=0; i<this.aBtn.length; i++){
this.aBtn[i].className='';
this.aDiv[i].style.display='none';
}
}
show(index){
this.aBtn[index].className='on';
this.aDiv[index].style.display='block';
}
}
//繼承
class AutoTab extends Tab{//第二要用,則繼承前一個就好
}
window.onload=function(){
new Tab('box');
new AutoTab('box2');
};
</script>
</head>
<body>
<div id="box" class="box">
<input class="on" type="button" value="aaa">
<input type="button" value="bbb">
<input type="button" value="ccc">
<div style="display: block;">1111</div>
<div>2222</div>
<div>3333</div>
</div>
<div id="box2" class="box">
<input class="on" type="button" value="aaa">
<input type="button" value="bbb">
<input type="button" value="ccc">
<div style="display: block;">1111</div>
<div>2222</div>
<div>3333</div>
</div>
</body>
</html>