利用tree命令,可以快速输出目录树形结构,通过不同参数的设置,还可以灵活控制目录层级、忽略目录、设置树形结构颜色等

阅读全文 »

手把手教你写一个node-cli工具,主要实现了自动初始化项目、自动克隆远程git仓库、自动创建component || page模板文件...

阅读全文 »

最近在日常开发中,项目报了一个莫名其妙的错误:

1
Already included file name 'd:/work/work/git/base-biz/src/modules/nps/pages/ItemRadioInput.vue' differs from file name 'd:/work/work/git/base-biz/src/modules/nps/pages/itemRadioInput.vue' only in casing.

看字面意思,好像是重复引用了,于是全局搜索了一下相关文件的引入,结果并没有,又反复确认了引入路径,发现也完全没什么问题。

/images/articles/already-included-file-name/error

后来经过查询资料,得到以下两种解决方案:

1. 去掉引入文件的.vue后缀名:

1
import ItemRadioInput from "./ItemRadioInput";

2. 利用alias别名,用绝对路径形式引入相关文件:

1
import ItemRadioInput from "@/components/ItemRadioInput.vue";

本文永久链接: https://www.mulianju.com/already-included-file-name/

使用计算机的时候,经常会遇到各种问题导致的无法上网、网速慢、连接异常等问题,多数情况,都是缓存导致的,通过刷新缓存,可以解决绝大部分问题。
那么常见的清楚网络缓存的方式呢,我这里简单收集了几种:

以下命令均可在CMD(win + R,输入cmd即可打开)环境下执行,最好在管理员模式

1. 重置Winsock目录

1
netsh winsock reset

netsh winsock reset命令,作用是重置 Winsock 目录。如果一台机器上的Winsock协议配置有问题的话将会导致网络连接等问题,就需要用netsh winsock reset命令来重置Winsock目录借以恢复网络。这个命令可以重新初始化网络环境,以解决由于软件冲突、病毒原因造成的参数错误问题。 netsh是一个能够通过命令行操作几乎所有网络相关设置的接口,比如设置IP,DNS,网卡,无线网络等,Winsock是系统内部目录,Winsock是Windows网络编程接口,winsock工作在应用层,它提供与底层传输协议无关的高层数据传输编程接口,reset是对Winsock的重置操作。当执行完winsock的命令重启计算机后,需要重新配置IP。

重置 Winsock 目录后,需要重启电脑才能生效。

部分网游上不去的时候,也可以用此方式解决,比如我之前玩的一个养成类游戏:浪漫庄园

2. 清除DNS缓存

1
ipconfig /flushdns

ipconfig /flushdns 这是清除DNS缓存用的。

当访问一个网站时系统将从DNS缓存中读取该域名所对应的IP地址,当查找不到时就会到系统中查找hosts文件,如果还没有那么才会向DNS服务器请求一个DNS查询,DNS服务器将返回该域名所对应的IP,在你的系统收到解析地址以后将使用该IP地址进行访问,同时将解析缓存到本地的DNS缓存中。

如果DNS地址无法解析,或者是DNS缓存中的地址错误,一般才会使用ipconfig/flushdns来清除所有的DNS缓存。

3. 清除DHCP缓存

1
2
ipconfig /release
ipconfig /renew

ipconfig /release:为释放现有的IP地址。
ipconfig /renew:命令则是向DHCP服务器发出请求,并租用一个IP地址。

本文永久链接: https://www.mulianju.com/clear-network-cache/

当我们使用https协议来使用git时,每次进行gitpush或者pull等远程操作时,都会需要输入用户名和密码,当提交或拉取操作比较频繁时,开发体验尤其差,那我们该如何解决这个问题呢

设置身份

git是分布式版本控制系统,所以,每个机器都必须自报家门,即你的名字和邮箱地址

1
2
git config --global user.name 'mulianju'
git config --global user.email 'mulianju@qq.com'

注意git config命令的–global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。

配置好之后可以使用

1
git config -l

查看配置。

保存证书

如果以上操作没有解决问题,我们可以直接在磁盘上保留证书。

1
git config --global credential.helper store

