KevinSwift

  • 首页
  • 关于
  • 标签
  • 分类
  • 相册

虚拟dom简易实现

发表于 2020-03-15 | 分类于 vue笔记

虚拟dom的实质就是用js来描述一个dom对象,这个dom对象包括标签名div,p等
属性class id等,还有子元素children
首先是html文件的简易实现,定义了2个DOM结构,在1s后触发patch算法,进行dom的更新和页面上的渲染

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
li {
color: red;
}
</style>
<script src="./vdom/vnode.js"></script>
<script src="./vdom/patch.js"></script>
</head>

<body>
<script>
var ul = new VNode("ul", { class: "ul" }, [
new VNode("p", { "class": "li" }, [], 'virtual,dom'),
new VNode("li", { "class": "li" }, [], "mvvm"),
new VNode('li', { class: 'li' }, [], 'virtual dom'),
new VNode('input', { type: 'text' }),
new VNode('li', { class: 'li' }, [], 'virtual dom'),
new VNode('li', {}, [], 'mvvm'),
new VNode('li', { class: 'li' }, [], 'buppt')
]);
var ul2 = new VNode('ul', { class: 'ul' }, [
new VNode('li', { class: 'li' }, [], 'buppt'),
new VNode('li', { class: 'li' }, [], 'mvvm'),
new VNode('p', {}, [], 'h1 dom'),
new VNode('li', { class: 'li' }, [], 'h1 dom'),
new VNode('div', {}, [], 'h1 dom'),
new VNode('input', { type: 'text' }, []),
]);
document.body.appendChild(ul.render());
setTimeout(() => {
console.log("vnode changed");
patch(ul, ul2);
}, 2000);
</script>
</body>

</html>

vnode表示对应的dom节点的js描述。主要定义了有哪些属性,还有render方法
调用createElement来渲染成真实的dom结构

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
class VNode {
constructor(tagName, props = {}, children = [], text = '') {
//主要记录一个虚拟元素节点的标签名称,属性,子节点,文本内容,对应
//的真实虚拟dom中的element元素,render函数是将这个虚拟的元素节点
//渲染成真正的一个真实dom节点的过程
this.tagName = tagName;
this.props = props;
this.children = children;
this.text = text;
this.key = props && props.key;
var count = 0;
children.forEach(child => {
if (child instanceof VNode)
count += child.count;
count++;
});
this.count = count;
};
render () {
//将虚拟dom生成真实的dom
let element = document.createElement(this.tagName);
for (let key in this.props) {
//设置属性
element.setAttribute(key, this.props[key]);
}
//添加子元素
for (let child of this.children) {
if (child instanceof VNode) {
//递归调用自己 将子元素一个个添加进父元素中
element.appendChild(child.render());
}
}
if (this.text) {
element.appendChild(document.createTextNode(this.text));
}
this.elm = element;
console.log(element);
return element;
}
}

patch算法,拿到两个vnode类后,进行对比,diff算法的本质类似于树的层次遍历,
所以时间复杂度是O(N)。在拿到两个vnode类后,首先判断父节点的属性是否是相同的,如果是不相同的。直接进行替换。不用管子元素。
随后再判断若2个都是文本节点,再进行文本的替换,如果新的节点有子元素,老的没有,则进行添加。老的有,新的没有,则进行删除。否则后续才是真正的updateChildren的方法
patch方法定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function patch (oldVnode, vnode) {
//新老虚拟dom节点的比较
if (isUndef(vnode))
return;
if (oldVnode === vnode) //判断同一层树的结构有没有发生变化
return;
if (isSameVNode(oldVnode, vnode)) //只有在父元素 例如属性 节点名称一样的情况下再去比较子元素
patchVnode(oldVnode, vnode);
else {
//不是相同节点 老的虚拟dom中的父元素节点找到,随后进行插入,将老的删除,新的添加
const ParentElm = oldVnode.elm.parentNode;
createElm(vnode, ParentElm, oldVnode.elm);
removeVnodes(parentElm, [oldVnode], 0, 0);
}
};

属性相同了再判断是不是文本节点,是就替换,还有子元素是否有进行比较,2者都有才进行updateChildren方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function patchVnode (oldVnode, vnode) {
//将孩子节点拿到
var ch = vnode.children;
var oldCh = oldVnode.children;
//如果不是文本节点 首先判断父元素是否属性相同,属性相同的情况下再去判断是不是
//文本元素,如果是文本元素就直接替换掉 否则再比较子元素
if (isUndef(vnode.text)) {
if (isDef(ch) && isDef(oldCh)) {
//就进行新前 新后的一些遍历算法 //如果都有子元素 且子元素和新元素d
updateChildren(oldVnode.elm, oldCh, ch);
} else if (isDef(ch)) {
if (isDef(ch.text)) {
setTextContent(oldVnode.elm, '');
addVnodes(oldVnode, ch, 0, ch.length - 1);
} else if (isDef(oldCh)) {
removeVnodes(oldVnode.elm, oldCh, 0, oldCh.length - 1);
}
}
} else {
setTextContent(oldVnode.elm, vnode.text);
}
}

定义4个下标,新前,旧前,新后,旧后,进行新前与新前,新后与旧后,新前与旧后,
新后与旧前的比较。若都不满足,如果当前元素有key的话,去老树中找该key的节点。
没有key则将新树的头与老树的头到尾一一比较下来。如果有相同的,就把老树的这个节点移动到老树的头前,newStartIdx++;如果没有相同的,就新建这个节点,插到老树的头前,newStartIdx++。

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
function updateChildren(parentElm, oldCh, newCh,){
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx]
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) {
patchVnode(oldStartVnode, newEndVnode)
insertBefore(parentElm, oldStartVnode.elm, oldEndVnode.elm.nextSibling)
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) {
patchVnode(oldEndVnode, newStartVnode)
insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) {
createElm(newStartVnode, parentElm, oldStartVnode.elm)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode)
oldCh[idxInOld] = undefined
insertBefore(parentElm,vnodeToMove.elm, oldStartVnode.elm)
} else {
createElm(newStartVnode, parentElm, oldStartVnode.elm)
}
}
newStartVnode = newCh[++newStartIdx]
}
}

