主頁 > 企業開發 > TypeScript高級用法詳解

TypeScript高級用法詳解

2020-10-08 06:01:00 企業開發

引言

作為一門強大的靜態型別檢查工具,如今在許多中大型應用程式以及流行的JS庫中均能看到TypeScript的身影,JS作為一門弱型別語言,在我們寫代碼的程序中稍不留神便會修改掉變數的型別,從而導致一些出乎意料的運行時錯誤,然而TypeScript在編譯程序中便能幫我們解決這個難題,不僅在JS中引入了強型別檢查,并且編譯后的JS代碼能夠運行在任何瀏覽器環境,Node環境和任何支持ECMAScript 3(或更高版本)的JS引擎中,最近公司剛好準備使用TypeScript來對現有系統進行重構,以前使用TypeScript的機會也不多,特別是一些有用的高級用法,所以借著這次機會,重新鞏固夯實一下這方面的知識點,如果有錯誤的地方,還請指出,

1、類繼承

在ES5中,我們一般通過函式或者基于原型的繼承來封裝一些組件公共的部分方便復用,然而在TypeScript中,我們可以像類似Java語言中以面向物件的方式使用類繼承來創建可復用的組件,我們可以通過class關鍵字來創建類,并基于它使用new運算子來實體化一個物件,為了將多個類的公共部分進行抽象,我們可以創建一個父類并讓子類通過extends關鍵字來繼承父類,從而減少一些冗余代碼的撰寫增加代碼的可復用性和可維護性,示例如下:

class Parent {
    readonly x: number;
    constructor() {
        this.x = 1;
    }
    
    print() {
        console.log(this.x);
    }
}

class Child extends Parent {
    readonly y: number;
    constructor() {
        // 注意此處必須優先呼叫super()方法
        super();
        this.y = 2;
    }
    
    print() {
        // 通過super呼叫父類原型上的方法,但是方法中的this指向的是子類的實體
        super.print();
        console.log(this.y);
    }
}

const child = new Child();
console.log(child.print()) // -> 1 2

在上述示例中,Child子類中對父類的print方法進行重寫,同時在內部使用super.print()來呼叫父類的公共邏輯,從而實作邏輯復用,class關鍵字作為建構式的語法糖,在經過TypeScript編譯后,最侄訓被轉換為兼容性好的瀏覽器可識別的ES5代碼,class在面向物件的編程范式中非常常見,因此為了弄清楚其背后的實作機制,我們不妨多花點時間來看下經過編譯轉換之后的代碼是什么樣子的(當然這部分已經比較熟悉的同學可以直接跳過),

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    }
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Parent = /** @class */ (function () {
    function Parent() {
        this.x = 1;
    }
    Parent.prototype.print = function () {
        console.log(this.x);
    };
    return Parent;
}());
var Child = /** @class */ (function (_super) {
    __extends(Child, _super);
    function Child() {
        var _this = 
        // 注意此處必須優先呼叫super()方法
        _super.call(this) || this;
        _this.y = 2;
        return _this;
    }
    Child.prototype.print = function () {
        // 通過super呼叫父類原型上的方法,但是方法中的this指向的是子類的實體
        _super.prototype.print.call(this);
        console.log(this.y);
    };
    return Child;
}(Parent));
var child = new Child();
console.log(child.print()); // -> 1 2