使用这个帮助程序会将你的密码存储在磁盘上,只受文件系统权限保护。如果这不是一个可以接受的安全权衡,请尝试使用 git-credential-cache1,或者找到一个与操作系统提供的安全存储集成的帮助器。

该命令将无限期地将证书存储在磁盘上供将来的 Git 程序使用。


以上两种操作完成后,首次操作时还是需要再输入一次用户名和密码,如果生效了,第二次以后就不用了

参考资料:

本文永久链接: https://www.mulianju.com/git-always-need-password/

implements

implements是一个类实现一个接口用的关键字.实现一个接口,必须实现接口中的所有方法。

用途:类实现一个接口用的关键字
性质:抽象方法

特点

  1. 接口可以被多重实现(implements),抽象类只能被单一继承(extends)
  2. 接口只有定义,抽象类可以有定义和实现
  3. 接口的字段定义默认为:public static final, 抽象类字段默认是”friendly”(本包可见)

与Extends的区别

  • extends,表示对父类的继承,可以实现父类,也可以调用父类初始化 this.parent()。而且会覆盖父类定义的变量或者函数。
  • implements,表示对接口的实现,接口通过关键字interface 进行定义。eg:public class S implements F,在接口F中对方法进行声明,在类S中对该方法进行实现。

这两种实现的具体使用,是要看项目的实际情况:

  • 需要实现,不可以修改,用implements,只定义接口。
  • 需要具体实现,或者可以被修改,扩展性好,用extends

本文永久链接: https://www.mulianju.com/implements/

抽象类(Abstract class)

Abstract class指的是用关键字abstract修饰的类,叫做抽象类,是不允许实例化的类,不能直接创建对象,必须要通过子类创建才能使用abstract类的方法。

别称:Abstract class
抽象类:用关键字abstract修饰的类
特点:不允许实例化的类这,所以一般它需要被进行扩展继承

所属分类

抽象类
抽象类是不允许实例化的类,因此一般它需要被进行扩展继承。

方法特征

  1. 在类声明中使用abstract修饰符以指示某个类只能是其他类的基类。标记为抽象或包含在抽象类中的成员必须通过从抽象类派生的类来实现。

  2. abstract类声明方法存在但不能实例化,它用于要创建一个体现某些基本行为的类,并为该类声明方法。

  3. 虽然abstract类不能创建实例,但是可以创建一个具体子类的实例,其类型为这个抽象类。

  4. 不能有抽象构造函数或抽象静态方法。

  5. abstract类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类。

  6. 如果一个类中含有abstract方法,那么这个类必须使用abstract来修饰。反之,abstract类中却可以没有abstract方法。

  7. 一个子类只能继承一个父类,但可以通过实现多个接口(interface)实现多重继承。

本文永久链接: https://www.mulianju.com/abstract-class/

装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

JavaScript的装饰器提案历经一波三折,目前仍处于Stage 2阶段,而且在语法和实现上经历了较大的改版,距离正式成为ECMA语言标准尚需时日。在TypeScript愈发流行的今天,它已推出了这个实验性功能,一些框架如angular、nestjs都已经大量使用了装饰器。

介绍

装饰器是一种特殊类型的声明,它能够被附加到类声明,属性, 访问符,方法或方法参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器。

参考lib.es5.d.tsECMAScript APIs部分对于装饰器的定义:

1
2
3
4
5
6
7
8
9
// 1481行-1484行
// 类装饰器
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
// 属性装饰器
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
// 方法装饰器
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
// 参数装饰器
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

类装饰器

接口定义:

1
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

类装饰器表达式,由定义知道,传入1个参数:

  • target —— 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。

先看一个最简单的装饰器,普通装饰器,只有一个参数target,当把这个@helloWord装饰器作用在HelloWordClass类上,这个target参数传递的就是HelloWordClass类。

1
2
3
4
5
6
7
8
9
10
11
function helloWord(target: any) {
console.log('hello Word!');
}

@helloWord
class HelloWordClass {
constructor() {
console.log('我是构造函数')
}
name: string = 'mulianju';
}

执行结果:

1
'hello Word!'

类装饰器3种类型

  • 普通装饰器(无法传参)
  • 装饰器工厂(可传参)
  • 重载构造函数

普通装饰器(无法传参)

