观察者模式

What is the Observer Mode?

发布者( Subject ) + 订阅者( Observer ) = 观察者模式

观察者模式定义了对象之间的一对多的依赖关系,当对象( Subject )发生状态改变时,其依赖者( Observer )都会收到状态的改变并更新。

打个比方。你家里所有人都比较关心你,当你有了对象你告诉他们,我有对象了!他们就会接受到你的消息,知道你有对象了(然而这是不可能的~)。

这里把你的家人就比作订阅者,而你作为发布者,你发布了新的状态(有女朋友了),他们收到状态的改变就会更新。

React 中的观察者模式

Context (上下文)

Context (上下文) 是典型的观察者模式。Context 通过组件树提供了一个传递数据的方法,避免了在每一个层级手动的传递数据。

API

React.createContext:创建一个上下文的容器(组件), defaultValue可以设置共享的默认数据

1
const {Provider, Consumer} = React.createContext(defaultValue);

Provider: 和他的名字一样。生产共享数据的地方。可以看做发布者

1
<Provider value={/*共享的数据*/} />

Consumer: 可以看做观察者。是获取 Provider 产生的数据。(必须在生产者层次之下,才能通过回调的方式拿到共享的数据源)

1
2
3
<Consumer>
{value => /*根据上下文 进行渲染相应内容*/}
</Consumer>

缺点

  • 不能处理异步的情况
  • 没有 action , state 的值都是被直接修改,state 的数据安全性不及 redux。
  • context ,并不会减少代码量。Provider 和 Consumer 必须来自同一 React.createContext 调用,发布订阅的单一性

软件设计原则

  • 对会变化的部分进行封装,从而使其他部分部分受到影响。使系统更具有弹性
  • 实现交互对象的松耦合关系,当交互关系改变时,影响减到最小。使系统更具有弹性

读《从零编写一个简单 React 框架》有感

What is Virtual DOM

Virtual DOM 就是将 DOM 抽象成一个以 JavaScript 对象为节点的虚拟 DOM 树,对这颗抽象树进行创建节点,删除节点以及修改节点的操作

1
2
3
4
5
6
7
8
9
10
11
const obj = {
tag:'div',
attrs:{
className:"test"
},
children:[
tag:'span',
attrs:{
className:"text"
},]
}

Why use Virtual DOM

在直接操作 DOM 时浏览器会执行该操作。当在频繁操作 DOM 元素时,不断重复执行操作,大大影响性能。
React Virtual DOM 经过 diff 算法得出一些需要修改的最小单位,在实现最少的 DOM 操作前提下更新视图

How to create Virtual DOM

  • 基于 babel 的在线编辑器,可以将先把代码变成抽象语法树
  • 进行对应的处理
  • 输出成浏览器可以识别的代码‑即 js 对象
1
2
3
4
5
class App extends React.Component{
render(){
return <div>123</div>
}
}

会被编译成

1
2
3
4
5
6
7
8
...
_createClass(App, [{
key: "render",
value: function render() {
return React.createElement("div", null, "123");
}
}]
);

最核心的一段 jsx 代码, return <div>123</div> 被转换成了: return React.createElement("div", null, "123");
可见,我们在用 React 写 jsx 代码时,都会被转换成 React.createElement 的形式

Implement a Simple React frame

简单的说:React frame = Virtual DOM + diff 算法 + 生命周期优化
那么,要实现框架,首先要有把 Virtual DOM 转换为真实的 DOM 的方法

创建 React 对象

1
2
3
4
5
6
7
8
9
const React = {};
React.createElement = function(tag, attrs, ...children) {
return {
tag,
attrs,
children
};
};
export default React;

上述方法是为了将 Babel 转换的代码中把对应的参数变成一个特定格式(DOM 结构)的对象,然后返回

render 方法的实现

  • 定义 ReactDOM 对象
  • 实现 render 方法,实现虚实转换
  • 考虑子节点的嵌套,对子节点递归渲染子节点
