沐鳴測速註冊_css屬性_env()和constant()設置安全區域

env()和constant(),是IOS11新增特性,Webkit的css函數,用於設定安全區域與邊界的距離,有4個預定義變量:

safe-area-inset-left:安全區域距離左邊邊界的距離

safe-area-inset-right:安全區域距離右邊邊界的距離

safe-area-inset-top:安全區域距離頂部邊界的距離

safe-area-inset-bottom :安全距離底部邊界的距離

而env()和constant()函數有個必要的使用前提,H5網頁設置viewport-fit=cover的時候才生效,小程序里的viewport-fit默認是cover。

未適配底部效果:

適配底部代碼:

height: calc(96rpx+ constant(safe-area-inset-bottom));//兼容 IOS<11.2
height: calc(96rpx + env(safe-area-inset-bottom));//兼容 IOS>11.2
padding-bottom: constant(safe-area-inset-bottom);//兼容 IOS<11.2
padding-bottom: env(safe-area-inset-bottom);//兼容 IOS>11.2

// 先constant再env

適配後效果:

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9863

沐鳴註冊網站_你應該知道的TypeScript高級概念

接口

例如我們這定義一個叫做printPost的函數,那這個函數可以接收一個文章對象參數post,然後在函數的內部去打印文章的title, 然後再去打印他的content屬性。

function printPost (post) {
    console.log(post.title);
    console.log(post.content);
}

那這個時候對於這個函數所接收的post對象他就有一定的要求,也就是我們所傳入的這個對象必須要存在一個title屬性和一個content屬性,只不過這種要求他實際上是隱性的,他沒有明確的表達出來。

那這種情況下我們就可以使用接口去表現出來這種約束,這裏我們可以嘗試先去定義一個接口。

定義接口的方式呢就是使用interface這樣一個關鍵詞,然後後面跟上接口的名稱,這裏我們可以叫做post,然後就是一對{},然後{}裏面就可以添加具體的成員限制。這裏我們添加一個title和content,類型都是string。

interface Post {
    title: string;
    content: string;
}

注意這裏我們可以使用逗號分割成員,但是更標準的做法是使用分號去分割,而且呢這個分號跟js中絕大多數的分號是一樣的,可以省略,那關於是否應該在代碼當中明確使用每一個分號,個人的編碼習慣是不加,你可以根據你所在的團隊或者是項目對應的編碼規範來去決定要不要加分號,這個問題我們不做過多討論。

完成過後我們這裏可以給這個post參數的類型設置為我們剛剛所定義的Post接口。

function printPost (post: Post) {
    console.log(post.title);
    console.log(post.content);
}

printPost({
    title: 'hello',
    content: 'typescript'
})

那此時就是显示的要求我們所傳入的對象他必須要有title和content這兩個成員了,那這就是接口的一個基本作用。

一句話去總結,接口就是用來約束對象的結構,那一個對象去實現一個接口,他就必須要去擁有這個接口當中所約束的所有的成員。

我們可以編譯一下這個代碼,編譯過後我們打開對應的js文件,我們在js當中並不會發現有任何跟接口相關的代碼,也就是說TypeScript中的接口他只是用來為我們有結構的數據去做類型約束的,在實際運行階段呢,實際這種接口他並沒有意義。

可選成員,只讀成員

對於接口中約定的成員,還有一些特殊的用法,我們依次來看一下。

首先是可選成員,如果說我們在一個對象當中,我們某一個成員他是可有可無的話,那這樣的話我們對於約束這個對象的接口來說我們可以使用可選成員這樣一個特性。

例如我們這裏添加一個subTitle這樣一個成員,他的類型同樣是string,不過我們這裏的文章不一定是每一個都有subTitle,這種況下我們就可以在subTitle後面添加一個問號,這就表示我們這個subTitle成員他是可有可無的了。

interface Post {
    title: string;
    content: string;
    subTitle?: string;
}

那這種用法呢其實就是相當於給這個subTitle標記他的類型是string或者是undefined。這就是可選成員。

接下來我們再來看一下只讀成員這樣一個特性,那這裏我們再給Post接口添加一個summary這樣一個成員,那一般邏輯上來講的話文章的summary他都是從文章的內容當中自動提取出來的,所以說我們不應該允許外界去設置他。

那這種情況下我們可以使用readonly這樣一個關鍵詞,去修飾一下這裏的summary。那添加了readonly過後我們這個summary他在初始化完成過後就不能夠再去修改了。如果我們再去修改就會報錯。這就是只讀成員。

interface Post {
    title: string;
    content: string;
    subTitle?: string;
    readonly summary: string;
}

最後我們再來看一個動態成員的用法,那這種用法一般是適用於一些具有動態成員對象,例如程序當中的緩存對象,那他在運行過程中就會出現一些動態的鍵值。

這裏我們來新建一個新的接口,因為我們在定義的時候我們是無法知道會有那些具體的成員,所以說我們就不能夠去指定,具體的成員名稱,而是使用一個[], 這個[]中使用key: string。

這個key並不是固定的,可以是任意的名稱, 只是代表了我們屬性的名稱,他是一個格式,然後後面這個string就是成員名的類型,也就是鍵的類型,後面我們可以跟上動態屬性的值為string。

interface Cache {
    [key: string]: string;
}

完成以後我們再來創建一個cache對象,讓他去實現這個接口,那這個時候我們就可以在這個cache對象上動態的去添加任意的成員了, 只不過這些成員他都必須是stringd類型的鍵值。

const cache: Cache = {};

cache.foo = 'value1';
cache.bar = 'value2'

類的基本使用

類可以說是面向對象編程中一個最重要的概念,關於類的作用這裏我們再簡單描述一下。他就是用來描述一類具體事物的抽象特徵,我們可以以生活角度去舉例。

例如手機就屬於一個類型,那這個類型的特徵呢就是能夠打電話,發信息。那在這個類型下面呢他還會有一些細分的子類,那這種子類他一定會滿足父類的所有特徵,然後再多出來一些額外的特徵。例如只能手機,他除了可以打電話發短信還能夠使用一些app。

那我們是不能直接去使用類的,而是去使用這個類的具體事物,例如你手中的只能手機。

那類比到程序的角度,類也是一樣的,他可以用來去描述一類具體對象的一些抽象成員,那在ES6以前,JavaScript都是通過函數然後配合原型去模擬實現的類。那從ES6開始,JavaScript中有了專門的class。

而在TypeScript中,我們除了可以使用所有ECMAScript的標準當中所有類的功能,他還添加了一些額外的功能和用法,例如我們對類成員有特殊的訪問修飾符,還有一些抽象類的概念。

那對於ECMAScript標準中的class,我們這裏就不單獨去介紹了,如果你不太熟悉,你可以去參考ECMAScript前面的文章。

這裏我們來着重介紹一下,在TypeScript中額外多出來的一些新的類的一些特性,我們可以先來聲明一個叫做Person的類型,然後我們在這個類型當中去聲明一下constructor構造桉樹,在構造函數當中我們接收一個name和age參數,那這裏我們仍然可以使用類型註解的方式去標註我們這個地方每個參數的類型。

然後在這個構造函數的裏面我們可以使用this去為當前這個類型的屬性去賦值,不過這裏直接去使用this訪問當前類的屬性會報錯,說的是當前這個Person類型上面並不存在對應的name和age。

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

這是因為在TypeScript中我們需要明確在類型中取聲明他所擁有的一些屬性,而不是直接在構造函數當中動態通過this去添加。

那在類型聲明屬性的方式就是直接在類當中去定義, 那這個語法呢是ECMAScript2016標準當中定義的,那我們同樣可以在這裏給name和gae屬性添加類型。那他也可以通過等號去直接賦值一個初始值,不過一般情況下我們還是會在構造函數中動態的為屬性賦值。

需要注意的是,在TypeScript中類的屬性他必須要有一個初始值,可以在等號後面去賦值,或者是在構造函數當中去初始化,兩者必須做其一,否則就會報錯。

class Person {
    name: string = 'init name';
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

那以上就是在TypeScript中類屬性在定義上的一些細微差異,其實具體來說就是我們類的屬性他在使用之前必須要現在類型當中去聲明,那這麼做的目的其實就是為了給我們的屬性去做一些類型的標註。

那除此之外呢我們仍然可以按照ES6標準當中的語法,為這個類型去聲明一些方法,例如我們這裏添加一個叫做sayHi的方法,那在這個方法當中我們仍然可以使用函數類型註解的方式去限制參數的類型和返回值的類型。

那在這個方法的內部呢我們同樣可以使用this去訪問當前實例對象,也就可以訪問到對應的屬性。

class Person {
    name: string = 'init name';
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    sayHi(msg: string): void {
        console.log(`I am ${this.name}, ${msg}`);
    }
}

那以上就是類在TypeScript中的一個基本使用。

類的訪問修飾符

接下來我們再來看幾個TypeScript中類的一些特殊用法,那首先就是類當中成員的訪問修飾符,類中的每一個成員都可以使用訪問修飾符去修飾他們。

例如我們這裏給age屬性前面去添加一個private,表示這個age屬性是一個私有屬性,這種私有屬性只能夠在類的內部去訪問,這裏我們創建一個Person對象, 我們打印tom的name屬性和age屬性。可以發現name可以訪問,age就會報錯,因為age已經被我們標記為了私有屬性。

class Person {
    name: string = 'init name';
    private age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    sayHi(msg: string): void {
        console.log(`I am ${this.name}, ${msg}`);
    }
}

const tom = new Person('tom', 18);

console.log(tom.name);
console.log(tom.age);

除了private以外,我們還可以使用public修飾符去修飾成員,意思是他是一個共有成員,不過再TypeScript中,類成員的訪問修飾符默認就是public,所以我們這裏加不加public效果都是一樣的。不過我們還是建議大家手動去加上這種public的修飾符,因為這樣的話,我們的代碼會更加容易理解一點。

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