以上就是轉換后的完整代碼,為了方便對比,這里將原來的注釋資訊保留,仔細研究這段代碼我們會發現以下幾個要點:

  1. 子類Child的建構式中super()方法被轉換成了var _this = _super.call(this) || this,這里的_super指的就是父類Parent,因此這句代碼的含義就是呼叫父類建構式并將this系結到子類的實體上,這樣的話子類實體便可擁有父類的x屬性,因此為了實作屬性繼承,我們必須在子類建構式中呼叫super()方法,如果不呼叫會編譯不通過,

  2. 子類Childprint方法中super.print()方法被轉換成了_super.prototype.print.call(this),這句代碼的含義就是呼叫父類原型上的print方法并將方法中的this指向子類實體,由于在上一步操作中我們已經繼承到父類的x屬性,因此這里我們將直接列印出子類實體的x屬性的值,

  3. extends關鍵字最終被轉換為__extends(Child, _super)方法,其中_super指的是父類Parent,為了方便查看,這里將_extends方法單獨提出來進行研究,

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    }
    return function (d, b) {
        // 第一部分
        extendStatics(d, b);
        
        // 第二部分
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();

在以上代碼中,主要可以分為兩個部分來進行理解,第一部分為extendStatics(d, b)方法,第二部分為該方法后面的兩行代碼,

第一部分

extendStatics方法內部雖然代碼量相對較多,但是不難發現其實還是主要為了兼容ES5版本的執行環境,在ES6中新增了Object.setPrototypeOf方法用于手動設定物件的原型,但是在ES5的環境中我們一般通過一個非標準的__proto__屬性來進行設定,Object.setPrototypeOf方法的原理其實也是通過該屬性來設定物件的原型,其實作方式如下:

Object.setPrototypeOf = function(obj, proto) {
    obj.__proto__ = proto;
    return obj;
}

extendStatics(d, b)方法中,d指子類Childb指父類Parent,因此該方法的作用可以解釋為:

// 將子類Child的__proto__屬性指向父類Parent
Child.__proto__ = Parent;

可以將這行代碼理解為建構式的繼承,或者叫靜態屬性和靜態方法的繼承,即屬性和方法不是掛載到建構式的prototype原型上的,而是直接掛載到建構式本身,因為在JS中函式本身也可以作為一個物件,并可以為其賦予任何其他的屬性,示例如下:

function Foo() {
  this.x = 1;
  this.y = 2;
}

Foo.bar = function() {
  console.log(3);
}

Foo.baz = 4;
console.log(Foo.bar()) // -> 3
console.log(Foo.baz) // -> 4

因此當我們在子類Child中以Child.someProperty訪問屬性時,如果子類中不存在就會通過Child.__proto__尋找父類的同名屬性,通過這種方式來實作靜態屬性和靜態方法的路徑查找,

第二部分

在第二部分中僅包含以下兩行代碼:

function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());

其中d指子類Childb指父類Parent,這里對于JS中實作繼承的幾種方式比較熟悉的同學可以一眼看出,這里使用了寄生組合式繼承的方式,通過借用一個中間函式__()來避免當修改子類的prototype上的方法時對父類的prototype所造成的影響,我們知道,在JS中通過建構式實體化一個物件之后,該物件會擁有一個__proto__屬性并指向其建構式的prototype屬性,示例如下:

function Foo() {
  this.x = 1;
  this.y = 2;
}

const foo = new Foo();
foo.__proto__ === Foo.prototype; // -> true

對于本例中,如果通過子類Child來實體化一個物件之后,會產生如下關聯:

const child = new Child();
child.__proto__ === (Child.prototype = new __());
child.__proto__.__proto__ === __.prototype === Parent.prototype; 

// 上述代碼等價于下面這種方式
Child.prototype.__proto__ === Parent.prototype;

因此當我們在子類Child的實體child物件中通過child.someMethod()呼叫某個方法時,如果在實體中不存在該方法,則會沿著__proto__繼續往上查找,最侄訓經過父類Parentprototype原型,即通過這種方式來實作方法的繼承,

基于對以上兩個部分的分析,我們可以總結出以下兩點:

// 表示建構式的繼承,或者叫做靜態屬性和靜態方法的繼承,總是指向父類
1. Child.__proto__ === Parent;

// 表示方法的繼承,總是指向父類的prototype屬性
2. Child.prototype.__proto__ === Parent.prototype;

2、訪問修飾符

TypeScript為我們提供了訪問修飾符(Access Modifiers)來限制在class外部對內部屬性的訪問,訪問修飾符主要包含以下三種:

  • public:公共修飾符,其修飾的屬性和方法都是公有的,可以在任何地方被訪問到,默認情況下所有屬性和方法都是public的,
  • private:私有修飾符,其修飾的屬性和方法在class外部不可見,
  • protected:受保護修飾符,和private比較相似,但是其修飾的屬性和方法在子類內部是被允許訪問的