if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, newCh, newStartIdx, newEndIdx)
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}

当oldStartIndex > oldEndIndex时,表明老的遍历完,新的没有遍历完,就添加新的
当newStartIndex > newEndIndex时,表明新的遍历完,老的没有遍历完,就删除老的
参考博客:https://www.cnblogs.com/wind-lanyan/p/9061684.html
github地址:https://github.com/buppt/virtual-dom-mvvm
https://github.com/KevinSwiftiOS/VueVirtual-DOM

vue双向绑定原理简单实现

发表于 2020-03-11 | 分类于 vue笔记

在3.0以前,vue主要的双向绑定原理就是通过object.defineProperty 自己只是知道大概原理,现在手动实现下
首先是data-binding.html的书写
在内部定义一个new Vue 然后简易的进行数据绑定 input绑定p h1 h2

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="buppt" content="https://github.com/buppt">
<title>data binding</title>
</head>

<body>
<h1 id="name"></h1>
<p id="num"></p>
<h2 id="name2"></h2>
<input id="input" type="text">
<script src="./instance/index.js"></script>
<script src="./observer/index.js"></script>
<script src="./observer/dep.js"></script>
<script src="./observer/watcher.js"></script>
<script>
var name1 = document.getElementById("name");
var num = document.getElementById("num");
var name2 = document.getElementById("name2");
var input = document.getElementById("input");
var selfVue = new Vue({
name: "hello",
a: 1
});
selfVue.bindData(name1, "name"); //name1元素绑定name属性
selfVue.bindData(num, "a");
selfVue.bindData(name2, "name");
selfVue.bindData(input, "name");



</script>
</body>

随后是vue类的定义

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
class Vue {
constructor(data) {
this.data = data;
observe(data); //调用observe类 来将该对象变为可监听的
}
bindData (elm, name) {
var self = this;
if (elm.tagName == 'INPUT') {
elm.addEventListener('input', function (e) {

var newValue = e.target.value;
var val = self.data[name]
if (val === newValue) {
return;
}
self.data[name] = newValue;
});
} else {
elm.innerHTML = this.data[name]
}
new Watcher(this, name, function (val) { //在这里会触发watcher的构造函数,随后在构造函数内部调 用get
elm.innerHTML = val;
});

};

debounce (func, wait) { //因为是频繁触发,所以最好是用防抖方式来做到缓冲
var timeout = null;

return function () {
var args = arguments;
var context = this;
if (timeout)
clearTimeout(timeout);
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
}


}

Observer类的定义

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
class Observer {
constructor(data) {
this.data = data;
this.walk(data); //调用walk遍历属性
}
walk (data) {
//遍历对象中的属性
Object.keys(data).forEach(function (key) {
defineReactive(data, key, data[key]); //对每个属性调用defineReactive来变成可监听的
})
}
}



function observe (value) {
if (!value || typeof value !== "object") {
return;
}
return new Observer(value);
}

function defineReactive (data, key, val) {
//为每一个属性确立一个Dep类 然后dep类里面有deps数组
const dep = new Dep();
let childOb = observe(val); //子属性也要做
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
if (Dep.target) { //Dep.target的设定尤其重要
dep.depend();
if (childOb) {
childOb.dep.depend();
}
}
return val;
},
set: function (newVal) {
if (val == newVal) {
return;
} else {
val = newVal;
dep.notify(); //set的时候对应的watcher触发跟新操作
}
}
})
}

Dep类的实现

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
class Dep {
constructor() {
this.subs = [];
}
addSub (sub) {
this.subs.push(sub); //用来保存watcher 实际在vue源码中会判断id 防止重复push
}
notify () {
this.subs.forEach((sub) => {
sub.update(); //每个进行更新
})
}
depend () {
if (Dep.target) {
Dep.target.addDep(this); //执行watcher的addDep
}
}
}
Dep.target = null;
const targetStack = []
function pushTarget (_target) {

if (Dep.target)
targetStack.push(Dep.target);
Dep.target = _target;
// console.log(targetStack);
}
function popTarget () {
Dep.target = targetStack.pop();
}