    sayHi(msg: string): void {
        console.log(`I am ${this.name}, ${msg}`);
    }
}

最後還有一個叫做protected修飾符,說的就是受保護的,我們可以添加一個gender的屬性,他的訪問修飾符我們就使用protected,我們同樣在構造函數中初始化一下gender。

完成過後我們在實例對象上去訪問gender,會發現也是訪問不到的,也就是protected也不能在外部直接訪問。

class Person {
    public name: string = 'init name';
    private age: number;
    protected gender: boolean;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
        this.gender = true;
    }

    sayHi(msg: string): void {
        console.log(`I am ${this.name}, ${msg}`);
    }
}

const tom = new Person('tom', 18);

console.log(tom.name);
// console.log(tom.age);
console.log(tom.gender);

那他跟private的區別到底在什麼地方呢, 這裏我們可以再定義一個叫做Student的類型,我們讓這個類型去繼承自Person,我們在構造函數中嘗試訪問父類的gender,是可以訪問到的。那意思就是protected只允許在子類當中去訪問對應的成員。

class Student extends Person {
    constructor(name: string, age: number) {
        super(name, age); // 子類需要調用super將參數傳給父類。
        console.log(this.gender);
    }
}

那以上就是TypeScript當中對於類額外添加的三個訪問修飾符。分別是private,protected和public,那他們的作用可以用來去控制類當中的成員的可訪問級別。

那這裏還有一個需要注意的點,就是對於構造函數的訪問修飾符,那構造函數的訪問修飾符默認也是public,那如果說我們把它設置為private,那這個類型就不能夠在外部被實例化了,也不能夠被繼承,那在這樣一種情況下,我們就只能夠在這個類的內部去添加一個靜態方法,然後在靜態方法當中去創建這個類型的實例,因為private只允許在內部訪問。

例如我們這裏再去添加一個叫create的靜態方法,那static也是ES6標準當中定義的,然後我們就可以在這個create方法中去使用new的方式去創建這個類型的實例,因為new的方式就是調用了這個類型的構造函數。

此時我們就可以在外部去使用create靜態方法創建Student類型的對象了。

class Student extends Person {
    private constructor(name: string, age: number) {
        super(name, age); // 子類需要調用super將參數傳給父類。
        console.log(this.gender);
    }

    static create(name: string, age: number) {
        return new Student(name, age);
    }
}

const jack = Student.create('jack', 18);

那如果我們把構造函數標記為protected,這樣一個類型也是不能在外面被實例化的,但是相比於private他是允許繼承的,這裏我們就不單獨演示了。

類的只讀屬性

對屬性成員我們除了可以使用private和protected去控制它的訪問級別,我們還可以使用一個叫做readonly的關鍵詞去把這個成員設置為只讀的。

這裏我們將gender屬性設置為readonly,注意這裏如果說我們的屬性已經有了訪問修飾符的話,那readonly應該跟在訪問修飾符的後面,對於只讀屬性,我們可以選擇在類型聲明的時候直接通過等號的方式去初始化,也可以在構造函數當中去初始化,二者只能選其一。

也就是說我們不能在聲明的時候初始化,然後在構造函數中修改它,因為這樣的話已經破壞了readonly。

class Person {
    public name: string = 'init name';
    private age: number;
    protected readonly gender: boolean;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
        this.gender = true;
    }

    sayHi(msg: string): void {
        console.log(`I am ${this.name}, ${msg}`);
        console.log(this.age);
    }
}

const tom = new Person('tom', 18);

console.log(tom.name);

在初始化過後呢, 這個gender屬性就不允許再被修改了,無論是在內部還是外部,他都是不允許修改的。

以上就是readonly這樣一個只讀屬性,還是比較好理解的。

類與接口

相比於類,接口的概念要更為抽象一點,我們可以接着之前所說的手機的例子來去做比。我們說手機他是一個類型,這個實例的類型都是能夠打電話,發短信的,因為手機這個類的特徵呢就是打電話,發短信。

但是呢,我們能夠打電話的,不僅僅只有手機,在以前還有比較常見的座機,也能夠打電話,但是座機並不屬於手機這個類目,而是一個單獨的類目,因為他不能夠發短信,也不能夠拿着到處跑。

那在這種情況下就會出現,不同的類與類之間也會有一些共同的特徵,那對於這些公共的特徵我們一般會使用接口去抽象,那你可以理解為手機也可以打電話,因為他實現了能夠打電話的協議,而座機也能夠打電話因為他也實現了這個相同的協議。

那這裏所說的協議呢我們在程序當中就叫做接口,當然如果說你是第一次接受這種概念的話,那可能理解起來會有些吃力,個人的經驗就是多思考,多從生活的角度去想,如果實在想不通,更粗暴的辦法就是不斷的去用,用的過程當中慢慢的去總結規律時間長了自然也就好了。

class Person {
    eat (food: string): void {}
    run (distance: number) {}
}

class Animal {
    eat (food: string): void {}
    run (distance: number) {}
}

這裏我們來看一個例子,我們定義好了兩個類型,分別是Person和Animal,也就是人類和動物類,那他們實際上是兩個完全不同的類型,但是他們之間也會有一些相同的特性。例如他們都會吃東西都會跑。

那這種情況下就屬於不同的類型實現了一個相同的接口,那可能有人會問,我們為什麼不給他們之間抽象一個公共的父類,然後把公共的方法都定義到父類當中。

那這個原因也很簡單,雖然人和動物都會吃,都會跑,但是我們說人吃東西和狗吃東西能是一樣的么,那他們只是都有這樣的能力,而這個能力的實現肯定是不一樣的。

那在這種情況下我們就可以使用接口去約束這兩個類型之間公共的能力,我們定義一個接口叫做EatAndRun,然後我們在這個接口當中分別去添加eat和run這兩個方法成員的約束。

那這裏我們就需要使用這種函數簽名的方式去約束這兩個方法的類型,而我們這裡是不做具體的方法實現的。

interface EatAndRun {
    eat (food: string): void;
    run (distance: number): void;
}

那有了這個接口過後呢,我們再到Person類型後面,我們使用implements實現以下這個EatAndRun接口, 那此時在我們這個類型中就必須要有對應的成員,如果沒有就會報錯,因為我們實現這個接口就必須有他對應的成員。

class Person implements EatAndRun {
    eat (food: string): void {}
    run (distance: number) {}
}

那這裏我們需要注意的一點是,在C#和Java這樣的一些語言當中他建議我們盡可能讓每一個接口的定義更加簡單,更加細化。就是我們EatAndRun這樣一個接口當中我們抽象了兩個方法,那就相當於抽象了兩個能力,那這兩個能力必然會同時存在么?是不一定的。

例如說摩托車也會跑,但是就不會吃東西,所以說我們更為合理的就是一個接口只去約束一個能力,然後讓一個類型同時去實現多個接口,那這樣的話會更加合理一些,那我們這裏可以把這個接口拆成一個Eat接口和Run接口,每個接口只有一個成員。

然後我們就可以在類型的後面使用逗號的方式,同時去實現Eat和Run這兩個接口。

interface Eat {
    eat (food: string): void;
}

interface Run {
    run (distance: number): void;
}

class Person implements Eat, Run {
    eat (food: string): void {}
    run (distance: number) {}
}

那以上呢就是用接口去對類進行一些抽象,那這裏再多說一句題外話,就是大家千萬不要把自己框死在某一門語言或者是技術上面,最好可以多接觸,多學習一些周邊的語言或者技術,因為這樣的話可以補充你的知識體系。

那最簡單來說,一個只了解JavaScript的開發人員,即便說他對JavaScript有多麼精通,那他也不可能設計出一些比較高級的產品。

例如我們現在比較主流的一些框架,他們大都採用一些MVVM的這樣一些思想,那這些思想呢他實際上最早出現在微軟的WPS技術當中的,如果說你有更寬的知識面的話,那你可以更好的把多家的思想融合到一起,所以說我們的視野應該放寬一點。

抽象類

最後我們再來了解一下抽象類,那抽象類在某種程度上來說跟接口有點類似,那他也是用來約束子類當中必須要有某一個成員。

但是不同於接口的是,抽象類他可以包含一些具體的實現,而接口他只能夠是一個成員的一個抽象,他不包含具體的實現。

那一般比較大的類目我們都建議大家使用抽象類,例如我們剛剛所說的動物類,那其實他就應該是抽象的,因為我們所說的動物他只是一個泛指,他並不夠具體,那在他的下面一定會有一些更細化的劃分,比如說小狗,小貓之類的。

而且我們在生活當中一般都會說我們買了一條狗,或者說買了一隻貓,從來沒有人說我們買了一個動物。

abstract class Animal {
    eat (food: string): void {}
}

我們有一個Animal類型,那他應該像我們剛剛說的那樣,被定義成抽象類,定義抽象類的方式就是在class關鍵詞前面添加一個abstract,那這個類型被定義成抽象類過後,他就只能夠被繼承,不能夠再去實例化。

在這種情況下我們就必須使用子類去繼承這個抽象類, 這裏我們定義一個叫做Dog的類型, 然後我們讓他繼承自Animal,那在抽象類當中我們還可以去定義一些抽象方法,那這種抽象方法我們可以使用abstract關鍵詞來修飾,我們這裏定義一個叫做run的抽象方法, 需要注意的是抽象方法也不需要方法體.

當父類中有抽象方法時,我們的子類就必須要去實現這個方法。