我們通過一些示例來對幾種修飾符進行對比:

class Human {
    public name: string;
    public age: number;
    public constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const man = new Human('tom', 20);
console.log(man.name, man.age); // -> tom 20
man.age = 21;
console.log(man.age); // -> 21

在上述示例中,由于我們將訪問修飾符設定為public,因此我們通過實體man來訪問nameage屬性是被允許的,同時對age屬性重新賦值也是允許的,但是在某些情況下,我們希望某些屬性是對外不可見的,同時不允許被修改,那么我們就可以使用private修飾符:

class Human {
    public name: string;
    private age: number; // 此處修改為使用private修飾符
    public constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const man = new Human('tom', 20);
console.log(man.name); // -> tom
console.log(man.age);
// -> Property 'age' is private and only accessible within class 'Human'.

我們將age屬性的修飾符修改為private后,在外部通過man.age對其進行訪問,TypeScript在編譯階段就會發現其是一個私有屬性并最終將會報錯,

注意:在TypeScript編譯之后的代碼中并沒有限制對私有屬性的存取操作,

編譯后的代碼如下:

var Human = /** @class */ (function () {
    function Human(name, age) {
        this.name = name;
        this.age = age;
    }
    return Human;
}());
var man = new Human('tom', 20);
console.log(man.name); // -> tom
console.log(man.age); // -> 20

使用private修飾符修飾的屬性或者方法在子類中也是不允許訪問的,示例如下:

class Human {
    public name: string;
    private age: number;
    public constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

class Woman extends Human {
    private gender: number = 0;
    public constructor(name: string, age: number) {
        super(name, age);
        console.log(this.age);
    }
}

const woman = new Woman('Alice', 18);
// -> Property 'age' is private and only accessible within class 'Human'.

在上述示例中由于在父類Humanage屬性被設定為private,因此在子類Woman中無法訪問到age屬性,為了讓在子類中允許訪問age屬性,我們可以使用protected修飾符來對其進行修飾:

class Human {
    public name: string;
    protected age: number; // 此處修改為使用protected修飾符
    public constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

class Woman extends Human {
    private gender: number = 0;
    public constructor(name: string, age: number) {
        super(name, age);
        console.log(this.age);
    }
}

const woman = new Woman('Alice', 18); // -> 18

當我們將private修飾符用于建構式時,則表示該類不允許被繼承或實體化,示例如下:

class Human {
    public name: string;
    public age: number;
    
    // 此處修改為使用private修飾符
    private constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

class Woman extends Human {
    private gender: number = 0;
    public constructor(name: string, age: number) {
        super(name, age);
    }
}

const man = new Human('Alice', 18);
// -> Cannot extend a class 'Human'. Class constructor is marked as private.
// -> Constructor of class 'Human' is private and only accessible within the class declaration.

當我們將protected修飾符用于建構式時,則表示該類只允許被繼承,示例如下:

class Human {
    public name: string;
    public age: number;
    
    // 此處修改為使用protected修飾符
    protected constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

class Woman extends Human {
    private gender: number = 0;
    public constructor(name: string, age: number) {
        super(name, age);
    }
}

const man = new Human('Alice', 18);
// -> Constructor of class 'Human' is protected and only accessible within the class declaration.

另外我們還可以直接將修飾符放到建構式的引數中,示例如下:

class Human {
    // public name: string;
    // private age: number;
    
    public constructor(public name: string, private age: number) {
        this.name = name;
        this.age = age;
    }
}

const man = new Human('tom', 20);
console.log(man.name); // -> tom
console.log(man.age);
// -> Property 'age' is private and only accessible within class 'Human'.

3、介面與構造器簽名

當我們的專案中擁有很多不同的類時并且這些類之間可能存在某方面的共同點,為了描述這種共同點,我們可以將其提取到一個介面(interface)中用于集中維護,并使用implements關鍵字來實作這個介面,示例如下:

interface IHuman {
    name: string;
    age: number;
    walk(): void;
}

class Human implements IHuman {
    
