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

以下命令均可在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/

问题

刚刚在安装electron遇到个问题,无论npm还是yarn都会报unable to verify the first certificate这个错误,翻译过来就是“无法验证第一个证书”(第一个证书是什么鬼,为啥是第一个)。

原因

提到证书,首先就会想到https协议,经过搜索相关问题,果然查到:2017年2月27日起,npm不再支持自签名证书。npm install走的是https协议,需要通过数字证书来保证的。

解决方案

网络上的解决方案有很多,我这里总结一下:

1. 取消ssl验证

1
2
3
4
5

// 取消`ssl`验证
npm config set strict-ssl false // npm
yarn config set strict-ssl false // yarn

设置完成后,我们可以通过以下命令来查看设置后的状态

1
2
3
4
5

// 取消`ssl`验证
npm config get strict-ssl // false
yarn config get strict-ssl // false

确认没问题后,再进行安装操作,理论上就可以跳过ssl验证了。

2. 设置一个空证书

1
2
3
4
5
6
7
8

# npm
npm install npm -g --ca=""
npm config set ca=""

npm install npm -g --ca=""
npm config set ca=""

一般情况,上面两种方案就已经可以搞定了。

3. 取消ssl验证

如果以上方案还是没能解决问题,那一定就是因为开了代理工具了,比如FastGitHub

https是一种安全的协议,确保服务端与客户端之间传送正确性的。现在中间多了一个代理转发,有一些连接会觉得这样不靠谱,不允许连接。

为了绕过这个验证,可以通过修改npm源来实现:

1
2
3
4
5
6
7
8

# electron可以单独设置下载源
set ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/

# npm可以直接设置全局下载源
npm config set registry http://registry.cnpmjs.org/
npm config set registry http://registry.npm.taobao.org/

我们也可以通过.npmrc来设置单独某一个项目的下载源

1
2
3
# registry=https://registry.npm.taobao.org
# sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/

当然,nrm这种下载源管理工具也是个不错的选择

1
2
3
4
5
6
7
8
9
10
11
12

# 首先全局安装nrm
npm i nrm -g
...

# 查看全部的有效下载源
nrm ls
...

# 使用某个源,例如taobao
nrm use taobao

本文永久链接: https://www.mulianju.com/unable-to-verify-the-first-certificate/

Windows Subsystem for Linux(简称WSL)是一个在Windows 10上能够运行原生Linux二进制可执行文件(ELF格式)的兼容层。它是由微软与Canonical公司合作开发,其目标是使纯正的Ubuntu、Debian等映像能下载和解压到用户的本地计算机,并且映像内的工具和实用工具能在此子系统上原生运行。

Windows 11版本默认集成了WSL虚拟系统,用户可以在应用商店选择安装多个linux版本,笔者安装的版本为Ubuntu-20.04版本。

系统安装后,一段时间没有使用,最尴尬的事情发生了,root密码不记得了。。。

不过好在Windows还留了一个后门,可以通过外部去跳过linux虚拟机内部的验证,直接重置任意用户的密码。

具体步骤:

  1. 管理员身份打开Powershell或cmd
  2. 输入wsl -u root,用root身份登入Ubuntu系统,此操作可以跳过Ubuntu验证,无需输入密码即可直接登录
  3. root身份进入系统后,剩下的操作就比较常规了,输入passwd [用户名(可以是root)]
  4. 输入并确认密码
  5. 重新打开WSL即可

这个相当于后门的东西,令人非常诧异,这么搞相当于root权限直接放开了,完全无安全性可言。不过,毕竟wsl只是微软的一个虚拟系统,物理目录都是开放的,root权限有没有都可以无限制访问内部任何目录和文件。那这样来说好像也说的过去哈?

本文永久链接: https://www.mulianju.com/reset-wsl-root-password/

最近公司内部要优化创作者后台,关于后台编辑器部分,领导委托我做了一个简单的调研。

基于github和百度搜索的数据,收集整理出以下几款编辑器的数据