那此時我們再去使用這個子類所創建的對象時,就會同時擁有父類當中的一些實例方法以及自身所實現的方法。那這就是抽象類的基本使用。

abstract class Animal {
    eat (food: string): void {}
    abstract run (distance: number): void;
}

class Dog extends Animal {
    run (distance: number): void {}
}

關於抽象類呢,更多的還是去理解他的概念,他在使用上並沒有什麼複雜的地方。

泛型

泛型(Generics)是指在定義函數、接口或者類的時候, 不預先指定其類型,而是在使用是手動指定其類型的一種特性。

比如我們需要創建一個函數, 這個函數會返回任何它傳入的值。

function identity(arg: any): any {
  return arg
}

identity(3) // 3

這代代碼編譯不會出錯,但是存在一個顯而易見的缺陷, 就是沒有辦法約束輸出的類型與輸入的類型保持一致。

這時,我們可以使用泛型來解決這個問題;

function identity<T>(arg: T): T {
  return arg
}

identity(3) // 3

我們在函數名後面加了 , 其中的 T 表示任意輸入的類型, 後面的 T 即表示輸出的類型,且與輸入保持一致。

當然我們也可以在調用時手動指定輸入與輸出的類型, 如上述函數指定 string 類型:

identity<number>(3) // 3

在泛型函數內部使用類型變量時, 由於事先並不知道它是那種類型, 所以不能隨意操作它的屬性和方法:

function loggingIdentity<T>(arg: T): T {
  console.log(arg.length)   // err 
  return arg
}

上述函數中 類型 T 上不一定存在 length 屬性, 所以編譯的時候就報錯了。

這時,我們可以的對泛型進行約束,對這個函數傳入的值約束必須包含 length 的屬性, 這就是泛型約束:

interface lengthwise {
  length: number
}

function loggingIdentity<T extends lengthwise>(arg: T): T {
  console.log(arg.length)   // err 
  return arg
}

loggingIdentity({a: 1, length: 1})  // 1
loggingIdentity('str') // 3
loggingIdentity(6) // err  傳入是參數中未能包含 length 屬性

這樣我們就可以通過泛型約束的方法對函數傳入的參數進行約束限制。

多個參數時也可以在泛型約束中使用類型參數 如你聲明了一個類型參數, 它被另一類型參數所約束。現在想要用屬性名從對象里湖區這個屬性。並且還需確保這個屬性存在於這個對象上, 因此需要咋這兩個類型之間使用約束,

簡單舉例來說: 定義一個函數, 接受兩個參數 第一個是個對象 obj,第二個個參數是第一參數 key 是對象裏面的鍵名, 需要輸入 obj[key]

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}

let obj = { a: 1, b: 2, c: 3 }

getProperty(obj, 'a') // success
getProperty(obj, 'm') // err obj 中不存在 m 這個參數

我們可以為泛型中的類型參數指定默認類型。當使用泛型時沒有在代碼中直接指定類型參數,從實際值參數中也無法推測出時,這個默認類型就會起作用

function createArr<T = string>(length: number, value: T): Array<T> {
  let result: T[] = []
  for( let i = 0; i < lenght; i++ ) {
    result[i] = value
  }
  return result
}

簡單來說,泛型就是把我們定義時不能夠明確的類型變成一個參數,讓我們在使用的時候再去傳遞這樣一個類型參數。

類型聲明

在項目開發過程中我們難免會用到一些第三方的npm模塊,而這些npm模塊他不一定都是通過TypeScript編寫的,所以說他所提供的成員呢就不會有強類型的體驗。

比如我們這裏安裝一個lodash的模塊,那這個模塊當中就提供了很多工具函數,安裝完成過後我們回到代碼當中,我們使用ES Module的方式import導入這個模塊,我們這裏導入的時候TypeScript就已經報出了錯誤,找不到類型聲明的文件,我們暫時忽略。

這裏我們提取一下camelCase的函數,那這個函數的作用就是把一個字符串轉換成駝峰格式,那他的參數應該是一個string,返回值也應該是一個string,但是我們在調用這個函數逇時候並沒有任何的類型提示。

import { camelCase } from 'lodash';

const res = camelCase('hello typed');

那在這種情況下我們就需要單獨的類型聲明,這裏我們可以使用detar語句來去聲明一下這個函數的類型,具體的語法就是declare function 後面跟上函數簽名, 參數類型是input,類型是string,返回值也應該是string

declare function camelCase (input: string ): string;

那有了這樣一個聲明過後,我們再去使用這個camelCase函數。這個時候就會有對應的類型限制了。

那這就是所謂的類型聲明,說白了就是一個成員他在定義的時候因為種種原因他沒有聲明一個明確的類型,然後我們在使用的時候我們可以單獨為他再做出一個明確的聲明。

那這種用法存在的原因呢,就是為了考慮兼容一些普通的js模塊,由於TypeScript的社區非常強大,目前一些比較常用的npm模塊都已經提供了對應的聲明,我們只需要安裝一下它所對應的這個類型聲明模塊就可以了。

lodash的報錯模塊已經提示了,告訴我們需要安裝一個@types/lodash的模塊,那這個模塊其實就是我們lodash所對應的類型聲明模塊,我們可以安裝這個模塊。安裝完成過後,lodash模塊就不會報錯了。在ts中.d.ts文件都是做類型聲明的文件,

除了類型聲明模塊,現在越來越多的模塊已經在內部繼承了這種類型的聲明文件,很多時候我們都不需要單獨安裝這種類型聲明模塊。

例如我們這裏安裝一個query-string的模塊,這個模塊的作用就是用來去解析url當中的query-string字符串,那在這個模塊當中他就已經包含了類型聲明文件。

我們這裏直接導入這個模塊,我們所導入的這個對象他就直接會有類型約束。

那以上就是對TypeScript當中所使用第三方模塊類型聲明的介紹。

那這裏我們再來總結一下,在TypeScript當中我們去引用第三方模塊,如果這個模塊當中不包含所對應的類型聲明文件,那我們就可以嘗試去安裝一個所對應的類型聲明模塊,那這個類型聲明模塊一般就是@types/模塊名。

那如果也沒有這樣一個對應的類型聲明模塊,那這種情況下我們就只能自己使用declare語句去聲明所對應的模塊類型,那對於declare詳細的語法這裏我們不再單獨介紹了,有需要的話可以單獨去查詢一下官方文檔。

作者:隱冬

鏈接:https://juejin.cn/post/6897779423858737166

來源:掘金 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9859

沐鳴註冊平台_編寫高質量可維護的代碼——異步優化

前言

在現在前端開發中,異步操作的頻次已經越來越高了,特別對於數據接口請求和定時器的使用,使得我們不得不關注異步在業務中碰到的場景,以及對異步的優化。錯誤的異步處理可能會帶來很多問題,諸如頁面渲染、重複加載等問題。

下面我們就先簡單的從 JavaScript 中有大致的哪幾種異步類型為切入點,然後再列舉一些業務中我們會碰到的場景來逐個分析下,我們該如何解決。

異步實現種類

首先關於異步實現的方式上大致有如下幾種:

callback

callback 即回調函數。這傢伙出現很早很早了,他其實是處理異步的基本方法。並且回調的概念不單單出現在JavaScript,你也會在 Java 或者 C# 等後端語言中也能找到他的影子。

回調函數簡單的說其實就是給另外一個寄主函數作為傳參的函數。在寄主函數執行完成或者執行到特定階段之後觸發調用回調函數並執行,然後把執行結果再返回給寄主函數的過程。

比如我們熟悉的 setTimeout 或者 react 中的 setState 的第二個方法都是以回調函數方式去解決異步的實現。

setTimeout(() => {
   //等待0.2s之後再做具體的業務操作
   this.doSomething();
}, 200);
this.setState({
  count: res.count,
}, () => {
  //在更新完count之後再做具體的業務操作
  this.doSomething();
});

Promise

Promise 是個好東西,有了它之後我們可以對異步進行很多操作,並且可以把異步以鏈式的方式進行操作。

其實在 JQuery 中的 deferred 和它就有點像,都是採用回調函數的解決方案,都可以做鏈式調用,但是在Promise 中增加了錯誤的 catch 方法可以更加方便的處理異常場景,並且它內置狀態(resolve, reject,pending),狀態只能由 pending 變為另外兩種的其中一種,且改變后不可逆也不可再度修改。

let promise = new Promise((resolve, reject) => { 
  reject("對不起,你不是我的菜");
});
promise.then((data) => {
console.log('第一次success' + data);
  return '第一次success' + data
},(error) => {
console.log(error) }
).then((data2) => {
  console.log('第二次success' + data2);
},(error2) => { 
  console.log(error2) }
).catch((e) => {
  console.log('抓到錯誤啦' + e);
});

await/async

await/async 其實是 Promise 的一種升級版本,使用 await/async 調用異步的時候是從上到下,順序執行,就像在寫同步代碼一樣,這更加的符合我們編寫代碼的習慣和思維邏輯,所以容易理解。 整體代碼邏輯也會更加的清晰。

async function asyncDemoFn() {
  const data1 = await getData1();
  const data2 = await getData2(data1);
  const data3 =  await getData3(data2);
  console.log(data3)
}
await asyncDemoFn()

generator

generator 中文名叫構造器,是 ES6 中的一個新東西,我相信很多人在現實的代碼中很少能接觸到它,所以它相對而言對大家來說還是比較晦澀,但是這傢伙還是很強的,簡單來說它能控制異步調用,並且其實是一個狀態機。