    public constructor(public name: string, public age: number) {
        this.name = name;
        this.age = age;
    }

    walk(): void {
        console.log('I am walking...');
    }
}

上述代碼在編譯階段能順利通過,但是我們注意到在Human類中包含constructor建構式,如果我們想在介面中為該建構式定義一個簽名并讓Human類來實作這個介面,看會發生什么:

interface HumanConstructor {
  new (name: string, age: number);    
}

class Human implements HumanConstructor {
    
    public constructor(public name: string, public age: number) {
        this.name = name;
        this.age = age;
    }

    walk(): void {
        console.log('I am walking...');
    }
}
// -> Class 'Human' incorrectly implements interface 'HumanConstructor'.
// -> Type 'Human' provides no match for the signature 'new (name: string, age: number): any'.

然而TypeScript會編譯出錯,告訴我們錯誤地實作了HumanConstructor介面,這是因為當一個類實作一個介面時,只會對實體部分進行編譯檢查,類的靜態部分是不會被編譯器檢查的,因此這里我們嘗試換種方式,直接操作類的靜態部分,示例如下:

interface HumanConstructor {
  new (name: string, age: number);    
}

interface IHuman {
    name: string;
    age: number;
    walk(): void;
}

class Human implements IHuman {
    
    public constructor(public name: string, public age: number) {
        this.name = name;
        this.age = age;
    }

    walk(): void {
        console.log('I am walking...');
    }
}

// 定義一個工廠方法
function createHuman(constructor: HumanConstructor, name: string, age: number): IHuman {
    return new constructor(name, age);
}

const man = createHuman(Human, 'tom', 18);
console.log(man.name, man.age); // -> tom 18

在上述示例中通過額外創建一個工廠方法createHuman并將建構式作為第一個引數傳入,此時當我們呼叫createHuman(Human, 'tom', 18)時編譯器便會檢查第一個引數是否符合HumanConstructor介面的構造器簽名,

4、宣告合并

在宣告合并中最常見的合并型別就是介面了,因此這里先從介面開始介紹幾種比較常見的合并方式,

4.1 介面合并

示例代碼如下:

interface A {
    name: string;
}

interface A {
    age: number;
}

// 等價于
interface A {
    name: string;
    age: number;
}

const a: A = {name: 'tom', age: 18};

介面合并的方式比較容易理解,即宣告多個同名的介面,每個介面中包含不同的屬性宣告,最終這些來自多個介面的屬性宣告會被合并到同一個介面中,

注意:所有同名介面中的非函式成員必須唯一,如果不唯一則必須保證型別相同,否則編譯器會報錯,對于函式成員,后宣告的同名介面會覆寫掉之前宣告的同名介面,即后宣告的同名介面中的函式相當于一次多載,具有更高的優先級,

4.2 函式合并

函式的合并可以簡單理解為函式的多載,即通過同時定義多個不同型別引數或不同型別回傳值的同名函式來實作,示例代碼如下:

// 函式定義
function foo(x: number): number;
function foo(x: string): string;

// 函式具體實作
function foo(x: number | string): number | string {
    if (typeof x === 'number') {
        return (x).toFixed(2);
    }
    
    return x.substring(0, x.length - 1);
}

在上述示例中,我們對foo函式進行多次定義,每次定義的函式引數型別不同,回傳值型別不同,最后一次為函式的具體實作,在實作中只有在兼容到前面的所有定義時,編譯器才不會報錯,

注意:TypeScript編譯器會優先從最開始的函式定義進行匹配,因此如果多個函式定義存在包含關系,則需要將最精確的函式定義放到最前面,否則將始終不會被匹配到,

4.3 型別別名聯合

型別別名聯合與介面合并有所區別,型別別名不會新建一個型別,只是創建一個新的別名來對多個型別進行參考,同時不能像介面一樣被實作(implements)繼承(extends),示例如下:

type HumanProperty = {
    name: string;
    age: number;
    gender: number;
};

type HumanBehavior = {
    eat(): void;
    walk(): void;
}

type Human = HumanProperty & HumanBehavior;

let woman: Human = {
    name: 'tom',
    age: 18,
    gender: 0,
    eat() {
        console.log('I can eat.');
    },
    walk() {
        console.log('I can walk.');
    }
}

class HumanComponent extends Human {
    constructor(public name: string, public age: number, public gender: number) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    
    eat() {
        console.log('I can eat.');
    }
    