quill

github repository: https://github.com/quilljs/quill/
github stars: 31.1k
github contributors: 127
官网: https://quilljs.com/

quill

Quill是一个跨平台的功能强大的富文本编辑器。开发者可以通过简单的API来控制编辑器的内容。主流的黑白清新风,美观,功能上虽然不是很多,甚至还没有表格,但它的代码高亮是最完美的,因为它本身就支持了hignlight.js,同样支持行内编辑模式,可自定义。

trix

github repository: https://github.com/basecamp/trix
github stars: 16.5k
github contributors: 37
官网: https://trix-editor.org/

trix

在Web应用程序中编写格式精美的文本。Trix是一个WYSIWYG编辑器,用于编写消息,评论,文章和列表 - 大多数Web应用程序的简单文档。它具有复杂的文档模型,支持嵌入式附件,并输出简洁和一致的HTML。
Trix是来自Basecamp的开源项目,Basecamp是Ruby on Rails的创建者。数百万人信任他们的文本发送到Basecamp,Trix给了他们最好的编辑体验。

大多数WYSIWYG编辑器都是HTML contenteditable和execCommandAPI的包装器,他们是由Microsoft设计,支持在Internet Explorer 5.5中实时编辑网页,最终由其他浏览器进行反向设计和复制。

因为这些API从未完全规定好或文档化,并且因为WYSIWYG HTML编辑器的范围很广,所以每个浏览器的实现都有自己的一组错误和怪癖,需要JavaScript开发人员可以解决这些不一致问题。

Trix通过将contenteditable视为I / O设备来回避这些不一致:当输入进入编辑器时,Trix该输入转换为其内部文档模型的编辑操作,然后将该文档重新呈现回编辑器。这使得Trix可以完全控制每次击键后发生的事情,并且完全无需使用execCommand。

wangEditor

github repository: https://github.com/wangeditor-team/wangEditor
github stars: 12.5k
github contributors: 44
官网: https://www.wangeditor.com/

wangEditor

由一个20个人组合的国内团队维护,轻量级,小巧实用,配置方便,使用简单。可以自定义皮肤功能,免费开源,用户数量也较多。
wangEditor是用javascript编写的 轻量级web富文本编辑器 ,依赖于jQuery和fontAwesome字体库, 支持所有浏览器 。使用 wangEditor 可以轻松创建web富文本框,并可以自定义扩展菜单功能。wangEditor所有源码都已经在github上开源下载。

缺点:Demo、文档不是很全。若想更好的使用,还需阅读一下源码,好在作者代码注释写的很全。

优点:作者一直有更新,并且有活跃的技术QQ群: 164999061(人已满),710646022(人已满),901247714(人已满),606602511,作者能及时的回答问题。源码注释非常好。

wangEditor是我们项目目前在用的编辑器。

ckeditor

github repository: https://github.com/ckeditor/
github contributors: 130+74
github stars: 5.5k+5.2k
官网: https://ckeditor.com/

ckeditor

非常经典的富文本编辑器,官方下载量过千万,有高性能的实时预览,它特有行内编辑功能。

CKEditor 是新一代的 FCKeditor,是一个重新开发的版本。CKEditor 是全球最优秀的网页在线文字编辑器之一,因其惊人的性能与可扩展性而广泛地被运用于各大网站。

可配合使用的扩展有文件管理器 KCFinder。

github上,目前拆分为ckeditor4和ckeditor5两个主要仓库分别维护,以上数据分别取自这两个仓库

summernote

github repository: https://github.com/summernote/summernote/
github stars: 10.4k
github contributors: 278
官网: https://summernote.org/

summernote

Summernote 是一个简单灵活的所见即所得的 HTML 在线编辑器,比较容易上手,使用体验流畅,支持各种主流浏览器,基于 jQuery 和 Bootstrap 构建,支持快捷键操作,提供大量可定制的选项。

本次调研中github贡献者最多的一个。

TinyMCE