function* foo() {
  for (let i = 1; i <= 3; i++) {
    let x = yield `等我一下唄,i = ${i}`;
    console.log(x);
  }
}
setTimeout(() => {
  console.log('終於輪到我了');
}, 1);
var a = foo();
console.log(a); // foo {<closed>}
var b = a.next();
console.log(b); // {value: "等我一下唄,i = 1", done: false}
var c = a.next();
console.log(c); // {value: "等我一下唄,i = 2", done: false}
var d = a.next();
console.log(d); // {value: "等我一下唄,i = 3", done: false}
var e = a.next();
console.log(e); // {value: undefined, done: true}
// 終於輪到我了

上面代碼的函數 foo 是一個協程,它的厲害的地方就是 yield 命令。它表示執行到此處,執行權將交給其他協程。也就是說,yield 命令是異步兩個階段的分界線。

協程遇到 yield 命令就暫停,等到執行權返回,再從暫停的地方繼續往後執行。它的最大優點,就是代碼的寫法非常像同步操作,如果去除 yield 命令,簡直一模一樣。

再來個有點貼近點場景方式來使用下 generator。比如現在在頁面中我們需要自動的執行 checkAuth 和checkAddress 檢查,我們就用 generator 的方式去實現自動檢查上述兩異步檢查。

const checkAuth = () => {
    return new Promise((resolve)=>{
        setTimeout(()=>{
           resolve('checkAuth1') 
        },1000)
    })
}
const checkAddress = () => {
    return new Promise((resolve)=>{
        setTimeout(()=>{
            resolve('checkAddress2')
        },2000)
    })
}
var steps = [checkAuth,checkAddress]
function* foo(checkList) {
  for (let i = 0; i < checkList.length; i++) {
    let x = yield checkList[i]();
    console.log(x);
  }
}
var stepsGen = foo(steps)
var run = async (gen)=>{
    var isFinnish = false
    do{
       const {done,value} = gen.next()
       console.log('done:',done)
       console.log('value:',value)
       const result = await value
       console.log('result:',result)
       
       isFinnish = done
    }while(!isFinnish)
    console.log('isFinnish:',isFinnish)
}
run(stepsGen)

種類對比

從時間維度從早到晚:callback,promise, generator,await/async

await/async 是目前對於異步的終極形式

callback 讓我們有了基本的方式去處理異步情況,Promise 告別了 callback 的回調地獄並且增加 resolve,reject 和 catch 等方法讓我們能處理不同的情況,generator 增加了對於異步的可操作性,類似一個狀態機可暫時停住多個異步的執行,然後在合適的時候繼續執行剩餘的異步調用,await/async 讓異步調用更加語義化,並且自動執行異步

異步業務中碰到的場景

回調地獄

在使用回調函數的時候我們可能會有這樣的場景,B 需要在 A 的返回之後再繼續調用,所以在這樣有先後關係的時候就存在了一個叫回調地獄的問題了。

getData1().then((resData1) => {
  getData2(resData1).then((resData2) => {
  getData3(resData2).then((resData3)=>{
    console.log('resData3:', resData3)
    })
  });
});

碰到這樣的情況我們可以試着用 await/async 方式去解這種有多個深層嵌套的問題。

async function asyncDemoFn2() {
  const resData1 = await getData1();
  const resData2 = await getData2(resData1);
  const resData3 =  await getData3(resData2);
  console.log(resData3)
}
await asyncDemoFn2()

異步循環

在業務中我們最最經常碰到的就是其實還是存在多個異步調用的順序問題,大致上可以分為如下幾種:

并行執行

在并行執行的時候,我們可以直接使用 Promise 的 all 方法

Promise.all([getData1(),getData2(),getData3()]).then(res={
    console.log('res:',res)
})

順序執行

在順序執行中,我們可以有如下的兩種方式去做

使用 async/await 配合 for

const sources = [getData1,getData2,getData3]
async function promiseQueue() {
  console.log('開始');
  for (let targetSource in sources) {
    await targetSource();
  }
  console.log('完成');
};
promiseQueue()

使用 async/await 配合 while

//getData1,getData2,getData3 都為promise對象
const sources = [getData1,getData2,getData3]
async function promiseQueue() {
  let index = 0
  console.log('開始');
  while(index >=0 && index < sources.length){
    await targetSource();
    index++
  }
  console.log('完成');
};
promiseQueue()

使用 async/await 配合 reduce

//getData1,getData2,getData3 都為promise對象
const sources = [getData1,getData2,getData3]
sources.reduce(async (previousValue, currentValue)=>{
  await previousValue
  return currentValue()
},Promise.resolve())

使用遞歸

const sources = [getData1,getData2,getData3]
function promiseQueue(list , index = 0) {
  const len = list.length
  console.log('開始');
  if(index >= 0 && index < len){
    list[index]().then(()=>{
      promiseQueue(list, index+1)      
    })
  }
  console.log('完成');
}
promiseQueue(sources)

結尾

今天只是關於異步的普通使用場景的討論,並且做了些簡單的例子。其實關於異步的使用還有很多很多複雜的使用場景。更多的奇思妙想正等着你。

參考文獻

JS 異步編程六種方案

Async/Await替代Promise的6個理由

Javascript異步編程的4種方法

本文來自:https://zoo.team/article/asynchronization-optimizing

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9855

沐鳴娛樂業務:_開源的核心是管理和運營社區

開源越來越受歡迎

2019 年的 IDC 北美開源軟件使用調查显示,71% 的企業正在使用開源軟件,54% 的企業計劃擴大使用範圍。2020 年的 RedHat 企業開源現狀調查显示,有 95% 的 IT 領導者認為企業開源對於企業基礎架構軟件戰略至關重要。

一方面企業越來越接受開源軟件,另一方面企業也開始參与開源。現在思考的主要問題,不再是用不用,而是怎樣用好開源軟件。

可以說開源迎來了一個大時代,對協作模式、研發流程、產品發布等方方面面產生了深遠的影響。

開源的核心是管理和運營社區

開源不是公開源代碼,而是管理和運營社區。

很多關於開源的討論在強調,license、管理風險、安全風險等防守策略。對於大企業,這些無法避免,為了獲得上層的支持,卻給開源帶來了極大的負擔,最終沒有精力管理和運營社區。

社區的特徵是共識與協作。開源項目是開源社區的共識,也是開源協作的對象。社區成員質量越高、數量越多,就越能代表該領域的先進性。

在有限的人群上,可以構建無限的社區。但在無限的社區上,個人又只能投入有限的精力和時間。這就是開源項目之間爭奪的焦點。

更多高質量的人、更長時間地參与是開源項目成功的關鍵。

開源與商業並不衝突

前面說到開源主要是在管理和運營社區,而社區又具有自發性和開放性,提供了很多免費的服務。

之前的一位同事說開源就是白嫖,搭了一個框架,啥都沒有,寫了 Bug 想着社區給擦屁股。他就是站在社區的角度在思考。社區是反感被利用,被商業化的。

但,你看 Google 開源的 Kubernetes,各大廠商都有發行託管版本、如火如荼的 CKA/CKD 認證考試,也有商業化部分卻並沒有引起社區反感。這些廠商在 Kubernetes TOC 卻佔有一席之地,提交了很多 PR,促進了開源項目的發展。

開源社區的用戶也需要商業服務。在軟件生命周期內,有很多臟活累活是用戶不想參与的,都可以成為商業服務的切入點。開源與商業是相互促進與成就的。

像經營企業一樣經營開源項目

我更願意以經營企業類比開源項目。開源項目不是項目,不是一次性的。開源項目應該與企業一樣,追求永續經營。

一個企業軟件的發布是一群人共同努力的結果。需要產品經理、設計師、研發、測試、文檔、市場、運營等人員的通力合作,才能交付一個可靠的版本。

可以將整個開源協作理解為一條供應鏈,需要社區分工協作,人人參與,人人貢獻,最終完成組裝達成目標。管理開源項目,就是管理這樣一條供應鏈。

我想起一個故事:在災荒之年,能吃的都被吃了,大家聚在一起準備遷徙,但餓着肚子實在走不動。突然,有個人拿出來一塊肉,準備和大家一起吃了再走。接着,有人找來了鍋,有人在拾柴火,有人準備挑水,有人挖來了野菜,有人拿出了攢着的麵粉…… 就這樣煮出來滿滿一大鍋肉湯,大家吃飽一起上路了。

這是一個自由的群體,時刻有人離開,也會有人加入。社區治理是一個很大的話題,也是一個很老的話題,可以在社區設立子部門,分工協作;可以建立指揮中心避免分歧,一致向前。但能不能按時開飯,每次有沒有肉,很大程度在於能不能激發大家參与的积極性。

目標是貢獻者而不是 star

參与需求反饋、測試、設計、討論、提建議、文檔撰寫、宣傳、問題回復等,都可以稱之為貢獻者,而不局限於寫代碼。

star 是一個十分量化的指標,經常被用來衡量一個開源項目的受歡迎程度。我認為 DAU/MAU 也同樣適用於開源項目。

觀測開源項目應該看的是有多少貢獻者、貢獻者的質量、每天合併了多少 PR、一個 PR 的合併周期、提交了多少 Issues,一個 Issue 得到回復的等待時間。活的爛項目比死的好項目更具有持久地生命力。只要有人在社區參与貢獻,錯誤的內容終究會得到糾正。

明確目標之後,社區的很多決策就會簡單很多。貢獻者最重要,那麼你就會重視社區用戶的反饋,認真地看待用戶提出的問題,線上會議,線下走訪,深入交流,甩掉那些沒有成效的事情。

聚攏足夠多的意見領袖,高質量的貢獻者才是關鍵。

雲原生可能是個機會

我在之前的文檔中提到 現在是雲原生最好的時代 。

雲原生的技術架構具有分佈式的特點,天生有利於開源協作。也反映出,組織架構與軟件架構具有一致性。