这里主要用到堆栈的概念 因为相对于vue1的绑定到每个dom元素这么的细腻,
vue2的组件跟新通知到组件级别 组件级别内部再使用diff算法,所以比如A在渲染的时候遇到B,所以A要先保存,再渲染B,B渲染好以后弹出,A再渲染
watcher类的实现

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
class Watcher {
constructor(vm, expression, cb) {
this.cb = cb;
this.vm = vm;
this.expression = expression;
this.value = this.get();
}
update () {
this.run();
}
run () {
const value = this.get(); //这里也会触发get操作 所以会在addSub那里判断是否是重复性加入组件
var oldVal = this.value;
this.value = value;
if (value != oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
}
get () {
Dep.target = this;
pushTarget(this);
//属性值触发 触发属性的get操作进行Dep.target的添加
var value = this.vm.data[this.expression];
popTarget();
return value;
}
addDep (dep) {
dep.addSub(this);
}
}

详见
https://segmentfault.com/q/1010000010095427
https://www.cnblogs.com/natsu07/p/10371448.html
github连接:https://github.com/KevinSwiftiOS/VueDataBinding

setState原理解析

发表于 2020-03-08

setState是一个合成事件,react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件。
在一个事件handler中,不管setState会调用多少次,他们也会在函数执行后执行,被归结为一次渲染
可以优化性能, 这个等到最后一起执行的行为被称为batching。

1
2
3
4
5
6
7
8
9
10

//假设现在this.state.value = 0;

function eventHandler(){
this.setState({value:this.state.value + 1});
this.setState({value:this.state.value + 1});
this.setState({value:this.state.value + 1});
}

//最后this.state.value仍然会是1,不是3;

所以这时候要更新的话,应该传入一个函数给setState,这个函数有2个参数,第一个是previousState,第二个为props.

1
2
3
4
5
6
7
8
9
// 假设 this.state = { value: 0 };

function eventHandler(){
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
}

//现在this.state.value === 3;

当setState不在事件handler中,而在一个回调中,这样的batching异步就不会发生

1
2
3
4
5
6
promise.then(() => {

// 不在事件函数中,所以setState立刻执行
this.setState({a: true}); // 重新渲染 {a: true, b: false }
this.setState({b: true}); // 重新渲染 {a: true, b: true }
})

####batchUpdate
batchUpdate的本质是一个批量更新

Batch Update 即「批量更新」。在 MV* 框架中,Batch Update 可以理解为将一段时间内对 model 的修改批量更新到 view 的机制。以 React 为例,我们在 componentDidMount 生命周期连续调用 setState :
React中的batch update是通过一个事务机制来实现的。
Transaction对一个函数进行包装,让react有机会在一个函数运行前后执行特定的逻辑。从而完成整个 Batch Update 流程的控制。
在事务初始化状态,一个update Queue被创建,调用setState方法的时候,不会马上跟新。而是被推入update Queue当中。当执行结束后会执行queue的flush操作,进行setState赋值。

css浮动

发表于 2020-01-08 | 分类于 js相关

(图文详细)最通俗易懂的CSS 浮动float属性详解
声明:本文属于搬砖大神的文章到自己的博客上,原文地址为:https://www.cnblogs.com/iyangyuan/archive/2013/03/27/2983813.html

教程开始:

首先要知道,div是块级元素,在页面中独占一行,自上而下排列,也就是传说中的流。如下图:

这里写图片描述

可以看出,即使div1的宽度很小,页面中一行可以容下div1和div2,div2也不会排在div1后边,因为div元素是独占一行的。

注意,以上这些理论,是指标准流中的div。
小菜认为,无论多么复杂的布局,其基本出发点均是:“如何在一行显示多个div元素”。
显然标准流已经无法满足需求,这就要用到浮动。

浮动可以理解为让某个div元素脱离标准流,漂浮在标准流之上,和标准流不是一个层次。

例如,假设上图中的div2浮动,那么它将脱离标准流,但div1、div3、div4仍然在标准流当中,所以div3会自动向上移动,占据div2的位置,重新组成一个流。如图:

这里写图片描述

从图中可以看出,由于对div2设置浮动,因此它不再属于标准流,div3自动上移顶替div2的位置,div1、div3、div4依次排列,成为一个新的流。又因为浮动是漂浮在标准流之上的,因此div2挡住了一部分div3,div3看起来变“矮”了。

这里div2用的是左浮动(float:left;),可以理解为漂浮起来后靠左排列,右浮动(float:right;)当然就是靠右排列。这里的靠左、靠右是说页面的左、右边缘。

如果我们把div2采用右浮动,会是如下效果:

这里写图片描述

此时div2靠页面右边缘排列,不再遮挡div3,读者可以清晰的看到上面所讲的div1、div3、div4组成的流。

目前为止我们只浮动了一个div元素,多个呢?

下面我们把div2和div3都加上左浮动,效果如图:

这里写图片描述

同理,由于div2、div3浮动,它们不再属于标准流,因此div4会自动上移,与div1组成一个“新”标准流,而浮动是漂浮在标准流之上,因此div2又挡住了div4。

咳咳,到重点了,当同时对div2、div3设置浮动之后,div3会跟随在div2之后,不知道读者有没有发现,一直到现在,div2在每个例子中都是浮动的,但并没有跟随到div1之后。因此,我们可以得出一个重要结论:

假如某个div元素A是浮动的,如果A元素上一个元素也是浮动的,那么A元素会跟随在上一个元素的后边(如果一行放不下这两个元素,那么A元素会被挤到下一行);如果A元素上一个元素是标准流中的元素,那么A的相对垂直位置不会改变,也就是说A的顶部总是和上一个元素的底部对齐。

div的顺序是HTML代码中div的顺序决定的。
靠近页面边缘的一端是前,远离页面边缘的一端是后。

为了帮助读者理解,再举几个例子。

假如我们把div2、div3、div4都设置成左浮动,效果如下:

这里写图片描述

根据上边的结论,跟着小菜理解一遍:先从div4开始分析,它发现上边的元素div3是浮动的,所以div4会跟随在div3之后;div3发现上边的元素div2也是浮动的,所以div3会跟随在div2之后;而div2发现上边的元素div1是标准流中的元素,因此div2的相对垂直位置不变,顶部仍然和div1元素的底部对齐。

由于是左浮动,左边靠近页面边缘,所以左边是前,因此div2在最左边。

假如把div2、div3、div4都设置成右浮动,效果如下:

这里写图片描述

道理和左浮动基本一样,只不过需要注意一下前后对应关系。由于是右浮动,因此右边靠近页面边缘,所以右边是前,因此div2在最右边。

假如我们把div2、div4左浮动,效果图如下:

这里写图片描述

依然是根据结论,div2、div4浮动,脱离了标准流,因此div3将会自动上移,与div1组成标准流。div2发现上一个元素div1是标准流中的元素,因此div2相对垂直位置不变,与div1底部对齐。div4发现上一个元素div3是标准流中的元素,因此div4的顶部和div3的底部对齐,并且总是成立的,因为从图中可以看出,div3上移后,div4也跟着上移,div4总是保证自己的顶部和上一个元素div3(标准流中的元素)的底部对齐。

至此,恭喜读者已经掌握了添加浮动,但还有清除浮动,有上边的基础清除浮动非常容易理解。

经过上边的学习,可以看出:元素浮动之前,也就是在标准流中,是竖向排列的,而浮动之后可以理解为横向排列。

清除浮动可以理解为打破横向排列。

定义非常容易理解,但是读者实际使用时可能会发现不是这么回事。
定义没有错,只不过它描述的太模糊,让我们不知所措。

根据上边的基础,假如页面中只有两个元素div1、div2,它们都是左浮动,场景如下:

这里写图片描述

此时div1、div2都浮动,根据规则,div2会跟随在div1后边,但我们仍然希望div2能排列在div1下边,就像div1没有浮动,div2左浮动那样。

这时候就要用到清除浮动(clear),如果单纯根据官方定义,读者可能会尝试这样写:在div1的CSS样式中添加clear:right;,理解为不允许div1的右边有浮动元素,由于div2是浮动元素,因此会自动下移一行来满足规则。

其实这种理解是不正确的,这样做没有任何效果。看小菜定论:
对于CSS的清除浮动(clear),一定要牢记:这个规则只能影响使用清除的元素本身,不能影响其他元素。

怎么理解呢?就拿上边的例子来说,我们是想让div2移动,但我们却是在div1元素的CSS样式中使用了清除浮动,试图通过清除div1右边的浮动元素(clear:right;)来强迫div2下移,这是不可行的,因为这个清除浮动是在div1中调用的,它只能影响div1,不能影响div2。

根据小菜定论,要想让div2下移,就必须在div2的CSS样式中使用浮动。本例中div2的左边有浮动元素div1,因此只要在div2的CSS样式中使用clear:left;来指定div2元素左边不允许出现浮动元素,这样div2就被迫下移一行。

这里写图片描述

那么假如页面中只有两个元素div1、div2,它们都是右浮动呢?读者此时应该已经能自己推测场景,如下:

这里写图片描述

此时如果要让div2下移到div1下边,要如何做呢?

同样根据小菜定论,我们希望移动的是div2,就必须在div2的CSS样式中调用浮动,因为浮动只能影响调用它的元素。

可以看出div2的右边有一个浮动元素div1,那么我们可以在div2的CSS样式中使用clear:right;来指定div2的右边不允许出现浮动元素,这样div2就被迫下移一行,排到div1下边。

这里写图片描述

至此,读者已经掌握了CSS+DIV浮动定位基本原理,足以应付常见的布局。

其实,万变不离其宗,只要读者用心体会,再复杂的布局都可以通过小菜总结的规律搞定。

写在后面的话:

必须严正声明,CSS这块极其混乱,尤其是浏览器的兼容性问题,小菜水平有限,本文很可能有不当之处,望读者见谅。

其实真不想写这么长的文章,可为了读者能够理解,总是不由自主的想多举些例子。

为了减轻读者心理压力,本文没有任何CSS、HTML代码,因为现在很多教程上来就是一大堆CSS代码,看到就烦,别说细读了。

最后预祝读者阅读愉快,开心掌握知识。

nodejs中的垃圾回收机制

发表于 2019-12-13 | 分类于 js相关 , node相关

nodejs是基于v8引擎开发的,v8的设计是为浏览器设计的,所以V8的内存相对较少,可以通过node –max-old-space-size=1700(单位是MB)
V8的内存分为2代,一种是新生代,主要存放对象为存活时间比较短的对象,另一种是老生代,主要存放较长时间或常驻内存的对象.

垃圾回收算法

Scavenge算法
新生代主要是通过Scavenge算法进行垃圾回收,该算法主要采用了Cheney算法。
Cheney算法
是一种采用了复制的方式实现的垃圾回收算法,将堆内存一分为二,每一部分空间成为semispace。在这2个semispace空间中,只有一个处于使用状态,另一个处于闲置状态,处于使用中的空间称为FROM空间,处于闲置状态的称为TO空间,在我们分配对象的时候,先在FROM空间中分配空间。
垃圾回收时,先在from空间中查找存活的对象,将其复制到to空间中,而非存活的对象将会被释放,随后对from和to空间中空间进行角色对换,如果一个对象经过多次复制依然存活的话,那么她被认为是存活时间比较长的对象,这种生命周期较长的对象会被移到老生代中,老生代中会采用新的算法进行管理。该过程被称为晋升。
晋升后的老生代采用mark-sweep算法和mark-compact算法
是在标记阶段遍历堆中所有的对象,并且标记活的对象,在随后的清除阶段中,只清除没有被标记的对象。
Scavenge算法是只复制活着的对象,而Mark-Sweep只清除死亡的对象。
但是Mark-Sweep算法存在一个重大的问题就是进行一次清理后,会造成内存碎片,使内存出现不连续的状态。这种内存碎片会对后续的内存分配造成问题,一旦有一个大对象要分配的情况,所有的碎片空间都无法完成此次分配,就会提前出发垃圾回收,而这次垃圾回收是没有必要的。因而为了解决Mark-Sweep内存碎片问题,Mark-Compact被提出来。
Mark-Compact算法
参考链接

js中的继承

发表于 2019-12-08 | 分类于 js相关

js中的继承与其他强类型语言有所不同

利用原型链

将原型链指向另外一个类型的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}