1
2
3
4
5
6
7
8
9
10
11
function helloWord(target: any) {
console.log('hello Word!');
}

@helloWord
class HelloWordClass {
constructor() {
console.log('我是构造函数')
}
name: string = 'mulianju';
}

装饰器工厂(可传参)

增加了一个静态变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function helloWord(isTest: boolean) {
return function(target: any) {
// 添加静态变量
target.isTestable = isTest;
}
}

@helloWord(false)
class HelloWordClass {
constructor() {
console.log('我是构造函数')
}
name: string = 'mulianju';
}
let p = new HelloWordClass();
console.log(HelloWordClass.isTestable);

重载构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function helloWord(target: any) {
return class extends target {
sayHello(){
console.log("Hello")
}
}
}

@helloWord
class HelloWordClass {
constructor() {
console.log('我是构造函数')
}
name: string = 'mulianju';
}

属性装饰器

接口定义:

1
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;

属性装饰器表达式会在运行时当作函数被调用,由定义知道,传入2个参数:

  • target —— 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • propertyKey —— 属性的名称。
  • 没有返回值。

按照上面的接口形式,定义了一个defaultValue()装饰器方法,就算是用private也是能生效的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function defaultValue(value: string) {
return function (target: any, propertyName: string) {
target[propertyName] = value;
}
}

class HelloWordClass {
constructor() {
console.log('我是构造函数')
}
@defaultValue('mulianju')
private name: string | undefined;
}
let p = new HelloWordClass();
console.log(p.name);

输出结果:

1
2
我是构造函数
mulianju // 这里打印出设置的默认值

通过属性描述来修改属性值

对于属性的装饰器,是没有返回descriptor的,并且装饰器函数的返回值也会被忽略掉。还可以通过自己获取descriptor,并进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function defaultValue(value: string) {
return function (target: any, propertyName: string) {
let descriptor = Object.getOwnPropertyDescriptor(target, propertyName);

Object.defineProperty(target, propertyName, {
...descriptor,
value
})
}
}

class HelloWordClass {
constructor() {
console.log('我是构造函数')
}
@defaultValue('mulianju')
private name: string | undefined;
}
let p = new HelloWordClass();
console.log(p.name);

输出结果:

1
2
我是构造函数
mulianju // 这里打印出设置的默认值

这种方式通过Object.getOwnPropertyDescriptor和Object.defineProperty方法对属性进行定义。这种方式适用面更广,可以针对属性描述进行修改。

方法装饰器

接口定义:

1
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;

方法装饰器接受三个参数:

  • target —— 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • propertyKey —— 属性的名称。
  • descriptor —— 方法的属性描述符。

返回属性描述符或者没有返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function logFunc(params: string) {
return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
// target === HelloWordClass.prototype
// propertyName === "sayHello"
// propertyDesciptor === Object.getOwnPropertyDescriptor(HelloWordClass.prototype, "sayHello")

console.log(params);
// 被装饰的函数
const method = descriptor.value;
descriptor.value = function (...args: any[]) {
let start = new Date().valueOf();
// 将 sayHello 的参数列表转换为字符串
args = args.map(arg => String(arg));
console.log('参数args = ' + args);
try {
// // 调用 sayHello() 并获取其返回值
return method.apply(this, args)
} finally {
let end = new Date().valueOf();
console.log(`start: ${start} end: ${end} consume: ${end - start}`)
}
};
return descriptor;
}
}

class HelloWordClass {
constructor() {
console.log('我是构造函数')
}
private nameVar: string | undefined;

@logFunc('log装饰器')
sayHello(name: string) {
console.log(name + ' sayHello');
}
}
let pHello = new HelloWordClass();
pHello.sayHello('mulianju');

输出结果:

1
2
3
4
5
log装饰器
我是构造函数
参数args = mulianju
mulianju sayHello
start: 1574331292433 end: 1574331292434 consume: 1

方法参数装饰器

接口定义:

1
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

方法参数装饰器会接收三个参数:

  • target —— 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • propertyKey —— 属性的名称。
  • parameterIndex —— 参数数组中的位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function logParameter(target: any, propertyName: string, index: number) {
// 为相应方法生成元数据键,以储存被装饰的参数的位置
const metadataKey = `log_${propertyName}_parameters`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
} else {
target[metadataKey] = [index];
}
}