傳統的基於單機 Linux 系統的軟件設施,正在往基於 Kubernetes 系統的軟件設施遷移。涉及整個軟件體系的方方面面,各行各業也在努力融入其中。

這是雲原生的機會,更是開源社區的機會。

本文轉載自: 陳少文的博客 。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9851

沐鳴主管:_vue項目埋點

主流埋點方案

目前主流的埋點方案包括

  1. 代碼埋點
  2. 可視化埋點
  3. 無埋點

一、代碼埋點

在需要埋點的節點調用接口,攜帶數據上傳。如百度統計等;

 缺點

工作量較大,每一個組件的埋點都需要添加相應的代碼,入侵業務代碼,增加項目複雜度。

二、可視化埋點

通過可視化交互的手段,代替代碼埋點。將業務代碼和埋點代碼分離,提供一個可視化交互的頁面,輸入為業務代碼,通過這個可視化系統,可以在業務代碼中自定義的增加埋點事件等等,最後輸出的代碼耦合了業務代碼和埋點代碼。

可視化埋點聽起來比較高大上,實際上跟代碼埋點還是區別不大。也就是用一個系統來實現手動插入代碼埋點的過程。

 缺點:

業務屬性數據,例如,訂單號、金額、商品數據量等,通常要調用後台的接口,可視化埋點在這方面的支持有限;

需要藉助第三方工具實現。

三、無埋點

無埋點並不是說不需要埋點,而是全部埋點,前端的任意一個事件都被綁定一個標識,所有的事件都別記錄下來。
通過定期上傳記錄文件,配合文件解析,解析出來我們想要的數據,
並生成可視化報告供專業人員分析因此實現“無埋點”統計。

 缺點

無法靈活的定製各個事件所需要上傳的數據

無埋點採集全量數據,給數據傳輸和服務器增加壓力

代碼埋點分類

代碼埋點分為 命令式埋點和聲明式埋點

命令式埋點

顧名思義,開發者需要手動在需要埋點的節點處進行埋點。如點擊按鈕或鏈接后的回調函數、頁面ready時進行請求的發送。大家肯定都很熟悉這樣的代碼

$(document).ready(function(){    
 
    // ... 這裏存在一些業務邏輯    
 
    sendRequest(params);  //埋點
 
});
 
 // 按鈕點擊時發送埋點請求
 
 $('button').click(function(){    
 
    // ... 這裏存在一些業務邏輯    
 
    sendRequest(params);  //埋點
 
});

聲明式埋點

聲明式埋點對命令式埋點做了改進,將埋點的代碼與具體的業務邏輯解耦。從而提高埋點的效率和代碼的可維護性。代碼如下:

// key表示埋點的唯一標識;act表示埋點方式
<button v-log="{caption:'登錄頁', paras: '用戶點擊驗證碼發送'}">發送驗證碼</button>



//自定義指令
vue.directive('log', {
 
    bind( el, binding ){
 
 
        el.addEventListener('click', ()=>{
 
            Axios.post  //發送請求
 
        })
 
    }
 
})

只需要在需要記錄的組件中配置使用v-log指令,加上詳情參數就可以完成用戶軌跡記錄。如下:

// caption表示埋點的模塊;paras表示用戶的行為
<button v-log="{caption:'登錄頁', paras: '用戶點擊驗證碼發送'}">發送驗證碼</button>

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/8336

沐鳴平台首頁_裝飾者模式和TypeScript裝飾器

導讀

本文主要為三方面的內容:

  1. 裝飾者模式的概念和使用
  2. Typescript裝飾器的使用、執行順序
  3. 編譯后的源碼分析

學習的目的是對裝飾者模式模式有進一步的理解,並運用在自己的項目中;對TypeScript裝飾器的理解,更好的使用裝飾器,例如在 nodejs web 框架中、 vue-property-decorator 中,或者是自定義裝飾器,能熟練運用並掌握其基本的實現原理。

裝飾者模式介紹

裝飾者模式(Decorator Pattern)也稱為裝飾器模式,在不改變對象自身的基礎上,動態增加額外的職責。屬於結構型模式的一種。

使用裝飾者模式的優點:把對象核心職責和要裝飾的功能分開了。非侵入式的行為修改。

舉個例子來說,原本長相一般的女孩,藉助美顏功能,也能拍出逆天的顏值。只要善於運用輔助的裝飾功能,開啟瘦臉,增大眼睛,來點磨皮后,咔嚓一拍,驚艷無比。

經過這一系列疊加的裝飾,你還是你,長相不增不減,卻能在鏡頭前增加了多重美。如果你願意,還可以嘗試不同的裝飾風格,只要裝飾功能做的好,你就能成為“百變星君”。

可以用代碼錶示,把每個功能抽象成一個類:

// 女孩子
class Girl {
  faceValue() {
    console.log('我原本的臉')
  }
}

class ThinFace  {
  constructor(girl) {
    this.girl = girl;
  }
  faceValue() {
    this.girl.faceValue();
    console.log('開啟瘦臉')
  }
}

class IncreasingEyes  {
  constructor(girl) {
    this.girl = girl;
  }
  faceValue() {
    this.girl.faceValue();
    console.log('增大眼睛')
  }
}

let girl = new Girl();
girl = new ThinFace(girl);
girl = new IncreasingEyes(girl);

// 閃瞎你的眼
girl.faceValue(); // 

從代碼的表現來看,將一個對象嵌入到另一個對象中,相當於通過一個對象對另一個對象進行包裝,形成一條包裝鏈。調用后,隨着包裝的鏈條傳遞給每一個對象,讓每個對象都有處理的機會。

這種方式在增加刪除裝飾功能上都有極大的靈活性,假如你有勇氣展示真實的臉,去掉瘦臉的包裝即可,這對其他功能毫無影響;假如要增加磨皮,再來個功能類,繼續裝飾下去,對其他功能也無影響,可以並存運行。

在 JavaScript 中增加小功能使用類,顯的有點笨重,JavaScript 的優點是靈活,可以使用對象來表示:

let girl = {
  faceValue() {
    console.log('我原本的臉')
  }
}
function thinFace() {
  console.log('開啟瘦臉')
}
function IncreasingEyes() {
  console.log('增大眼睛')
}

girl.faceValue = function(){
  const originalFaveValue = girl.faceValue;  // 原來的功能
  return function() {
    originalFaveValue.call(girl);
    thinFace.call(girl);
  }
}()
girl.faceValue = function(){
  const originalFaveValue = girl.faceValue;  // 原來的功能
  return function() {
    originalFaveValue.call(girl);
    IncreasingEyes.call(girl);
  }
}()

girl.faceValue();

在不改變原來代碼的基礎上,通過先保留原來函數,重新改寫,在重寫的代碼中調用原來保留的函數。

用一張圖來表示裝飾者模式的原理:

從圖中可以看出來,通過一層層的包裝,增加了原先對象的功能。

TypeScript中的裝飾器

TypeScript 中的裝飾器使用 @expression 這種形式,expression 求值後為一個函數,它在運行時被調用,被裝飾的聲明信息會被做為參數傳入。

Javascript規範里的裝飾器目前處在 建議徵集的第二階段,也就意味着不能在原生代碼中直接使用,瀏覽器暫不支持。

可以通過 babel 或 TypeScript 工具在編譯階段,把裝飾器語法轉換成瀏覽器可執行的代碼。(最後會有編譯后的源碼分析)

以下主要討論 TypeScript 中裝飾器的使用。

TypeScript 中的裝飾器可以被附加到類聲明、方法、 訪問符(getter/setter)、屬性和參數上。

開啟對裝飾器的支持,命令行 編譯文件時:

tsc --target ES5 --experimentalDecorators test.ts

配置文件 tsconfig.json

