Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to solve the problems related to JavaScript

2025-02-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly explains "how to solve JavaScript-related problems". The content of the explanation is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "how to solve JavaScript-related problems".

How to explicitly set properties on a window object

For developers who have used JavaScript, this line of code is no stranger to window.MyNamespace = window.MyNamespace | | {};. In order to avoid conflicts during development, we generally set separate namespaces for certain functions.

However, in TS, for window.MyNamespace = window.MyNamespace | | {};, the TS compiler prompts the following exception message:

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

The above exception message means that there is no MyNamespace attribute on the Window & typeof globalThis cross type. So how to solve this problem? The easiest way is to use type assertions:

(window as any) .MyNamespace = {}

Although the above problems can be solved by using the any method, a better way is to extend the Window interface in the lib.dom.d.ts file to solve the above problems as follows:

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

Let's take a look at the Window interface declared in the lib.dom.d.ts file:

/ * * 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 {/ / have omitted most of the content readonly devicePixelRatio: number; readonly document: Document; readonly top: Window; readonly window: Window & typeof globalThis; addEventListener (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void RemoveEventListener (type: K, listener: (this: Window, ev: WindowEventMap [K]) = > any, options?: boolean | EventListenerOptions): void; [index: number]: Window;}

Above we declare two Window interfaces with the same name, which does not cause a conflict. TypeScript automatically merges interfaces, that is, putting members of both sides into an interface with the same name.

Second, how to assign attributes to objects dynamically

In JavaScript, we can easily assign properties to objects dynamically, such as:

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

The above code works fine in JavaScript, but in TypeScript, the compiler prompts for the following exception message:

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

The {} type represents an object that does not contain a member, so the type does not contain a name property. To solve this problem, we can declare a LooseObject type:

Interface LooseObject {[key: string]: any}

This type describes the LooseObject type in the form of an index signature. It is acceptable that the key type is a string and the value type is a field of type any. With the LooseObject type, we can solve the above problem in the following ways:

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

For the LooseObject type, its constraints are very loose. In some application scenarios, we not only want to support dynamic attributes, but also want to be able to declare some required and optional attributes.

For example, for a Developer interface that represents a developer, we want the name property to be required, the age property to be optional, and the ability to set string-type properties dynamically. In response to this need, we can do this:

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

In fact, in addition to using index signatures, we can also use TypeScript's built-in tool type Record to define Developer interfaces:

/ / type Record = {[P in K]: t;} interface Developer extends Record {name: string; age?: number;} let developer: Developer = {name: "semlinker"}; developer.age = 30; developer.city = "XiaMen"

Third, how to understand generics

For readers new to TypeScript generics, it will be strange to see the syntax for the first time. It's nothing special, just like passing parameters, we pass the type we want to use for a particular function call.

Referring to the picture above, when we call identity (1), the Number type is like parameter 1, which populates the type wherever T appears. The internal T in the figure is called a type variable, which is the type placeholder we want to pass to the identity function, and it is assigned to the value parameter to replace its type: in this case T acts as a type, not a specific Number type.

Where T stands for Type and is usually used as the first type variable name when defining generics. But in fact T can be replaced by any valid name. In addition to T, the following is what common generic variables represent:

K (Key): represents the key type in the object

V (Value): represents the value type in the object

E (Element): represents the element type.

In fact, we can not only define one type variable, we can introduce any number of type variables we want to define. For example, we introduce a new type variable U to extend the identity function we defined:

Function identity (value: t, message: U): t {console.log (message); return value;} console.log (identity (68, "Semlinker"))

In addition to explicitly setting values for type variables, a more common practice is to have the compiler automatically select these types, making the code more concise. We can omit the angle brackets completely, such as:

Function identity (value: t, message: U): t {console.log (message); return value;} console.log (identity (68, "Semlinker"))

For the above code, the compiler is smart enough to know our parameter types and assign them to T and U without requiring the developer to specify them explicitly.

Fourth, how to understand the function of decorators

In TypeScript, decorators are divided into four categories: class decorator, attribute decorator, method decorator and parameter decorator. The essence of the decorator is a function through which we can easily define the metadata related to the object.

For example, in the ionic-native project, it uses the Plugin decorator to define information about the Device plug-in in IonicNative:

@ 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 {}

In the above code, the Plugin function is called the decorator factory, and after calling this function, it returns the class decorator to decorate the Device class. The Plugin factory function is defined as follows:

/ / 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) {/ / add the attribute in the config object as a static attribute to the cls class for (let prop in config) {cls [prop] = config [prop] } cls ['installed'] = function (printWarning?: boolean) {return! getPlugin (config.pluginRef);}; / / omit other content return cls;};}

