国产gaysexchina男同gay,japanrcep老熟妇乱子伦视频,吃奶呻吟打开双腿做受动态图,成人色网站,国产av一区二区三区最新精品

JavaScript 版俄羅斯方塊——轉(zhuǎn)換為 TypeScript

2018-06-08 13:55 更新

寫 JavaScript 版俄羅斯方塊的目的是為試驗(yàn)了技術(shù)和框架。最初的版本 通過(guò) Gulp + Webpack + Babel,搭建了一個(gè) ES6 的前端構(gòu)建環(huán)境;之后的一個(gè)版本 通過(guò)重構(gòu)技術(shù)對(duì)模型部分進(jìn)行較全面的重構(gòu),同時(shí)引入了 私有成員寫法,也在重構(gòu)的過(guò)程中發(fā)現(xiàn),用 TypeScript 來(lái)寫腳本是個(gè)比較好的選擇。

下面就開(kāi)始把 主要工作分支 working 切換為 TypeScript 腳本。


傳送門


引入 TypeScript 環(huán)境

安裝 TypeScript

如果沒(méi)有 安裝 TypeScript,首先肯定是要安裝的。TypeScript 我也不是第一次用,這次主要是用新發(fā)布的 2.0 版本嘗試一下新特性。

用 NPM 安裝 TypeScript,這在 Visual Studio Code 中會(huì)用到,最新版是 2.0.3,所以安裝的時(shí)候不用加版本標(biāo)簽了。

npm install typescript

配置 Visual Studio Code

之前有人問(wèn) tsc 編譯器 2.0.3 與 VScode 代碼語(yǔ)言服務(wù) 1.8.10 版本不匹配 怎么解決,這里我已經(jīng)回答過(guò)一次如何配置 VSCode 的語(yǔ)言服務(wù),這里再簡(jiǎn)單的描述一下。

根據(jù) VSCode 官方文檔,需要配置 "typescript.tsdk" 參數(shù),可以在全局 settings.json 中配置,也可以僅為 VSCode 項(xiàng)目配置(.vscode/settings.json)。

首先是找到 TypeScript 安裝的位置,用 npm list -g typescript 命令:

$ npm list -g typescript
C:\Users\james\AppData\Roaming\npm
+-- typescript@2.0.3
`-- typings@1.3.3
  `-- typings-core@1.4.1
    `-- typescript@1.8.7

npm 的位置是 C:/Users/james/AppData/Roaming/npm,后面拼上 node_modules/typescript/lib 就是 TypeScript 語(yǔ)言服務(wù)和庫(kù)的位置了,所以完整的位置是

C:/Users/james/AppData/Roaming/npm/node_modules/typescript/lib

為項(xiàng)目引入 TypeScript

之前已經(jīng)提到,前端項(xiàng)目的源碼是放在 src 目錄下,所以從控制臺(tái)進(jìn)入 src 項(xiàng)目。如果 VSCode 安裝了 Start any shell 插件,可以直接在 VSCode 中打開(kāi),我個(gè)人比較喜歡用 Git Bash。

在 src 目錄下使用 tsc -init 命令,tsc(TypeScript CLI)會(huì)創(chuàng)建 tsconfig.json 配置文件?;旧喜挥酶?,但是需要我們加入 "outFile" 選項(xiàng)指定輸出目錄:

{
    "compilerOptions": {
        "target": "es5",
        "noImplicitAny": false,
        "sourceMap": true,
        "removeComments": true,
        "outFile": "../js/tetris.js"
    },
    "include": [
        "scripts/**/*"
    ]
}

配置好之后直接在 src 目錄下就可以通過(guò)命令 tsc 編譯 ts 腳本。不過(guò)這里還是準(zhǔn)備用 gulp 來(lái)統(tǒng)一構(gòu)建,所以配置一下 npm 項(xiàng)目(package.json)。

因?yàn)椴恍枰幾g ES6 的 JavaScript,webpack 和 babel 暫時(shí)不需要了,所以一并 uninstall 掉。保持開(kāi)發(fā)環(huán)境和源碼干凈是個(gè)好習(xí)慣。

npm install gulp-typescript
npm uninstall babel-core babel-loader babel-preset-es2015 webpack

隨后修改 gulpfile.js,刪除 webpack 任務(wù),添加 typescript 任務(wù)

gulp.task("typescript", callback => {
    const ts = require("gulp-typescript");
    const tsProj = ts.createProject("tsconfig.json");
    const result = tsProj.src()
        .pipe(sourcemaps.init())
        .pipe(tsProj());
    return result.js
        .pipe(sourcemaps.write("../js", {
            sourceRoot: "../src/scripts"
        }))
        .pipe(gulp.dest("../js"));
});

配置 gulp-typescript 和 sourcemap 還是花了些時(shí)間試驗(yàn)。sourcemap 是參照 less 任務(wù)的配置進(jìn)行了,試驗(yàn)過(guò)程中發(fā)現(xiàn)路徑配置略有不同,根據(jù)試驗(yàn)結(jié)果修正即可。

到此環(huán)境基本上就搭好了

JavaScript → TypeScript

雖然說(shuō) TypeScript 是 JavaScript 的超級(jí),理論上來(lái)說(shuō)只需要把 .js 更名 為 .ts 就能完成 JavaScript 到 TypeScript 的轉(zhuǎn)換。用 git mv x.js x.ts 把文件名一個(gè)個(gè)改完之后,發(fā)現(xiàn)并不是想像的這么簡(jiǎn)單,編譯結(jié)果有一大堆錯(cuò)誤提示。

GIT 不熟,所以不知道如何批量重命名,只好用 git mv 一個(gè)人重命名了,希望 GIT 高手能指點(diǎn)一二

當(dāng)時(shí)也沒(méi)去細(xì)想,直接就把代碼改成了以前習(xí)慣的 ts 文件結(jié)構(gòu),用命名空間把代碼都包了一層?,F(xiàn)在想來(lái),有可能是因?yàn)?"target": "es5" 這個(gè)選項(xiàng)的原因,畢竟之前的 JS 源碼中用了 ES6 的模塊語(yǔ)法,而 TypeScript 雖然可以把 ES6 模塊語(yǔ)法轉(zhuǎn)換成 AMD 或者 System 等模塊語(yǔ)法,卻需要配置。

另外,TypeScript 所有類的數(shù)據(jù)成員(字段,F(xiàn)ield)需要提前申明。這也是造成編譯不能通過(guò)的原因之一。

仍然以最小的 Point 為例,看看改造結(jié)果

namespace tetris.model {
    export interface IPoint {
        x: number;
        y: number;
    }

    export class Point {
        private _x: number;
        private _y: number;

        constructor(point: IPoint);
        constructor(x: number, y: number);
        constructor(x: any, y?: number) {
            if (y === void 0) {
                this._x = x.x;
                this._y = x.y;
            } else {
                this._x = x;
                this._y = y;
            }
        }

        get x(): number {
            return this._x;
        }

        get y(): number {
            return this._y;
        }

        set(x: number = this._x, y: number = this._y) {
            this._x = x;
            this._y = y;
        }

        move(offsetX: number = 0, offsetY: number = 0): Point {
            return new Point(this.x + offsetX, this.y + offsetY);
        }
    }
}

這段代碼用到了命名空間、接口、類、私有屬性、重載(overload) 等語(yǔ)言特性,僅于篇幅,就不詳述了,TypeScript Documentation 中有詳細(xì)的教程。

TypeScript 提供了 private 關(guān)鍵字,但最終轉(zhuǎn)換出來(lái)的 JavaScript 中,所有 private 屬性仍然可以被外部訪問(wèn),也就是說(shuō),TypeScript 的 private、protected 等修飾詞僅用于它自己的語(yǔ)法檢查。從減少項(xiàng)目代碼本身的的 BUG 這一目的來(lái)說(shuō),已經(jīng)夠了。但如果是寫類庫(kù),考慮到不少用戶的 Hacking 天賦,還是有些欠缺。

本項(xiàng)目不用考慮 Hacking 的問(wèn)題,所以代碼轉(zhuǎn)換的過(guò)程中,所有 Symbol 實(shí)現(xiàn)的私有化都換成了 private。

TypeScript GitHub Issue 中有人提到希望轉(zhuǎn)換的代碼中用 Symbol 來(lái)實(shí)現(xiàn)真正的私有化,但經(jīng)過(guò)一群人的 激烈討論(全英文,有興趣自己去看吧),被否決了。也許以后 TypeScript 會(huì)認(rèn)真考慮這個(gè)問(wèn)題,但至少現(xiàn)在沒(méi)實(shí)現(xiàn)。

引入模塊

定義在同一個(gè)命名空間中東西,哪怕是分文件寫的,都不需要 import。但是如果是沒(méi)有 export 的東西,就只能在同一個(gè)命名空間塊中使用。

這里的 importexport 并不是 ES6 模塊的語(yǔ)言特性,而是 TypeScript 的語(yǔ)言特性,在這一點(diǎn)上,TypeScript 和 ES6 在語(yǔ)法上很容易混淆,比如 export class 是 TS 語(yǔ)法,也是 ES6 語(yǔ)法,tsc 會(huì)根據(jù)使用場(chǎng)景不同來(lái)區(qū)分,但是 export default class 就是 ES6 語(yǔ)法了,TS 需要配置支持。

import Point = model.Point 這種寫法是 TS 的語(yǔ)法,主要用于簡(jiǎn)化帶命名空間的名稱,這個(gè)和 ES6 的語(yǔ)法差別還是比較大的,不容易搞混。

不過(guò)由此可見(jiàn)一斑,TypeScript 前途漫漫啊。

TypeScript 帶來(lái)的好處

在 ES6 剛發(fā)布前后那段時(shí)間,TypeScript 帶來(lái)的好處之一就是可以使用 ES6 的類語(yǔ)法來(lái)簡(jiǎn)化類定義和繼承。不過(guò)隨著 ES6 和 Babel 等工具的廣泛使用,這已經(jīng)不再是 TypeScript 的優(yōu)勢(shì)。

不過(guò)從 TypeScript 2.0 的發(fā)布說(shuō)明中,可以感覺(jué)到 TypeScript 抓住了重點(diǎn)——靜態(tài)化 JavaScript。對(duì)于動(dòng)態(tài)語(yǔ)言最大的問(wèn)題就是,錯(cuò)誤要在運(yùn)行中去遇見(jiàn)。而靜態(tài)語(yǔ)言在編譯過(guò)程就能檢查出來(lái)幾乎所有的語(yǔ)法錯(cuò)誤和部分可能的邏輯錯(cuò)誤。

即使這個(gè)小小的試驗(yàn)性的俄羅斯方塊程序,在改寫為 TypeScript 的過(guò)程中,也發(fā)現(xiàn)了一些問(wèn)題

自注釋代碼

我比較推崇寫自注釋代碼——我并不是說(shuō)不應(yīng)該寫注釋,而是說(shuō),代碼變量和方法本身就應(yīng)該起到一定的注釋作用。很多所謂的注釋,其實(shí)就是把英文的方法和變量名稱翻譯成中文而,這樣的注釋,其實(shí)沒(méi)啥作用。

JavaScript 中的自注釋只能通過(guò)名稱來(lái)實(shí)現(xiàn),而 TypeScript 中還可以提供類型、重載等信息。比如 Point 構(gòu)造函數(shù),在 JavaScript 中

constructor(x, y) {
    if (typeof x === "object") {
        x = x.x; y = x.y;
    }
    // ...
}

光從構(gòu)造函數(shù)的申明上來(lái)看,完全不會(huì)知道可以傳入一個(gè)帶 xy 屬性的對(duì)象來(lái)代碼分別傳入 xy。但是 TypeScript 的函數(shù)申明就很明白

constructor(point: IPoint);
constructor(x: number, y: number);
constructor(x: any, y?: number) {
    // 這里是實(shí)現(xiàn)
}

使用類型的問(wèn)題

當(dāng)初定義 Point 類的時(shí)候,就是希望能把它用在項(xiàng)目中,便于以后的重構(gòu)。然后,改寫為 TS 的過(guò)程中卻出現(xiàn)了好幾個(gè)類型不匹配的錯(cuò)誤,都是因?yàn)橹苯邮褂昧俗址繉?duì)象 { x: v1, y: v2 } 這種形式來(lái)代替 Point 對(duì)象。

忘記了返回值

Block 類的 moveLeft()、moveRight()、moveDown() 等方法在設(shè)計(jì)的時(shí)候是計(jì)劃返回 this 以便于鏈?zhǔn)秸{(diào)用的。不過(guò)很不幸,JavaScript 不檢查返回值,所以 moveDown 忘了返回。

但是 TypeScript 中如果對(duì)方法申明了返回值類型,就會(huì)檢查回返值,所以這個(gè)錯(cuò)誤一下子就被發(fā)現(xiàn)了。

空值檢查

雖然由于后面提到的坑,最終沒(méi)有使用 TypeScript 的嚴(yán)格空檢查模式。但是這個(gè)模式仍然幫助我檢查出來(lái)幾個(gè)可能產(chǎn)生空引用錯(cuò)誤的地方。真心希望 TypeScript 能更快的完善,以便可以更廣泛的使用這些嚴(yán)格模式來(lái)幫助檢查錯(cuò)誤。

檢查未使用的變量和參數(shù)

TypeScript 2.0 的這兩個(gè)選項(xiàng)可以檢查未使用的局部變量和參數(shù),這對(duì)于凈化代碼是很有幫助的。不過(guò)因?yàn)閰?shù)定義有時(shí)候是涉及到接口約定,并不是說(shuō)沒(méi)有在程序中用到就一定沒(méi)用,所以最終我取消了對(duì)未使用參數(shù)的檢查。

TypeScript 的坑

代碼轉(zhuǎn)換過(guò)程中還是遇到不少坑的

嚴(yán)格空檢查模式下不能正確識(shí)別 Array.prototype.filter 結(jié)果類型

嚴(yán)格空檢查模式是 TypeScript 2.0 的新特性,這個(gè)模式下 null 是一個(gè)獨(dú)立的數(shù)據(jù)類型,而不是所有對(duì)象類型都可以有 null 值。

在 fasten 操作和刪除行操作的時(shí)候,都會(huì)用到 filter() 來(lái)過(guò)濾出有效的 BlockPoint 對(duì)象,比如

this._puzzle.fastened = this._matrix.reduce((all, row) => {
    return all.concat(row.filter(t => t));
}, []);

這里 this._matrix 是一個(gè) BlockPoint | null 的二維數(shù)組,而 Puzzle::fastened 被定義為 BlockPoint 的一維數(shù)組,它們的元素類型之間,就是一個(gè) null 類型的區(qū)別,很顯然,通過(guò) row.filter(t => t) 得到的結(jié)果已經(jīng)不可能包含 null 了,所以結(jié)果類型應(yīng)該是 Array<BlockPoint> 而不是 Array<BlockPoint | null>。然而 TypeScript 2.0 仍然推斷為 Array<BlockPoint | null>。在 GitHub Issue 上已經(jīng)有很多人提出這個(gè)問(wèn)題,估計(jì)會(huì)在 2.1 中解決。

本項(xiàng)目中,實(shí)在不想為這個(gè)個(gè)事情去寫循環(huán)處理,所以只好去掉了 "strictNullChecks": true 參數(shù)配置,不使用嚴(yán)格空檢查模式。

沒(méi)有自動(dòng)依賴檢查

項(xiàng)目代碼編譯過(guò)了之后,運(yùn)行時(shí)會(huì)出現(xiàn)一些類型引用的錯(cuò)誤,比如某個(gè)類的基類需要先于它定義之類的。很顯然,TypeScript 并沒(méi)有很好的去分析依賴關(guān)系。官方解決方案是手工加入 /// <reference path="..." /> 來(lái)申明依賴。所以源碼中會(huì)發(fā)現(xiàn)不少這樣的文件頭。


傳送門

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)