{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

裝飾器的使用

裝飾器實際上就是一個函數,在使用時前面加上 @ 符號,寫在要裝飾的聲明之前,多個裝飾器同時作用在一個聲明時,可以寫一行或換行寫:

// 換行寫
@test1
@test2
declaration

//寫一行
@test1 @test2 ...
declaration

定義 face.ts 文件:

function thinFace() {
  console.log('開啟瘦臉')
}

@thinFace
class Girl {
}

編譯成 js 代碼,在運行時,會直接調用 thinFace 函數。這個裝飾器作用在類上,稱之為類裝飾器。

如果需要附加多個功能,可以組合多個裝飾器一起使用:

function thinFace() {
  console.log('開啟瘦臉')
}
function IncreasingEyes() {
  console.log('增大眼睛')
}

@thinFace
@IncreasingEyes
class Girl {
}

多個裝飾器組合在一起,在運行時,要注意,調用順序是 從下至上 依次調用,正好和書寫的順序相反。例子中給出的運行結果是:

'增大眼睛'
'開啟瘦臉'

如果你要在一個裝飾器中給類添加屬性,在其他的裝飾器中使用,那就要寫在最後一個裝飾器中,因為最後寫的裝飾器最先調用。

裝飾器工廠

有時需要給裝飾器傳遞一些參數,這要藉助於裝飾器工廠函數。裝飾器工廠函數實際上就是一個高階函數,在調用后返回一個函數,返回的函數作為裝飾器函數。

function thinFace(value: string){
  console.log('1-瘦臉工廠方法')
  return function(){
    console.log(`4-我是瘦臉的裝飾器,要瘦臉${value}`)
  }
}
function IncreasingEyes(value: string) {
  console.log('2-增大眼睛工廠方法')
  return function(){
    console.log(`3-我是增大眼睛的裝飾器,要${value}`)
  }
}

@thinFace('50%')
@IncreasingEyes('增大一倍')
class Girl {
}

@ 符號後為調用工廠函數,依次從上到下執行,目的是求得裝飾器函數。裝飾器函數的運行順序依然是從下到上依次執行。

運行的結果為:

1-瘦臉工廠方法
2-增大眼睛工廠方法
3-我是增大眼睛的裝飾器,要增大一倍
4-我是瘦臉的裝飾器,要瘦臉50%

總結一下:

  1. 寫了工廠函數,從上到下依次執行,求得裝飾器函數。
  2. 裝飾器函數的執行順序是 從下到上 依次執行。

類裝飾器

作用在類聲明上的裝飾器,可以給我們改變類的機會。在執行裝飾器函數時,會把類構造函數傳遞給裝飾器函數。

function classDecorator(value: string){
  return function(constructor){
    console.log('接收一個構造函數')
  }
}

function thinFace(constructor){
  constructor.prototype.thinFaceFeature = function() {
    console.log('瘦臉功能')
  }
}

@thinFace
@classDecorator('類裝飾器')
class Girl {}

let g = new Girl();

g.thinFaceFeature(); // '瘦臉功能'

上面的例子中,拿到傳遞構造函數后,就可以給構造函數原型上增加新的方法,甚至也可以繼承別的類。

方法裝飾器

作用在類的方法上,有靜態方法和原型方法。作用在靜態方法上,裝飾器函數接收的是類構造函數;作用在原型方法上,裝飾器函數接收的是原型對象。
這裏拿作用在原型方法上舉例。


function methodDecorator(value: string, Girl){
  return function(prototype, key, descriptor){
    console.log('接收原型對象,裝飾的屬性名,屬性描述符', Girl.prototype === prototype)
  }
}

function thinFace(prototype, key, descriptor){
  // 保留原來的方法邏輯
  let originalMethod = descriptor.value;
  // 改寫,增加邏輯,並執行原有邏輯
  descriptor.value = function(){
    originalMethod.call(this);  // 注意修改this的指向
    console.log('開啟瘦臉模式')
  }
}

class Girl {

  @thinFace
  @methodDecorator('方式裝飾器', Girl)
  faceValue(){
    console.log('我是原本的面目')
  }
}

let g = new Girl();

g.faceValue();

從代碼中可以看出,裝飾器函數接收三個參數,原型對象、方法名、描述對象。對描述對象陌生的,可以參考 這裏;

要增強功能,可以先保留原來的函數,改寫描述對象的 value 為另一函數。

當使用 g.faceValue() 訪問方法時,訪問的就是描述對象 value 對應的值。

在改寫的函數中增加邏輯,並執行原來保留的原函數。注意原函數要用 call 或 apply 將 this 指向原型對象。

屬性裝飾器

作用在類中定義的屬性上,這些屬性不是原型上的屬性,而是通過類實例化得到的實例對象上的屬性。

裝飾器同樣會接受兩個參數,原型對象,和屬性名。而沒有屬性描述對象,為什麼呢?這與TypeScript是如何初始化屬性裝飾器的有關。 目前沒有辦法在定義一個原型對象的成員時描述一個實例屬性。

function propertyDecorator(value: string, Girl){
  return function(prototype, key){
    console.log('接收原型對象,裝飾的屬性名,屬性描述符', Girl.prototype === prototype)
  }
}

function thinFace(prototype, key){
  console.log(prototype, key)
}

class Girl {
  @thinFace
  @propertyDecorator('屬性裝飾器', Girl)
  public age: number = 18;
}

let g = new Girl();

console.log(g.age); // 18

其他裝飾器的寫法

下面組合多個裝飾器寫在一起,出了上面提到的三種,還有 訪問符裝飾器、參數裝飾器。這些裝飾器在一起時,會有執行順序。


function classDecorator(value: string){
  console.log(value)
  return function(){}
}
function propertyDecorator(value: string) {
  console.log(value)
  return function(){
    console.log('propertyDecorator')
  }
}
function methodDecorator(value: string) {
  console.log(value)
  return function(){
    console.log('methodDecorator')
  }
}
function paramDecorator(value: string) {
  console.log(value)
  return function(){
    console.log('paramDecorator')
  }
}
function AccessDecorator(value: string) {
  console.log(value)
  return function(){
    console.log('AccessDecorator')
  }
}
function thinFace(){
  console.log('瘦臉')
}
function IncreasingEyes() {
  console.log('增大眼睛')
}


@thinFace
@classDecorator('類裝飾器')
class Girl {
  @propertyDecorator('屬性裝飾器')
  age: number = 18;
  
  @AccessDecorator('訪問符裝飾器')
  get city(){}

  @methodDecorator('方法裝飾器')
  @IncreasingEyes
  faceValue(){
    console.log('原本的臉')
  }

  getAge(@paramDecorator('參數裝飾器') name: string){}
}

運行了這段編譯后的代碼,會發現這些訪問器的順序是,屬性裝飾器 -> 訪問符裝飾器 -> 方法裝飾器 -> 參數裝飾器 -> 類裝飾器。

更詳細的用法可以參考官網文檔:https://www.tslang.cn/docs/handbook/decorators.html#decorator-factories

裝飾器運行時代碼分析

裝飾器在瀏覽器中不支持,沒辦法直接使用,需要經過工具編譯成瀏覽器可執行的代碼。

分析一下通過工具編譯后的代碼。

生成 face.js 文件:

tsc --target ES5 --experimentalDecorators face.ts

打開 face.js 文件,會看到一段被壓縮后的代碼,可以格式化一下。

先看這段代碼:

__decorate([
    propertyDecorator('屬性裝飾器')
], Girl.prototype, "age", void 0);
__decorate([
    AccessDecorator('訪問符裝飾器')
], Girl.prototype, "city", null);
__decorate([
    methodDecorator('方法裝飾器'),
    IncreasingEyes
], Girl.prototype, "faceValue", null);
__decorate([
    __param(0, paramDecorator('參數裝飾器'))
], Girl.prototype, "getAge", null);
Girl = __decorate([
    thinFace,
    classDecorator('類裝飾器')
], Girl);

__decorate 的作用就是執行裝飾器函數,從這段代碼中能夠看出很多信息,印證上面得到的結論。

通過__decorate調用順序,可以看出來,多個類型的裝飾器一起使用時,順序是,屬性裝飾器 -> 訪問符裝飾器 -> 方法裝飾器 -> 參數裝飾器 -> 類裝飾器。

調用了 __decorate 函數,根據使用的裝飾器類型不同,傳入的參數也不相同。

第一個參數傳入的都一樣,為數組,這樣確保和我們書寫的順序一致,每一項是求值后的裝飾器函數,如果寫的是 @propertyDecorator() 則一上來就執行,得到裝飾器函數,這跟上面分析的一致。

類裝飾器會把類作為第二個參數,其他的裝飾器,把原型對象作為第二個參數,屬性名作為第三個,第四個是 null 或 void 0void 0的值為undefined,也就等於沒傳參數

要記住傳給 __decorate 函數參數的個數和值,在深入到 __decorate 源碼中, 會根據這些值來決定執行裝飾器函數時,傳入參數的多少。

好,來看 __decorate 函數實現:

// 已存在此函數,直接使用,否則自己定義
var __decorate = (this && this.__decorate) ||
// 接收四個參數: 
//decorators存放裝飾器函數的數組、target原型對象|類,
//key屬性名、desc描述(undefined或null)
function(decorators, target, key, desc) {
  var c = arguments.length,
  // 拿到參數的個數
  r = c < 3 // 參數小於三個,說明是類裝飾器,直接拿到類
    ? target
    : desc === null // 第四個參數為 null,則需要描述對象;屬性裝飾器傳入是  void 0,沒有描述對象。
        ? desc = Object.getOwnPropertyDescriptor(target, key) 
        : desc,
  d;
  // 如果提供了Reflect.decorate方法,直接調用;否則自己實現
  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 // 參數小於3個,說明是類裝飾器,執行裝飾器函數,直接傳入類
            ? d(r) 
            : c > 3 // 參數大於三個,是方法裝飾器、訪問符裝飾器、參數裝飾器,則執行傳入描述對象
              ? d(target, key, r) 
              : d(target, key) // 為屬性裝飾器,不傳入描述對象
            ) || r;

  // 給被裝飾的屬性,設置得到的描述對象,主要是針對,方法、屬性來說的
  /*** 
     * r 的值分兩種情況,
     *  一種是通過上面的 Object.getOwnPropertyDescriptor 得到的值
     *  另一種,是裝飾器函數執行后的返回值,作為描述對象。
     *      一般不給裝飾器函數返回值。
    */
  return c > 3 && r && Object.defineProperty(target, key, r),r;
};

上面的參數裝飾器,調用了一個函數為 __params

var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};

目的是,要給裝飾器函數傳入參數的位置 paramIndex

看了編譯后的源碼,相信會對裝飾器的理解更深刻。

以上如有偏差歡迎指正學習,謝謝。~~~~

github博客地址:https://github.com/WYseven/blog,歡迎star。

如果對你有幫助,請關注【前端技能解鎖】

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/8720

沐鳴平台註冊登錄_git提交代碼commit失敗的解決方法

原因:

不同操作系統下處理行尾結束符的方法是不同的,windows下是CRLF(句尾使用回車和換行),unix下是LF(只換行),mac下是CR(只回車)  

方法1:

把編輯器中Git中Warn if CRLF line separators are about to be committed取消勾選,就可以提交了;  

方法2:

GIt在安裝的時候,Configuring the line ending conversions選第三個,Checkout as-is,commit as-is 

 

方法3:

Git命令行面板裏面,輸入git config –global core.autocrlf false  

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/8715

沐鳴註冊_Git 如何優雅地回退代碼? | 案例分析

枕邊書 | 作者

承香墨影 | 校對

https://www.cnblogs.com/zhenbianshu/p/12018714.html | 原文

