跳到主要内容

27 篇博文 含有标签「JS」

Javascript

查看所有标签

单例模式 (Singleton Pattern)

· 阅读需 1 分钟

单例模式提供方法,返回相同的缓存实例对象。

定义一个 Singleton 类,它应该具备以下特征:

  • 实例对象私有,不可被直接访问
  • 实现静态公有的 getInstance() 方法获取实例对象
  • 类不可使用 new 来实例化

示例

单例模式示例代码如下:

class Singleton {
private static instance: Singleton;

// 私有构造函数,不可用 new 实例化
private constructor() { }

public static getInstance(): Singleton {
if (!this.instance) {
this.instance = new Singleton();
}

return Singleton.instance;
}

// 单例也有一些业务逻辑,在实例上执行
public printHello() {
console.log('Hello');
}
}

const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
s1.printHello(); // Hello
s1 === s2; // true

可以出 s1s2 引用的是同一个实例对象。

TS 中 Declare 的作用

· 阅读需 5 分钟

在很多项目里可以看到 *.d.ts 这样的文件,在这里面有时会有很多 declare 出现。它的作用是,声明一个模块、变量、类型...

有时候,一些库不提供类型声明,使用它的时候就会很迷茫,没有代码提示。有时候,使用某个SDK,如果不去声明一下它,会报错说 “找不到名称xxx” ,这时候就需要我们自己去声明一下它。

可以直接在使用它的ts文件里声明,也可以在 *.d.ts 里进行全局范围的声明。

声明名称空间(模块)

某个公司实现了一个库,叫 BOS3D,项目中要使用这个库,且已经把他挂到了 window 对象上,我们在项目里可以直接使用 BOS3D.Version 或者 new BOS3D.Viewer(),但是在TS写的项目中,会提示找不到名称BOS3D,这时候需要自己简要地声明一下这个东西。

declare namespace BOS3D {
var MODE: 'prod' | 'dev' | 'test';
const Version = 4;

class Viewer {
constructor();
}
}

new BOS3D.Viewer(); // 可以使用了
BOS3D.Version; // 4

经过简单的声明之后,就不报错了,也有简单的提示了。

声明变量

使用 declare 来声明变量,可以用 var, const, let 关键字来完成声明。如果想要定义只读的变量,就用 const。如果想要变量是块级作用域,就用 let。

declare var age: number;
declare const year: number;
declare let month: number;

声明带属性的对象

假如有一个 myLib 全局变量,里面有一个 makeGreeting 函数,和一个 numberOfGreetings 属性来储存问候次数。应该这样来定义声明文件。

declare namespace myLib {
function makeGreeting(s: string): string;
let numberOfGreetings: number;
}

重载函数签名

这种场景,在编译器不知道函数定义在哪里时,告诉编译器该函数是怎样定义的类型签名,而不是在自己实现函数签名重载时使用。

正确用法:

// 使用了某些未知库,找不到函数定义,没有代码提示;这样来自己定义一下,方便开发;
declare function getPerson(s: string): object;
declare function getPerson(n: number): object;

错误用法:

declare function getPerson(s: string): object;
declare function getPerson(n: number): object;
function getPerson(arg: string | number): object {
// ...
}

如果自己实现函数签名重载,直接 function 连串下来。不用 declare,它的作用只是告诉编译器东西是怎么定义的,和实现毫无关系。

声明类

使用 declare class 来描述一个类,或者一个 似类 的对象,类可以定义属性或者方法,也包括构造器。

declare class Greeter {
constructor(greeting: string);
greeting: string;
showGreeting(): void;
}

const myGreeter = new Greeter("hello, world");
myGreeter.greeting = "howdy";
myGreeter.showGreeting();

class SpecialGreeter extends Greeter {
constructor() {
super("Very special greetings");
}
}

这是定义类型!!不是实现,运行肯定会报错!只是方便开发过程中有代码提示。

组织类型

用名称空间 namespace 把类型给组织起来,更规范管理类型。

declare namespace GreetingLib {
interface LogOptions {
verbose?: boolean;
}
interface AlertOptions {
modal: boolean;
title?: string;
color?: string;
}
}

// 可以嵌套定义名称空间,也可以这样通过 xxx.xx 定义内嵌的名称空间
declare namespace GreetingLib.Options {
interface Log {
verbose?: boolean;
}
interface Alert {
modal: boolean;
title?: string;
color?: string;
}
}

function exportLog(arg: GreetingLib.Options.Log) {
//...
}
标签:

TS 中 module 和 namespace

· 阅读需 2 分钟

模块和名称空间,实际用起来没什么区别,但是概念上有些不同。

模块的设计理念是分割和组织代码文件,一个文件就是一个module。在一个文件中访问另一个文件必须要加载(import或者require)它。