function SubType() {
this.subProperty = false;
}
//继承superProperty
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
return this.subProperty;
}
var instance = new SubType();
console.log(instance.getSuperValue());

上述继承主要方式是通过创建SuperType实例,实现的本质是通过重写原型对象。
注意此时instance的constrctor是指向SuperType的,因为被重写了
注意问题:在通过原型链继承时,原来的实例属性就变成了原型属性,原型属性若是引用类型则会出现共享问题,改变一个就会引起其他的也改变.
colors会在子类中全部共享。

1
2
3
function SuperType() {
this.colors = [1,2,3];
}

借用构造函数

使用apply和call,相当于子类有了自己的属性,和在父类中同名。

1
2
3
4
5
6
function SuperType() {
this.colors = [1,2,3];
}
function SubType() {
SuperType.call(this);
} 也可以像call中传递参数

使用组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
function SuperType(){
this.property = true;
this.colors = [1,2,3];
}
SuperType.prototype.getValue = function() {
return this.colors;
}
function SubType() {
this.name = "ckq";
SuperType.call(this);
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

利用组合继承,将父类中的所有属性都可以拿到子类中。然后通过修改原型链的方式,指向父类的实例,但是要注意,要修改constructor,是他指向自己,不指向父类。

寄生式继承

思路,创建一个封装继承过程的函数,通过将原有对象克隆下来到新的对象,在新的对象上添加新的方法

1
2
3
4
5
6
function createAnother(orignial){
var clone = object(orignial);
clone.sayHi = function() {
alert('hi');
}
}

寄生式组合继承

组合继承的毛病,无论在什么情况下,都会2次调用构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function inhertProperty(SubType,SuperType){
var prototype = Object(SuperType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
//将父类拷贝下来,副本的constructor修改,subType的prototype指向新的prototype.
function SuperType(name){
this.name = "ckq";
this.colors = [1,2,3];
}
function SubType(name,age){
SuperType.call(this,name);

}
inhertProperty(SubType,SuperType);

css选择器优先级和em标签

发表于 2019-12-01 | 分类于 css相关

css添加样式的几种方法,
1.行内样式style = “” 权重 (1,0,0,0) 1000
2.id选择器 即#的个数 权重(0,1,0,0) 100
3.类选择器 .outerClass (0,0,1,0) 10
4.元素选择器 p; (0,0,0,1) 1
**!important表示强制应用该样式,例如

1
2
3
button{
width:150px !important;
}

与上面的选择器相遇的时候,强调使用该样式
渲染规则:
1.如果比较后权重相同,那么后者会覆盖前者,后渲染的会胜出。
2.内联样式 > id选择器样式 > 类选择器样式 > 标签选择器样式
css选择器的使用,尽量避免使用!important和内联样式,id通常和class也是分开的,前者通常用于js中元素的定位,后者多用于css选择器
在实际情况下,10个class也不能逆转1个id。

单位

px:像素单位
em:表示相对尺寸,相对于当前对象文本的父元素的font-size.font-size参考对象为父元素,
rem:也表示相对尺寸,其参考对象为根元素的font-size.因此只需要确定根元素的font-size.

垂直水平居中方法汇总

发表于 2019-11-30 | 分类于 html相关

水平居中

行内元素水平居中

text-align:center可以实现在块级元素内的行内元素的居中,对inline,inline-block,inline-table均有效
如果内部包含着另外一个块级元素,可以把它有块级元素转换为行内块元素

1
2
3
 <div style="text-align: center;">
<div style="width: 100px;height:100px;background:blue;display: inline-block"></div>
</div>

块级元素水平居中

将这个块级元素设置左右边距margin-left,margin-right为auto

1
2
3
<div>
<div style="width: 100px;height:100px;background:blue;margin: auto;"></div>
</div>

利用table+margin的方法

将子元素设置为display:table 在表现上类似于block元素

1
2
3
<div>
<span style="width:100px;height:100px;background:blue;display: table;margin: auto;"></div>
</div>

使用absolute+transform方法

将父元素设置为相对定位 将子元素设置为绝对定位,向右移动子元素,移动距离为父元素的一半,最后再移动子元素的一半宽度来达到居中的效果
absolute 定位针对父元素中不是static定位的第一个父元素进行定位

1
2
3
4
<div style="position: relative;">
<div style="position: absolute;left: 50%;transform: translateX(-50%);background: blue;width: 100px;height: 100px;">

</div>

使用flex+justify-content方法

1
2
3
4
.parent{
display:flex;
justify-content:center;
}

多个块级元素水平居中 使用flex布局

1
2
3
4
.parent{
display:flex;
justify-content:center;
}

对于水平排列的块级元素设置为inline-block,在父元素上设置为text-align:center即可。

1
2
3
4
<div style="text-align: center;">
<div style="display: inline-block;width: 50px;height: 50px;background: blue;"></div>
<div style="display: inline-block;width: 50px;height: 50px;background: blue;"></div>
</div>

浮动元素水平居中(重点考察)

**对于定宽的浮动元素,通过设置子元素relative+负margin,通过设置向右浮动为父容器宽度的50%,随后向左偏移自己的宽度的一半即可。

1
2
3
4
5
<div>
<div style="float: left;background: blue;width: 100px;height: 100px;
position: relative;left: 50%;margin-left: -50px;
"></div>
</div>

不定宽的浮动元素,外层嵌套一个浮动元素为float:left,随后设置定位为向左left:50%,
内部也设置为相对定位:float:left:50%;

1
2
<div style="float: left;left:50%; position: relative;">
<div style="float: left;left:50%;width:100px;height:100px;background: blue;"></div>

通用方法:使用flex布局

1
2
3
4
<div style="display: flex;justify-content: center;">
<div style="float:left;background: blue;width:20%;height: 100px;
"></div>
</div>

绝对定位元素水平居中(重点考察)

1
2
3
4
<div>
<div style="background: blue;width:20%;height: 100px;position: absolute;margin: 0 auto;left: 0;right: 0;
"></div>
</div>

通过设置子元素的position为absolute后,设置margin:0,auto,随后left和right设置为0即可。

垂直居中

单行内联元素垂直居中

父元素设置line-height等于行高

1
2
3
<div style="height: 500px;line-height:500px">
<span>曹凯强</span>
</div>

多行内联元素垂直居中 使用flex,flex-direction为column定义主轴方向,

1
2
3
4
5
<div style="display:flex;flex-direction: column;justify-content: center;height:500px">
<p>Dance like nobody is watching, code like everybody is. </p>
<p> Dance like nobody is watching, code like everybody is.
Dance like nobody is watching, code like everybody is.</p>
</div>

利用表布局,设置子元素的display为table-cell 设置vertical-algin为middle即可

1
2
3
4
5
6
7
<div style = "display: table;height: 1000px;">
<div style="display: table-cell;vertical-align: middle;"
<p>Dance like nobody is watching, code like everybody is. </p>
<p> Dance like nobody is watching, code like everybody is.
Dance like nobody is watching, code like everybody is.</p>
</div>
</div>

块级元素的垂直居中

绝对定位为距离顶部50%,并设置margin-top偏移元素高度的一半

1
2
3
4
5
6
7
8
9
position:absolute;
top:50%;
margin-top:-50px;

<div style="position: relative;height: 500px;">
<div style="position: absolute;width:100px;height:100px;top:50%;left: 50%;margin-top: -50px;margin-left: -50px;background: blue;"
</div>

</div>

使用absolute + transform

1
2
top:50%;
transform:translateY(-50%)

使用flex + align-items 子元素垂直居中 在父元素中设置

1
2
display:flex;
align-items:center;

使用table-cell+vertical-align 注意 是父元素 将父元素转换为一个表格表单类似于td,tr,再通过设置vertical-align设置

水平垂直居中

设置子元素的position:absolute 随后设置margin:auto

1
2
3
4
5
6
7
8
9
10
11
12
#container{
position:relative;
height:100px;
}
#center{
position:absolute;
top:0;
left:0;
right:0;
bottom:0;
margin:auto;
}

使用flex布局 在主轴和纵轴上都设置对齐方向

1
2
3
4
5
#container{
display:flex;
justify-content:center;
align-item:center;
}