今天給大家推薦一篇 Git 回滾代碼的案例文章,相信大家在日常工作中,也會面臨突發情況,需要緊急回滾到之前某個版本的代碼,那本篇文章就有了一定的參考意義。另外一個規範的 Gitflow 工作流程,也是可以幫助我們在回滾的時候節省太多的功能,否則分支提交錯綜複雜,可能在新版本上直接修改代碼會更簡單。

前言

從接觸編程就開始使用 Git 進行代碼管理,先是自己玩 Github,又在工作中使用 Gitlab,雖然使用時間挺長,可是也只進行一些常用操作,如推/拉代碼、提交、合併等,更複雜的操作從沒使用過,看過的教程也逐漸淡忘了,有些對不起 Linus 大神。

出來混總是要還的,前些天就遇到了 Git 里一種十分糟心的場景,併為之前沒有深入理解 Git 命令,付出了一下午時間的代價。

先介紹一下這種場景,我們一個項目從 N 版本升到 A 版本時引入了另一項目的 jar 包,又陸續發布了 B、C 版本,但在 C 版本后忽然發現了 A 版本引入的 jar 包有極大的性能問題,B、C 版本都是基於 A 版本發布的,要修復 jar 包性能問題,等 jar 包再發版還得幾天,可此時線上又有緊急的 Bug 要修,於是就陷入了進退兩難的境地。

最後決定先將代碼回退到 A 版本之前,再基於舊版本修復 Bug,也就開始了五個小時的受苦之路。

基礎試探

revert

首先肯定的是 revert, git revert commit_id 能產生一個 與 commit_id 完全相反的提交,即 commit_id 里是添加, revert 提交里就是刪除。

但是使用 git log 查看了提交記錄后,我就打消了這種想法,因為提交次數太多了,中途還有幾次從其他分支的 merge 操作。”得益於” 我們不太乾淨的提交記錄,要完成從 C 版本到 N 版本的 revert,我需要倒序執行 revert 操作幾十次,如果其中順序錯了一次,最終結果必然是不符合預期的。

另外我們知道我們在進行代碼 merge 時,也會把 merge 信息產生一次新的提交,而 revert 這次 merge commit 時需要指定 m 參數,即指定 mainline ,這個 mainline 是主線,也是我們要保留代碼的主分支,從 feature 分支往 develop 分支合併,或由 develop 分支合併到 master 的提交還好確定,但 feature 分支互相合併時,我那裡知道那個是主線啊。

所以 revert 的方案被廢棄了。

Reset

然後就考慮 reset 了, reset 也能使代碼回到某次提交,但跟 revert 不同的是, reset 是將提交的 HEAD 指針指到某次提交,之後的提交記錄會丟棄,就像從沒有過這麼一次提交。

但由於我們都在 feature 分支開發,我在 feature 分支上將代碼回退到某次提交后,將其合併到 develop 分支時卻被提示報錯。這是因為 feature 分支回退了提交后,在 git 的 workflow 里,feature 分支是落後於 develop 分支的,而合併向 develop 分支,又需要和 develop 分支保持最新的同步,需要將 develop 分支的數據合併到 feature 分支上,而合併后,原來被 reset 的代碼又回來了。

這個時候另一個可選項是在 master 分支上執行 reset,使用 –hard 選項完全拋棄這些舊代碼,reset 后再強制推到遠端。

master> git reset --hard commit_id
master> git push --force origin master

但還是有問題,首先,我們的 master 分支在 gitlab 里是被保護的,不能使用 force push,畢竟風險挺大了,萬一有人 reset 到最開始的提交再強制 push 的話,雖然可以使用 reflog 恢復,但也是一番折騰。

另外,reset 畢竟太野蠻,我們還是想能保留提交歷史,以後排查問題也可以參考。

升級融合

rebase

只好用搜索引擎繼續搜索,看到有人提出可以先使用 rebase 把多個提交合併成一個提交,再使用 revert 產生一次反提交,這種方法的思路非常清晰,把 revert 和 rebase 兩個命令搭配得很好,相當於使用 revert 回退的升級版。

先說一下 rebase,rebase 是 “變基” 的意思,這裏的 “基”,在我理解是指 「多次」 commit 形成的 git workflow,使用 rebase,我們可以改變這些歷史提交,修改 commit 信息,將多個 commit 進行組合。

介紹 rebase 的文檔有很多,我們直接來說用它來進行代碼回退的步驟。

首先,切出一個新分支 F,使用 git log 查詢一下要回退到的 commit 版本 N。

使用命令 git rebase -i N , -i 指定交互模式后,會打開 git rebase 編輯界面,形如:

pick 6fa5869 commit1
pick 0b84ee7 commit2
pick 986c6c8 commit3
pick 91a0dcc commit4

這些 commit 自舊到新由上而下排列,我們只需要在 commit_id 前添加操作命令即可,在合併 commit 這個需求里,我們可以選擇 pick(p) 最舊的 commit1,然後在後續的 commit_id 前添加  squash(s) 命令,將這些 commits 都合併到最舊的 commit1 上。

保存 rebase 結果后,再編輯 commit 信息,使這次 rebase 失效,git 會將之前的這些 commit 都刪除,並將其更改合併為一個新的 commit5,如果出錯了,也可以使用 git rebase –abort/–continue/–edit-todo 對之前的編輯進行撤銷、繼續編輯。

這個時候,主分支上的提交記錄是 older, commit1, commit2, commit3, commit4 ,而 F 分支上的提交記錄是  older, commit5 ,由於 F 分支的祖先節點是 older,明顯落後於主分支的 commit4,將 F 分支向主分支合併是不允許的,所以我們需要執行  git merge master 將主分支向 F 分支合併,合併后 git 會發現 commit1 到 commit4 提交的內容和 F 分支上 commit5 的修改內容是完全相同的,會自動進行合併,內容不變,但多了一個 commit5。

再在 F 分支上對 commit5 進行一次 revert 反提交,就實現了把 commit1 到 commit4 的提交全部回退。

這種方法的取巧之處在於巧妙地利用了 rebase 操作歷史提交的功能,和 git 識別修改相同自動合併的特性,操作雖然複雜,但歷史提交保留得還算完整。

rebase 這種修改歷史提交的功非常實用,能夠很好地解決我們遇到的一個小功能提交了好多次才好使,而把 git 歷史弄得亂七八糟的問題,只需要注意避免在多人同時開發的分支使用就行了。

遺憾的是,當天我並沒有理解到 rebase 的這種思想,又由於試了幾個方法都不行太過於慌亂,在 rebase 完成后,向主分支合併被拒之後對這些方式的可行性產生了懷疑,又加上有同事提出聽起來更可行的方式,就中斷了操作。

文件操作

這種更可行的方式就是對文件操作,然後讓 git 來識別變更,具體是:

從主分支上切出一個跟主分支完全相同的分支 F。

從文件管理系統複製項目文件夾為 bak,在 bak 內使用  git checkout N 將代碼切到想要的歷史提交,這時候 git 會將 bak 內的文件恢復到 N 狀態。

在從文件管理系統內,將 bak 文件夾下  除了 .git 文件夾下的所有內容複製粘貼到原項目目錄下。git 會純從文件級別識別到變更,然後更新工作區。

在原項目目錄下執行  add 和 commit ,完成反提交。

這種方式的巧妙之處在於利用 git 本身對文件的識別,不牽涉到對 workflow 操作。

小結

最後終於靠着文件操作方式成功完成了代碼回退,事後想來真是一把心酸淚。

為了讓我的五個小時不白費,復盤一下當時的場景,學習並總結一下四種代碼回退的方式:

revert 適合需要回退的歷史提交不多,且無合併衝突的情景。

如果你可以向 master 強推代碼,且想讓 git log 里不再出現被回退代碼的痕迹,可以使用  git reset –hard + git push –force 的方式。

如果你有些 geek 精神,追求用 “正規而正統” 的方式來回退代碼,rebase + revert 滿足你的需求。

如果你不在乎是否優雅,想用最簡單,最直接的方式,文件操作正合適。

git 真的是非常牛逼的代碼管理工具,入手簡單,三五個命令組合起來就足夠完成工作需求,又對 geeker 們非常友好,你想要的騷操作它都支持,學無止境啊。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/8347

沐鳴娛樂業務:_徹底弄懂GMT、UTC、時區和夏令時

前言

格林威治時間、世界時、祖魯時間、GMT、UTC、跨時區、夏令時,這些眼花繚亂的時間術語,我們可能都不陌生,但是真正遇到問題,可能又不那麼確定,不得不再去查一查,處理完可能過段時間又忘記。今天,我們徹底來梳理一下它們。

一、GMT

什麼是GMT

GMT(Greenwich Mean Time), 格林威治平時(也稱格林威治時間)。

它規定太陽每天經過位於英國倫敦郊區的皇家格林威治天文台的時間為中午12點。

GMT的歷史

格林威治皇家天文台為了海上霸權的擴張計劃,在十七世紀就開始進行天體觀測。為了天文觀測,選擇了穿過英國倫敦格林威治天文檯子午儀中心的一條經線作為零度參考線,這條線,簡稱格林威治子午線。

1884年10月在美國華盛頓召開了一個國際子午線會議,該會議將格林威治子午線設定為本初子午線,並將格林威治平時 (GMT, Greenwich Mean Time) 作為世界時間標準(UT, Universal Time)。由此也確定了全球24小時自然時區的劃分,所有時區都以和 GMT 之間的偏移量做為參考。

1972年之前,格林威治時間(GMT)一直是世界時間的標準。1972年之後,GMT 不再是一個時間標準了。

二、UTC

什麼是UTC

