JS基礎整理(2)—定義類和繼承的幾種方法

1. 創建類

1.1. 簡單的類

類指一組對象從同一個原型對象繼承屬性,原型對象是類的核心特征。
定義一個原型對象,然后用Object.create()創建一個繼承它的對象,我們就定義了一個JavaScript類。

//工廠函數,用于創建Range對象
function range(from, to) {
  //使用Object.create()創建一個對象,繼承原型對象
  let r = Object.create(range.methods);
  r.from = from;
  r.to = to;
  return r;
}
// 定義一個原型對象
range.methods = {
  includes(x){
    //通過this引用調用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+")";}
}

let r = range(1,10); // 創建一個對象
console.log(r.includes(2));
console.log(r.includes(11));
console.log(r.toString());
console.log([...r]);

1.2. 使用構造函數的類

上面的方法定義了JavaScript類,但是它沒有定義構造函數。構造函數是專門用于初始化新對象的函數,使用new關鍵字調用構造函數會自動創建新對象。構造函數調用的關鍵在于構造函數的prototype屬性被用作新對象的原型。

只有函數對象才有prototype屬性,同一個構造函數創建的所有對象都繼承同一個對象。

在不支持的ES6 class關鍵字的JavaScript版本中,用以下的方法創建類。

// 構造函數
function Range(from, to){
  this.from = from;
  this.to = to;
}
// 所有Range對象都繼承這個對象,prototype這個名字是強制的
Range.prototype={
  //不要使用箭頭函數,因為箭頭函數沒有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關鍵字調用構造函數
let r = new Range(1,10);
console.log(r.includes(2));
console.log(r.includes(11));
console.log(r.toString());
console.log([...r]);

對比以上兩個例子,有以下區別:

  • 工廠函數命名為range(),構造函數命名為Range();
  • 創建對象的時候,工廠函數使用raneg(),構造函數使用new Range()

函數體內有一個特殊表達式new.target用于判斷函數是否作為構造函數,如果new.target不是undefined,說明函數作為構造函數,會自動創建新對象

function F(){
  if(!new.target) return new F();
}

上面提到,構造函數調用的關鍵在于構造函數的prototype屬性被用作新對象的原型。所以,每個普通JavaScript函數自動擁有一個prototype屬性,這個屬性有一個不可枚舉的constructor屬性。
constructor屬性的值就是該函數對象本身

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 = {}重寫了預定義的Range.prototype對象,所以Range的實例都沒有constructor屬性。

let o = new F();
console.log(o.constructor === F); // true
console.log(r.constructor === Range); // ?

上面r.constructor === Range返回的是false。
常用的方法是使用預定義的原型對象及其constructor屬性,然后通過以下方式添加方法:

Range.prototype.includes = function(x){}

1.3. 使用ES6的class

ES6引入class關鍵字,可以使用新語法創建類

class Range{
  //實際上定義的函數不叫constructor
  //class會定義一個新變量Range,并將這個特殊構造函數的值賦給改變量
  constructor(from, to){
    this.from = from;
    this.to = to;
  }
  //methods
  //方法之間沒有逗號
  //不支持key:value形式
  includes(x){
    //通過this引用調用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.`);}
}
  1. 原型鏈繼承
function Cat(){}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

let cat = new Cat();
cat.sleep();
  1. 構造繼承
function Cat2(name){
  //使用父類的構造函數來增強子類實例, 復制父類的實例屬性給子類
  Animal.call(this);
  this.name = name;
}
//實例并不是父類的實例,只是子類的實例
//只能繼承父類的屬性和方法,不能繼承原型鏈的
//無法實現函數復用,每個子類都有父類實例函數的副本,影響性能
let c2 = new Cat2("cat2");
c2.sleep();
  1. 實例繼承
function Cat3(name){
  //為父類實例添加新特性,作為子類實例返回
  let instance = new Animal();
  instance.name = name||'Tom';
  return instance;
}
//實例是父類的實例,不是子類的實例
let c3 = new Cat3();
c3.sleep();
  1. 拷貝繼承
function Cat4(name){
  let a = new Animal();
  //拷貝父類的屬性和方法
  //效率較低,內存占用高
  //無法獲取父類不可枚舉的方法(不能使用for in訪問)
  for(let p in a){
    Cat4.prototype[p] = a[p];
  }
  this.name = name;
}

let c4 = new Cat4('cat4');
c4.sleep();
  1. 組合繼承
function Cat5(name){
  Animal.call(this);
  this.name = name ||'Tom';
}
//通過調用父類構造,可以繼承實例屬性/方法,也可以繼承原型屬性/方法
//將父類實例作為子類原型,實現函數復用
//調用了兩次父類構造函數,生成了兩份實例
Cat5.prototype = new Animal();
Cat5.prototype.constructor = Cat;
//既是子類的實例,也是父類的實例
let c5 = new Cat5('cat5');
c5.sleep();
  1. 寄生組合繼承
//調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點
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();
  1. 基于ES6的class的繼承
class Span extends Range{
  constructor(start, length){
    if(length>0){
      super(start, start+length);
    }else{
      super(start+length, start);
    }
  }
}

最近在看高頻面試題,經常看到繼承的問題,之后再來補充一下~~~~
還有類涉及對象和原型鏈的問題,可能也會總結一下

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容