grid与margin:auto结合

`

#container{
display:grid;
}

#center{
margin:auto;
}

vue面试笔记(1)

发表于 2019-11-03 | 分类于 vue笔记

对vue的理解

是一个渐进式(progress web app)js(根据业务需要而选择需要什么,最基本的UI操作,到vue-router,vuex状态管理)框架,可以把一个网页分割成许多的组件,当其他网页有类似的功能的时候,直接让封装的组件进行复用,
是构建用户界面的声明式框架,不关心具体是如何实现的。

mvvm的理解

是model-view-viewModel的缩写,model为数据模型,可以在model中定义数据修改和操作的业务逻辑。view代表UI组件,负责将数据模型转化为UI展现出来,
viewModel是一个同步view和Model的对象。
view和model通过viewModel之间相连。model和viewmodel之间的交互是双向的。因此view和model的数据变化会同步
viewModel通过双向的数据绑定把view和model层连接了起来。view和model之间的同步工作完全是自动的,无需人为干涉。开发者只需要关注业务逻辑。无需操作DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

mvvm和mvc区别

mvc中的controller转变成了mvvm中的viewModel,mvvm主要解决了mvc中大量的dom操作使页面渲染性能降低的原因,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到 View 。

v-model(双向绑定的原理)

vue的双向数据绑定是通过数据劫持结合发布者订阅者模式实现的,数据劫持通过object.defineProperty()来劫持对象数据的setter对象和getter操作。
在数据变动时做你向做的事情。
原理:通过observer来监听自己model的变化,通过compile来解析编译模板的指令,利用watcher搭建起observer和compile之间的桥梁,
达到数据变化 -> 视图更新 ,
在初始化vue的时候,遍历data的对象,给每一个键值对利用object.defineProperty对data中的所有数据加上get和set方法,利用事件监听dom的机制,让视图去改变数据。
(angular js中实现的机制是脏检查),react中是拉的机制**,
收集依赖和触发依赖

1
2
3
<template>
<h1{{name}}</h1>
</template>

把用到数据name的地方都收集起来,然后等属性发生变化的时候,把之前收集好的依赖循环触发一遍即可。
在getter中收集依赖,在setter中触发依赖

依赖放的地方

将依赖收集来的地方放在dep类的数组中,帮我们来管理依赖,使用这个类,我们可以收集依赖,删除依赖或者向依赖发送通知。

依赖是谁

依赖的地方可能有很多,一个地方是模板,一个地方是watch,需要抽离出一个能几种处理这些情况的类。
然后,在收集阶段只收集这个封装好的类的实例即可。通知也通知他一个。随后它在负责通知其他地方。就叫watcher.
将watcher实例保存到dep中。
将object中所有属性转换为可侦测的。通过observer类将object所有属性转化为getter/setter
有以下几步:
第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化

第二步:compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

第三步:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:

  • 在自身实例化时往属性订阅器(dep)里面添加自己
  • 自身必须有一个 update()方法
  • 待属性变动 dep.notice()通知时,能调用自身的 update() 方法,并触发 Compile 中绑定的回调,则功成身退。
    第四步:MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。

vue和react angular的区别

相同点:
react和vue都是用虚拟DOM Virtual DOM
中心思想相同:一切都是组件,组件实例之间可以嵌套
都有着合理的钩子函数
都不内置ajax、route等核心包,以插件的形式加载
都有配套的路由和负责处理全局状态管理的库;
不同点:
react采用jsx渲染页面,vue使用简单的模板
vue是双向数据流,react是单向数据流(需要了解)
vuejs在模板中提供了指令,过滤器,可以非常方便,快速操作DOM。
vue比react的运行速度更快。

react和angular的区别

相同点:都是单向数据流
不同点:react中没有指令,angular提供了丰富的指令。

webpack笔记(1)

发表于 2019-11-03 | 分类于 webpack笔记

总体流程

webpack在启动后会从entry里面配置的module开始,递归解析entry依赖的所有module.每找到一个Module,就会根据配置的loader去找对应的转换规则,
对module进行转换后,再解析出当前Module所依赖的Module,这些模块会以entry作为分组,一个entry及其所依赖的所有module会被分配到一个组也就是一个chunk中,
最后,webpack将这些chunk转换为文件输出。整个流程中,webpack会广播出各种事件,插件能够在适当时机执行plugin里面的逻辑。

核心概念

entry:入口
module:webpack中一切皆模块,一个文件对应一个模块,webpack会从配置的entry开始递归解析依赖的module
chunk:代码块,一个chunk可以由多个模块组成,用于代码的分割和合并。bundle是webpack打包出来的文件。
loader:模块转换器,用于将模块的原内容按照需求转换成新内容
plugin:扩展插件,在webpack的构建流程中的特定时机注入扩展的逻辑,来改变构建结果或做我们想做的事情
output:输出:在webpack经过一系列流程后,将最终的代码进行输出。

基本功能

代码转换:ts转换为js,scss编译成css等
文件优化:压缩js,css,html代码,压缩合并图片
代码分割:提取多个页面的公共代码,提取首屏不需要执行的代码并且让其异步加载
模块合并:在采用模块化开发的项目中,构建功能将模块分类合并成一个文件
自动刷新:热跟新,监听本地代码的变化,自动构建,刷新浏览器
代码校验:在代码提交到仓库前,检查代码是否符合规范(eslint),以及单元测试是否通过
自动发布:更新完代码后,自动构建上线发布。

webpack和gulp,grunt有什么区别

都是打包工具,grunt和gulp在早期更加流行,现在不大流行。
gulp和grunt都是基于任务或者流的思想,找到一个或者类似的文件,对其进行一系列的操作,更新流上的输出。
webpack是基于入口的,会自动递归的解析所需要加载的所有资源文件,用不同的loader进行处理,用plugin来扩展webpack功能。
gulp和grunt需要开发者将前端构建过程拆分成不同的任务,并且合理控制任务之间的流程,然而webpack只要开发者找到入口文件,并弄清楚各个文件用什么loader加载。

webpack简单实用

本项目安装webpack

1
npm i webpack --save-dev

配置webpack.config.js 配置webpack的6大概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const path = require('path');
module.exports = {
entry:'./main.js', //入口文件
output:{
filename:'bundle.js', //所有依赖的模块合并并且输出到一个bundle,js文件
path:path.resolve(__dirname,'./dist').//输出文件都放到dist目录下
},
module:{
rules:[
{
test:/\css$',
use:['style-loader','css-loader']
}
]
},
plugin:[
new ExtractTextPlugin({
filename:`[name]_[contentlength"8].css` //从js文件中提取出.css文件的名称 这样bundle.js下面就没有css代码了,生成新的css文件,再写入index.html中。
})
]
};