class HelloWordClass {
constructor() {
console.log('我是构造函数')
}
private nameVar: string | undefined;

sayHello(@logParameter name: string) {
console.log(name + ' sayHello');
}
}
let pHello = new HelloWordClass();
pHello.sayHello('mulianju');

访问器装饰器

访问器就是添加有get、set前缀的函数,用于控制属性的赋值及取值操作。
访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义。

访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的名字。
  • 成员的属性描述符。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function enumerable(value: boolean) {
return function (
target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}

class HelloWordClass {
constructor() {
console.log('我是构造函数')
}

private _name: string = 'mulianju';
private _age: number = 10;

@enumerable(true)
get name() {
return this._name;
}

set name(name: string) {
this._name = name;
}

@enumerable(false)
get age() {
return this._age;
}

set age(age: number) {
this._age = name;
}
}
let pHello = new HelloWordClass();
for (let prop in pHello) {
console.log(`property = ${prop}`);
}

enumerable属性描述符:
当且仅当该属性的enumerabletrue时,该属性才能够出现在对象的枚举属性中。通过Object.defineProperty()创建属性,enumerable默认为false

如果一个属性的enumerable为false,通过对象还是能访问的到这个属性,但下面三个操作不会取到该属性。

  • for…in循环
  • Object.keys方法
  • JSON.stringify方法

我们定义了两个访问器 name 和 age,并通过装饰器设置是否将其列入清单,据此决定对象的行为。name 将列入清单,而 age 不会。

所以控制台输出结果:

1
2
3
4
5
我是构造函数 
property = _name
property = _age
property = name
// 少了一个属性age

装饰器执行顺序

类中不同声明上的装饰器将按以下规定的顺序应用:

  1. 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
  2. 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。
  3. 参数装饰器应用到构造函数。
  4. 类装饰器应用到类。

注意:TypeScript 不允许同时装饰单一成员的 get 和 set 访问器。这是因为装饰器可以应用于属性描述符,属性描述符结合了 get 和 set 访问器,而不是分别应用于每项声明。

装饰器模式的优缺点

优点

  • 动态扩展类功能,比类继承灵活,且对客户端透明;
  • 继承关系的一种替代方案。相比与类继承的父子关系,装饰模式 更像是一种-组合关系(is-a);
  • 可以对同一个被装饰对象进行多次装饰,创建出不同行为的复合功能。

缺点

  • 多层装饰比较复杂(灵活的同时会带来复杂性的增加);
  • 装饰嵌套过多,会产生过多小对象(每个装饰层都创建一个相应的对象);
  • 装饰嵌套过多,易于出错,且调试排查比较麻烦(需要一层一层对装饰器进行排查,以确定是哪一个装饰层出错)。

参考资料

本文永久链接: https://www.mulianju.com/decorator/

箭头函数和普通函数有什么区别?

  1. 箭头函数比普通函数更加简洁:

    • 如果函数没有参数,可以只写一个括号;
    • 如果只有一个参数,可以省略括号,当然有些项目eslint会有相关要去,推荐保留这个小括号;
    • 可以省略函数体大括号,这种情况只适用于函数体只有一条语句(或者可以拼合成一条语句)的情况,并且此写法会把此条语句的执行结果作为返回值;
    • 无返回值的函数体,可以给这个语句前面加一个void关键字,当然不加void时,效果也一样。
  2. 箭头函数不存在自己的作用域,所以箭头函数里的this会继承箭头函数声明环境的this,并且这个指向是不能改变的,call、apply、bind这种“对象冒充”方法,对箭头函数无效。这个特性也是我们日常撸码最多利用的箭头函数的特性。

  3. 箭头函数缺少一些普通函数的特性:

    • 箭头函数没有自己的arguments;
    • 箭头函数没有prototype。
  4. 箭头函数有一些使用限制:

    • 箭头函数不能用作构造函数;
    • 箭头函数不能用作Generator函数,不能使用yeild关键字。

本文永久链接: https://www.mulianju.com/difference-of-arrow-functions-and-normal-functions/