搜尋
首頁web前端js教程js中的類別向傳統類別模式轉變的建構子詳解(附範例)

js中的類別向傳統類別模式轉變的建構子詳解(附範例)

Sep 21, 2018 am 10:41 AM
classes6javascript前端建構函數

這篇文章帶給大家的內容是關於js中的類別向傳統類別模式轉變的建構子詳解(附範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

前言

JS基於原型的'類',一直被轉行前端的碼僚們大呼驚奇,但接近傳統模式使用class關鍵字定義的出現,卻使得一些前端同行深感遺憾而紛紛留言:“還我獨特的JS”、“淨搞些沒實質的東西”、“自己沒有類還非要往別家的類上靠”,甚至是“已轉行”等等。有情緒很正常,畢竟新知識意味著更多時間與精力的開銷,又不是簡單的閉眼享受。

然而歷史的軸印前行依舊,對於class可以肯定的一點是你不能對面試官說:「拜託,不是小弟不懂,僅僅是不願意了解,您換個問題唄!」一雖然方面class只是語法糖,但extends對繼承的改進還是不錯的。另一方面今後可能在‘類’上出現的新特性應該是由class而不是構造函數承載,誰也不確定它將來會出落得怎樣標緻。因此,來來來,慢慢的喝下這碗熱氣騰騰的紅糖薑湯

1 class

ECMAScript中沒有類的概念,我們的實例是基於原型由構造函數生成具有動態屬性和方法的物件。不過為了與國際接軌,描述的更為簡單和高大上,還是會使用‘類’這個詞。所以JS的類別等同於建構子。 ES6的class只是個語法糖,定義產生的物件依然建構子。不過為了與建構函數模式區分開,我們稱之為類別模式。學習class需要有建構函式和原型物件的知識,具體可以自行百度。

// --- 使用构造函数
function C () {
  console.log('New someone.');
}

C.a = function () { return 'a'; }; // 静态方法

C.prototype.b = function () { return 'b'; }; // 原型方法


// --- 使用class
class C {
  static a() { return 'a'; } // 静态方法
  
  constructor() { console.log('New someone.'); } // 构造方法
  
  b() { return 'b'; } // 原型方法
};

1.1 與變數比較

關鍵字class類似定義函數的關鍵字function,其定義的方式有宣告式和表達式(匿名式和命名式)兩種。透過宣告式定義的變數的性質與function不同,更為類似let和const,不會事先解析,不存在變數提升,不與全域作用域掛鉤和擁有暫時性死區等。 class定義產生的變數就是建構函數,也因此,類別可以寫成立即執行的模式。

// --- 声明式
class C {}
function F() {}

// --- 匿名表达式
let C = class {};
let F = function () {};

// --- 命名表达式
let C = class CC {};
let F = function FF() {};

// --- 本质是个函数
class C {}
console.log(typeof C); // 'function'
console.log(Object.prototype.toString.call(C)); // '[object Function]'
console.log(C.hasOwnProperty('prototype')); // true

// --- 不存在变量提升
C; // 报错,不存在C。
class C {}
// 存在提前解析和变量提升
F; // 不报错,F已被声明和赋值。
function F() {}

// --- 自执行模式
let c = new (class {
})();
let f = new (function () {
})();

1.2 與物件比較

類別內容({}裡面)的形式與物件字面量相似。不過類別內容裡面只能定義方法不能定義屬性,方法的形式只能是函數簡寫式,方法間不用也不能用逗號分隔。方法名可以是括號的表達式,也可以是Symbol值。方法分為三類,建構方法(constructor方法)、原型方法(存在於建構子的prototype屬性上)和靜態方法(存在於建構子本身上)

class C {
  // 原型方法a
  a() { console.log('a'); }
  // 构造方法,每次生成实例时都会被调用并返回新实例。
  constructor() {}
  // 静态方法b,带static关键字。
  static b() { console.log('b'); }
  // 原型方法,带括号的表达式
  ['a' + 'b']() { console.log('ab'); }
  // 原型方法,使用Symbol值
  [Symbol.for('s')]() { console.log('symbol s'); }
}

C.b(); // b

let c = new C();
c.a(); // a
c.ab(); // ab
c[Symbol.for('s')](); // symbol s

不能直接定義屬性,並不表示類別不能有原型或靜態屬性。解析class會形成一個建構函數,因此只需像為建構函數添加屬性一樣為類別添加即可。更直接也是推薦的是只使用getter函數定義唯讀屬性。為什麼不能直接設定屬性?是技術不成熟?是官方希望傳遞某種想法?抑或僅僅是筆者隨意拋出的問題?

// --- 直接在C类(构造函数)上修改
class C {}
C.a = 'a';
C.b = function () { return 'b'; };
C.prototype.c = 'c';
C.prototype.d = function () { return 'd'; };

let c = new C();
c.c; // c
c.d(); // d

// --- 使用setter和getter
// 定义只能获取不能修改的原型或静态属性
class C {
  get a() { return 'a'; }
  static get b() { return 'b'; }
}

let c = new C();
c.a; // a
c.a = '1'; // 赋值没用,只有get没有set无法修改。

1.3 與建構子比較

以下是使用建構子和類別實作相同功能的程式碼。直覺上,class簡化了程式碼,讓內容更聚合。 constructor方法體等同建構函式的函式體,如果沒有明確定義此方法,一個空的constructor方法會被預設為傳回新的實例。與ES5一樣,也可以自訂傳回另一個物件而不是新實例。

// --- 构造函数
function C(a) {
  this.a = a;
}

// 静态属性和方法
C.b = 'b';
C.c = function () { return 'c'; };

// 原型属性和方法
C.prototype.d = 'd';
C.prototype.e = function () { return 'e'; };
Object.defineProperty(C.prototype, 'f', { // 只读属性
  get() {
    return 'f';
  }
});

// --- 类
class C {
  static c() { return 'c'; }
  
  constructor(a) {
    this.a = a;
  }
  
  e() { return 'e'; }
  get f() { return 'f'; }
}

C.b = 'b';
C.prototype.d = 'd';

類別雖然是個函數,但只能透過new產生實例而不能直接呼叫。類別內部所定義的全部方法是不可枚舉的,在建構函式本身和prototype上新增的屬性和方法是可枚舉的。類別內部定義的方法預設是嚴格模式,無需明確聲明。以上三點增加了類別的嚴謹性,比較遺憾的是,依然還沒有直接定義私有屬性和方法的方式。

// --- 能否直接调用
class C {}
C(); // 报错

function C() {}
C(); // 可以


// --- 是否可枚举
class C {
  static a() {} // 不可枚举
  b() {} // 不可枚举
}

C.c = function () {}; // 可枚举
C.prototype.d = function () {}; // 可枚举

isEnumerable(C, ['a', 'c']); // a false, c true
isEnumerable(C.prototype, ['b', 'd']); // b false, d true

function isEnumerable(target, keys) {
  let obj = Object.getOwnPropertyDescriptors(target);
  keys.forEach(k => {
    console.log(k, obj[k].enumerable);
  });
}


// --- 是否为严格模式
class C {
  a() {
    let is = false;
    try {
      n = 1;
    } catch (e) {
      is = true;
    }
    console.log(is ? 'true' : 'false');
  }
}

C.prototype.b = function () {
  let is = false;
  try {
    n = 1;
  } catch (e) {
    is = true;
  }
  console.log(is ? 'true' : 'false');
};

let c = new C();
c.a(); // true,是严格模式。
c.b(); // false,不是严格模式。

在方法前面加上static關鍵字表示此方法為靜態方法,它存在於類別本身,不能被實例直接存取。靜態方法中的this指向類別本身。因為處於不同物件上,靜態方法和原型方法可以重新命名。 ES6新增了一個指令new.target,指涉new後面的建構子或class,指令的使用有某些限制,請看下面範例。

// --- static
class C {
  static a() { console.log(this === C); }
  a() { console.log(this instanceof C); }
}

let c = new C();
C.a(); // true
c.a(); // true


// --- new.target
// 构造函数
function C() {
  console.log(new.target);
}

C.prototype.a = function () { console.log(new.target); };

let c = new C(); // 打印出C
c.a(); // 在普通方法中为undefined。

// --- 类
class C {
  constructor() { console.log(new.target); }
  a() { console.log(new.target); }
}

let c = new C(); // 打印出C
c.a(); // 在普通方法中为undefined。

// --- 在函数外部使用会报错
new.target; // 报错

2 extends

ES5中的經典繼承方法是寄生組合式繼承,子類別會分別繼承父類別實例和原型上的屬性和方法。 ES6中的繼承本質也是如此,不過實作方式有所改變,具體如下面的程式碼。可以看到,原型上的繼承是使用extends關鍵字這個更接近傳統語言的形式,實例上的繼承是透過呼叫super完成子類別this塑造。表面上看,方式更為的統一簡潔。

class C1 {
  constructor(a) { this.a = a; }
  b() { console.log('b'); }
}

class C extends C1 { // 继承原型数据
  constructor() {
    super('a'); // 继承实例数据
  }
}

2.1 与构造函数对比

使用extends继承,不仅仅会将子类的prototype属性的原型对象(__proto__)设置为父类的prototype,还会将子类本身的原型对象(__proto__)设置为父类本身。这意味着子类不单单会继承父类的原型数据,也会继承父类本身拥有的静态属性和方法。而ES5的经典继承只会继承父类的原型数据。不单单是财富,连老爸的名气也要获得,不错不错。

class C1 {
  static get a() { console.log('a'); }
  static b() { console.log('b'); }
}

class C extends C1 {
}
// 等价,没有构造方法会默认添加。
class C extends C1 {
  constructor(...args) {
    super(...args);
  }
}

let c = new C();
C.a; // a,继承了父类的静态属性。
C.b(); // b,继承了父类的静态方法。
console.log(Object.getPrototypeOf(C) === C1); // true,C的原型对象为C1
console.log(Object.getPrototypeOf(C.prototype) === C1.prototype); // true,C的prototype属性的原型对象为C1的prototype

ES5中的实例继承,是先创造子类的实例对象this,再通过call或apply方法,在this上添加父类的实例属性和方法。当然也可以选择不继承父类的实例数据。而ES6不同,它的设计使得实例继承更为优秀和严谨。

在ES6的实例继承中,是先调用super方法创建父类的this(依旧指向子类)和添加父类的实例数据,再通过子类的构造函数修饰this,与ES5正好相反。ES6规定在子类的constructor方法里,在使用到this之前,必须先调用super方法得到子类的this。不调用super方法,意味着子类得不到this对象。

class C1 {
  constructor() {
    console.log('C1', this instanceof C);
  }
}

class C extends C1 {
  constructor() {
    super(); // 在super()之前不能使用this,否则报错。
    console.log('C');
  }
}

new C(); // 先打印出C1 true,再打印C。

2.2 super

关键字super比较奇葩,在不同的环境和使用方式下,它会指代不同的东西(总的说可以指代对象或方法两种)。而且在不显式的指明是作为对象或方法使用时,比如console.log(super),会直接报错。

作为函数时。super只能存在于子类的构造方法中,这时它指代父类构造函数。

作为对象时。super在静态方法中指代父类本身,在构造方法和原型方法中指代父类的prototype属性。不过通过super调用父类方法时,方法的this依旧指向子类。即是说,通过super调用父类的静态方法时,该方法的this指向子类本身;调用父类的原型方法时,该方法的this指向该(子类的)实例。而且通过super对某属性赋值时,在子类的原型方法里指代该实例,在子类的静态方法里指代子类本身,毕竟直接在子类中通过super修改父类是很危险的。

很迷糊对吧,疯疯癫癫的,还是结合着代码看吧!

class C1 {
  static a() {
    console.log(this === C);
  }
  b() {
    console.log(this instanceof C);
  }
}

class C extends C1 {
  static c() {
    console.log(super.a); // 此时super指向C1,打印出function a。
    
    this.x = 2; // this等于C。
    super.x = 3; // 此时super等于this,即C。
    console.log(super.x); // 此时super指向C1,打印出undefined。
    console.log(this.x); // 值已改为3。

    super.a(); // 打印出true,a方法的this指向C。
  }

  constructor() {
    super(); // 指代父类的构造函数
    
    console.log(super.c); // 此时super指向C1.prototype,打印出function c。

    this.x = 2; // this等于新实例。
    super.x = 3; // 此时super等于this,即实例本身。
    console.log(super.x); // 此时super指向C1.prototype,打印出undefined。
    console.log(this.x); // 值已改为3。

    super.b(); // 打印出true,b方法的this指向实例本身。
  }
}

2.3 继承原生构造函数

使用构造函数模式,构建继承了原生数据结构(比如Array)的子类,有许多缺陷的。一方面由上文可知,原始继承是先创建子类this,再通过父类构造函数进行修饰,因此无法获取到父类的内部属性(隐藏属性)。另一方面,原生构造函数会直接忽略call或apply方法传入的this,导致子类根本无法获取到父类的实例属性和方法。

function MyArray(...args) {
  Array.apply(this, args);
}

MyArray.prototype = Array.prototype;
// MyArray.prototype.constructor = MyArray;

let arr = new MyArray(1, 2, 3); // arr为对象,没有储存值。
arr.push(4, 5); // 在arr上新增了0,1和length属性。
arr.map(d => d); // 返回数组[4, 5]
arr.length = 1; // arr并没有更新,依旧有0,1属性,且arr[1]为5。

创建类的过程,是先构造一个属于父类却指向子类的this(绕口),再通过父类和子类的构造函数进行修饰。因此可以规避构造函数的问题,获取到父类的实例属性和方法,包括内部属性。进而真正的创建原生数据结构的子类,从而简单的扩展原生数据类型。另外还可以通过设置Symbol.species属性,使得衍生对象为原生类而不是自定义子类的实例。

class MyArray extends Array { // 实现是如此的简单
  static get [Symbol.species]() { return Array; }
}

let arr = new MyArray(1, 2, 3); // arr为数组,储存有1,2,3。
arr.map(d => d); // 返回数组[1, 2, 3]
arr.length = 1; // arr正常更新,已包含必要的内部属性。

需要注意的是继承Object的子类。ES6改变了Object构造函数的行为,一旦发现其不是通过new Object()这种形式调用的,构造函数会忽略传入的参数。由此导致Object子类无法正常初始化,但这不是个大问题。

class MyObject extends Object {
  static get [Symbol.species]() { return Object; }
}

let o = new MyObject({ id: 1 });
console.log(o.hasOwnPropoty('id')); // false,没有被正确初始化

以上是js中的類別向傳統類別模式轉變的建構子詳解(附範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript框架:為現代網絡開發提供動力JavaScript框架:為現代網絡開發提供動力May 02, 2025 am 12:04 AM

JavaScript框架的強大之處在於簡化開發、提升用戶體驗和應用性能。選擇框架時應考慮:1.項目規模和復雜度,2.團隊經驗,3.生態系統和社區支持。

JavaScript,C和瀏覽器之間的關係JavaScript,C和瀏覽器之間的關係May 01, 2025 am 12:06 AM

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

node.js流帶打字稿node.js流帶打字稿Apr 30, 2025 am 08:22 AM

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE

Python vs. JavaScript:性能和效率注意事項Python vs. JavaScript:性能和效率注意事項Apr 30, 2025 am 12:08 AM

Python和JavaScript在性能和效率方面的差異主要體現在:1)Python作為解釋型語言,運行速度較慢,但開發效率高,適合快速原型開發;2)JavaScript在瀏覽器中受限於單線程,但在Node.js中可利用多線程和異步I/O提升性能,兩者在實際項目中各有優勢。