1
2
3
4
5
6
7
8
// ReactDom
const ReactDom = {};
//vnode 虚拟 dom ,即 js 对象
//container 即对应的根标签 包裹元素
const render = function(vnode, container) {
return container.appendChild(_render(vnode));
};
ReactDom.render = render;
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
//_render 方法
//_render 方法,接受虚拟 dom 对象,返回真实 dom 对象:
export function _render(vnode) {
console.log('_render');
// 如果传入的是 null ,字符串或者数字 那么直接转换成真实 dom
if (vnode === undefined || vnode === null || typeof vnode === 'boolean')
vnode = '';
if (typeof vnode === 'number') vnode = String(vnode);

if (typeof vnode === 'string') {
let textNode = document.createTextNode(vnode);
return textNode;
}
if (typeof vnode.tag === 'function') {
const component = createComponent(vnode.tag, vnode.attrs);
setComponentProps(component, vnode.attrs);
return component.base;
}

const dom = document.createElement(vnode.tag);
// 传入的是个 dom 元素,而且它有属性,需要一个 handleDealAttrs 方法,处理成真实 dom 的属性
if (vnode.attrs) {
Object.keys(vnode.attrs).forEach(key => {
const value = vnode.attrs[key];
handleDealAttrs(dom, key, value);
});
}

vnode.children && vnode.children.forEach(child => render(child, dom)); // 递归渲染子节点

return 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
function handleDealAttrs(dom, name, value) {
if (name === 'className') name = 'class';
if (/on\w+/.test(name)) {
name = name.toLowerCase();
dom[name] = value || '';
} else if (name === 'style') {
if (!value || typeof value === 'string') {
dom.style.cssText = value || '';
} else if (value && typeof value === 'object') {
for (let name in value) {
dom.style[name] =
typeof value[name] === 'number' ? value[name] + 'px' : value[name];
}
}
} else {
if (name in dom) {
dom[name] = value || '';
}
if (value) {
dom.setAttribute(name, value);
} else {
dom.removeAttribute(name);
}
}
}

diff 算法的实现

两种 diff 算法:

  • 将新 Virtual DOM 和旧 DOM 对比
  • 将新 Virtual DOM 和旧 Virtual DOM 对比

JS 实现双向数据绑定 MVVM

双向数据绑定

1
2
Event.onchange; // 事件会在域的内容改变时发生。
Object.defineProperty(obj,key,value) // 在一个对象上存取数据,或者修改数据

输入框绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// html
<input id="input" type="text" />

// javascript
const data = {};
const input = document.getElementById('input');
Object.defineProperty(data,'text',{
set(value){
input.value = value;
this.value = value;
}
});

input.onchange = (e) =>{
data.text = e.target.value;
}

CSS实现水平垂直居中

文本居中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="box">
<span class="child">inline和inline-block元素</span>
</div>

.box {
width: 400px;
height: 400px;
background: #ccc;
display: table-cell;
text-align: center; // 文本居中
vertical-align: middle; // 垂直居中
}
.box .child {
display: inline-block;
width: 100px;
height: 100px;
background: red;
}

元素居中

误区:CSS 元素定位为左上角

css.jpg

FLEX模型实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="box">
<div class="child"></div>
</div>

.box {
width: 400px;
height: 400px;
background: #ccc;
display:flex;
justify-content:center; // X轴居中
align-items: center; // Y轴居中
}
.box .child {
display: inline-block;
width: 100px;
height: 100px;
background: red;
}

绝对定位 + margin 实现(已知宽高)

绝对定位和 margin -left:-自身宽度一半,margin-top: -自身高度的一半 (已知宽高)

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="box"></div>

.box {
position: absolute;
width: 100px;
height: 100px;
background: red;
left: 50%;
top: 50%;
color: #fff;
margin-left: -50px;
margin-top: -50px;
}

绝对定位 + transform 实现(未知宽高)

1
2
3
4
5
6
7
8
9
10
11
<div class="box"></div>

.box {
position: absolute;
width: 200px;
height: 200px;
background-color: #1E90FF;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

JS 文件二进制上传/读取的机制

前言

最近写项目时遇到了向后台请求文件时,后台返回传输文件格式为二进制流的情况。正好借此机会了解文件二进制上传/读取的机制。

Blob 对象

文档:BLOB - MDN Web Docs

Blob对象表示一个不可变的, 原始数据的类似文件对象,可以作为二进制文件存放的容器

构造函数

1
var blob = new Blob(array[optional], options[optional]);
  • 第一个参数:任意类型的对象的混合体
  • 第二个参数:用于指定将要放入 Blob 中的数据的类型

基本属性

  • size :Blob 对象包含的字节数。(只读)
  • type :Blob 对象包含的数据类型,如果类型未知则返回空字符串

文件二进制传输之上传

创建 FormData 对象异步上传二进制文件

文档:FormData - MDN Web Docs

1
2
3
FormData.append(); // 添加键值对
FormData.delete(); // 删除键值对
FormData.entries(); // 返回允许遍历此对象中包含的所有键/值对的迭代器

创建一个空 FormData 对象,向 FormData 对象中添加文件,创建 xhr 请求发送至服务器

1
2
3
4
5
let formData = new FormData();
formData.append('file', file)
let xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.send(formData);

文件二进制传输之读取

文件二进制流转文件下载的方法:

  • 通过 FileReader 对象实现异步读取二进制文件
  • 通过 Blob 对象和 msSaveBlob(blob, fileName) 以本地方式保存文件
  • 通过 createObjectURL 把 Blob 对象指向到一个 URL, 再赋予到a标签

创建 FileReader 对象异步读取二进制文件

文档:FileReader - MDN Web Docs

1
2
FileReader.onload; //事件在读取完成后触发。
FileReader.readAsDataURL(); // 转换为base64

验证码示例

1
<img id="captcha" alt="captcha" src=""/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// xhr
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = "blob";
xhr.onload = function () {
// 请求完成
if (this.status === 200) {
// 返回200
let blob = this.response;
let reader = new FileReader(); // 创建 FileReader() 对象
reader.readAsDataURL(blob); // 转换为base64,直接放入src
let captcha = document.getElementById('captcha');
reader.onload = function(e){
captcha.src = e.target.result; // 输入文件流
console.log(captcha.src);
};
captcha.onload = function() {
window.URL.revokeObjectURL(captcha.src); // 释放
};
}
};
// 发送ajax请求
xhr.send();

下载文件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let blob = this.response;
let reader = new FileReader();
reader.readAsDataURL(blob); // 转换为base64,直接放入a标签href
reader.onload = function(e) {
// 转换完成,创建一个a标签用于下载
let a = document.createElement('a');
a.href = e.target.result;
a.download = 'fileName';
};
a.onload = function() {
window.URL.revokeObjectURL(a.href); // 释放
};
document.body.appendChild(a); // 修复firefox中无法触发click
a.click();
document.body.removeChild(a);

通过 msSaveBlob(blob, fileName) 保存文件

1
2
3
if (window.navigator.msSaveOrOpenBlob) {  
navigator.msSaveBlob(blob, fileName);
}

参考资料

JavaScript 中 Blob 对象(掘金网)
JS : Blob() 转换二进制下载文件流实例(CSDN论坛)

JS 事件流的概念

事件流的概念.png

事件流

  • 捕获事件流:从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点。
  • 冒泡事件流:从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点。

阻止冒泡

e.stopPropagation() 可以做到阻止冒泡事件

事件捕获

addEventListener() 可以实现事件的捕获

语法

1
document.addEventListener(event, function, useCapture)

参数

1
2
3
4
5
event:监听的事件
func:事件捕获后触发的函数
useCapture:boolean 对象。
true 时,在事件捕获阶段触发函数
false 时,在事件冒泡阶段触发函数

初级算法-链表

删除链表中的节点

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。

现有一个链表 – head = [4,5,1,9],它可以表示为:
0108

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} node
* @return {void} Do not return anything, modify node in-place instead.
*/
var deleteNode = function(node) {
var nodeNext =node.next;
node.val = nodeNext.val; // 将下个节点的值赋给节点
node.next = nodeNext.next // 将下个节点的指向赋给节点
};

删除链表的倒数第 n 个节点

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

思路

用双指针来实现。先用 fast 指针前进 n ,然后让 slow 从 head 开始和前进了 n 个节点的 fast 一起前进,直到 fast 到最后一个结点,此时 slow 的下一个节点就是要删除的节点。(若 fast 一开始前进 n 就已经不在链表中了,说明要删除的节点正是 head 节点,则直接返回 head 的下一个节点)

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
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
if(head===null){
return head
}
if(n===0){
return head;
}
let fast=head;
let slow=head;
while(n>0){
fast = fast.next;
n--;
} // 先让fast指针前进n
if(fast === null){
return head.next;
} // 先让fast指针前进n,已经不在链表,直接删除头结点
while(fast.next !== null){
fast = fast.next;
slow = slow.next;
} // slow和fast一起前进
slow.next = slow.next.next; // fast到了末尾,slow.next就是要删除的结点
return head;
};

