ubuntu nodejs使用nvm管理和安装

2年前写了一篇当时配ubuntu环境的文章,里面有关于nvm的安装,但是过了这么长时间有些变化,而且想要独立出来,所有在这篇文章在详细介绍下nvm的安装和使用

NVM的安装

nvm可以帮助我们快速来回切换nodejs的版本,git仓库在https://github.com/creationix/nvm

在主页面READEME也有相关的安装教程,这里使用拉取git仓库的方式

  1. 首先,到防止仓库的目录下,clone仓库

    git clone https://github.com/creationix/nvm.git

  2. 进入NVM的目录

    cd nvm

  3. 切换到最新的一个版本,写这篇文章的时候是v0.33.11

    git checkout v0.33.11

  4. nvm.sh使用source命令执行

    source nvm.sh

  5. 执行nvm

    nvm

这个时候就可以看到nvm指令,正常运行了,但是关闭终端再打开就不行了,所以需要在~/.bashrc~/.profile或者~/.zshrc文件执行一次这个命令,需要指定绝对路径,使用pwd命令查看当前目录的路径,加上文件名就行了,类似下面的命令

1
2
export NVM_DIR="path/to/nvmdir"
[ -s "$NVM_DIR/nvm.sh"] && source "$NVM_DIR/nvm.sh"

NVM配置

在安装完成后,需要配置一下下载源,在国内可以使用淘宝源

继续在配置的文件里加入对下载源的环境变量

