五一之后第二周(5.11-5.17)的安排

重点js基础/Vue框架/Css/node

  • Vuex模拟实现
  • 手写一个简单的mvvm
  • BFC 以及 清除浮动以及原理
  • 闭包
  • 观察者模式
  • http1.x和http2.x

Vuex源码以及模拟实现

vuex的模拟实现

手写简单的mvvm

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<div id="app">
<h2>{{message}}</h2>
<input v-model="c" type="text">
</div>
<script src="./MVVM.js"></script>
<script>
let mvvm = new Mvvm({
el: '#app',
data: {
a: {
b: 1
},
c: 2,
message: 'this is Message'
}
});
console.log('mvvm', mvvm);
</script>

具体的实现:

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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// 创造一个实例
function Mvvm(options = {}) {
this.$options = options
let data = this._data = this.$options.data

// 数据劫持
observe(data)

//this 代理了 this._data
// mvvm._data.a.b 相等于 mvvm.a.b
for (let key in data) {
Object.defineProperty(this, key, {
configurable: true,
get() {
return this._data[key] // 访问this.a.b
},
set(newValue) {
this._data[key] = newValue;
}
})
}

// initComputed.call(this);
// 数据编译
new Compile(options.el, this)
}
// 数据劫持 给所有对象增加set get方法
function Observe(data = {}) {
let dep = new Dep()
// 给对象添加set get
for (let key in data) {
// 把data属性通过defineProperty的方式定义属性
let val = data[key];
// 递归继续向下找,实现深度的数据劫持
observe(val);
Object.defineProperty(data, key, {
configurable: true,
get() {
// 将watcher添加到订阅事件中 [watcher]
Dep.target && dep.addSub(Dep.target)
return val
},
set(newVal) {
if (val === newVal) return;
val = newVal;
observe(newVal)
}
})
}
}

// 方便递归调用
function observe(val) {
if (!val || typeof val !== 'object') {
return -1
}
return new Observe(val)
}

// 数据编译
function Compile(el, vm) {
// 将el挂载到实例上
vm.$el = document.querySelector(el);
// ?
let fragment = document.createDocumentFragment();
let child
while(child = vm.$el.firstChild) {
fragment.appendChild(child)
console.log('child', child);
console.log('fragment', fragment);
}
// 对 el内容进行替换
function replace(frag) {
Array.from(frag.childNodes).forEach(node => {
let txt = node.textContent;
let reg = /\{\{(.*?)\}\}/g; // 正则匹配{{}}
console.log('node', node);
if (node.nodeType === 3 && reg.test(txt)) {
function replaceTxt() {
node.textContent = txt.replace(reg, (matched, placeholder) => {
console.log(placeholder); // 匹配到的分组 如:song, album.name, singer...
new Watcher(vm, placeholder, replaceTxt); // 监听变化,进行匹配替换内容
return placeholder.split('.').reduce((val, key) => {
return val[key];
}, vm);
});
};
// 替换
replaceTxt();
}
if (node.nodeType === 1) { // 元素节点
let nodeAttr = node.attributes; // 获取dom上的所有属性,是个类数组
Array.from(nodeAttr).forEach(attr => {
let name = attr.name; // v-model type
let exp = attr.value; // c text
if (name.includes('v-')){
node.value = vm[exp]; // this.c 为 2
}
// 监听变化
new Watcher(vm, exp, function(newVal) {
node.value = newVal; // 当watcher触发时会自动将内容放进输入框中
});

node.addEventListener('input', e => {
let newVal = e.target.value;
// 相当于给this.c赋了一个新值
// 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新
vm[exp] = newVal;
});
});
}
// 如果还有子节点,继续递归replace
if (node.childNodes && node.childNodes.length) {
replace(node);
}
})
}
// 替换内容
replace(fragment);
vm.$el.appendChild(fragment); // 文档碎片放到el元素中
}
// 发布订阅
function Dep() {
this.subs = []
}
Dep.prototype = {
addSub(sub) {
// 订阅就是放入subs
this.subs.push(sub)
},
// this.subs数组里的每一项,都有一个update方法
notify() {
console.log('this.subs', this.subs);
this.subs.forEach(sub => {
sub.update()
})
}
}
// 监听者
// 通过Watcher这个类创建的实例,都拥有update方法
function Watcher(vm, exp, fn) {
if (!vm && !exp) return;
this.vm = vm;
this.exp = exp;
this.fn = fn;
Dep.target = this;
let arr = exp.split('.');
let val = vm;
arr.forEach(key => { // 取值
val = val[key]; // 获取到this.a.b,默认就会调用get方法
});
Dep.target = null;
}
Watcher.prototype.update = function() {
// notify的时候值已经更改了
// 再通过vm, exp来获取新的值
let arr = this.exp.split('.');
let val = this.vm;
arr.forEach(key => {
val = val[key]; // 通过get获取到新的值
});
this.fn(val)
}
// let watcher = new Watcher(() => console.log(111)); //
// let dep = new Dep();
// dep.addSub(watcher); // 将watcher放到数组中,watcher自带update方法, => [watcher]
// dep.addSub(watcher);
// dep.notify(); // 111, 111