UTC(Coodinated Universal Time),協調世界時,又稱世界統一時間、世界標準時間、國際協調時間。由於英文(CUT)和法文(TUC)的縮寫不同,作為妥協,簡稱UTC。

UTC 是現在全球通用的時間標準,全球各地都同意將各自的時間進行同步協調。UTC 時間是經過平均太陽時(以格林威治時間GMT為準)、地軸運動修正後的新時標以及以秒為單位的國際原子時所綜合精算而成。

在軍事中,協調世界時會使用“Z”來表示。又由於Z在無線電聯絡中使用“Zulu”作代稱,協調世界時也會被稱為”Zulu time”。

UTC 由兩部分構成:

  • 原子時間(TAI, International Atomic Time):
    結合了全球400個所有的原子鍾而得到的時間,它決定了我們每個人的鐘錶中,時間流動的速度。
  • 世界時間(UT, Universal Time):
    也稱天文時間,或太陽時,他的依據是地球的自轉,我們用它來確定多少原子時,對應於一個地球日的時間長度。

UTC的歷史

1960年,國際無線電諮詢委員會規範統一了 UTC 的概念,並在次年投入實際使用。

“Coordinated Universal Time”這個名字則在1967年才被正式採納。

1967年以前, UTC被數次調整過,原因是要使用閏秒(leap second)來將 UTC 與地球自轉時間進行統一。

三、GMT vs UTC

GMT是前世界標準時,UTC是現世界標準時。
UTC 比 GMT更精準,以原子時計時,適應現代社會的精確計時。
但在不需要精確到秒的情況下,二者可以視為等同。
每年格林尼治天文台會發調時信息,基於UTC。

四、時區

隨着火車鐵路與其他交通和通訊工具的發展,以及全球化貿易的推動,各地使用各自的當地太陽時間帶來了時間不統一的問題,在19世紀催生了統一時間標準的需求,時區由此誕生。

時區是如何定義的

從格林威治本初子午線起,經度每向東或者向西間隔15°,就劃分一個時區,在這個區域內,大家使用同樣的標準時間。

但實際上,為了照顧到行政上的方便,常將1個國家或1個省份劃在一起。所以時區並不嚴格按南北直線來劃分,而是按自然條件來劃分。另外:由於目前,國際上並沒有一個批准各國更改時區的機構。一些國家會由於特定原因改變自己的時區。

全球共分為24個標準時區,相鄰時區的時間相差一個小時。

在不同地區,同一個時區往往會有很多個不同的時區名稱,因為名稱中通常會包含該國該地區的地理信息。在夏令時期間,當地的時區名稱及字母縮寫會有所變化(通常會包含“daylight”或“summer”字樣)。

例如美國東部標準時間叫:EST,Estern Standard Time;而東部夏令時間叫:EDT,Estern Daylight Time。

想查看世界所有時區的名字可以訪問這個網站:https://www.timeanddate.com/t…

四、夏令時

什麼是夏令時

DST(Daylight Saving Time),夏令時又稱夏季時間,或者夏時制。

它是為節約能源而人為規定地方時間的制度。一般在天亮早的夏季人為將時間提前一小時,可以使人早起早睡,減少照明量,以充分利用光照資源,從而節約照明用電。

全球約40%的國家在夏季使用夏令時,其他國家則全年只使用標準時間。標準時間在有的國家也因此被相應地稱為冬季時間。

在施行夏令時的國家,一年裡面有一天只有23小時(夏令時開始那一天),有一天有25小時(夏令時結束那一天),其他時間每天都是24小時。

綠色部分為2019年統計的在全球施行冬夏令時的國家和地區。

夏令時的歷史

1784年,美國駐法國大使本傑明·富蘭克林(Benjamin Franklin)提出“日光節約時間制”。1908年,英國建築師威廉·維萊特(William Willett)再次提出,但當時該提案並未被採納。

1916年,處於一戰時期的德國政府下令將時鐘推至一個小時后,通過獲得額外一小時的日光來節省戰爭所需的煤炭,成為第一個實行夏時制的國家。隨後,英法俄美四個一戰參戰國紛紛效仿。

美國在一戰結束後於1919年取消夏時制,但在1942年二戰時,美國重新啟動夏令時制,1966年正式立法確定永久使用。1973至1975年石油危機爆發期間,美國連續兩年延長夏令時制,以節省石油。

歐洲大部分國家則是從1976年——第四次中東戰爭導致首次石油危機(1973年)的3年後才開始施行夏令時制。

1986年4月,中國國務院辦公廳發出《在全國範圍內實行夏時制的通知》,要求全民早睡早起節約能源:每年4月中旬的第一個星期日2時,將時鐘撥快一小時;10月中旬第一個星期日的2時,再將時鐘撥慢一小時。但此夏令時只實行了6年,在1992年停止施行,主因是中國東西地域廣闊卻只奉行一個北京時間,實時夏令時制帶來很多不切實際的反效果。

夏令時的爭議

從過去的100多年來看,夏令時往往是在國家發生嚴重危機(如戰爭和能源短缺)的情況下才會受到青睞。而在相對和平的近10年裡,這種時間制度則變得越來越不受歡迎。

它會使得人們的生物鐘被擾亂,常常陷入睡眠不足的情況,不僅對人體健康有害、導致車禍,還會對旅遊、航空領域造成極大的混亂。

另外,冬、夏令時究竟能否起到節能的作用,也仍有待商榷。美國一項截至2014年3月的研究表明,這種時間轉換制度最多能在3、4月幫助美國減少1%的用電量,而美國國家標準局則認為,夏令時對用電量沒有絲毫影響。

在俄羅斯,此前的一份報告也显示,夏令時幫助俄羅斯每年節約的電量,僅相當於兩三個火力發電廠的發電量,十分的“雞肋”。

去年(2019年)3月26日,作為全世界第一個提出並實行夏令時的國家,德國,在歐洲議會上以410比192的贊成票通過了取消冬、夏令時轉換制提案,擬定於2021年4月起,所有歐盟國家不再實行冬、夏令時轉換。待各成員國形成最終法案后,將選擇永久使用夏令時時間或是冬令時時間。

五、本地時間

在日常生活中所使用的時間我們通常稱之為本地時間。這個時間等於我們所在(或者所使用)時區內的當地時間,它由與世界標準時間(UTC)之間的偏移量來定義。這個偏移量可以表示為 UTC- 或 UTC+,後面接上偏移的小時和分鐘數。

六、JavaScript中的Date

得到本地時間,在不同時區打印 new Date() ,輸出的結果將會不一樣:

new Date();

得到本地時間距 1970年1月1日午夜(GMT時間)之間的毫秒數:

new Date().getTime();

返回本地時間與 GMT 時間之間的時間差,以分鐘為單位:

new Date().getTimezoneOffset();

如何在任何地方都能正確显示當地時間(只要知道該地的timezone):

//目標表時間,東八區
let timezone = 8;
//獲取本地時間與格林威治時間的時間差(注意是分鐘,記得轉換)
const diff = new Date().getTimezoneOffset();
//根據本地時間和時間差獲得格林威治時間
const absTime = new Date().getTime() + diff * 60 * 1000;
//根據格林威治時間和各地時區,得到各地時區的時間
let localTime = new Date(absTime + timeZone * 60 * 60 * 1000);
//處理夏令時(isDST為自己封裝的處理方法)
if(isDST(localTime, country)) {
  localTime = new Date(absTime + (timeZone + 1) * 60 * 60 * 1000);
}
return localTime;

結語

以上分別從定義、來源等維度解釋和擴展說明了GMT、UTC、時區和夏令時的概念、歷史、意義,並在最後列舉了這些概念在js項目中的一個非常實用的應用。

簡單地講, GMT 是以前的世界時間標準;UTC 是現在在使用的世界時間標準;時區是基於格林威治子午線來偏移的,往東為正,往西為負;夏令時是地方時間制度,施行夏令時的地方,每年有2天很特殊(一天只有23個小時,另一天有25個小時)。

從源頭上徹底了解了這些概念,將會讓我們在處理與時間相關的問題時如虎添翼。

文章同時發表於公眾號「前端手札」,喜歡的話可以關注一下哦。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/8731

沐鳴怎麼當代理?_JS幾種生成唯一id的方法

在開發中偶爾會遇到需要生成唯一id的時候,比如對數組的任意一項進行多次增刪改,這時就需要給每一項添加唯一標識符來加以區分。以下便是從網絡中搜集而來的各種生成唯一標識的方法,在此總結以供以後查閱。

方法1

function uuid() {
    var s = [];
    var hexDigits = "0123456789abcdef";
    for (var i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
    }
    s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
    s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
    s[8] = s[13] = s[18] = s[23] = "-";
 
    var uuid = s.join("");
    return uuid;
}

方法2

function guid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = Math.random() * 16 | 0,
            v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

方法3

 

function guid2() {
    function S4() {
        return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
    }
    return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
}

方法4

/*
    指定長度和基數
*/
function uuid2(len, radix) {
    var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
    var uuid = [],
        i;
    radix = radix || chars.length;
 
    if (len) {
        // Compact form
        for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
    } else {
        // rfc4122, version 4 form
        var r;
 
        // rfc4122 requires these characters
        uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
        uuid[14] = '4';
 
        // Fill in random data.  At i==19 set the high bits of clock sequence as
        // per rfc4122, sec. 4.1.5
        for (i = 0; i < 36; i++) {
            if (!uuid[i]) {
                r = 0 | Math.random() * 16;
                uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
            }
        }
    }
 
    return uuid.join('');
}

原本以為這類方法的核心應該是要使用New Date()來生成一個唯一的隨機數的,沒想到都沒有用上。。另外我暫時使用了第二種方法,沒錯,就是代碼少。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/8727