JSON WEB TOKEN 入门

前言

《基于Token的用户认证》中我们了解了 Token 认证的基本流程,而 JSON Web Token(缩写 JWT)是基于 Token 原理目前最流行的跨域认证解决方案,本文介绍它的原理和用法。

image.png

JWT 的原理

JWT 的原理:服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

1
2
3
4
5
{
"userId": "小花",
"role": "admin",
"lastLoginTime": "2019-11-24 19:24:24"
}

用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象验证用户身份。为保障安全性,服务器在生成这个对象的时候,会用算法生成签名(详见后文)。

数据结构


JWT 的三个部分依次如下。

  • HEADER(头部)
  • PAYLOAD(负载)
  • SIGNATURE(签名)

写成一行,就是下面的样子。

  • HEADER.PAYLOAD.SIGNATURE

实际 JWT 大概就像下面这样。

image.png

它是一个很长的字符串,中间用点(.)分隔成三个部分,HEADER 和 PAYLOAD 是 JSON 对象,均用 base64Url 转换为字符串。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。

HEADER(头部)


HEADER 部分是一个 JSON 对象,描述 JWT 的元数据,一般是这个样子:

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

上面代码中,alg 属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ 属性表示这个令牌(token)的类型(type),JWT 令牌统一写为 JWT

HEADER 是个 JSON 对象,会用 base64Url 算法转换为字符串。