By observing the method signature of the Plugin factory function, we can see that an object of type ClassDecorator will be returned after the function is called, where the declaration of type ClassDecorator is as follows:

Declare type ClassDecorator = (target: TFunction) = > TFunction | void

Class decorator, as its name implies, is used to decorate classes. It takes a parameter, target: TFunction, which represents the class of the decorator. After introducing the above, let's take a look at another question: what is the use of the @ symbol in @ Plugin ({...})?

In fact, the @ symbol in @ Plugin ({...}) is just grammatical sugar. Why is it grammatical sugar? Here let's take a look at the compiled ES5 code:

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; iMel -) 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))

From the generated code, @ Plugin ({...}) and @ Injectable () will eventually be converted into normal method calls, and the result of their calls will eventually be passed as an array to the _ _ decorate function, while inside the _ _ decorate function, their respective type decorators will be called with the Device class as parameters, thus extending the corresponding functionality.

In addition, if you have ever used Angular, I believe you are no stranger to the following code.

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

In the HttpService class decorated by the Injectable class decorator, we inject the HttpClient dependent object used to process the HTTP request by constructing injection. The API_URL corresponding object is injected through the Inject parameter decorator, which is called dependency injection (Dependency Injection).

For the sake of space, Brother Po will not continue to expand on what dependency injection is and how to implement dependency injection in TS. Interested friends can read the article "the Great IoC and DI".

Fifth, how to understand the function of function overloading

5.1 lovely and hateful union type

Because JavaScript is a dynamic language, we usually call the same function with different types of parameters, and the function returns different types of call results according to different parameters:

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

Because TypeScript is a superset of JavaScript, the above code can be used directly in TypeScript, but when the TypeScript compiler opens the configuration item for noImplicitAny, the above code prompts the following error message:

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

This information tells us that parameters x and y are implicitly of type any. To solve this problem, we can set a type for the parameter. Because we want the add function to support both string and number types, we can define a string | number union type, and we give the union a separate name:

Type Combinable = string | number

After defining the Combinable union type, let's update the add function:

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

After you explicitly set the type for the parameters of the add function, the previous error message disappears. So is the add function perfect at this time? let's actually test it:

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

In the above code, we use the strings' semlinker' and 'kakuqo'' as parameters to call the add function, and save the call result to a variable named result. At this time, we take it for granted that the variable type of result is string, so we can call the split method on the string object normally. But then the TypeScript compiler came up with the following error message:

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

It is clear that the split attribute does not exist on objects of type Combinable and number. Here comes the problem again, so how to solve it? At this point, we can take advantage of the function overloading provided by TypeScript.

5.2 function overload

Function overloading or method overloading is the ability to create multiple methods with the same name and different numbers or types of parameters.

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;}

In the above code, we provide multiple function type definitions for the add function to overload the function. In TypeScript, in addition to overloading ordinary functions, we can also overload member methods in a class.

Method overloading means that in the same class, the method has the same name and different parameters (different parameter types, different number of parameters, or different order of parameters with the same number of parameters). A technique that selects the method that matches it to perform the operation. So the condition for a member method in a class to be overloaded is that in the same class, the method name is the same and the parameter list is different. Let's give an example of member method overloading:

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')

