怎么解決JavaScript相關(guān)的問題

這篇文章主要講解了“怎么解決JavaScript相關(guān)的問題”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“怎么解決JavaScript相關(guān)的問題”吧!

我們提供的服務(wù)有:成都網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì)、外貿(mào)營銷網(wǎng)站建設(shè)、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、孟津ssl等。為成百上千企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的孟津網(wǎng)站制作公司

一、如何在 window 對象上顯式設(shè)置屬性

對于使用過 JavaScript 的開發(fā)者來說,對于 window.MyNamespace = window.MyNamespace || {};  這行代碼并不會(huì)陌生。為了避免開發(fā)過程中出現(xiàn)沖突,我們一般會(huì)為某些功能設(shè)置獨(dú)立的命名空間。

然而,在 TS 中對于 window.MyNamespace = window.MyNamespace || {}; 這行代碼,TS  編譯器會(huì)提示以下異常信息:

Property 'MyNamespace' does not exist on type 'Window & typeof globalThis'.(2339)

以上異常信息是說在 Window & typeof globalThis 交叉類型上不存在MyNamespace  屬性。那么如何解決這個(gè)問題呢?最簡單的方式就是使用類型斷言:

(window as any).MyNamespace = {};

雖然使用 any 大法可以解決上述問題,但更好的方式是擴(kuò)展 lib.dom.d.ts 文件中的Window 接口來解決上述問題,具體方式如下:

declare interface Window {   MyNamespace: any; }  window.MyNamespace = window.MyNamespace || {};

下面我們再來看一下 lib.dom.d.ts 文件中聲明的 Window 接口:

/**  * A window containing a DOM document; the document property   * points to the DOM document loaded in that window.   */ interface Window extends EventTarget, AnimationFrameProvider, GlobalEventHandlers,    WindowEventHandlers, WindowLocalStorage, WindowOrWorkerGlobalScope, WindowSessionStorage {     // 已省略大部分內(nèi)容     readonly devicePixelRatio: number;     readonly document: Document;     readonly top: Window;     readonly window: Window & typeof globalThis;     addEventListener(type: string, listener: EventListenerOrEventListenerObject,        options?: boolean | AddEventListenerOptions): void;     removeEventListener<K extends keyof WindowEventMap>(type: K,        listener: (this: Window, ev: WindowEventMap[K]) => any,        options?: boolean | EventListenerOptions): void;     [index: number]: Window; }

在上面我們聲明了兩個(gè)相同名稱的 Window 接口,這時(shí)并不會(huì)造成沖突。TypeScript  會(huì)自動(dòng)進(jìn)行接口合并,即把雙方的成員放到一個(gè)同名的接口中。

二、如何為對象動(dòng)態(tài)分配屬性

在 JavaScript 中,我們可以很容易地為對象動(dòng)態(tài)分配屬性,比如:

let developer = {}; developer.name = "semlinker";

以上代碼在 JavaScript 中可以正常運(yùn)行,但在 TypeScript 中,編譯器會(huì)提示以下異常信息:

Property 'name' does not exist on type '{}'.(2339)

{} 類型表示一個(gè)沒有包含成員的對象,所以該類型沒有包含 name 屬性。為了解決這個(gè)問題,我們可以聲明一個(gè) LooseObject 類型:

interface LooseObject {   [key: string]: any }

該類型使用 索引簽名 的形式描述 LooseObject 類型可以接受 key 類型是字符串,值的類型是 any 類型的字段。有了 LooseObject  類型之后,我們就可以通過以下方式來解決上述問題:

interface LooseObject {   [key: string]: any }  let developer: LooseObject = {}; developer.name = "semlinker";

對于 LooseObject  類型來說,它的約束是很寬松的。在一些應(yīng)用場景中,我們除了希望能支持動(dòng)態(tài)的屬性之外,也希望能夠聲明一些必選和可選的屬性。

比如對于一個(gè)表示開發(fā)者的 Developer 接口來說,我們希望它的 name 屬性是必填,而 age  屬性是可選的,此外還支持動(dòng)態(tài)地設(shè)置字符串類型的屬性。針對這個(gè)需求我們可以這樣做:

interface Developer {   name: string;   age?: number;   [key: string]: any }  let developer: Developer = { name: "semlinker" }; developer.age = 30; developer.city = "XiaMen";

其實(shí)除了使用 索引簽名 之外,我們也可以使用 TypeScript 內(nèi)置的工具類型 Record來定義 Developer 接口:

// type Record<K extends string | number | symbol, T> = { [P in K]: T; } interface Developer extends Record<string, any> {   name: string;   age?: number; }  let developer: Developer = { name: "semlinker" }; developer.age = 30; developer.city = "XiaMen";

三、如何理解泛型中的 <T>

對于剛接觸 TypeScript 泛型的讀者來說,首次看到語法會(huì)感到陌生。其實(shí)它沒有什么特別,就像傳遞參數(shù)一樣,我們傳遞了我們想要用于特定函數(shù)調(diào)用的類型。

怎么解決JavaScript相關(guān)的問題

參考上面的圖片,當(dāng)我們調(diào)用 identity(1) ,Number 類型就像參數(shù) 1 一樣,它將在出現(xiàn) T 的任何位置填充該類型。圖中內(nèi)部的 T 被稱為類型變量,它是我們希望傳遞給 identity 函數(shù)的類型占位符,同時(shí)它被分配給 value 參數(shù)用來代替它的類型:此時(shí) T  充當(dāng)?shù)氖穷愋?,而不是特定?Number 類型。