JavaScript的起源:探索其實施語言JavaScript的起源:探索其實施語言Apr 29, 2025 am 12:51 AM

JavaScript起源於1995年,由布蘭登·艾克創造,實現語言為C語言。 1.C語言為JavaScript提供了高性能和系統級編程能力。 2.JavaScript的內存管理和性能優化依賴於C語言。 3.C語言的跨平台特性幫助JavaScript在不同操作系統上高效運行。

幕後:什麼語言能力JavaScript?幕後:什麼語言能力JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript在瀏覽器和Node.js環境中運行,依賴JavaScript引擎解析和執行代碼。 1)解析階段生成抽象語法樹(AST);2)編譯階段將AST轉換為字節碼或機器碼;3)執行階段執行編譯後的代碼。

Python和JavaScript的未來:趨勢和預測Python和JavaScript的未來:趨勢和預測Apr 27, 2025 am 12:21 AM

Python和JavaScript的未來趨勢包括:1.Python將鞏固在科學計算和AI領域的地位,2.JavaScript將推動Web技術發展,3.跨平台開發將成為熱門,4.性能優化將是重點。兩者都將繼續在各自領域擴展應用場景,並在性能上有更多突破。

Python vs. JavaScript:開發環境和工具Python vs. JavaScript:開發環境和工具Apr 26, 2025 am 12:09 AM

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。