loader表示在遇到哪些文件时,用哪些loader去转换,从右到左的顺讯,首先实用css-loader,再使用style-loader转换器将css内容注入到js中。
plugin:是用来扩展webpack功能的,通过在构建流程中注入钩子函数实现,
常见的loader:
file-loader:把文件输出到一个文件夹当中,在代码中通过相对URL去引用输出的文件
image-loader:加载并且压缩图片文件
babel-loader:
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。eslint-loader:通过 ESLint 检查 JavaScript 代码
常见的plugin:
define-plugin:定义环境变量
cmomon-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
loader和plugin的不同:作用不同:webpack原生只能解析js文件,loader扩展其解析非js文件的能力,plugin让webpack更加的灵活,
在webpack运行的生命周期中会广播出很多的事件,plugin可以监听这些事件,在合适的时机通过webpack提供的api改变输出的结果。
不同的用法:loader在module.rules中配置,是为了模块的解析规则而存在,类型为数组,plugin在plugins中单独配置,类型为数组,参数都是通过构造函数的方式传入

DevServer的使用

主要功能:
1.提供http服务而不是用本地文件预览
2.监听文件的变化并且自动刷新网页,做到实时预览
3.支持source map,方便调试
devServer会启动一个http服务器用于服务网页请求。同时帮助启动一个wbpack,并且接受webpack发出的文件变更信号,通过websocket协议自动刷新网页做到实时预览.
webpack在启动时会开启监听模式,之后webpack会监听本地文件系统的变化,在发生变化后重新构建出新的结果。默认是关闭的,开启 webpack –watch
注意:通过devServer开启的webpack自动开启监听模式.
当发生变化时会重新执行构建的程序,然后通知DevServer,devServer会让webpack在构建出的js代码中注入一个代理客户端用于控制网页,网页和devServer之间通过webSocket协议通信,
以方便devServer接收客户端发送的命令,devServer在收到来自webpack的文件变化通知后,通过注入的客户端控制网页刷新

