1. 創(chuàng)建類
1.1. 簡單的類
類指一組對象從同一個原型對象繼承屬性肢础,原型對象
是類的核心特征。
定義一個原型對象,然后用Object.create()創(chuàng)建一個繼承它的對象,我們就定義了一個JavaScript類快耿。
//工廠函數(shù),用于創(chuàng)建Range對象
function range(from, to) {
//使用Object.create()創(chuàng)建一個對象芳绩,繼承原型對象
let r = Object.create(range.methods);
r.from = from;
r.to = to;
return r;
}
// 定義一個原型對象
range.methods = {
includes(x){
//通過this引用調(diào)用from和to的對象
return this.from<=x && x<=this.to;
},
// 生成器函數(shù):讓這個類的實例可迭代
*[Symbol.iterator](){
for(let x = Math.ceil(this.from); x<=this.to; x++){
yield x;
}
},
toString(){return "("+this.from +"..."+this.to+")";}
}
let r = range(1,10); // 創(chuàng)建一個對象
console.log(r.includes(2));
console.log(r.includes(11));
console.log(r.toString());
console.log([...r]);
1.2. 使用構(gòu)造函數(shù)的類
上面的方法定義了JavaScript類掀亥,但是它沒有定義構(gòu)造函數(shù)。構(gòu)造函數(shù)
是專門用于初始化新對象的函數(shù)妥色,使用new
關(guān)鍵字調(diào)用構(gòu)造函數(shù)會自動創(chuàng)建新對象搪花。構(gòu)造函數(shù)調(diào)用的關(guān)鍵在于構(gòu)造函數(shù)的prototype
屬性被用作新對象的原型。
只有函數(shù)對象才有prototype屬性嘹害,同一個構(gòu)造函數(shù)創(chuàng)建的所有對象都繼承同一個對象撮竿。
在不支持的ES6 class關(guān)鍵字的JavaScript版本中,用以下的方法創(chuàng)建類笔呀。
// 構(gòu)造函數(shù)
function Range(from, to){
this.from = from;
this.to = to;
}
// 所有Range對象都繼承這個對象幢踏,prototype這個名字是強制的
Range.prototype={
//不要使用箭頭函數(shù),因為箭頭函數(shù)沒有prototype屬性凿可,this是從定義它的上下文繼承的
includes:function(x) {
return this.from<=x && x<=this.to;
},
[Symbol.iterator]:function*() {
for(let x = Math.ceil(this.from); x<=this.to; x++){
yield x;
}
},
toString:function(){return "("+this.from +"..."+this.to+")";}
}
//以new關(guān)鍵字調(diào)用構(gòu)造函數(shù)
let r = new Range(1,10);
console.log(r.includes(2));
console.log(r.includes(11));
console.log(r.toString());
console.log([...r]);
對比以上兩個例子惑折,有以下區(qū)別:
- 工廠函數(shù)命名為range(),構(gòu)造函數(shù)命名為Range()枯跑;
- 創(chuàng)建對象的時候惨驶,工廠函數(shù)使用raneg(),構(gòu)造函數(shù)使用new Range()
函數(shù)體內(nèi)有一個特殊表達式new.target
用于判斷函數(shù)是否作為構(gòu)造函數(shù)敛助,如果new.target不是undefined粗卜,說明函數(shù)作為構(gòu)造函數(shù),會自動創(chuàng)建新對象
function F(){
if(!new.target) return new F();
}
上面提到纳击,構(gòu)造函數(shù)調(diào)用的關(guān)鍵在于構(gòu)造函數(shù)的prototype
屬性被用作新對象的原型续扔。所以,每個普通JavaScript函數(shù)自動擁有一個prototype
屬性焕数,這個屬性有一個不可枚舉的constructor
屬性纱昧。
constructor
屬性的值就是該函數(shù)對象本身
let F = function (x) {this.x = x}
let p = F.prototype;
let c = p.constructor;
console.log("c === F: ", c === F); // true, F.prototype.constructor === F
注意,上面的Range的例子中堡赔,由于用自己定義的對象Range.prototype = {}重寫了預(yù)定義的Range.prototype對象识脆,所以Range的實例都沒有constructor屬性。
let o = new F();
console.log(o.constructor === F); // true
console.log(r.constructor === Range); // ?
上面r.constructor === Range返回的是false善已。
常用的方法是使用預(yù)定義的原型對象及其constructor屬性灼捂,然后通過以下方式添加方法:
Range.prototype.includes = function(x){}
1.3. 使用ES6的class
ES6引入class
關(guān)鍵字,可以使用新語法創(chuàng)建類
class Range{
//實際上定義的函數(shù)不叫constructor
//class會定義一個新變量Range换团,并將這個特殊構(gòu)造函數(shù)的值賦給改變量
constructor(from, to){
this.from = from;
this.to = to;
}
//methods
//方法之間沒有逗號
//不支持key:value形式
includes(x){
//通過this引用調(diào)用from和to的對象
return this.from<=x && x<=this.to;
}
*[Symbol.iterator](){
for(let x = Math.ceil(this.from); x<=this.to; x++){
yield x;
}
}
toString(){return "("+this.from +"..."+this.to+")";}
}
2. 繼承的幾種方法
// 父類
function Animal(name){
this.name = name || "cat";
this.sleep = function(){console.log(`${this.name} is sleeping.`);}
}
- 原型鏈繼承
function Cat(){}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
let cat = new Cat();
cat.sleep();
- 構(gòu)造繼承
function Cat2(name){
//使用父類的構(gòu)造函數(shù)來增強子類實例, 復(fù)制父類的實例屬性給子類
Animal.call(this);
this.name = name;
}
//實例并不是父類的實例悉稠,只是子類的實例
//只能繼承父類的屬性和方法,不能繼承原型鏈的
//無法實現(xiàn)函數(shù)復(fù)用艘包,每個子類都有父類實例函數(shù)的副本的猛,影響性能
let c2 = new Cat2("cat2");
c2.sleep();
- 實例繼承
function Cat3(name){
//為父類實例添加新特性,作為子類實例返回
let instance = new Animal();
instance.name = name||'Tom';
return instance;
}
//實例是父類的實例想虎,不是子類的實例
let c3 = new Cat3();
c3.sleep();
- 拷貝繼承
function Cat4(name){
let a = new Animal();
//拷貝父類的屬性和方法
//效率較低衰絮,內(nèi)存占用高
//無法獲取父類不可枚舉的方法(不能使用for in訪問)
for(let p in a){
Cat4.prototype[p] = a[p];
}
this.name = name;
}
let c4 = new Cat4('cat4');
c4.sleep();
- 組合繼承
function Cat5(name){
Animal.call(this);
this.name = name ||'Tom';
}
//通過調(diào)用父類構(gòu)造,可以繼承實例屬性/方法磷醋,也可以繼承原型屬性/方法
//將父類實例作為子類原型猫牡,實現(xiàn)函數(shù)復(fù)用
//調(diào)用了兩次父類構(gòu)造函數(shù),生成了兩份實例
Cat5.prototype = new Animal();
Cat5.prototype.constructor = Cat;
//既是子類的實例邓线,也是父類的實例
let c5 = new Cat5('cat5');
c5.sleep();
- 寄生組合繼承
//調(diào)用兩次父類的構(gòu)造的時候淌友,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點
function Cat6(name){
Animal.call(this);
this.name = name || "Tom";
}
(function(){
//通過寄生方式骇陈,砍掉父類的實例屬性
let Super = function(){};
Super.prototype = Animal.prototype;
//將實例作為子類的原型
Cat6.prototype = new Super();
})();
Cat6.prototype.constructor = Cat6;
let c6 = new Cat6('cat6');
c6.sleep();
- 基于ES6的class的繼承
class Span extends Range{
constructor(start, length){
if(length>0){
super(start, start+length);
}else{
super(start+length, start);
}
}
}
最近在看高頻面試題震庭,經(jīng)常看到繼承的問題你雌,之后再來補充一下~~~~
還有類涉及對象和原型鏈的問題器联,可能也會總結(jié)一下