PAYLOAD(负载)


PAYLOAD 部分也是一个 JSON 对象,描述服务中需要负载的信息,一般是这个样子:

1
2
3
4
5
{
"userId": "小花",
"role": "admin",
"lastLoginTime": "2019-11-24 19:24:24"
}

PAYLOAD 部分也是一个 JSON 对象,会用 base64Url 算法转换为字符串。

SIGNATURE(签名)


SIGNATURE 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret),再使用 Header 里面指定的签名算法,按照下面的公式产生签名。

1
2
3
4
5
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload) + "." +
base64UrlEncode(your-256-bit-secret)
)

投入使用


Token.png

用户登录

用户首次登录,将用户密码发送给服务器

服务器生成 token

验证通过后,将用户的基本信息和密码结合我们设置的密钥 secret 通过 JWT 生成 token

存储

用户收到服务器经过 JWT 编码返回的 token 后,可以将token储存在 Cookie 里面,也可以储存在 localStorage。

通信

用户每次与服务器通信,都要带上这个 token 。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域。更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

1
2
Authorization: Bearer <token>
// 头信息 Authorization 字段

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

参考阅读

基于Token的用户认证
jsonwebtoken生成与解析token

初级算法-字符串

反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @param {character[]} s
* @return {void} Do not return anything, modify s in-place instead.
*/
let reverseString = function(s) {
let l=s.length/2, str =''
for(let i = 0;i < l; i ++){
str = s[i];
s[i] = s[s.length-1-i];
s[s.length-1-i] = str;
}
return str;
};

第一个唯一字符

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

1
2
indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
lastIndexOf() 方法可返回一个指定的字符串值最后出现的位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @param {string} s
* @return {number}
*/
let firstUniqChar = function(s) {
let l= s.length
for (let i = 0;i < l;i++) {
if (s.lastIndexOf(s[i]) === s.indexOf(s[i])) {
return i
}
}
return -1
};

有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

1
join() 方法用于把数组中的所有元素放入一个字符串。
1
2
3
4
5
6
7
8
9
10
11
/**
* @param {string} s
* @param {string} t
* @return {boolean}
*/
let isAnagram = function(s, t) {
if (s.length !== t.length) return false;
let ss = Array.from(s).sort().join(''),
st = Array.from(t).sort().join('');
return ss === st;
};

实现 strStr() 函数。

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。

1
2
3
4
5
6
7
8
/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
let strStr = function(haystack, needle) {
return haystack.indexOf(needle);
};
  • © 2017-2020 SeaHai
  • Powered by Hexo Theme Ayer
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信