模块热替换

除了重新刷新整个网页来实时预览,devServer还有一种被称为模块热替换的刷新技术,做到在不重新加载整个网页的情况下,通过将已跟新的模块替换老模块,再重新执行一次来实现实时预览。。
相对于默认的刷新机制能提供更快的响应速度和更好的开发体验。默认是关闭的,要开启热替换,只需要在devServer时带上–hot即可。

支持sourceMap

浏览器中的js代码都是编译器输出的代码,这些代码的可读性非常差,在这样的代码中debug是非常差的体验,调试工具可以通过sourceMap映射代码,让我们在源代码的基础上调试。
只需要在启动时加上–devtool source-map参数即可。这样打开chrome的开发者工具,就可在source中看到可调式的源代码了。

webpack-dev-server和http服务器例如nginx有什么区别

webpack-dev-server使用内存来存储webpack开发环境下的打包文件,并且可以使用模块热更新,相比于传统的http服务器更加的高效。

热跟新究竟是怎么做到的,具体说明原理

1.在webpack的watch模式下,文件系统中某一个文件发生了变化,webpack就监听到了文件变化,根据配置文件对模块进行重新打包编译,并且将打包后的代码通过js代码保存在内存中
2.webpack-dev-server(在服务端的),webpack-dev-server和webpack之间的接口交互,主要是dev-server的中间件webpack-dev-middleware和webpack之间的交互,
webpack-dev-middleware调用webpack暴露的API对代码的变化进行监控,并且告诉webpack,将打包好的代码保存到内存中。
3.webpack-dev-server对文件变化的控制,不同于第一步,并不是监控代码变化重新打包,当我们在配置文件中配置了devServer.watchContentBase为true的时候,
Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。
4.webpack-dev-server的工作,通过sockjs(webpack-dev-server)的依赖在浏览器端和服务器端建立一个websocket链接。将webpack编译打包的各个阶段状态信息告诉浏览器端,
浏览器根据这些socket进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。
5.webpack-dev-server/client并不能请求更新的代码,也不执行更新的操作,又把这些工作交回给了webpack,webpack/hot/-devserver工作是根据前者传过来的信息决定刷新浏览器还是进行模块热更新。
6.HotModuleReplacement.runtime是客户端HMR的中枢,接收上一步传递给他的新模块的hash值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。
7.hotModulePlugin会对比新旧模块,决定是否使用热更新。
8.最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。

利用webpack优化前端性能

1.压缩代码,删除冗余代码,利用webpack的uglifyJSPlugin和ParallelUglifyPlugin来压缩代码。利用cssnano来压缩css
2.利用CDN加速。将引用的静态资源路径改为CDN上的相对路径。。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径
3.删除死代码,tree-Shaking,将代码中永远不会走到的片段删除掉。–optimize-minimize来实现
4.提取公共代码

如何提高webpack的构建速度?

多入口情况下,使用CommonsChunkPlugin来提取公共代码通过externals配置来提取常用库利用DllPlugin和DllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。使用Happypack 实现多线程加速编译使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。 原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度使用Tree-shaking和Scope Hoisting来剔除多余代码

alias说明

resolve配置webpack如何寻找模块所对应的的文件,webpack内置js模块化语法的解析功能,默认采用模块化标准里约定的规则去找,也可以根据自己的需要去修改默认的规则。
resolve.alias通过别名将原导入路径映射成一个新的导入路径。