It is important to note that when the TypeScript compiler handles function overloading, it looks for the overloaded list and tries to use the first overloaded definition. Use this if it's a match. Therefore, when defining overloading, be sure to put the most precise definition first. In addition, in the Calculator class, add (a: Combinable, b: Combinable) {} is not part of the overloaded list, so for add member methods, we only define four overloaded methods.

What is the difference between interfaces and type

6.1 Objects/Functions

Both interfaces and type aliases can be used to describe the shape or function signature of an object:

Interface

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

Type alias

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

6.2 Other Types

Unlike interface types, type aliases can be used for some other types, such as primitive types, union types, and tuples:

/ / 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

Both interfaces and type aliases can be extended, but the syntax is different. In addition, interfaces and type aliases are not mutually exclusive. Interfaces can extend type aliases, but not vice versa.

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

Classes can implement interfaces or type aliases in the same way, but classes cannot implement federated types defined using type aliases:

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

Unlike type aliases, interfaces can be defined multiple times and are automatically merged into a single interface.

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

7. What's the difference between object, Object and {}

7.1 object Typ

The object type is a new type introduced by TypeScript 2.2, which is used to represent non-primitive types.

/ / 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 Typ

Object type: it is the type of all instances of the Object class and is defined by the following two interfaces:

The Object interface defines the properties on the Object.prototype prototype object

/ / 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;}

The ObjectConstructor interface defines the properties of the Object class.

/ / 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; / /} declare var Object: ObjectConstructor

All instances of the Object class inherit all the properties in the Object interface.

7.3 {} Typ

The {} type describes an object that has no members. When you try to access any properties of such an object, TypeScript generates a compile-time error.

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

However, you can still use all the properties and methods defined on the Object type, which can be used implicitly through JavaScript's prototype chain:

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

8. What is the difference between numeric enumeration and string enumeration

8.1 numeric enumeration

In JavaScript, Boolean variables contain a limited range of values, namely true and false. With enumerations in TypeScript, you can also customize similar types:

Enum NoYes {No, Yes,}

No and Yes are called members of enumerated NoYes. Each enumeration member has a name and a value. The default type for numeric enumeration member values is the number type. That is, the value of each member is a number:

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

In addition to having TypeScript specify the values of enumerated members for us, we can also assign values manually:

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

This explicit assignment through the equal sign is called initializer. If the value of a member in the enumeration is explicitly assigned, but the subsequent member does not display the assignment, TypeScript adds 1 as the value of the subsequent member based on the value of the current member.

8.2 string enumeration

In addition to numeric enumerations, we can also use strings as enumeration member values:

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

8.3 numeric enumeration vs string enumeration

What is the difference between numeric enumeration and string enumeration? Here let's take a look at the compilation results of numeric enumeration and string enumeration, respectively:

Numeric enumeration compilation result

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

String enumeration compilation results

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

By observing the above results, we know that numeric enumeration supports reverse mapping from member values to member names in addition to normal mapping from member names to member values. In addition, for pure string enumerations, we cannot omit any initialization procedures. Numeric enumerations are initialized with default values if they are not explicitly set.

8.4 assign out-of-bounds values to numeric enumerations

When it comes to enumerating numbers, let's look at another question:

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

I'm sure many readers will be surprised to see the line let value: Fonum = 12; the TS compiler doesn't prompt for any errors. It is clear that the number 12 is not a member of the Fonum enumeration. What causes it? Let's take a look at the answer from the boss of DanielRosenwasser in TypeScript issues 26362:

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.

This behavior is caused by bitwise operations. Sometimes SomeFlag.Foo | SomeFlag.Bar is used to generate another SomeFlag. Instead, you end up with numbers, and you don't want to force a fallback to SomeFlag.

After knowing the above, let's take a look at let value: Fonum = 12; this statement, the TS compiler will not report an error, because the number 12 can be calculated from the existing enumeration members of Fonum.

Let value: Fonum = Fonum.a

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report