    walk() {
        console.log('I can walk.');
    }
}
// -> 'Human' only refers to a type, but is being used as a value here.

5、keyof 索引查詢

在TypeScript中的keyof有點類似于JS中的Object.keys()方法,但是區別在于前者遍歷的是型別中的字串索引,后者遍歷的是物件中的鍵名,示例如下:

interface Rectangle {
    x: number;
    y: number;
    width: number;
    height: number;
}

type keys = keyof Rectangle;
// 等價于
type keys = "x" | "y" | "width" | "height";

// 這里使用了泛型,強制要求第二個引數的引數名必須包含在第一個引數的所有字串索引中
function getRectProperty<T extends object, K extends keyof T>(rect: T, property: K): T[K] {
    return rect[property];
} 

let rect: Rectangle = {
    x: 50,
    y: 50,
    width: 100,
    height: 200
};

console.log(getRectProperty(rect, 'width')); // -> 100
console.log(getRectProperty(rect, 'notExist'));
// -> Argument of type '"notExist"' is not assignable to parameter of type '"width" | "x" | "y" | "height"'.

在上述示例中我們通過使用keyof來限制函式的引數名property必須被包含在型別Rectangle的所有字串索引中,如果沒有被包含則編譯器會報錯,可以用來在編譯時檢測物件的屬性名是否書寫有誤,

6、Partial 可選屬性

在某些情況下,我們希望型別中的所有屬性都不是必需的,只有在某些條件下才存在,我們就可以使用Partial來將已宣告的型別中的所有屬性標識為可選的,示例如下:

// 該型別已內置在TypeScript中
type Partial<T> = {
    [P in keyof T]?: T[P]
};

interface Rectangle {
    x: number;
    y: number;
    width: number;
    height: number;
}

type PartialRectangle = Partial<Rectangle>;
// 等價于
type PartialRectangle = {
    x?: number;
    y?: number;
    width?: number;
    height?: number;
}

let rect: PartialRectangle = {
    width: 100,
    height: 200
};

在上述示例中由于我們使用Partial將所有屬性標識為可選的,因此最終rect物件中雖然只包含widthheight屬性,但是編譯器依舊沒有報錯,當我們不能明確地確定物件中包含哪些屬性時,我們就可以通過Partial來宣告,

7、Pick 部分選擇

在某些應用場景下,我們可能需要從一個已宣告的型別中抽取出一個子型別,在子型別中包含父型別中的部分或全部屬性,這時我們可以使用Pick來實作,示例代碼如下:

// 該型別已內置在TypeScript中
type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
};

interface User {
    id: number;
    name: string;
    age: number;
    gender: number;
    email: string;
}

type PickUser = Pick<User, "id" | "name" | "gender">;
// 等價于
type PickUser = {
    id: number;
    name: string;
    gender: number;
};

let user: PickUser = {
    id: 1,
    name: 'tom',
    gender: 1
};

在上述示例中,由于我們只關心user物件中的idnamegender是否存在,其他屬性不做明確規定,因此我們就可以使用PickUser介面中揀選出我們關心的屬性而忽略其他屬性的編譯檢查,

8、never 永不存在

never表示的是那些永不存在的值的型別,比如在函式中拋出例外或者無限回圈,never型別可以是任何型別的子型別,也可以賦值給任何型別,但是相反卻沒有一個型別可以作為never型別的子型別,示例如下:

// 函式拋出例外
function throwError(message: string): never {
    throw new Error(message);
}

// 函式自動推斷出回傳值為never型別
function reportError(message: string) {
    return throwError(message);
}

// 無限回圈
function loop(): never {
    while(true) {
        console.log(1);
    }
}

// never型別可以是任何型別的子型別
let n: never;
let a: string = n;
let b: number = n;
let c: boolean = n;
let d: null = n;
let e: undefined = n;
let f: any = n;

// 任何型別都不能賦值給never型別
let a: string = '123';
let b: number = 0;
let c: boolean = true;
let d: null = null;
let e: undefined = undefined;
let f: any = [];

let n: never = a;
// -> Type 'string' is not assignable to type 'never'.

let n: never = b;
// -> Type 'number' is not assignable to type 'never'.

let n: never = c;
// -> Type 'true' is not assignable to type 'never'.

let n: never = d;
// -> Type 'null' is not assignable to type 'never'.

let n: never = e;
// -> Type 'undefined' is not assignable to type 'never'.

let n: never = f;
// -> Type 'any' is not assignable to type 'never'.

9、Exclude 屬性排除

Pick相反,Pick用于揀選出我們需要關心的屬性,而Exclude用于排除掉我們不需要關心的屬性,示例如下:

// 該型別已內置在TypeScript中
// 這里使用了條件型別(Conditional Type),和JS中的三目運算子效果一致
type Exclude<T, U> = T extends U ? never : T;

interface User {
    id: number;
    name: string;
    age: number;
    gender: number;
    email: string;
}

type keys = keyof User; // -> "id" | "name" | "age" | "gender" | "email"

type ExcludeUser = Exclude<keys, "age" | "email">;
// 等價于
type ExcludeUser = "id" | "name" | "gender";

在上述示例中我們通過在ExcludeUser中傳入我們不需要關心的ageemail屬性,Exclude會幫助我們將不需要的屬性進行剔除,留下的屬性idnamegender即為我們需要關心的屬性,一般來說,Exclude很少單獨使用,可以與其他型別配合實作更復雜更有用的功能,

10、Omit 屬性忽略

在上一個用法中,我們使用Exclude來排除掉其他不需要的屬性,但是在上述示例中的寫法耦合度較高,當有其他型別也需要這樣處理時,就必須再實作一遍相同的邏輯,不妨我們再進一步封裝,隱藏這些底層的處理細節,只對外暴露簡單的公共介面,示例如下:

// 使用Pick和Exclude組合實作
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

interface User {
    id: number;
    name: string;
    age: number;
    gender: number;
    email: string;
}

// 表示忽略掉User介面中的age和email屬性
type OmitUser = Omit<User, "age" | "email">;
// 等價于
type OmitUser = {
  id: number;
  name: string;
  gender: number;
};

let user: OmitUser = {
    id: 1,
    name: 'tom',
    gender: 1
};

在上述示例中,我們需要忽略掉User介面中的ageemail屬性,則只需要將介面名和屬性傳入Omit即可,對于其他型別也是如此,大大提高了型別的可擴展能力,方便復用,

總結

在本文中總結了幾種TypeScript的使用技巧,如果在我們的TypeScript專案中發現有很多型別宣告的地方具有共性,那么不妨可以使用文中的幾種技巧來對其進行優化改善,增加代碼的可維護性和可復用性,筆者之前使用TypeScript的機會也不多,所以最近也是一邊學習一邊總結,如果文中有錯誤的地方,還希望能夠在評論區指正,

交流

如果你覺得這篇文章的內容對你有幫助,能否幫個忙關注一下筆者的公眾號[前端之境],每周都會努力原創一些前端技術干貨,關注公眾號后可以邀你加入前端技術交流群,我們可以一起互相交流,共同進步,

文章已同步更新至Github博客,若覺文章尚可,歡迎前往star!

你的一個點贊,值得讓我付出更多的努力!

逆境中成長,只有不斷地學習,才能成為更好的自己,與君共勉!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/162508.html

標籤:JavaScript

上一篇:vue.config.js的常用配置

下一篇:skyline加載arcgis發布的wms服務

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more