1
2
3
4
5
resolve:{
alias:{
components:'./src/components/'
}
}

所以import Button from ‘components/button’导入时,被实际替换成了import Button from ‘./src/components/button’

配置单页和多页应用

单页应用是webpack的标准默哀是,直接在entry中指定单页应用的入口即可
多页应用:使用webpack的autoWebPlugin来完成简单自动化的攻坚,前提是项目的目录结构必须遵守他预设的规范。
多页应用的注意点:
1.每个页面的公共代码可以抽离出来,避免重复的加载。
业务的加载,页面的需求会不断的加载,所以一定要让入口的配置更加灵活,避免每次添加新页面的时候还需要修改构建配置。
webpack面试题1
webpack面试题2

webpack配置单页和多页的应用程序

单个页面

1
2
3
module.exports = {
entry:'./path/to/my/entry/file.js'
}

多页面应用程序

1
2
3
4
5
6
module.exports = {
entry:{
pageOne:'./src/pageOne/index.js',
pageTwo:'./src/pageTwo/index.js'
}
}

npm打包时注意的规范
利用webpack来上传npm包
npm模块需要注意以下几个问题
1.要支持commonjs模块化规范,所以要求打包后的最后结果也遵守该规范
2.npm模块最后编写的结果应该是es5标准的
3.npm大小应该是尽量小
4.发布的模块不能将依赖的模块一起打包,应该让用户自行选择去安装,可以避免模块应用这儿再次打包时出现底层模块重复打包的情况。
5.UI组件类的模块应该将依赖的其他资源文件,例如.css文件也需要包含在发布的模块中。
基于以上需要注意的问题,我们可以对于webpack配置做以下扩展和优化:
1CommonJS模块化规范的解决方案: 设置output.libraryTarget=’commonjs’使输出的代码符合CommonJS 模块化规范,以供给其它模块导入
2.使用输出ES5代码的解决方案:使用babel-loader把 ES6 代码转换成 ES5 的代码。再通过开启devtool: ‘source-map’输出SourceMap以发布调试。
3.Npm包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入transform-runtime插件
4.不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack哪些模块不需要打包。
5.对于依赖的资源文件打包的解决方案:通过css-loader和extract-text-webpack-plugin来实现,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
module: {
rules: [
{
// 增加对 CSS 文件的支持
test: /\.css/,
// 提取出 Chunk 中的 CSS 代码到单独的文件中
use: ExtractTextPlugin.extract({
use: ['css-loader']
}),
},
]
},
plugins: [
new ExtractTextPlugin({
// 输出的 CSS 文件名称
filename: 'index.css',
}),
],
};

webpack流程概括

1.webpack的运行流程是一个串行的过程,从启动到结束会依次执行下面的流程

1.1初始化参数:从配置文件和shell语句中读取和合并参数,得出最终的参数,在这个过程当中还会执行配置文件中的插件的实例化语句(new Plugin())

1.2开始编译:用上一步得到的参数初始化compilier对象,Compilier负责文件的监听和启动编译,在compilier实例中包含了完整的webpack配置,全局只有一个compilier实例
加载插件,依次调用插件的apply方法,让插件可以监听后续的所有节点事件节点,同时会向插件中传入compilier实例的引用,以方便插件通过compilier调用webpack提供的API。
加载所有配置的插件,通过执行对象的run方法开始执行编译。

1.3确定入口:根据配置中的entry找出所有入口文件

1.4编译模块:从入口文件出发,调用所有配置的loader对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。

1.5完成模块编译:在经过第4步使用loader翻译完成所有的模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。

1.6输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再将每个chunk转换成一个个文件加入到输出列表中,这是修改文件内容的最后机会.

1.7输出完成:在确定好输出内容之后,根据配置确定好的路径和文件名,将文件的内容输出。

注意:webpack会在确定的时间点广播特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用webpack提供的API改变webpack的运行结果。

webpack大致流程

初始化:启动构建,读取和合并配置参数,加载plugin,实例化compiler.

编译:从entry出发,针对每个module串行调用对应的loader去编译文件的内容,再找到该module依赖的module,递归的进行编译处理。

输出:将编译后的module组装成chunk,将chunk再转换成文件,输出到对应的文件中。

初始化详细流程:

初始化参数:从配置文件和shell语句中读取与合并参数,得出最终的参数,在这个过程中还会进行插件实例化的语句new plugin()
实例化compilier:用上一步得到的参数初始化compilier实例,compilier负责文件的监听和启动编译,在compilier实例中包括了完整的webpack配置,全局只有一个compilier实例
加载插件:依次调用插件的apply方法,让插件可以监听后续的所有事件节点,同时向插件中传入compilier的引用,以方便插件通过compilier调用webpack的API。
environment:开始使用Nodejs风格的文件系统到compilier对象,以方便后续文件的搜寻和读取。
entry-option:读取配置的entry,为每个entry实例化一个entryPlugin,为后面的递归遍历创造条件
after-plugin:调用完所有内置的和配置的插件的apply方法。
after-resolvers:根据配置来初始化resolver,resolver负责在文件系统中寻找指定路径的文件

编译阶段

run:启动一次新的编译
watch-run:在监听模式下启动编译,这个事件中可以捕获哪些文件发生了变化从而产生一次新的编译。
compilier:告诉插件新的编译将要启动,同时会给插件新的compilier对象。
compliation:当webpack以开发模式运行的时候,每当检测到文件的变化时,便会有一次新的compilation被创建。
make:一个新的compliation创建完毕后,就会从entry开始读取文件,开始递归的解析。
after-compiler:一次细腻的compliation执行完成。
invalid:当遇到文件不存在的时候,文件的编译错误便会触发这个错误。

输出阶段

should-emit 所有需要输出的文件已经生成,询问插件有哪些文件需要输出,有哪些不需要输出。
emit:确定好要输出哪些文件后,执行文件的输出,可以在这里获取和修改输出的内容。
done:成功完成一次完整的编译和输出流程。
failed:如果在输出阶段的流程中遇到错误,就会跳到这个本步骤。

12…6
KevinSwift

KevinSwift

58 日志
28 分类
64 标签
© 2020 KevinSwift
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4