其中 T 代表 Type,在定義泛型時(shí)通常用作第一個(gè)類型變量名稱。但實(shí)際上 T 可以用任何有效名稱代替。除了 T  之外,以下是常見泛型變量代表的意思:

  • K(Key):表示對象中的鍵類型;

  • V(Value):表示對象中的值類型;

  • E(Element):表示元素類型。

其實(shí)并不是只能定義一個(gè)類型變量,我們可以引入希望定義的任何數(shù)量的類型變量。比如我們引入一個(gè)新的類型變量 U,用于擴(kuò)展我們定義的 identity  函數(shù):

function identity <T, U>(value: T, message: U) : T {   console.log(message);   return value; }  console.log(identity<Number, string>(68, "Semlinker"));

怎么解決JavaScript相關(guān)的問題

除了為類型變量顯式設(shè)定值之外,一種更常見的做法是使編譯器自動(dòng)選擇這些類型,從而使代碼更簡潔。我們可以完全省略尖括號,比如:

function identity <T, U>(value: T, message: U) : T {   console.log(message);   return value; }  console.log(identity(68, "Semlinker"));

對于上述代碼,編譯器足夠聰明,能夠知道我們的參數(shù)類型,并將它們賦值給 T 和 U,而不需要開發(fā)人員顯式指定它們。

四、如何理解裝飾器的作用

在 TypeScript  中裝飾器分為類裝飾器、屬性裝飾器、方法裝飾器和參數(shù)裝飾器四大類。裝飾器的本質(zhì)是一個(gè)函數(shù),通過裝飾器我們可以方便地定義與對象相關(guān)的元數(shù)據(jù)。

比如在 ionic-native 項(xiàng)目中,它使用 Plugin 裝飾器來定義 IonicNative 中 Device 插件的相關(guān)信息:

@Plugin({   pluginName: 'Device',   plugin: 'cordova-plugin-device',   pluginRef: 'device',   repo: 'https://github.com/apache/cordova-plugin-device',   platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'], }) @Injectable() export class Device extends IonicNativePlugin {}

在以上代碼中 Plugin 函數(shù)被稱為裝飾器工廠,調(diào)用該函數(shù)之后會(huì)返回類裝飾器,用于裝飾 Device 類。Plugin 工廠函數(shù)的定義如下:

// https://github.com/ionic-team/ionic-native/blob/v3.x/src/%40ionic-native/core/decorators.ts export function Plugin(config: PluginConfig): ClassDecorator {   return function(cls: any) {     // 把config對象中屬性,作為靜態(tài)屬性添加到cls類上     for (let prop in config) {       cls[prop] = config[prop];     }      cls['installed'] = function(printWarning?: boolean) {       return !!getPlugin(config.pluginRef);     };     // 省略其他內(nèi)容     return cls;   }; }

通過觀察 Plugin 工廠函數(shù)的方法簽名,我們可以知道調(diào)用該函數(shù)之后會(huì)返回 ClassDecorator 類型的對象,其中 ClassDecorator  類型的聲明如下所示:

declare type ClassDecorator = <TFunction extends Function>(target: TFunction)    => TFunction | void;

類裝飾器顧名思義,就是用來裝飾類的。它接收一個(gè)參數(shù) &mdash;&mdash; target: TFunction,表示被裝飾器的類。介紹完上述內(nèi)容之后,我們來看另一個(gè)問題  @Plugin({...}) 中的 @ 符號有什么用?

其實(shí) @Plugin({...}) 中的 @ 符號只是語法糖,為什么說是語法糖呢?這里我們來看一下編譯生成的 ES5 代碼:

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {     var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;     if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);     else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;     return c > 3 && r && Object.defineProperty(target, key, r), r; };  var Device = /** @class */ (function (_super) {     __extends(Device, _super);     function Device() {         return _super !== null && _super.apply(this, arguments) || this;     }     Device = __decorate([         Plugin({             pluginName: 'Device',             plugin: 'cordova-plugin-device',             pluginRef: 'device',             repo: 'https://github.com/apache/cordova-plugin-device',             platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],         }),         Injectable()     ], Device);     return Device; }(IonicNativePlugin));

通過生成的代碼可知,@Plugin({...}) 和 @Injectable()  最終會(huì)被轉(zhuǎn)換成普通的方法調(diào)用,它們的調(diào)用結(jié)果最終會(huì)以數(shù)組的形式作為參數(shù)傳遞給 __decorate 函數(shù),而在 __decorate 函數(shù)內(nèi)部會(huì)以 Device  類作為參數(shù)調(diào)用各自的類型裝飾器,從而擴(kuò)展對應(yīng)的功能。

此外,如果你有使用過 Angular,相信你對以下代碼并不會(huì)陌生。

const API_URL = new InjectionToken('apiUrl');  @Injectable() export class HttpService {   constructor(     private httpClient: HttpClient,     @Inject(API_URL) private apiUrl: string   ) {} }

在 Injectable 類裝飾器修飾的 HttpService 類中,我們通過構(gòu)造注入的方式注入了用于處理 HTTP 請求的 HttpClient  依賴對象。而通過 Inject 參數(shù)裝飾器注入了API_URL 對應(yīng)的對象,這種方式我們稱之為依賴注入(Dependency Injection)。

關(guān)于什么是依賴注入,在 TS 中如何實(shí)現(xiàn)依賴注入功能,出于篇幅考慮,這里阿寶哥就不繼續(xù)展開了。感興趣的小伙伴可以閱讀 “了不起的 IoC 與 DI”  這篇文章。

五、如何理解函數(shù)重載的作用

5.1 可愛又可恨的聯(lián)合類型

由于 JavaScript 是一個(gè)動(dòng)態(tài)語言,我們通常會(huì)使用不同類型的參數(shù)來調(diào)用同一個(gè)函數(shù),該函數(shù)會(huì)根據(jù)不同的參數(shù)而返回不同的類型的調(diào)用結(jié)果:

function add(x, y) {   return x + y; }  add(1, 2); // 3 add("1", "2"); //"12"

由于 TypeScript 是 JavaScript 的超集,因此以上的代碼可以直接在 TypeScript 中使用,但當(dāng) TypeScript  編譯器開啟 noImplicitAny 的配置項(xiàng)時(shí),以上代碼會(huì)提示以下錯(cuò)誤信息:

Parameter 'x' implicitly has an 'any' type. Parameter 'y' implicitly has an 'any' type.

該信息告訴我們參數(shù) x 和參數(shù) y 隱式具有 any 類型。為了解決這個(gè)問題,我們可以為參數(shù)設(shè)置一個(gè)類型。因?yàn)槲覀兿M?add 函數(shù)同時(shí)支持 string  和 number 類型,因此我們可以定義一個(gè) string | number 聯(lián)合類型,同時(shí)我們?yōu)樵撀?lián)合類型取個(gè)別名:

type Combinable = string | number;

在定義完 Combinable 聯(lián)合類型后,我們來更新一下 add 函數(shù):

function add(a: Combinable, b: Combinable) {   if (typeof a === 'string' || typeof b === 'string') {     return a.toString() + b.toString();   }   return a + b; }

為 add 函數(shù)的參數(shù)顯式設(shè)置類型之后,之前錯(cuò)誤的提示消息就消失了。那么此時(shí)的 add 函數(shù)就完美了么,我們來實(shí)際測試一下:

const result = add('semlinker', ' kakuqo'); result.split(' ');

在上面代碼中,我們分別使用 'semlinker' 和 ' kakuqo' 這兩個(gè)字符串作為參數(shù)調(diào)用 add 函數(shù),并把調(diào)用結(jié)果保存到一個(gè)名為  result 的變量上,這時(shí)候我們想當(dāng)然的認(rèn)為此時(shí) result 的變量的類型為 string,所以我們就可以正常調(diào)用字符串對象上的 split 方法。但這時(shí)  TypeScript 編譯器又出現(xiàn)以下錯(cuò)誤信息了:

Property 'split' does not exist on type 'Combinable'. Property 'split' does not exist on type 'number'.

很明顯 Combinable 和 number 類型的對象上并不存在 split 屬性。問題又來了,那如何解決呢?這時(shí)我們就可以利用 TypeScript  提供的函數(shù)重載。

5.2 函數(shù)重載

函數(shù)重載或方法重載是使用相同名稱和不同參數(shù)數(shù)量或類型創(chuàng)建多個(gè)方法的一種能力。

function add(a: number, b: number): number; function add(a: string, b: string): string; function add(a: string, b: number): string; function add(a: number, b: string): string; function add(a: Combinable, b: Combinable) {   // type Combinable = string | number;   if (typeof a === 'string' || typeof b === 'string') {     return a.toString() + b.toString();   }   return a + b; }

在以上代碼中,我們?yōu)?add 函數(shù)提供了多個(gè)函數(shù)類型定義,從而實(shí)現(xiàn)函數(shù)的重載。在 TypeScript  中除了可以重載普通函數(shù)之外,我們還可以重載類中的成員方法。

方法重載是指在同一個(gè)類中方法同名,參數(shù)不同(參數(shù)類型不同、參數(shù)個(gè)數(shù)不同或參數(shù)個(gè)數(shù)相同時(shí)參數(shù)的先后順序不同),調(diào)用時(shí)根據(jù)實(shí)參的形式,選擇與它匹配的方法執(zhí)行操作的一種技術(shù)。所以類中成員方法滿足重載的條件是:在同一個(gè)類中,方法名相同且參數(shù)列表不同。下面我們來舉一個(gè)成員方法重載的例子:

class Calculator {   add(a: number, b: number): number;   add(a: string, b: string): string;   add(a: string, b: number): string;   add(a: number, b: string): string;   add(a: Combinable, b: Combinable) {   if (typeof a === 'string' || typeof b === 'string') {     return a.toString() + b.toString();   }     return a + b;   } }  const calculator = new Calculator(); const result = calculator.add('Semlinker', ' Kakuqo');

這里需要注意的是,當(dāng) TypeScript 編譯器處理函數(shù)重載時(shí),它會(huì)查找重載列表,嘗試使用第一個(gè)重載定義。 如果匹配的話就使用這個(gè)。  因此,在定義重載的時(shí)候,一定要把最精確的定義放在最前面。另外在 Calculator 類中,add(a: Combinable, b: Combinable){  } 并不是重載列表的一部分,因此對于 add 成員方法來說,我們只定義了四個(gè)重載方法。

六、interfaces 與 type 之間有什么區(qū)別

6.1 Objects/Functions

接口和類型別名都可以用來描述對象的形狀或函數(shù)簽名:

接口

interface Point {   x: number;   y: number; }  interface SetPoint {   (x: number, y: number): void; }

類型別名

