1. 程式人生 > >TypeScript高階用法詳解

TypeScript高階用法詳解

引言

作為一門強大的靜態型別檢查工具,如今在許多中大型應用程式以及流行的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!

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

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

相關推薦

TypeScript高階用法

引言 作為一門強大的靜態型別檢查工具,如今在許多中大型應用程式以及流行的JS庫中均能看到TypeScript的身影。JS作為一門弱型別語言,在我們寫程式碼的過程中稍不留神便會修改掉變數的型別,從而導致一些出乎意料的執行時錯誤。然而TypeScript在編譯過程中便能幫我們解決這個難題,不僅在JS中引入了強型別

Python中sorted()函式的高階用法

sorted()函式的作用是對物件進行排序 函式函式格式: sorted(iterable,key,reverse),key引數可傳入一個自定義函式 下邊通過具體例子說明sorted的具體用法: 一維陣列,直接排序即可: #!/usr/bin/env python # -*-

Python裝飾器的從入門到高階用法!

首先要恭喜你,點進了這一篇十足乾貨。 不怕感動自己,這篇文章,小編足足整理了三天之久。絕對值得收藏,以備後用。 今天小明要講的是,Python中的裝飾器內容。 我會從裝飾器的入門用法逐步講到其高階用法 進群進群:700341555可以獲取Python各類入門學習資料! 這是我的微

Django基礎(11): 表單集合Formset的高階用法

Formset(表單集)是多個表單的集合。Formset在Web開發中應用很普遍,它可以讓使用者在同一個頁面上提交多張表單,一鍵新增多個數據,比如一個頁面上新增多個使用者資訊。今天小編我就介紹下Django Formset的基礎知識,Formset的分類以及如何使用Forms

Junit4.x高階用法(一)

最近整理程式碼的時候,總習慣把一些常用的工具類和方法等都寫在junit中,這樣可以方便於在想用的時候直接copy,在用junit的時候學到了一些比較有用的東西,記錄如下: 1.使用junit進行超

JavaScript中return的用法

style 返回 www log tle blog 意思 charset fun 1、定義:return 從字面上的看就是返回,官方定義return語句將終止當前函數並返回當前函數的值,可以看下下面的示例代碼: <!DOCTYPE html><html l

SVN trunk(主線) branch(分支) tag(標記) 用法和詳細操作步驟

trac load mar span 必須 最可 objc copy 右鍵 原文地址:http://blog.csdn.net/vbirdbest/article/details/51122637 使用場景: 假如你的項目(這裏指的是手機客戶端項目)的某個版本(例如1.0

js 定時器用法——setTimeout()、setInterval()、clearTimeout()、clearInterval()

ntb 幫助 .get tint num 用法 -c 函數 tel 在js應用中,定時器的作用就是可以設定當到達一個時間來執行一個函數,或者每隔幾秒重復執行某段函數。這裏面涉及到了三個函數方法:setInterval()、setTimeout()、clearI

selenium用法

key url enc element api code 需要 int question selenium用法詳解 selenium主要是用來做自動化測試,支持多種瀏覽器,爬蟲中主要用來解決JavaScript渲染問題。 模擬瀏覽器進行網頁加載,當requests,url

C# ListView用法

ont 結束 server 發生 匹配 鼠標 之前 小圖標 order 一、ListView類 1、常用的基本屬性: (1)FullRowSelect:設置是否行選擇模式。(默認為false) 提示:只有在Details視圖該屬性才有意義

linux cp命令參數及用法---linux 復制文件命令cp

linux file linux cp命令參數及用法詳解---linux 復制文件命令cp [root@Linux ~]# cp [-adfilprsu] 來源檔(source) 目的檔(destination)[root@linux

Python數據類型方法簡介一————字符串的用法

python 字符串連接 字符串用法 符串是Python中的重要的數據類型之一,並且字符串是不可修改的。 字符串就是引號(單、雙和三引號)之間的字符集合。(字符串必須在引號之內,引號必須成對)註:單、雙和三引號在使用上並無太大的區別; 引號之間可以采取交叉使用的方式避免過多轉義;

C# ListView用法(轉)

分組 創建 cti 排列 checkbox 定義 com 程序 erl 一、ListView類 1、常用的基本屬性: (1)FullRowSelect:設置是否行選擇模式。(默認為false) 提示:只有在Details視圖該屬性才有

java中的instanceof用法

定義 xtend print 繼承 interface 參數 保留 如果 ack   instanceof是Java的一個二元操作符(運算符),也是Java的保留關鍵字。它的作用是判斷其左邊對象是否為其右邊類的實例,返回的是boolean類型的數據。用它來判斷某個對象是否是

@RequestMapping 用法

同時 get() turn example track find 說明 tex -h 簡介: @RequestMapping RequestMapping是一個用來處理請求地址映射的註解,可用於類或方法上。用於類上,表示類中的所有響應請求的方法都是以該地址作為父路徑。

Css中路徑data:image/png;base64的用法 (轉載)

javascrip base64編碼 asc cda 文件的 color 情況 ont 背景圖片 大家可能註意到了,網頁上有些圖片的src或css背景圖片的url後面跟了一大串字符,比如: background-image:url(data:image/png;bas

global用法

global 在函數內傳遞參數1、global一般用在函數內,將外部變量參數傳遞至函數內部,用法為:<?php $name = "why"; function changeName(){ global $name; $name = "what";

java中靜態代碼塊的用法—— static用法

super關鍵字 了解 裝載 static關鍵字 super 屬於 註意 lock 自動 (一)java 靜態代碼塊 靜態方法區別一般情況下,如果有些代碼必須在項目啟動的時候就執行的時候,需要使用靜態代碼塊,這種代碼是主動執行的;需要在項目啟動的時候就初始化,在不創建對象的

<!CDATA[]]用法

引號 ica lap 用法 bsp mar ret message eight 所有 XML 文檔中的文本均會被解析器解析。 只有 CDATA 區段(CDATA section)中的文本會被解析器忽略。 PCDATA PCDATA 指的是被解析的字符數據(Parsed

Es6 Promise 用法

set 問題 得到 math clas promise 回調 console spa Promise是什麽?? 打印出來看看 console.dir(Promise) 這麽一看就明白了,Promise是一個構造函數,自己身上有all、reject、r