github repository: https://github.com/tinymce/
github stars: 9.9k
github contributors: 215
官网: https://www.tiny.cloud/

TinyMCE

TinyMCE是一个轻量级的,基于浏览器的,所见即所得编辑器,支持目前流行的各种浏览器,支持图片在线处理,插件多,功能非常强大,易于集成,并且拥有可定制的主题。由JavaScript写成。功能配置灵活简单(两行代码就可以将编辑器嵌入网页中),支持AJAX。另一特点是加载速度非常快,如果你的服务器采用的脚本语言是 PHP,那还可以进一步优化。最重要的是,TinyMCE是一个根据LGPL license发布的自由软件,你可以把它用于商业应用。

UEditor

github repository: https://github.com/fex-team/ueditor
github stars: 6k
github contributors: 45
官网: https://ueditor.baidu.com/website/

UEditor

UEditor 是由百度 web 前端研发部开发所见即所得富文本web编辑器,具有轻量、可定制、注重用户体验等特点。

主要特点:

轻量级:

代码精简,加载迅速。

定制化:

全新的分层理念,满足多元化的需求。采用三层架构:

  1. 核心层: 为命令层提供底层API,如range/selection/domUtils类。
  2. 命令插件层: 基于核心层开发command命令,命令之间相互独立。
  3. 界面层: 为命令层提供用户使用界面。
    满足不同层次用户的需求。

浏览器兼容:

兼容Mozilla, MSIE, FireFox, Maxthon,Safari 和Chrome,实现浏览器无差别化。

注重细节:

  • 统一不同浏览器下表格选取方式。
  • 提供黑/白名单的过滤机制。
  • 更好的word支持,实现粘贴转换机制。

稳定性:

经过专业的QA团队测试,通过上千个测试用例,包括自动化用例和手动用例,目前仍然在不断完善中。


以上数据部分均取自github,时间为2021年10月21日
观点部分,只代表个人观点,有什么建议和意见欢迎指正

本文永久链接: https://www.mulianju.com/WYSIWYG-Editors/

我们用JS进行浮点数运算时,比如0.1 + 0.2,经常会得到一个意想不到的结果:

1
2
console.log(0.1 + 0.2) // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3) // false

这个结果是不是很匪夷所思,那么是什么造成这个结果的呢?

原理分析

简单来说就是因为:EcmaScrpt规范定义,JavaScript中所有的数字(包括整数和小数)都只有一种类型–Number。而Number的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数。它的优点是可以归一化处理整数和小数,节省储存空间。而实际计算的时候会转换成二进制计算再转成十进制。进制转换之后会很长,舍去一部分,计算再转回来,就有了精度误差。

更深度的解析请参考:JavaScript 浮点数陷阱以及解法

事实上,这个问题并不只出现在JS里,其他遵循IEEE 754标准的语言,同样存在此问题。

解决方案

这个问题既然已经存在很长时间了,那么解决方案自然也就有很多了。

官方方案

TC39 已经有一个 Stage 3 的提案 proposal bigint,大数问题有望彻底解决。在浏览器正式支持前,可以使用 Babel 7.0 来实现,它的内部是自动转换成 big-integer 来计算,这样能保持精度但运算效率会降低。

主动缩小精度范围

利用toPrecision或toFixed主动把溢出造成的细微差异过滤掉。这种方案吧,只适合浮点数,对于整数的溢出,是束手无策的。

转为整数计算

这种方案跟上面的也差不多,只适合小数。对于包含n位小数的number,先乘10的n次方,再进行计算,最后结果再除10的n次方归位

转为字符串再进行计算

这种应该是目前最普遍的解决方案了,大多数成熟计算框架都采用的此方案。优点是完全不受number存储空间的限制,可以胜任更准确更精细的计算,缺点是性能相对较差。

借助第三方计算框架

math.js

Math.js是一个用于JavaScript和Node.js的扩展数学库。它具有支持符号计算的灵活表达式解析器,大量内置函数和常量,并提供了集成的解决方案来处理不同的数据类型,例如数字,大数,复数,分数,单位和矩阵。强大且易于使用。