type Point = {   x: number;   y: number; };  type SetPoint = (x: number, y: number) => void;

6.2 Other Types

與接口類型不一樣,類型別名可以用于一些其他類型,比如原始類型、聯(lián)合類型和元組:

// primitive type Name = string;  // object type PartialPointX = { x: number; }; type PartialPointY = { y: number; };  // union type PartialPoint = PartialPointX | PartialPointY;  // tuple type Data = [number, string];

6.3 Extend

接口和類型別名都能夠被擴(kuò)展,但語法有所不同。此外,接口和類型別名不是互斥的。接口可以擴(kuò)展類型別名,而反過來是不行的。

Interface extends interface

interface PartialPointX { x: number; } interface Point extends PartialPointX {    y: number;  }

Type alias extends type alias

type PartialPointX = { x: number; }; type Point = PartialPointX & { y: number; };

Interface extends type alias

type PartialPointX = { x: number; }; interface Point extends PartialPointX { y: number; }

Type alias extends interface

interface PartialPointX { x: number; } type Point = PartialPointX & { y: number; };

6.4 Implements

類可以以相同的方式實(shí)現(xiàn)接口或類型別名,但類不能實(shí)現(xiàn)使用類型別名定義的聯(lián)合類型:

interface Point {   x: number;   y: number; }  class SomePoint implements Point {   x = 1;   y = 2; }  type Point2 = {   x: number;   y: number; };  class SomePoint2 implements Point2 {   x = 1;   y = 2; }  type PartialPoint = { x: number; } | { y: number; };  // A class can only implement an object type or  // intersection of object types with statically known members. class SomePartialPoint implements PartialPoint { // Error   x = 1;   y = 2; }

6.5 Declaration merging

與類型別名不同,接口可以定義多次,會(huì)被自動(dòng)合并為單個(gè)接口。

interface Point { x: number; } interface Point { y: number; }  const point: Point = { x: 1, y: 2 };

七、object, Object 和 {} 之間有什么區(qū)別

7.1 object 類型

object 類型是:TypeScript 2.2 引入的新類型,它用于表示非原始類型。

// node_modules/typescript/lib/lib.es5.d.ts interface ObjectConstructor {   create(o: object | null): any;   // ... }  const proto = {};  Object.create(proto);     // OK Object.create(null);      // OK Object.create(undefined); // Error Object.create(1337);      // Error Object.create(true);      // Error Object.create("oops");    // Error

7.2 Object 類型

Object 類型:它是所有 Object 類的實(shí)例的類型,它由以下兩個(gè)接口來定義:

  • Object 接口定義了 Object.prototype 原型對象上的屬性;

// node_modules/typescript/lib/lib.es5.d.ts interface Object {   constructor: Function;   toString(): string;   toLocaleString(): string;   valueOf(): Object;   hasOwnProperty(v: PropertyKey): boolean;   isPrototypeOf(v: Object): boolean;   propertyIsEnumerable(v: PropertyKey): boolean; }
  • ObjectConstructor 接口定義了 Object 類的屬性。

// node_modules/typescript/lib/lib.es5.d.ts interface ObjectConstructor {   /** Invocation via `new` */   new(value?: any): Object;   /** Invocation via function calls */   (value?: any): any;   readonly prototype: Object;   getPrototypeOf(o: any): any;   // &middot;&middot;&middot; }  declare var Object: ObjectConstructor;

Object 類的所有實(shí)例都繼承了 Object 接口中的所有屬性。

7.3 {} 類型

{} 類型描述了一個(gè)沒有成員的對象。當(dāng)你試圖訪問這樣一個(gè)對象的任意屬性時(shí),TypeScript 會(huì)產(chǎn)生一個(gè)編譯時(shí)錯(cuò)誤。

// Type {} const obj = {};  // Error: Property 'prop' does not exist on type '{}'. obj.prop = "semlinker";

但是,你仍然可以使用在 Object 類型上定義的所有屬性和方法,這些屬性和方法可通過 JavaScript 的原型鏈隱式地使用:

// Type {} const obj = {};  // "[object Object]" obj.toString();