1
2
3
export NVM_DIR="path/to/nvmdir"
export NVM_NODEJS_ORG_MIRROR="https://npm.taobao.org/mirrors/node/"
[ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh"

这样就配置完成了

NVM下载和使用node

nvm下载node只需要

nvm install

命令就能下载了

下载完成后调用

nvm use

即可~~~!

END

2018-11-16 修复代码中[ -s "$NVM_DIR/nvm.sh"] 最后少了个空格的问题

2018-11-07 完成

2018-11-07 立项

ubuntu18.04 搜狗输入法安装

虽然搜狗输入法没有支持到ubuntu18.04,但是还是可以通过一些方法安装上,这篇文章记录我在ubuntu18.04安装搜狗输入法的过程

首先安装fcitx

sudo apt install fcitx-bin

sudo apt-get install fcitx-table

配置fcitx

进入配置

打开语言配置,点击管理安装的语言

将IBUS替换为fcitx

然后重启系统

安装搜狗输入法

访问搜狗输入法For Linux https://pinyin.sogou.com/linux/?r=pinyin,下载最新的安装包

然后重启系统

查看配置

点击右上角的小键盘

在config中查看输入法配置

如果已经有搜狗拼音了就已经可以用了,如果没有需要手动加一下(一般自动有了)

好了咋们可以打中文啦~~~!

哈哈哈

END

2018-11-07 完成

2018-11-07 立项

元编程是什么

过去常常听说元编程,但是一直不要清楚它是什么意思,这篇文章简单介绍下自己查到的相关知识

元编程

编程(program)是指编写代码去实现某些功能,而元编程(metaprogram)则是指以其他程序为数据,扩展程序/语言的特性的程序的编写,在一般情况下就是通过编程让一个语言获得它本身没有的一些编程特性,或者一个程序可以在运行中修改自身的

javascript简单的元编程例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A{
constructor(){
if(new.target === A){
this.x = 1
this.y = 1
} else {
this.z = 2
}
}
}

class B extends A{
constructor(){
super()
}
}

let a = new A()
let b = new B()

console.log(JSON.stringify(a))
console.log(JSON.stringify(b))

在这个例子中,使用new.target的方式来根据不同的情况进行了初始化,实现了A类在程序运行中在不同情况下的不同实例化能力,看似底层相同,其实不一样

参考文献

Javascript元编程
百度百科_元编程
MDN_new.target

END

2018-10-30 立项

2018-10-29 立项

JS监控DOM大小的变化

由于浏览器原生不自带DOM元素的resize事件,一直想弄一个JS监控DOM元素大小变化的功能,之前想过很多方法都各有优缺点,但是最近发现了一个很不错的方法,虽然以前有设想过但一直没有时间去实现,知道看到有人实现了和我的设想相同的一种比较优良的监听方法,这篇文章将主要讲述两种监听方法,以及优劣分析

DOM大小的变化的诱因

DOM大小的决定因素

在寻找如何监听DOM大小变化的解决办法之前,这里先列举DOM元素宽高的数值的情况:

  1. 自己设置的固定宽高,如样式设置style="width:100px;height:100px"
  2. 由上层元素决定,如样式设置style="width:50%;height:50%"
  3. 有下层元素决定,内容撑大

宽高各自独立,可以混合出现

DOM大小改变的原因

知道了决定的DOM大小的因素的3种情况,可以很快的列出大部分导致DOM大小变化的情况:

  1. 元素修改自己的宽高样式

    元素修改自己的宽高属性,是最简单最直接也是最常见的DOM大小变化的情况

  2. 当元素大小由上层元素(包括窗口大小)决定,上层元素大小发生变动时

  3. 当袁术大小由下层元素决定,下层元素大小发生变动时

上面三种是最常见也是最容易想到的,仔细思考还存在其他情况

  1. 元素的class的内容改变

    当元素的class的内容的宽高变化时,元素是不能监听到class内容变化的,虽然这个操作很不好,但是在切换页面皮肤时确实可能存在这样的问题

  2. 过渡/动画

    当元素设置了过渡/动画效果时,元素的宽高是可能在每一帧的出现变化的

这些因素也是最难以监听和获取的可以叫”隐式变化”,之前3个比较简单的变化由于可以在节点树上和style上明显看出变化,可以简单叫”显式变化”

显式变化可以通过MutationObserver监听来达到事件流驱动节约性能的效果,但是隐式变化也是同样需要检测出来的,下面将列举两个检测办法,都各自有优缺点

解决办法

循环比对

最最简单直接有效的方法当然是每一帧去计算和比对需要监听的DOM的大小是否发生改变

这个方法很简单,也能很快的思考出来,但是这个方法很多文章或者人都认为有很大的性能消耗,其实这个方法的性能消耗在现在的计算机性能下是很小的,相较于每次DOM大小变化导致的重绘消耗的性能,占比很小

覆盖情况

100%,所有元素都可以这样监听

性能分析

展示性能分析前,先先说下测试展示用的机器的配置,CPU是interl i7-4790k,GPU为GTX960

前6秒为禁止状态,后6秒为改动大小的状态

100节点监听

在前6秒里,可以看到以js的执行为主(毕竟没有重绘的需求),一直在执行dom大小的变化检测,但占据的性能并不是很多,大约1%。

在后4秒里,由于出现了dom的大小变化,页面还是执行渲染,在这段时间JS(这里JS只执行循环对比)的消耗比例很低,大约2.5%(在正常监听的时候会有一些操作,比例是会明显增大的),反而是渲染会占据近乎2倍的消耗

其他数量的节点的测试数据

|节点数|静态总时间|静态JS耗时|动态总时间|动态JS耗时|
|—|—|—|—|—|—|—|
|100|5990ms|84.3ms(1.40%)|4020ms|111.1ms(2.76%)|
|500|5928ms|204.2ms(3.44%)|4760ms|209.7ms(4.41%)|
|1000|6007ms|342.9ms(5.71%)|5125ms|342ms(6.67%)|
|2000|6580ms|654ms(9.94%)|7955ms|1094.9ms(13.76%)|
|5000|7667ms|1880.6ms(24.53%)|6277ms|823.3ms(13.11%)|

根据上面的表可以看到静态和动态的时间大体是跟着监听的DOM节点的数量成正比的,动态消耗的时间比静态略多可能是由于对多一层调用的堆栈导致,在5000节点的时候,动态渲染消耗的时间反而少了,是由于5000节点同时改变大小,消耗了大量的资源进行重绘,这时候的帧数也开始明显降低

这个图可以看到资源几乎被完全利用了,而且大部分是渲染消耗的

小结

这个方法可以覆盖全部的变动情况,而且可以监听所有的DOM对象,但是在监听的节点数量很多的情况下(500+)的时候,消耗的性能就很多了,在这之前的消耗还是在可接受的范围内(<5%)的,而且一般实际上是没有这么多需要监听的节点的~~~,一般都在100节点以内,性能消耗是在1%内的,是一个完全满足需求的方案,没必要为了一个几乎不会遇到的需求而放弃这个简单有效的方案

接下来介绍一下,一个基于事件实现的监听方案

DOM滚动事件驱动方案

这个方案是通过监听DOM元素的scroll事件来进行的,在大家可以在这篇文章了解下详情,这里只简单介绍下原理

scroll事件发生的原理

当一个元素的scrollLeftscrollTop属性发生改变的时候,这个元素就会触发scroll事件,scroll事件是不会冒泡的

父元素大小变化时内部元素的对scrollLeft和scrollTop的影响

当父元素大小变化时,内部的元素(内容)在一定条件下会影响scrollLeftscrollTop的值,从而触发scroll函数,下面都以高度为例(宽度其实相同)

父元素变大

在一般情况下父元素变大是不会影响内容的

但是当父元素变大的幅度超过了剩余的内容,那么内容会跟着底部向下移动,导致scrollTop缩小

这样我们就能监听到scroll事件啦!

父元素变小

放大了的原理理解了话,那么放小来看下

如果是父元素变小,如果内容的大小不变,那么scrollTop永远都不会发生变化,为了让其发生变化,这里需要让子元素能跟着父元素变化,而且必须变化幅度大于父元素,才能促使父元素由于超过展示范围,去改变scrollTop

当内容的高度是父元素的100%以上时,由于速度比父元素缩小的块,导致父元素必须修改scrollTop来达到允许的最大的scrollTop,通过这个原理我们就可以监听到父元素的缩小啦!!!

在代码中最好使用200%及其以上,因为在浏览器中DOM的宽高都是整数的,如果是用200%一下会导致收缩不明显,而会漏掉一部分

放大放小混合

上面两种方法都只能实现一直情况,但是只要混合一下就能实现同时监听放大缩小了,一方变动的同时需要恢复scrollTop来让另一方也能跟上

性能分析

这个相较于循环比对方法要复杂的多,但是节约了DOM没有变动时(大部分情况)的计算性能,使用事件驱动是必然性能优化很多的

小结

这个方法虽然很好的解决了循环比对的性能问题,但是会污染使用者的DOM树结构(添加了额外的东西),而且必须要求DOM能够在下面插入节点,文本(TextNode)、图片(ImageElement)等等这些元素就么法了,同时需要父元素是一个相对元素,不然就不能模拟捕捉父元素的宽高,限制条件还是很多的

封装源码

循环监听

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class DomResizeWatcher{
constructor(){
this.datas = new Map()

this._init()
}

_init(){
this._check()
}

_check(){
Array.from(this.datas.values()).forEach(data => {
data.check()
})

requestAnimationFrame(this._check.bind(this))
}

addResizeEventListener(dom, fun){
let data = this.datas.get(dom)
if(!data){
data = new DomResizeWatcherData(dom)
this.datas.set(dom, data)
}
data.addResizeEventListener(fun)
}

removeResizeEventListener(dom, fun){
let data = this.datas.get(dom)
if(!data) return

data.removeResizeEventListener(fun)

if(data.getFunCount() > 0) return

this.datas.delete(dom)
}
}

class DomResizeWatcherData{
constructor(dom){
this.dom = dom
this.funs = new Set()
this.size = this._getDomSize()
}

_getDomSize(){
let height = this.dom.clientHeight
let width = this.dom.clientWidth

return {
height,
width
}
}

_trigger(){
let dom = this.dom
this.funs.forEach(fun => {
fun.apply(dom)
})
}

getFunCount(){
return this.funs.size
}

addResizeEventListener(fun){
this.funs.add(fun)
}

removeResizeEventListener(fun){
this.funs.delete(fun)
}

check(){
let size = this._getDomSize()
if(size.height !== this.size.height
|| size.width !== this.size.width){
this._trigger()
this.size = size
}
}
}

scroll事件驱动

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
class FrameCounter{
constructor(){
this.time = 0
this._run = false
}

_runFun(){
if(this._run){
this.time++
requestAnimationFrame(this._runFun.bind(this))
}
}

start(){
this._run = true
this._runFun()
}

stop(){
this._run = false
}
}

class DOMResizeWatcher{
constructor(){
this.datas = new Map()
this.timer = new FrameCounter()
this.timer.start()
}

addResizeEventListener(dom, fun){
let data = this.datas.get(dom)
if(!data){
data = new DOMResizeWatcherData(dom, this.timer)
this.datas.set(dom, data)
}
data.addResizeEventListener(fun)
}

removeResizeEventListener(dom, fun){
let data = this.datas.get(dom)
if(!data) return

if(fun){
data.removeResizeEventListener(fun)

if(data.getFunCount() > 0) return
}

data.destory()
this.datas.delete(dom)
}
}

class DOMResizeWatcherData{

constructor(dom, timer){
this.dom = dom
this.funs = new Set()
this.trigged = false
this.timer = timer

this.insideBigEl = null
this.insideSmallEl = null

this._init()
}

_init(){
//监听变大的DOM
let insideBig = document.createElement("div")
insideBig.style = "position: absolute;top:0;left: 0;bottom: 0;right: 0;overflow: hidden;visibility: hidden;z-index:-1"
insideBig.innerHTML = `<div style="width:${DOMResizeWatcherData.bigNumber}px;height:${DOMResizeWatcherData.bigNumber}px"></div>`

//监听变小的DOM
let insideSmall = document.createElement("div")
insideSmall.style = "position: absolute;top:0;left: 0;bottom: 0;right: 0;overflow: hidden;visibility: hidden;z-index:-1"
insideSmall.innerHTML = `<div style="width:300%;height:300%"></div>`

this.insideBigEl = insideBig
this.insideSmallEl = insideSmall

try{
this.dom.appendChild(insideBig)
this.dom.appendChild(insideSmall)
} catch(e) {
throw new Error("DOMElement can't appendChild! try another way!")
}

insideSmall.scrollTop = DOMResizeWatcherData.bigNumber
insideSmall.scrollLeft = DOMResizeWatcherData.bigNumber
insideBig.scrollTop = DOMResizeWatcherData.bigNumber
insideBig.scrollLeft = DOMResizeWatcherData.bigNumber

insideBig.addEventListener("scroll", _ => {
insideSmall.scrollTop = DOMResizeWatcherData.bigNumber
insideSmall.scrollLeft = DOMResizeWatcherData.bigNumber
this._trigger()
})
insideSmall.addEventListener("scroll", _ => {
insideBig.scrollTop = DOMResizeWatcherData.bigNumber
insideBig.scrollLeft = DOMResizeWatcherData.bigNumber
this._trigger()
})
}

_trigger(){
if(this.triggertime === this.timer.time) return
this.triggertime = this.timer.time

let dom = this.dom
this.funs.forEach(fun => {
fun.apply(dom)
})
}

getFunCount(){
return this.funs.size
}

addResizeEventListener(fun){
this.funs.add(fun)
}

removeResizeEventListener(fun){
this.funs.delete(fun)
}

destory(){
try{
this.dom.removeChild(this.insideBigEl)
this.dom.removeChild(this.insideSmallEl)
} catch(e){}
}
}
DOMResizeWatcherData.bigNumber = 9999999

参考资料

scrolling官方规范

巧妙监测元素尺寸变化

END

2018-10-23 完成

2018-10-17 立项

设计模式系列之装饰器模式(Decorator Pattern)

本文简单讲述装饰模式

功能

**装饰模式(Decorator Pattern)**可以动态的给一个对象添加额外的功能。

也就是可以在保持原始功能的情况下对原始功能增加额外的修饰能力的模式,例如人可以使用衣服来装饰自己的外表。

UML结构图

实例

人物衣着装饰

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include "pch.h"
#include <iostream>
using namespace std;

//原本类
class Person {
public :
Person() {};
Person(char* name){
this->name = name;
}

virtual void show() {
cout << name<< endl;
}

private:
char* name;
};

//服装类
class Clothes : public Person {
protected:
Person * component;

public:
void decorate(Person * component) {
this->component = component;
}

virtual void show() {
if (component != NULL) {
component->show();
}
}
};

//具体服装类
class Tshirts : public Clothes {
public:
Tshirts(int number) {
this->number = number;
}

virtual void show() {
cout << number << "号体恤 ";
Clothes::show();
}

private:
int number;
};

class Jeans : public Clothes {
public:
virtual void show() {
cout << "牛仔裤 ";
Clothes::show();
}

private:
int number;
};

int main()
{
char name[] = "爱和圣殿";
Person person(name);
Tshirts tshirts(11);
Jeans jeans;

tshirts.decorate(&person);
jeans.decorate(&tshirts);

jeans.show();
}

在这个实例中看到牛仔裤调用show一般很容易让人晕,其实这里的牛仔裤在工厂函数输出后是一个person对象,经过修饰后的一个人!,这里很容易被误导

END

2018-10-11 完成

2018-10-11 立项

设计模式系列之观察者模式(Observer pattern)

本文将简单介绍观察者模式(Observer pattern)

功能

观察者模式(Observer pattern)**也叫发布-订阅模式(Publish/Subject)模式**,实现了一种一对多的依赖关系,可以让多个观察者对象监听同一个主题对象、这个主题对象在状态发生变化时,会通知各个观察者对象,使它们根据收到的消息进行更新。

UML结构图

实例

对象的观察者模式

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <vector>

using namespace std;

class Observer {
public:
virtual void update() {}
};

class Subject {
public:
void attach(Observer * o) {
observers.push_back(o);
}

void detach(Observer * o) {
for (vector<Observer *>::iterator it = observers.begin(); it!=observers.end(); it++) {
if (*it == o) {
observers.erase(it);
break;
}
}
}

void notify() {
int count = observers.size();
for (int i = 0; i < count; i++) {
observers.at(i)->update();
}
}

private:
vector<Observer*> observers;
};

class ConcreteSubject : public Subject {
public:
string subjectState;
};

class ConcreteObserver : public Observer {
public:
ConcreteObserver(string name, ConcreteSubject * subject) {
this->name = name;
this->subject = subject;
}
string oberverState;
virtual void update() {
oberverState = subject->subjectState;
cout << "观察者:" << name.c_str() << " 新状态:" <<oberverState.c_str() << endl;
}
private:
string name;
ConcreteSubject * subject;
};

int main()
{
ConcreteSubject subject1;

ConcreteObserver observer1("A", &subject1);
ConcreteObserver observer2("B", &subject1);

subject1.attach(&observer1);
subject1.attach(&observer2);

subject1.subjectState = "123";
subject1.notify();
}

但是在这个例子中观察者对象和主题对象需要互相知道,还是有一定的耦合性,除了基于对象,还可以基于函数的来,由于C++没有自带托管功能,具体实现就暂时不展示了

END

2018-10-11 完成

2018-10-11 立项

MutationObserver简介

在某些情况下需要监听DOM元素的属性变化,而这些变化不会触发原生事件,难以监听就很尴尬,为了解决这样的情况退推出了MutationObserver 这个WebApi来解决问题,下面就来介绍如何使用它

MutationObserver的使用

下面是基础的示例

1
2
3
4
let targetNode = document.querySelector("#targetNode")
let config = { attributes: true, childList: true, subtree: true, characterData: true }
let observer = new MutationObserver(mutationsList => console.log(mutationsList))
observer.observe(targetNode, config)

可以看到在添加了一个节点后有输出

使用MutationObserver需要3个参数,一个是用于接收事件的回调函数callback,一个是需要监听的节点targetNode,还有一个是监听的配置MutationObserverInit

observe是用来进行监听的函数,同时还有disconnect函数用来阻止监听

callback以及targetNode都很好理解,下面主要介绍MutationObserverInit的详细内容,同时会伴随介绍不同配置下的响应内容

MutationObserverInit

MutationObserverInit是描述的这次监听的内容的配置对象主要包含下面几个属性

属性名 属性类型 默认值 属性描述
attributeFilter Array null 需要监听的属性的列表,为空就是监听全部
attributeOldValue Boolean false 是否记录改变前的旧值
attributes Boolean false 是否监听元素属性变化
characterData Boolean false 是否监听characterData内容的变化,主要涉及Text, ProcessingInstruction, and Comment相关的接口。
characterDataOldValue Boolean false 是否记录characterData内容的旧值
childList Boolean false 是否监听节点的子节点的添加删除操作
subtree Boolean false 是否监听子树相关变化

其中主要监听的内容为3大块:attributes(节点属性),characterData(字符数据),childList(子节点的增删)

attributes

attributes有关的属性包括attributeFilter, attributeOldValue, attributes三个,其中attributes是开关监听节点属性变换的参数

1
2
3
4
5
6
let attributesChangeEl = document.querySelector("#attributes-change")
let config = { attributes: true }
let observer1 = new MutationObserver(mutationsList => {
console.log(mutationsList)
})
observer1.observe(attributesChangeEl, config)

尝试改变颜色的属性,可以看到输出了事件

attributeOldValue

attributeOldValue可以帮助开发者获取改变前的数据

1
2
3
4
5
6
let attributesChangeEl = document.querySelector("#attributes-change")
let config = { attributes: true }
let observer1 = new MutationObserver(mutationsList => {
console.log(mutationsList)
})
observer1.observe(attributesChangeEl, config)

图中可以看到多了一个oldValue的属性来获取改变前的值

attributeFilter

attributeFilter可以帮助开发者只捕获特定属性的变化

1
2
3
4
5
6
let attributesChangeEl = document.querySelector("#attributes-change")
let config = { attributes: true, attributeOldValue: true, attributeFilter: [ "data-a"] }
let observer1 = new MutationObserver(mutationsList => {
console.log(mutationsList)
})
observer1.observe(attributesChangeEl, config)

上面的操作可以看到,只有修改data-a属性的时候才会有事件发生

characterData

characterData主要用在文本节点上面,而且监听的必须是文本节点,这里使用subtree属性监听节点下的子文本节点来简化代码

1
2
3
4
5
6
let characterdataChangeEl = document.querySelector("#characterdata-change")
let config2 = { characterData: true, subtree: true }
let observer2 = new MutationObserver(mutationsList => {
console.log(mutationsList)
})
observer2.observe(characterdataChangeEl, config2)

上图可以看到在改变节点的内的时候有输出数据,characterDataOldValue的功能和attributeOldValue相同,这里就不做演示了

childList

childList属性可以允许监听当前节点的子节点的增删变化

1
2
3
4
5
6
7
8
9
10
11
12
13
let childlistChangeEl = document.querySelector("#childlist-change")
let config3 = { childList: true }
let observer3 = new MutationObserver(mutationsList => {
console.log(mutationsList)
})
observer3.observe(childlistChangeEl, config3)

document.querySelector("#childlist-change-btn").addEventListener("click", function(){
childlistChangeEl.removeChild(childlistChangeEl.children[0])
let div = document.createElement("div")
div.innerHTML = Date.now()
childlistChangeEl.appendChild(div)
})

图中可以看到在修改子节点后有输出数据,添加和删除的节点分别在属性addedNodesremovedNodes里面可以获取

总结与注意

通过上面的内容大致对MutationObserver有了一定的了解了,详细还是看官方的规范以及现实面对的情况具体再去深入了解吧~~~

注意一

文本节点的内容变动需要直接监听文本节点,或者在父节点设置subtree的属性否者不会监听到

参考资料

MDN_MutationObserver

END

2018-10-08 完成

2017-09-28 立项

C++虚函数

最近上了一门课老师让研究一下C++的虚函数的实现原理,emmmmm,于是就有了这篇文章,一起来看下C++虚函数的底层原理吧~~~

面向内存编程

面对C++这个底层的语言,有的时候不得不说的面向内存编程,为此这边文章先从C++对象占用的内存大小来看,这里先看一个简单的C++对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <stdlib.h>
using namespace std;

class SimpleClass{
public:
int x;
float y;
};

int main()
{
SimpleClass s;

cout << sizeof(s) << endl;
system("pause");

return 0;
}

输出8意味着这个对象实例化后是8个字节,其中4个字节为int类型的x属性,另4个字节为float类型的y属性一半

这时候再加一个函数试试看呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <stdlib.h>
using namespace std;

class SimpleClass{
public:
int x;
float y;
void print(){
cout << x << endl;
}
};

int main()
{
SimpleClass s;

cout << sizeof(s) << endl;
system("pause");

return 0;
}

这段代码输出结果依然是8,可以看到一个普通的成员函数是不会占据实例对象的内存的,因为成员函数是处于类作用域下面的全局函数,为此函数不会占据存储空间,但是看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <stdlib.h>
using namespace std;

class SimpleClass{
public:
void print(){
cout << "hello" << endl;
}
};

int main()
{
SimpleClass s;

cout << sizeof(s) << endl;
system("pause");

return 0;
}

纯粹的只有函数的类,实例化后并不是不占用内存,而是依然占据的1个字节的内存,不为0的原因是占位,防止冲突,接下来看下虚函数的内存空间是如何的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <stdlib.h>
using namespace std;

class SimpleClass{
public:
virtual void print(){
cout << "hello" << endl;
}
};

int main()
{
SimpleClass s;

cout << sizeof(s) << endl;
system("pause");

return 0;
}

这里可以看到从1个占位字节上升到了4个字节,是不是很困惑?虚函数和普通函数的差别在哪呢?下面将会从面向对象编程的思维来看待

面向对象的思维

首先看下下面简单的代码

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
#include <iostream>
#include <stdlib.h>
using namespace std;

class A
{
public:
void print()
{
cout<<"This is A"<<endl;
}
};

class B : A
{
public:
void print()
{
cout<<"This is B"<<endl;
}
};

int main()
{
A a;
B b;

a.print();
b.print();
system("pause");

return 0;
}

这个的结果很明显是

1
2
This is A
This is B

当然使用指针的时候结果当然也是如此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main()
{
A a;
B b;

A * pa = &a;
B * pb = &b;

pa->print();
pb->print();
system("pause");

return 0;
}

但是这样的代码呢?

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
#include <iostream>
#include <stdlib.h>
using namespace std;

class A
{
public:
void print()
{
cout<<"This is A"<<endl;
}
};

class B : public A
{
public:
void print()
{
cout<<"This is B"<<endl;
}
};

int main()
{
A a;
B b;

A * pa = &a;
A * pb = &b;

pa->print();
pb->print();
system("pause");

return 0;
}

如果了解清楚一点是可以知道输出结果会变成

1
2
This is A
This is A

多态

在上面的代码中,A、B类的print函数都是各自定义了自己的输出,当想要某种输出的时候必须要显式的指明类型,这对于的程序的扩展性很不好,所以需要多态来解决问题,多态就是将接口与实现进行分离,不同个体提供相同的方法,但因个体差异,而采用不同的策略。而virtual关键词定义的虚函数就是实现多态的方式

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

#include <iostream>
#include <stdlib.h>
using namespace std;

class A
{
public:
virtual void print()
{
cout<<"This is A"<<endl;
}
};

class B : public A
{
public:
virtual void print()
{
cout<<"This is B"<<endl;
}
};

int main()
{
A a;
B b;

A * pa = &a;
A * pb = &b;

pa->print();
pb->print();
system("pause");

return 0;
}

这下结果就变回了

1
2
This is A
This is B

也许现在还是觉得这样可能多次一举,但是这样改变一下呢

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <stdlib.h>
using namespace std;

class A
{
public:
void print()
{
cout<<"This is A print"<<endl;
}

virtual void virtualPrint()
{
cout<<"This is A virtualPrint"<<endl;
}
};

class B : public A
{
public:
void print()
{
cout<<"This is B print"<<endl;
}

virtual void virtualPrint()
{
cout<<"This is B virtualPrint"<<endl;
}
};

A * getPrinter(){
A * p;
if(rand() % 2 == 0){
p = new A();
} else {
p = new B();
}
return p;
}

int main()
{
A a;
B b;

for(int i = 0; i < 10; i++){
getPrinter()->print();
getPrinter()->virtualPrint();
}
system("pause");

return 0;
}

getPrinter函数作为一个生产printer的函数,使用虚函数后外部就不需要知道其具体的类型能正确的执行各自的操作,而不使用虚函数就全部执行的指定的类型的函数

那么虚函数的实现原理是什么呢?

虚函数表

在上面查看内存的时候发现添加了虚函数以后,空的类实例化会占据4个字节的内存,而这4个字节(在64位系统就是8字节)的内存其实就是一个指针,指向了一个虚函数表

大致结构就是这样,具体也可以在宇宙第一IDE种查看下面的类

1
2
3
4
5
6
7
8
9
10
class SimpleClass{
public:
virtual void f1(){
cout << "f1" << endl;
}

virtual void f2() {
cout << "f2" << endl;
}
};

可以看到它的结构是这样的

虚函数的多态,同名函数会覆盖父类的函数,不同名的会追加

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
#include <iostream>
#include <stdlib.h>
using namespace std;

class SimpleClass{
public:
virtual void f1(){
cout << "f1" << endl;
}

virtual void f2() {
cout << "f2" << endl;
}
};

class BigClass : public SimpleClass {
public:
virtual void f1() {
cout << "f11" << endl;
}

virtual void f3() {
cout << "f3" << endl;
}
};

int main()
{
SimpleClass simpleClass;
BigClass sigClass;

return 0;
}

可以从图中看到,重写了的函数在虚函数表中的指针地址不一样,没有重写的保持和父类一致

总结

虚函数这里差不多就介绍这么多了,详细可以继续深入探究,在这里总结一下简单的内容:

  1. 实例化的对象至少有一个字节的占位符
  2. 普通成员函数处于类的全局作用域下,不会占据实例化对象的内存空间
  3. 类定义了虚函数后,实例化后会多一个虚函数表指针,这个指针指向一个虚函数表
  4. 虚函数表里是指向函数实体的函数指针,多态重写函数后原来的指针会指向新的函数,新增的虚函数会依次排在函数表的后面

END

2018-09-26 完成

2018-09-25 立项

JS节流和防抖

JS的节流和防抖主要用于处理高频度的操作,通过限制执行频率来防止页面处理的内容过多导致页面卡顿,这篇将简单实现节流和防抖的函数

节流

节流相当于节省流量,节省资源,主要的概念就是在一段时间内所有操作都只执行一次有效操作,例如监听滚动的事件,如果每次监听操作的内容很复杂需要的性能很多,那么如果每次滚动都执行一次,必然会导致页面卡顿、假死,节流就是防止这样的事情发生

例如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
let fun = function(){
console.log("我会消耗页面100ms的性能", Date.now(), count)
}

let count = 0
let handle = setInterval(_ => {
fun()
count++
}, 10)
setTimeout(_ => {
clearInterval(handle)
}, 1000)

结果会很悲伤,为此我们可以通过节流的方式来进行限制,也就是100ms内只执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let fun_throttle = (function(){
let flag = false

return function(){
if (flag) return
flag = true

console.log("我会消耗页面100ms的性能", Date.now(), count)

setTimeout(_ => {
flag = false
}, 100)
}
})()

let count = 0
let handle = setInterval(_ => {
fun_throttle()
count++
}, 10)
setTimeout(_ => {
clearInterval(handle)
}, 1000)

可以看到只有某些操作才正常执行了,有很多操作被忽略了

防抖

和节流类似,防抖是指在一段时间内执行操作,那么就会一直延长运行执行下一次操作的时间

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
let fun_debounce = (function(){
let flag = false
let handle = null

return function(){
if (handle) clearTimeout(handle)
handle = setTimeout(_ => {
flag = false
}, 100)

if (flag) return
flag = true

console.log("我会消耗页面100ms的性能", Date.now(), count)
}
})()

let count = 0
let handle = setInterval(_ => {
fun_debounce()
count++
}, 10)
setTimeout(_ => {
clearInterval(handle)
}, 1000)

可以看到函数只执行了第一次,后面的操作由于时间间隔太短都被忽略了

函数封装

防抖和节流的概念很好理解,但是如果需要防抖和节流的地方都和上面写的一样,就很麻烦,所以可以将防抖和节流抽取成函数,将传入的函数进行处理就行了

防抖函数

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
40
41
42
/**
* 节流函数
* @param {*} fun 需要节流的函数
* @param {*} time 节流时间间隔
* @param {*} first 节流函数在节流时间间隔内是执行第一个还是最后一个
*/
function throttle(fun, time = 10, first = true){
if (first){
let flag = false

return function(){
if (flag) return
flag = true

let that = this
let args = arguments
fun.apply(that, args)

setTimeout(_ => {
flag = false
}, time)
}
} else {
let flag = false
let that = null
let args = null

return function(){
if (flag) {
that = this
args = arguments
return
}
flag = true

setTimeout(_ => {
fun.apply(that, args)
flag = false
}, time)
}
}
}

防抖函数

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
40
41
42
43
/**
* 函数防抖封装函数
* @param {*} fun 需要防抖的函数
* @param {*} time 防抖时间间隔
* @param {*} first 函数在一次的防抖时间内是执行第一个还是最后一个
*/
function debounce(fun, time = 10, first = true){
if (first){
let flag = false
let handle = null

return function(){
if (handle) clearTimeout(handle)
handle = setTimeout(_ => {
flag = false
}, time)

if (flag) return
flag = true

let that = this
let args = arguments
fun.apply(that, args)
}
} else {
let flag = false
let handle = null

return function(){
if (handle) clearTimeout(handle)

let that = this
let args = arguments
handle = setTimeout(_ => {
flag = false
fun.apply(that, args)
}, time)

if (flag) return
flag = true
}
}
}

效果

节流和防抖展示效果如下

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
let fun1 = function(count){
console.log("我会消耗页面100ms的性能:节流先执行", Date.now(), count)
}

let fun2 = function(count){
console.log("我会消耗页面100ms的性能:节流后执行", Date.now(), count)
}

let fun3 = function(count){
console.log("我会消耗页面100ms的性能:防抖先执行", Date.now(), count)
}

let fun4 = function(count){
console.log("我会消耗页面100ms的性能:防抖后执行", Date.now(), count)
}

let throttle1 = throttle(fun1, 100, true)
let throttle2 = throttle(fun2, 100, false)
let debounce1 = debounce(fun3, 100, true)
let debounce2 = debounce(fun4, 100, false)

let count = 0
let handle = setInterval(_ => {
throttle1(count)
throttle2(count)
debounce1(count)
debounce2(count)
count++
}, 10)
setTimeout(_ => {
clearInterval(handle)
}, 1000)

END

2018-09-21 完成

2018-08-03 立项

MongoDB在docker中备份与恢复

之前有文章讲述了mongodb如何进行备份与恢复,但是最近遇到了使用docker运行mongo时,导出和备份数据没有权限的问题

原因和解决思路

导致这样的问题的原因是mongodb默认只允许本地进行导入和导出,为此需要进入虚拟机内部导出数据后复制出来(或者挂载目录)

导出

首先使用docker exec -it <容器名/ID> /bin/sh进入docker容器内部

使用导出命令mongodump -d <数据库名称> -o <导出目录>导出数据到一个目录

然后使用docker cp <源路径> <目标路径>,路径可以是宿主机路径,也可以是<容器名/ID>:<容器内路径>表示的容器路径

这样就导出数据了~~

导入

导入数据只需要和导出数据相反即可

使用docker cp <源路径> <目标路径>将数据从本地复制到容器当中

使用docker exec -it <容器名/ID> /bin/sh进入docker容器内部,然后使用恢复命令mongorestore -d <数据库名称> <数据目录>将数据恢复即可

END

2018-07-20 完成

2018-07-20 立项