yun….一头雾水,我太难了!!!

BFC 以及 清除浮动以及原理

BFC(block formatting context) 块级格式化上下文,用于决定块盒子(block box)的布局及浮动相互影响范围的一个区域,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。

BFC创建触发条件

  • 根元素或其它包含它的元素;
  • 浮动 (元素的float不为none);
  • 绝对定位元素 (元素的position为absolute或fixed);
  • 行内块inline-blocks(元素的 display: inline-block);
  • 表格单元格(元素的display: table-cell,HTML表格单元格默认属性);
  • overflow的值不为visible的元素;
  • 弹性盒 flex boxes (元素的display: flex或inline-flex);
    平常中,设置overflow: hidden/scroll, float: right/left,position: absolute,display: flex/inline-flex;即当前元素创建了一个BFC

BFC约束规则

  • 内部的盒会在垂直方向一个接一个排列(可以看作BFC中有一个的常规流)
  • 处于同一个BFC中的元素相互影响,可能会发生外边距重叠((完整的说法是:属于同一个BFC的两个相邻Box的margin会发生重叠(塌陷),与方向无关。))
  • 每个元素的margin box的左边,与容器块border box的左边相接触(对于从左往右的格式化,否则相反),即使存在浮动也是如此
  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然
  • 计算BFC的高度时,考虑BFC所包含的所有元素,连浮动元素也参与计算
  • 浮动盒区域不叠加到BFC上,(BFC区域不会与float的元素区域叠加)

BFC阔以解决的问题

  • 垂直外边距重叠问题
  • 去除浮动 (BFC区域不会与float的元素区域叠加)
  • 自适用两列布局(float + overflow)

实例中理解BFC的约束规则

  • 如何实现左侧宽度固定,右侧宽度自适应

    1
    2
    3
    4
    5
    6
    <div class='box' >
    <div class='left'>
    </div>
    <div class='right'>
    </div>
    </div>
  • float + margin

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    .left{
    border: 10px solid red;
    background: green;
    border: 3px solid yellow;
    width: 200px;
    height: 200px;
    float: left;
    }
    .right{
    background: pink;
    border: 3px solid black;
    height: 200px;
    margin-left: 200px;
    }
    .box{
    background:#888;
    height: 100%;
    margin-left: 50px;
    }
  • float + calc

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    .left1{
    background: green; /* 绿色 */
    opacity: 0.5;
    width: 200px;
    height: 200px;
    float: left;
    }
    .right1{ /* 粉色 */
    background: pink;
    opacity: 0.5;
    height: 200px;
    float: right;
    width: calc(100% - 200px);
    }
  • float + overflow(BFC应用场景)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    div {
    height: 200px;
    }
    .left2 {
    width: 200px;
    float: left;
    background: green;
    }
    .right2 {
    background: pink;
    outline: 1px solid black;
    overflow: hidden;
    }

    利用BFC约束规则第六点:浮动盒区域不叠加到BFC上,(BFC区域不会与float的元素区域叠加)

  • 最佳选择 flex

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .box {
    display: flex; // 触发BFC
    }
    .left3 {
    width: 200px;
    background: green;
    }
    .right3 {
    flex: 1; // flex-grow: 1;flex-shrink: 1;flex-basis: 0%;
    background: pink;
    outline: 1px solid black;
    }

    清除浮动

    清除浮动主要有两种方式,分别是clear清除浮动和BFC清除浮动

  • clear清除浮动
    底层原理是在被清除浮动的元素上边或者下边添加足够的清除空间;

  • 浮动的三个特点很重要。

  1. 脱离文档流。
  2. 向左/向右浮动直到遇到父元素或者别的浮动元素。
  3. 浮动会导致父元素高度坍塌。
  • 解决父元素高度坍塌的方式就是清除浮动,常规的方法是clear清除浮动和BFC清除浮动,推荐clearfix的方式
