语法知识点
回调地狱问题
用await,或者有些库提供了sync方法
yield
用法跟python的一样。
yield只能用于生成器里,生成器是function*,例如:
export function* filter(rootNode: ts.Node, acceptedKind: ts.SyntaxKind) {
for (const iterObj of walkTree(rootNode)) {
if (iterObj[1].kind == acceptedKind) {
yield iterObj;
}
}
}
字符串插值
ts没有format函数,用``插值,样例如下:
let s = "aaa";
// 字符串插值
console.log(`i love ${s.length}`);
写文件
流式写:
function printAst(rootNode: ts.Node, outputFile: string) {
const wstream = fs.createWriteStream(outputFile);
try {
for (...) {
...
wstream.write(`${new Array(path.length + 1).join('----')} ${syntaxKindToName(node.kind)} ${node.pos} ${node.end}\n`);
}
wstream.end();
} finally {
wstream.close();
}
}
一次性写就用fs.writeFile
const/var/let的区别
const是常量,声明时初始化,不能修改。
let是变量,有块级作用域,超出块级作用域就会无法访问。
var是变量,它的作用域是当前执行上下文。
set集合
加入元素用add,判断元素用has。
用法如下:
const funcNames = new Set();
for (const iterObj of tsFileParser.filter(sourceFile, ts.SyntaxKind.CallExpression)) {
let node = iterObj.current as ts.CallExpression;
funcNames.add(getCallFuncName(node));
}
expect(funcNames.has("f")).toBe(true);
多变量赋值的写法
let beginPos = -1, endPos = -1;
顺带说一下,局部变量未声明初始值的话,值为undefined
列表切片
用slice方法
var students = ['killer', 'bear', 'tiger']
console.log(students.slice(1, -1))
as
类型断言,类似java里的downcast,例如:
let node = iterObj[1] as ts.StringLiteral;
console.log(node.pos, node.text)
正则表达式
正则有两种写法,一是斜杠字面量声明:
const pat = /<script\s+lang="ts"\s+setup>([\s\S]*?)<\/script>/g;
一是RegExp类构造声明:
const PAT_CN = new RegExp("[\u4e00-\u9fa5]");
RegExp有两个方法:test和exec,前者仅判断是否匹配,返回bool。后者返回匹配对象,类似于python里的match object。
完整样例:
test('test regex', () => {
// 这里我们要匹配换行符在内的任意字符,点号不行,需用[\s\S]
const pat = /<script\s+lang="ts"\s+setup>([\s\S]*?)<\/script>/g;
const s = "<script lang=\"ts\" setup>\n" +
"console.log(\"1\");\n" +
"</script>" +
"aaa " +
"<script lang=\"ts\" setup>\n" +
"console.log(\"2\");\n" +
"</script>";
const conts = []
let match;
//这里需用while循环,因RegExp.exec只会返回一次匹配,要获得所有匹配,得做循环
// match[0]是匹配的全文,match[1]是捕获的文本
while ((match = pat.exec(s)) !== null) {
conts.push(match[1]);
}
console.log(conts);
expect(conts.length).toBe(2);
})
package.json
scripts字段含义
"scripts": {
"build": "tsc",
"test": "jest"
},
用npm run执行scripts里的命令:
npm run build
npm run test
顺带说一下,要支持typescript和jest,要在package.json里配置:
"devDependencies": {
"@types/node": "^22.14.0",
"typescript": "^5.5.3",
"@types/jest": "^29.5.14",
"jest": "^29.7.0",
"ts-jest": "^29.3.1"
},
UT
使用jest。
默认情况下jest只识别js文件,需要对ts进行转译让jest识别。否则报错:
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
配置jest.config.js如下:
module.exports = {
// 预设库
preset: 'ts-jest',
// jest模拟的环境,可以选择node、jsdom来模拟node和浏览器环境
testEnvironment: 'node',
// 待测试的文件路径
testMatch: ['<rootDir>/tests/**/*.test.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
// 转义插件,方便jest理解代码
transform: {
'^.+\\.ts$': 'ts-jest',
},
// 不进行匹配的目录
transformIgnorePatterns: ['<rootDir>/node_modules/'],
// 处理 webpack 的别名,比如:将 @ 表示 根目录
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
},
};
要支持typescript下的UT,需在package.json里配置jest和ts-jest:
"devDependencies": {
...
"@types/jest": "^29.5.14",
"jest": "^29.7.0",
"ts-jest": "^29.3.1"
},
日志打印
使用winston:
export const LOGGER = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
winston.format.printf(({timestamp, level, message}) => {
return `${timestamp} [${level}]: ${message}`;
})
),
transports: [
new winston.transports.File({filename: 'website_checker.log', options: {flags: 'w'}})
]
});
使用combine format,打印时间戳、级别及信息。options: {flags: ‘w’}表示每次覆盖日志文件,默认是追加。
类
类的写法跟java很像,几乎不用额外学习。例子:
class StaticChecker {
private rootDir: string;
constructor(rootDir: string) {
this.rootDir = rootDir;
}
// 普通成员
public execute() {
...
}
// 生成器成员
* walkTree(rootNode: ts.Node): Generator<VisitResult> {
yield {path: [], current: rootNode};
...
}
...
}
const checker = new StaticChecker("proj_path");
checker.execute();
类的静态方法
方法前加static即可。
export class TsParseUtils {
static getCallFuncName(node: ts.CallExpression): string | null {
let ownerNode = node.getChildAt(0);
let secondNode = node.getChildAt(1);
if (secondNode.kind == ts.SyntaxKind.OpenParenToken) {
return ownerNode.getText();
} else {
return null;
}
}
正式代码里支持@符号
在tsconfig.json里配置paths选项:
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist",
// 支持import时的@符号
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"src"
]
}