八、數(shù)字枚舉與字符串枚舉之間有什么區(qū)別

8.1 數(shù)字枚舉

在 JavaScript 中布爾類型的變量含有有限范圍的值,即 true 和 false。而在 TypeScript  中利用枚舉,你也可以自定義相似的類型:

enum NoYes {   No,   Yes, }

No 和 Yes 被稱為枚舉 NoYes 的成員。每個(gè)枚舉成員都有一個(gè) name 和一個(gè) value。數(shù)字枚舉成員值的默認(rèn)類型是 number  類型。也就是說,每個(gè)成員的值都是一個(gè)數(shù)字:

enum NoYes {   No,   Yes, }  assert.equal(NoYes.No, 0); assert.equal(NoYes.Yes, 1);

除了讓 TypeScript 為我們指定枚舉成員的值之外,我們還可以手動(dòng)賦值:

enum NoYes {   No = 0,   Yes = 1, }

這種通過等號的顯式賦值稱為 initializer。如果枚舉中某個(gè)成員的值使用顯式方式賦值,但后續(xù)成員未顯示賦值, TypeScript  會(huì)基于當(dāng)前成員的值加 1 作為后續(xù)成員的值。

8.2 字符串枚舉

除了數(shù)字枚舉,我們還可以使用字符串作為枚舉成員值:

enum NoYes {   No = 'No',   Yes = 'Yes', }  assert.equal(NoYes.No, 'No'); assert.equal(NoYes.Yes, 'Yes');

8.3 數(shù)字枚舉 vs 字符串枚舉

數(shù)字枚舉與字符串枚舉有什么區(qū)別呢?這里我們來分別看一下數(shù)字枚舉和字符串枚舉編譯的結(jié)果:

數(shù)字枚舉編譯結(jié)果

"use strict"; var NoYes; (function (NoYes) {    NoYes[NoYes["No"] = 0] = "No";    NoYes[NoYes["Yes"] = 1] = "Yes"; })(NoYes || (NoYes = {}));

字符串枚舉編譯結(jié)果

"use strict"; var NoYes; (function (NoYes) {    NoYes["No"] = "No";    NoYes["Yes"] = "Yes"; })(NoYes || (NoYes = {}));

通過觀察以上結(jié)果,我們知道數(shù)值枚舉除了支持 從成員名稱到成員值 的普通映射之外,它還支持 從成員值到成員名稱  的反向映射。另外,對于純字符串枚舉,我們不能省略任何初始化程序。而數(shù)字枚舉如果沒有顯式設(shè)置值時(shí),則會(huì)使用默認(rèn)值進(jìn)行初始化。

8.4 為數(shù)字枚舉分配越界值

講到數(shù)字枚舉,這里我們再來看個(gè)問題:

const enum Fonum {   a = 1,   b = 2 }  let value: Fonum = 12; // Ok

相信很多讀者看到 let value: Fonum = 12; 這一行,TS 編譯器并未提示任何錯(cuò)誤會(huì)感到驚訝。很明顯數(shù)字 12 并不是 Fonum  枚舉的成員。 為什么會(huì)這樣呢?我們來看一下 TypeScript issues 26362 中 DanielRosenwasser 大佬的回答:

The behavior is motivated by bitwise operations. There are times when  SomeFlag.Foo | SomeFlag.Bar is intended to produce another SomeFlag. Instead you  end up with number, and you don't want to have to cast back to SomeFlag.

該行為是由按位運(yùn)算引起的。有時(shí) SomeFlag.Foo | SomeFlag.Bar 用于生成另一個(gè)  SomeFlag。相反,你最終得到的是數(shù)字,并且你不想強(qiáng)制回退到 SomeFlag。

了解完上述內(nèi)容,我們再來看一下 let value: Fonum = 12; 這個(gè)語句,該語句 TS 編譯器不會(huì)報(bào)錯(cuò),是因?yàn)閿?shù)字 12 是可以通過  Fonum 已有的枚舉成員計(jì)算而得。

let value: Fonum =    Fonum.a << Fonum.b << Fonum.a |  Fonum.a << Fonum.b; // 12