1
2
3
4
5
6
7
8
9
10
// 全浏览器通用的clearfix方案
// 引入了zoom以支持IE6/7
.clearfix::after {
display: table;
content: " ";
clear: both;
}
.clearfix{
*zoom: 1; // 支持IE6/7
}

参考链接

闭包

观察者模式

http1.x和http2.x

http1.x几大缺陷

  • 规定客户端对同一域的并发连接最多只能2个(一般是2-8个)
  • 线头阻塞(head of line block) 同一个连接中的请求,需要一个接一个串行发送和接收
  • 基于文本协议,请求和响应头信息大,无法压缩
  • 不能控制响应优先级,必须按照请求顺序响应
  • 只能单向请求,客户端请求什么,服务器返回什么

Http2

HTTP2 的前身是 SPDY协议(一个 Google 主导推行的应用层协议,作为对 HTTP1 的增强)。HTTP2必须在维持原来 HTTP 的范式(不改动 HTTP/1.x 的语义、方法、状态码、URI 以及首部字段等等)前提下,实现突破性能限制,改进传输性能,实现低延迟高吞吐量

Http2 特性

  • 传输内容使用二进制协议
  • 使用帧作为最小传输单位
  • 多路复用
  • 头压缩
  • 服务器推送
  • 优先级与依赖性
  • 可重置
  • 流量控制
  • HTTPS rfc 规范并没有要求 HTTP2 强制使用 TLS,但是目前世界所有浏览器和服务器实现都基于 HTTPS 来实现 HTTP2

二进制

HTTP1.x 时代,无论是传输内容还是头信息,都是文本/ASCII编码的,虽然这有利于直接从请求从观察出内容,但是却使得想要实现并发传输异常困难(存在空格或其他字符,很难判断消息的起始和结束)。使用二进制传输可以避免这个问题,因为传输内容只有1和0,通过下面第二点的“帧”规范规定格式,即可轻易识别出不同类型内容。同时使用二进制有一个显而易见的好处是:更小的传输体积

二进制分帧

HTTP2 在维持原有 HTTP 范式的前提下,实现突破性能限制,改进传输性能,实现低延迟和高吞吐量的其中一个关键是:在应用层(HTTP2)和传输层(TCP or UDP)之间增加了二进制分帧层

多路复用(Multiplexing)和流

多路复用是解决 HTTP1.x 缺陷第一点(并发问题)和第二点(HOLB线头问题)的核心技术点

头压缩

HPACK专门为头部压缩设计的算法,还被指定成单独的草案中。
HTTP2 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只需发送一次

Http2.0优势

  • 更小的传输体积,更小或者省略重复的头消息
  • 突破原有的 TCP 连接并发限制,使用一个 TCP 连接即可实现多请求并发,单链接也能减轻服务端的压力(更少的内存和 CPU 使用)
  • 解决 HOLB 线头问题,慢的请求或者先发送的请求不会阻塞其他请求的返回
  • 结合 CDN 提供实时性更高,延迟更低的内容分发代理服务,大大减少白屏时间
  • 数据传输优先级可控,使网站可以实现更灵活和强大的页面控制
  • 能在不中断 TCP 连接的情况下停止(重置)数据的发送

参考链接