引用TS官网的一句话:

It’s important to note that in TypeScript 1.5, the nomenclature has changed. “Internal modules” are now “namespaces”. “External modules” are now simply “modules”

自 TS 1.5 以后,namespace 可以看作是内部模块,modules 就是它原本的意思(外部模块)。命名空间顾名思义,提供了一个空间,在这里面定义的变量、接口、函数等,与其他空间隔离,也就是说他们的名称相同也不会互相影响,就像 Java 中的包。

示例

/// <reference path = "..." />,这个指令会引入要加载的ts文件,然后就可以使用其中的内容。

下面这些代码,在不同文件里共同定义了一个名称空间 Card,该名称空间下暴露了几个类,分别是 Circle, Triangle, Square。并在名称空间之外,定义了一个 Drawable 接口。

/// <reference path = "./Shape.ts" />

// Circle.ts
namespace Card {
export class Circle implements Drawable {
type = 'Circle';
constructor(){

}
draw() {
console.log('Draw Circle.');
}
}
}
// Shape.ts
interface Drawable {
draw(); // 定义抽象 draw 方法
}

namespace Card {
export class Triangle implements Drawable {
// 实现 draw 方法
draw() {
console.log('Draw Triangle.');
}
}
export class Square implements Drawable {
draw() {
console.log('Draw Square.');
}
}
}
/// <reference path="./Circle.ts" />
/// <reference path="./Shape.ts" />

// Test.ts
function testCircle(shape: Card.Circle) {
shape.draw();
}

function testTriangle(shape: Card.Triangle) {
shape.draw();
}

function testSquare(shape: Card.Square) {
shape.draw();
}

testCircle(new Card.Circle());
标签:

JS 递归过滤 JSON 数据

· 阅读需 5 分钟
Hanasaki
阿巴阿巴阿巴

负责人布置了个任务,写一个过滤 JSON 数据的方法,处理一下模型数据。递归绕的我真晕,脑子内存还是不太够。

样例数据

规则:bosclass 值为 boqs 的是树节点,bosclass 值为 wps 的是纯数据。如果树节点的 children 是空数组,那么把树节点移除掉。树的层级是无法预估的,可能有很多层。以下是过滤后的数据:

[
{
"bosclass": "boqs",
"children": [
{
"bosclass": "boqs",
"children": [
{
"bosclass": "wps",
"guid": "fa01a4a1f1d448a3bfb315dac41e1914"
},
{
"bosclass": "wps",
"guid": "cbf47dc52b2243c1a9d46bb046d8a13d"
}
],
"guid": "f785b4a061714dfe8fea367d12ddd29c"
}
],
"guid": "8bc6c9026bb9472f8a3e23b382f87ac1"
}
]

如何过滤呢?首先想到的方法是递归!

初探 Nodejs 内存泄漏

· 阅读需 3 分钟
Hanasaki
阿巴阿巴阿巴

这两天吃饱撑着,在探究为什么我的 Node 程序 Heap Usage 一直居高不下,而且会越来越多。

如何调试 NodeJS 程序

在VSCode中可以直接调试,选择Debug,显示所有自动调试配置,选择要运行的命令,就进入了调试模式,侧栏会显示出调试的信息。可以捕获 Heap 快照,拿到 ChromeDevTool 里面导入分析。

用 Node 的 --inspect 参数搭配 Chrome 实时分析

运行以下命令,进入了调试模式

node --inspect serve.js

在 Chrome 中打开 chrome://inspect,就可以看到 Open dedicated DevTool for Node,点击进入 Node 调试工具,就可以开始分析了。

放大问题

很多时候,人工是难以准确排查出问题的,因为会有很多不可预料的误差,导致每次运行的结果数据不准。这时候,要放大问题,大量测试多次触发漏洞,就能很清楚的发现问题。

k6 对接口进行测试,运行下列测试程序 k6 run test.js

// test.js
import http from 'k6/http';

export let options = {
vus: 100,
duration: '20s',
};

export default function () {
let res = http.post("http://localhost:5000/monitor/start", JSON.stringify({ phone: Math.random()*100000 }), { headers: { 'Content-Type': 'application/json' } })

console.log(res.body);
}

通过对比前后的 Heap Size,确实管用,明显增加了将近 8MB。

接着调用了一下其他的接口,调用 processMap.clear() 把保存子进程的 processMap 清空。内存果然又降回去了,但并没有和最开始一样,而是稍稍高出了一点点。

得出结论,引用计数法回收垃圾是实实在在的,这个例子验证了它。但是仍然困扰我的是,Heap 里的 [compiled code] 这部分会一直增加,无法被回收,增加的量很小...

未解之谜:每次访问接口,即使是访问 / 路径,返回一段文本, [compiled code] 也会不断增加?