官网:http://mathjs.org/
中文官网:https://www.mathjs.cn/
GitHub:https://github.com/josdejong/mathjs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

// 函数和常量
math.round(math.e, 3) // 2.718
math.atan2(3, -3) / math.pi // 0.75
math.log(10000, 10) // 4
math.sqrt(-4) // 2i
math.derivative('x^2 + x', 'x') // 2*x+1
math.pow([[-1, 2], [3, 1]], 2)
// [[7, 0], [0, 7]]
// 表达式
math.evaluate('1.2 * (2 + 4.5)') // 7.8
math.evaluate('12.7 cm to inch') // 5 inch
math.evaluate('sin(45 deg) ^ 2') // 0.5
math.evaluate('9 / 3 + 2i') // 3 + 2i
math.evaluate('det([-1, 2; 3, 1])') // -7
// 链接
math.chain(3)
.add(4)
.multiply(2)
.done() // 14

decimal.js

此库 与 bignumber.js相似,但是注意此库的精度是以 有效数字 来规定的,而不是小数点的位置. 此库中所有的运算都是四舍五入到 对应的精度(类似于python中的 decimal 模块),而不是仅仅只是除法运算四舍五入

官网:http://mikemcl.github.io/decimal.js/
GitHub:https://github.com/MikeMcl/decimal.js/

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

x = new Decimal(9) // '9'
y = new Decimal(x) // '9'

new Decimal('5032485723458348569331745.33434346346912144534543')
new Decimal('4.321e+4') // '43210'
new Decimal('-735.0918e-430') // '-7.350918e-428'
new Decimal('5.6700000') // '5.67'
new Decimal(Infinity) // 'Infinity'
new Decimal(NaN) // 'NaN'
new Decimal('.5') // '0.5'
new Decimal('-0b10110100.1') // '-180.5'
new Decimal('0xff.8') // '255.5'

new Decimal(0.046875) // '0.046875'
new Decimal('0.046875000000') // '0.046875'

new Decimal(4.6875e-2) // '0.046875'
new Decimal('468.75e-4') // '0.046875'

new Decimal('0b0.000011') // '0.046875'
new Decimal('0o0.03') // '0.046875'
new Decimal('0x0.0c') // '0.046875'

new Decimal('0b1.1p-5') // '0.046875'
new Decimal('0o1.4p-5') // '0.046875'
new Decimal('0x1.8p-5') // '0.046875'

bignumber.js

bignumber.js是一款用于任意精度十进制和非十进制算术的JavaScript库

官网:http://alexbardas.github.io/bignumber.js/
GitHub:https://github.com/alexbardas/bignumber.js

1
2
3
4
5
6
7
8
9
10
11
12
13

var BigNumber = require('big-number');
BigNumber(5)
.plus(97)
.minus(53)
.plus(434)
.multiply(5435423)
.add(321453)
.multiply(21)
.div(2)
.pow(2)
// 760056543044267246001

big.js

‎Big.js是JavaScript的”bignumber”实现。这意味着您可以使用它以任何精度存储任何量级的数字,并在 JavaScript 中对它们执行数学运算。‎

官网:http://mikemcl.github.io/big.js/
Github:https://github.com/MikeMcl/big.js/

1
2
3
4
5
6
7
8
9
10

x = new Big(9) // '9'
y = new Big(x) // '9'
new Big('5032485723458348569331745.33434346346912144534543')
new Big('4.321e+4') // '43210'
new Big('-735.0918e-430') // '-7.350918e-428'
Big(435.345) // '435.345'
new Big() // 'Error: [big.js] Invalid value'
Big() // No error, and a new Big constructor is returned

参考文章

深度剖析0.1 +0.2===0.30000000000000004的原因
如何解决0.1 +0.2===0.30000000000000004类问题
两分钟解惑 JS 小数计算精度问题
JavaScript 浮点数陷阱以及解法

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