九、使用 # 定義的私有字段與 private 修飾符定義字段有什么區(qū)別

在 TypeScript 3.8 版本就開始支持 ECMAScript 私有字段,使用方式如下:

class Person {   #name: string;    constructor(name: string) {     this.#name = name;   }    greet() {     console.log(`Hello, my name is ${this.#name}!`);   } }  let semlinker = new Person("Semlinker");  semlinker.#name; //     ~~~~~ // Property '#name' is not accessible outside class 'Person' // because it has a private identifier.

與常規(guī)屬性(甚至使用 private 修飾符聲明的屬性)不同,私有字段要牢記以下規(guī)則:

  • 私有字段以 # 字符開頭,有時(shí)我們稱之為私有名稱;

  • 每個(gè)私有字段名稱都唯一地限定于其包含的類;

  • 不能在私有字段上使用 TypeScript 可訪問性修飾符(如 public 或 private);

  • 私有字段不能在包含的類之外訪問,甚至不能被檢測到。

說到這里使用 # 定義的私有字段與 private 修飾符定義字段有什么區(qū)別呢?現(xiàn)在我們先來看一個(gè) private 的示例:

class Person {   constructor(private name: string){} }  let person = new Person("Semlinker"); console.log(person.name);

在上面代碼中,我們創(chuàng)建了一個(gè) Person 類,該類中使用 private 修飾符定義了一個(gè)私有屬性 name,接著使用該類創(chuàng)建一個(gè) person  對象,然后通過 person.name 來訪問 person 對象的私有屬性,這時(shí) TypeScript 編譯器會(huì)提示以下異常:

Property 'name' is private and only accessible within class 'Person'.(2341)

那如何解決這個(gè)異常呢?當(dāng)然你可以使用類型斷言把 person 轉(zhuǎn)為 any 類型:

console.log((person as any).name);

通過這種方式雖然解決了 TypeScript 編譯器的異常提示,但是在運(yùn)行時(shí)我們還是可以訪問到 Person  類內(nèi)部的私有屬性,為什么會(huì)這樣呢?我們來看一下編譯生成的 ES5 代碼,也許你就知道答案了:

var Person = /** @class */ (function () {     function Person(name) {       this.name = name;     }     return Person; }());  var person = new Person("Semlinker"); console.log(person.name);

這時(shí)相信有些小伙伴會(huì)好奇,在 TypeScript 3.8 以上版本通過 # 號定義的私有字段編譯后會(huì)生成什么代碼:

class Person {   #name: string;    constructor(name: string) {     this.#name = name;   }    greet() {     console.log(`Hello, my name is ${this.#name}!`);   } }

以上代碼目標(biāo)設(shè)置為 ES2015,會(huì)編譯生成以下代碼:

"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet)    || function (receiver, privateMap, value) {     if (!privateMap.has(receiver)) {       throw new TypeError("attempted to set private field on non-instance");     }     privateMap.set(receiver, value);     return value; };  var __classPrivateFieldGet = (this && this.__classPrivateFieldGet)    || function (receiver, privateMap) {     if (!privateMap.has(receiver)) {       throw new TypeError("attempted to get private field on non-instance");     }     return privateMap.get(receiver); };  var _name; class Person {     constructor(name) {       _name.set(this, void 0);       __classPrivateFieldSet(this, _name, name);     }     greet() {       console.log(`Hello, my name is ${__classPrivateFieldGet(this, _name)}!`);     } } _name = new WeakMap();

通過觀察上述代碼,使用 # 號定義的 ECMAScript 私有字段,會(huì)通過 WeakMap 對象來存儲(chǔ),同時(shí)編譯器會(huì)生成  __classPrivateFieldSet 和 __classPrivateFieldGet這兩個(gè)方法用于設(shè)置值和獲取值。

感謝各位的閱讀,以上就是“怎么解決JavaScript相關(guān)的問題”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對怎么解決JavaScript相關(guān)的問題這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

網(wǎng)站名稱:怎么解決JavaScript相關(guān)的問題
文章URL:http://muchs.cn/article6/jopeog.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營銷用戶體驗(yàn)、微信公眾號網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)公司建站公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)