0%

Ubuntu 18.04 Shadowsocks

安装 pip

sudo apt install python3-pip

安装 Shadowsocks

  • pip3 install https://github.com/shadowsocks/shadowsocks/archive/master.zip

  • 安装完后检查是否为 3.0.0 版本

    ssserver --version

    若显示 Shadowsocks 3.0.0 则进行下一步

配置 Shadowsocks

  • 创建 shadowsocks.json

    sudo vim /etc/shadowsocks.json

  • 编辑 shadowsocks.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "server": "0.0.0.0",
    "server_port": 8388,
    "local_address": "127.0.0.1",
    "local_port": 1080,
    "password": "password",
    "timeout": 300,
    "method": "aes-256-cfb",
    "fast_open": false,
    "workers": 1,
    "prefer_ipv6": false
    }

启动 Shadowsocks

ssserver -c /etc/shadowsocks.json -d start

开机自启

  • 创建 shadowsocks.service 文件

    vim /etc/systemd/system/shadowsocks.service

  • 复制粘贴一下内容,然后保存退出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [Unit]
    Description=Shadowsocks Server
    After=network.target

    [Service]
    ExecStart=/usr/local/bin/ssserver -c /etc/shadowsocks.json
    Restart=on-abort

    [Install]
    WantedBy=multi-user.target
  • 运行 shadowsocks.service

    systemctl start shadowsocks.service

  • 允许开机自动启动

    systemctl enable shadowsocks.service

  • 查看运行状态

    systemctl status shadowsocks.service

Install

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
# 更新包
sudo apt-get update
# 下载安装nginx
sudo apt-get install nginx

# test
sudo nginx -t

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

# 重启
sudo service nginx restart


# 删除nginx,-purge包括配置文件
sudo apt-get --purge remove nginx

# 移除全部不使用的软件包
sudo apt-get autoremove

# 罗列出与nginx相关的软件并删除
dpkg --get-selections|grep nginx
sudo apt-get --purge remove nginx
sudo apt-get --purge remove nginx-common
sudo apt-get --purge remove nginx-core

# 查看nginx正在运行的进程,如果有就kill掉
ps -ef |grep nginx
sudo kill -9 XXX
1
2
3
4
5
6
7
sudo systemctl status nginx
sudo systemctl stop nginx
sudo systemctl start nginx
sudo systemctl restart nginx
sudo systemctl reload nginx
sudo systemctl disable nginx
sudo systemctl enable nginx

配置 Nginx

最新版本 nginx 配置是由 4 个文件构成:

  • conf.d:用户自己定义的 conf 配置文件

  • sites-available:系统默认设置的配置文件

  • sites-enabled:由 sites-available 中的配置文件转换生成

  • nginx.conf:汇总以上三个配置文件的内容,同时配置我们所需要的参数

在部署需要的 web 服务时,我们可以拷贝 sites-enabled 中的 default 文件到 conf.d 并且修改名字为**.conf,然后进行配置

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
server {
# 服务启动时监听的端口
listen 80 default_server;
listen [::]:80 default_server;
# 服务启动时文件加载的路径
root /var/www/html/wordpress;
# 默认加载的第一个文件
index index.php index.html index.htm index.nginx-debian.html;
# 页面访问域名,如果没有域名也可以填写_
server_name www.123.com;

location / {
#页面加载失败后所跳转的页面
try_files $uri $uri/ =404;
}


# 以下配置只服务于php
# 将PHP脚本传递给在127.0.0.1:9000上监听的FastCGI服务器
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php7.0-cgi alone:
#fastcgi_pass 127.0.0.1:9000;
# With php7.0-fpm:
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}

# 如果Apache的文档为root,则拒绝访问.htaccess文件
location ~ /\.ht {
deny all;
}
}

注意事项:

  • apache 的端口也是 80,所以我们可以选择关闭 apache 或者,在这里更换端口,例如 81,82 等,但是我们需要吧这个端口开放出来

  • React、Vue 等由于是单页面应用,所以我们在刷新的会遇到资源加载不到的错误,这时我们需要把页面重定向到 index.html try_files \$uri /index.html;

  • 每次配置完成后,都需要重启 nginx

Table of Contents

bind this

  1. 隐式绑定

调用对象下面的方法,自动绑定这个对象,如果没有,则为 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function hello() {
console.log(this)
}

hello()
// 严格模式下是 undefined
// 非严格模式下是 window

const foo = {
name: 'foo',
hello: function() {
console.log(this.name)
}
}

foo.hello() // foo
  1. 显式绑定(call,apply,bind)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const foo = {
name: 'foo',
hello: function() {
console.log(this.name)
}
}

const bar = {
name: 'bar'
}

foo.hello.call(bar) // bar
foo.hello.apply(bar) // bar
const newFn = foo.hello.bind(bar)
newFn() // bar
  1. new 绑定
1
2
3
4
function Test() {
this.foo = 1
}
new Test() // this 指向 new 之后的实例
  1. 箭头函数

箭头函数中的 this 是父作用域中的 this

call

1
2
3
4
5
6
7
8
9
10
11
Function.prototype._call = function(context = window, ...args) {
// 生成唯一的 key ,避免其他属性被覆盖
const key = Symbol()
// 绑定 ctx 的 this
context[key] = this
// 获得结果
const result = context[key](...args)
// 删除 ctx 上的函数
delete context[key]
return result
}

apply

call 相同,只是接收的参数形式不同

1
2
3
4
5
6
7
Function.prototype._apply = function(context = window, args = []) {
const key = Symbol()
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}

bind

applycall 相同,只是返回的是函数

1
2
3
4
5
6
7
8
9
10
Function.prototype._bind = function(context = window, ...args) {
const fn = this
return function F() {
if (this instanceof F) {
return new fn(...args, ...arguments)
} else {
return fn._apply(context, [...args, ...arguments])
}
}
}

Table of Contents

Unicode

Unicode 编码单元(code points)的范围从 0 到 1,114,111(0x10FFFF)。开头的 128 个 Unicode 编码单元和 ASCII 字符编码一样

String.prototype.charAt()

从字符串中返回指定的字符

  • str.charAt(index)

  • index: 介于 0 和字符串长度减 1 之间的整数 (0 ~ length-1)

    • 如果不是一个数值,则默认为 0

    • 索引超出范围(小于 0 或不小于字符串的长度),则返回 ''

String.prototype.charCodeAt()

string 转 Unicode, 返回 0 到 65535 之间的整数,表示给定索引处的 UTF-16 代码单元,不能被一个 UTF-16 编码单元单独表示的情况下,需使用 codePointAt()

  • str.charCodeAt(index)

  • index: 介于 0 和字符串长度减 1 之间的整数 (0 ~ length-1)

    • 如果不是一个数值,则默认为 0

    • 索引超出范围(小于 0 或不小于字符串的长度),则返回 NaN

String.fromCharCode()

String 的静态方法,返回由指定的 UTF-16 代码单元序列创建的字符串
由于高位编码(higher values)字符是用两个低位编码(lower value)表示形成的一个字符,需要使用 String.fromCodePoint()

  • String.fromCharCode(num1[, ...[, numN]])

  • num1, ..., numN

    • 一系列 UTF-16 代码单元的数字。 范围介于 0 到 65535(0xFFFF)之间。 大于 0xFFFF 的数字将被截断。 不进行有效性检查
  • 返回一个长度为 N 的字符串,由 N 个指定的 UTF-16 代码单元组成

String.prototype.codePointAt()

string 转 Unicode

  • str.codePointAt(pos)

  • pos: 介于 0 和字符串长度减 1 之间的整数 (0 ~ length-1)

    • 如果不是一个数值,则默认为 0

    • 索引超出范围(小于 0 或不小于字符串的长度),则返回 undefined

String.fromCodePoint()

String 的静态方法

  • String.fromCodePoint(num1[, ...[, numN]])

  • num1, ..., numN

    • 一串 Unicode 编码位置,即“代码点”
  • 传入无效的 Unicode 编码,会抛出异常

javascript 语法基础 ECMAScript 的 Utils

Table of Contents

Number Conversion

1
2
3
4
5
6
// 十进制 转 其他进制
;(10).toString(2) // 10 转 2
;(10).toString(8) // 10 转 8

// 其他进制 转 十进制
parseInt(1010, 2) // 10

throttle

函数节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function throttle(fn, delay = 0) {
let last
let timer
return function() {
const now = Date.now()
if (last && now < last + delay) {
clearTimeout(timer)

timer = setTimeout(() => {
fn.apply(this, arguments)
last = now
}, delay)
} else {
fn.apply(this, arguments)
last = now
}
}
}

debounce

函数防抖

1
2
3
4
5
6
7
8
9
function debounce(fn, delay = 0) {
let timer = null
return function() {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}

shuffle

Fisher–Yates 乱序算法

1
2
3
4
5
6
7
8
function shuffle(arr) {
let length = arr.length
while (length > 1) {
let index = Math.floor(Math.random() * length--)
;[arr[length], arr[index]] = [arr[index], arr[length]]
}
return arr
}

randomInt

1
2
3
4
5
6
export function randomInt(min, max) {
if (min > max) {
;[min, max] = [max, min]
}
return Math.floor(min + Math.random() * (max - min + 1))
}

async/await-try/catch

简化 promise 函数的异常捕获,类 node API

1
2
3
export default function errorCaptured(promiseFunc) {
return promiseFunc.then((...args) => [null, ...args]).catch(err => [err])
}

sleep

1
export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

isStatic

检测数据是不是除了 symbol 外的原始数据

1
2
3
4
5
6
7
8
9
10
function isStatic(value) {
const type = typeof value
return (
type === 'string' ||
type === 'number' ||
type === 'boolean' ||
type === 'undefined' ||
value === null
)
}

isPrimitive

检测数据是不是原始数据

1
2
3
function isPrimitive(value) {
return isStatic(value) || typeof value === 'symbol'
}

isObject

判断数据是不是引用类型的数据 (例如: Array, Function, Object, Regexp, new Number(0),以及 new String(‘’))

1
2
3
4
function isObject(value) {
const type = typeof value
return value !== null && (type === 'object' || type === 'function')
}

isObjectLike

检查 value 是否是 类对象。 如果一个值是类对象,那么它不应该是 null,而且 typeof 后的结果是 “object”

1
2
3
function isObjectLike(value) {
return value !== null && typeof value === 'object'
}

toString

获取数据类型,返回结果为 Number、String、Object、Array 等

1
2
3
4
function _toString(value) {
return Object.prototype.toString.call(value).slice(8, -1)
}
// _toString([]) ==> Array

isPlainObject

判断数据是不是 Object 类型的数据

1
2
3
function isPlainObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}

isArray

判断数据是不是数组类型的数据

1
2
3
function isArray(value) {
return Object.prototype.toString.call(value) === '[object Array]'
}

ES6

1
Array.isArray

isRegExp

判断数据是不是正则对象

1
2
3
function isRegExp(value) {
return Object.prototype.toString.call(value) === '[object RegExp]'
}

isDate

判断数据是不是时间对象

1
2
3
function isDate(value) {
return Object.prototype.toString.call(value) === '[object Date]'
}

isNative

判断 value 是不是浏览器内置函数
内置函数 toString 后的主体代码块为 [native code] ,而非内置函数则为相关代码,所以非内置函数可以进行拷贝(toString 后掐头去尾再由 Function 转)

1
2
3
function isNative(value) {
return typeof value === 'function' && /native code/.test(value.toString())
}

isFunction

检查 value 是不是函数

1
2
3
function isFunction(value) {
return Object.prototype.toString.call(value) === '[object Function]'
}

isLength

检查 value 是否为有效的类数组长度

1
2
3
4
5
6
7
8
function isLength(value) {
return (
typeof value === 'number' &&
value > -1 &&
value % 1 === 0 &&
value <== Number.MAX_SAFE_INTEGER
)
}

isValidArrayIndex

判断变量是否含有效的数组索引

1
2
3
4
5
function isValidArrayIndex(val: any): boolean {
const n = parseFloat(String(val))
// n >= 0 && Math.floor(n) === n 保证了索引是一个大于等于 0 的整数
return n >= 0 && Math.floor(n) === n && isFinite(val)
}

isArrayLike

检查 value 是否是类数组
如果一个值被认为是类数组,那么它不是一个函数,并且 value.length 是个整数,大于等于 0,小于或等于 Number.MAX_SAFE_INTEGER。这里字符串也将被当作类数组。

1
2
3
function isArrayLike(value) {
return value !== null && isLength(value.length) && !isFunction(value)
}

hasOwn

检查是否自身属性,而不是原型链上的

1
2
3
function hasOwnProperty(value, key) {
return Object.prototype.hasOwnProperty.call(value, key)
}

isEmpty

检查 value 是否为空
如果是 null,直接返回 true;如果是类数组,判断数据长度;如果是 Object 对象,判断是否具有属性;如果是其他数据,直接返回 true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function isEmpty(value) {
if (value === null) {
return true
}
if (isArrayLike(value)) {
return !value.length
} else if (isPlainObject(value)) {
for (let key in value) {
if (hasOwnProperty(value, key)) {
return false
}
}
}
return true
}

inBrowser

检测当前宿主环境是否是浏览器

1
2
// 通过判断 `window` 对象是否存在即可
const inBrowser = typeof window !== 'undefined'

hasProto

检查当前环境是否可以使用对象的 __proto__ 属性

1
2
3
// 一个对象的 __proto__ 属性指向了其构造函数的原型
// 从一个空的对象字面量开始沿着原型链逐级检查。
const hasProto = '__proto__' in {}

userAgent

获取当浏览器的 user agent

1
2
// toLowerCase目的是 为了后续的各种环境检测
const UA = inBrowser && window.navigator.userAgent.toLowerCase()

browserType

使用正则去匹配 UA 中是否包含’msie’或者’trident’这两个字符串即可判断是否为 IE 浏览器

1
2
3
4
const isIE = UA && /msie|trident/.test(UA)
const isIE9 = UA && UA.indexOf('msie 9.0') > 0
const isEdge = UA && UA.indexOf('edge/') > 0
const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge

toString

将给定变量的值转换为 string 类型并返回

1
2
3
4
5
6
7
export function toString(val: any): string {
return val == null
? ''
: typeof val === 'object'
? JSON.stringify(val, null, 2)
: String(val)
}

cached

记忆函数:缓存函数的运算结果

1
2
3
4
5
6
function cached(fn) {
const cache = Object.create(null)
return function(str) {
return cache[str] || (cache[str] = fn(str))
}
}

isReserved

检测字符串是否以 $ 或者 _ 开头

1
2
3
4
5
// charCodeAt() 方法可返回指定位置的字符的 Unicode 编码
export function isReserved(str: string): boolean {
const c = (str + '').charCodeAt(0)
return c === 0x24 || c === 0x5f
}

charCodeAt

从传递进来的字母序列中找到缺失的字母并返回它。 如:fearNotLetter(“abce”) 应该返回 “d”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function fearNotLetter(str) {
//将字符串转为ASCII码,并存入数组
let arr = []
for (let i = 0; i < str.length; i++) {
arr.push(str.charCodeAt(i))
}
for (let j = 1; j < arr.length; j++) {
let num = arr[j] - arr[j - 1]
//判断后一项减前一项是否为1,若不为1,则缺失该字符的前一项
if (num != 1) {
//将缺失字符ASCII转为字符并返回
return String.fromCharCode(arr[j] - 1)
}
}
return undefined
}
fearNotLetter('abce') // "d"

camelize

连字符转驼峰命名

1
2
3
4
5
6
7
8
9
const camelizeRE = /-(\w)/g
function camelize(str) {
return str.replace(camelizeRE, function(_, c) {
return c ? c.toUpperCase() : ''
})
}
//ab-cd-ef ==> abCdEf
//使用记忆函数
const _camelize = cached(camelize)

hyphenate

驼峰命名转横线命名:拆分字符串,使用 - 相连,并且转换为小写

1
2
3
4
5
6
7
const hyphenateRE = /\B([A-Z])/g
function hyphenate(str) {
return str.replace(hyphenateRE, '-$1').toLowerCase()
}
//abCd ==> ab-cd
//使用记忆函数
const _hyphenate = cached(hyphenate)

capitalize

字符串首位大写

1
2
3
4
5
6
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
// abc ==> Abc
//使用记忆函数
const _capitalize = cached(capitalize)

extend

将属性混合到目标对象中

1
2
3
4
5
6
function extend(to, _from) {
for (const key in _from) {
to[key] = _from[key]
}
return to
}

deepClone

  • 简单的深克隆
1
2
3
function deepClone(target) {
return JSON.parse(JSON.stringify(target))
}

Array - unique

  • 基于 Set 简单实现
1
2
3
function unique(arr) {
return [...new Set(arr)]
}

isNaN

检查数据是否是非数字值
原生的 isNaN 会把参数转换成数字(valueof),而 null、true、false 以及长度小于等于 1 的数组(元素为非 NaN 数据)会被转换成数字,这不是我想要的。Symbol 类型的数据不具有 valueof 接口,所以 isNaN 会抛出错误,这里放在后面,可避免错误

1
2
3
4
function _isNaN(value) {
const type = typeof value
return !(type === 'string' || type === 'number') || isNaN(v)
}

Array - max

求取数组中非 NaN 数据中的最大值

1
2
3
4
5
function max(arr) {
arr = arr.filter(item => !_isNaN(item))
return arr.length ? Math.max.apply(null, arr) : undefined
}
//max([1, 2, '11', null, 'fdf', []]) ==> 11

Array - min

求取数组中非 NaN 数据中的最小值

1
2
3
4
5
function min(arr) {
arr = arr.filter(item => !_isNaN(item))
return arr.length ? Math.min.apply(null, arr) : undefined
}
//min([1, 2, '11', null, 'fdf', []]) ==> 1

目录

XSS 攻击全称跨站脚本攻击,为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为 XSS, XSS 是一种在 web 应用中的计算机安全漏洞,它允许恶意 web 用户将代码植入到提供给其它用户使用的页面中。(任何用户输入都是不可信的)

分类

反射型

  • 只是简单地把用户输入的数据 “反射” 给浏览器, 反射型 XSS 也叫“非持久型 XSS”(Non-persistent XSS)。

  • 比如:直接将用户输入用于网站页面,URL 中的 Query, Params 或者 用户输入的信息直接拼接 DOM 节点等

  • <a href="http://localhost:3000/?name=<script>alert('hello')</script>"
      >点击进入网站并进行XSS攻击</a
    >
    
    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
      如果目标网站直接将 url 中的 query.name 渲染到页面上,就会被攻击

    ### 存储型

    - 存储型 XSS 会把用户输入的数据“存储”在服务器端(数据库)。存储型 XSS 通常也叫做“持久型 XSS”(Persistent XSS)

    - 比如:用户发表的文章或评论中,可以输入恶意代码,发表成功存入数据库之后每个用户访问该文章或评论时,都会调用恶意代码,被劫持。

    ## 防御

    常见的 Web 漏洞如 XSS,SQL 注入等,都要求攻击者输入一些特殊字符,所以可以通过输入检查,过滤掉这些字符。

    - React、Vue 等都有对 XSS 进行处理

    - React `dangerouslySetInnerHTML` 会有风险

    - Vue `v-html` 会有风险

    - GitHub 上有比较成熟的 XSS 库,可参考 [GitHub - XSS](https://github.com/leizongmin/js-xss)

    - 防止 Cookie 被劫持

    Cookie 中设置 HttpOnly 之后,JS 将无法读取 Cookie 信息

    - 对下列特殊字符进行过滤或者编码,可以简单的防范
    & &amp; < &lt; > &gt; " &quot; ' &#39

Table of Contents

竖直居中

flex

标准的竖直居中方式,适用于 IE10+ 和 现代浏览器

1
2
3
4
.parent {
display: flex;
align-items: center;
}

line-height

  • 必须是单行

  • 子元素必须是行内元素(inline, inline-block)

  • 父元素已知高度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.parent {
height: 100px;
line-height: 100px;
}

.child {
display: inline;
}
/* or */
.child {
display: inline-block;
height: 10px;
line-height: 10px;
}

::before ::after

  • 子元素必须是行内元素
1
2
3
4
5
6
7
.parent::before {
content: '';
width: 0;
height: 100%;
display: inline-block;
vertical-align: middle;
}

calc

  • 已知子元素的高度

  • 竖直居中的 top 值为 50% - (height / 2)

1
2
3
4
5
6
.child {
width: 30px;
height: 30px;
position: relative;
top: calc(50% - 15px);
}

transform

通用的竖直居中,适用于 IE9+ 和 现代浏览器

1
2
3
4
5
.child {
position: relative;
top: 50%;
transform: translateY(-50%);
}

absolute

  • 因为绝对定位的元素是会互相覆盖的,所以只适用于单个子元素

  • 必须指定子元素的高度,子元素没有高度就是 100%

1
2
3
4
5
6
7
8
9
10
11
12
13
.parent {
position: relative;
}

.child {
height: 50px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}

table

模拟 table 的竖直居中

1
2
3
4
.parent {
display: table-cell;
vertical-align: middle;
}

水平集中

块级元素

1
2
3
4
.child {
display: block;
margin: 0 auto;
}

行内元素

1
2
3
.parent {
text-align: center;
}

flex

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

webpack demos

Table of Contents

核心概念

  • Entry: 入口

  • Output: 输出结果

  • Module: 模块,webpack 中一切皆是模块

  • Loader: 模块转换器,用于把模块原内容按照需求转换成新内容

  • Plugin: 扩展插件,在 webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要做的事情

loader

Webpack loaders API

  • 从右到左,链式执行

  • 上一个 Loader 的处理结果给下一个接着处理

  • node module 写法

  • module 依赖

  • return && this.callback()

路径

webpack 中查找 loader 的路径

  • loader 的绝对路径

  • 设置 loader 的别名

  • 设置 loader 的查找模块

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
const path = require('path')

const resolve = (...dir) => path.resolve(__dirname, ...dir)

module.exports = {
mode: 'development',
entry: resolve('src/index.js'),
output: {
filename: 'build.js',
path: resolve('dist')
},
resolveLoader: {
// loader 查找模块
modules: ['node_modules', resolve('loaders')],
// loader 别名
alias: {
'x-loader': resolve('loaders/x-loader.js')
}
},
module: {
rules: [
{
test: /\.js$/,
// loader 绝对路径
use: resolve('loaders/x-loader.js')
},
{
test: /\.js$/,
use: ['1-loader', '2-loader', '3-loader']
}
]
}
}

顺序

webpack 中 loader 的默认执行顺序是从下到上,从右向左执行
enforce 调用的优先级(权重),调整调用顺序

顺序:前置(pre) > 普通(normal) > 行内(inline) > 后置(post)

1
2
3
4
5
6
// -! 不会执行之前的 pre 和 normal loader 的处理
// ! 不会执行之前的 normal loader 的处理
// !! 什么都不执行,只执行自己
// import '-!inline-loader!./a.js'
// import '!inline-loader!./a.js'
import '!!inline-loader!./a.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
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
// loader 绝对路径
use: resolve('loaders/x-loader.js')
},
{
test: /\.js$/,
use: ['1-loader'],
enforce: 'pre'
},
{
test: /\.js$/,
use: ['2-loader']
},
{
test: /\.js$/,
use: ['3-loader'],
enforce: 'post'
}
]
}
}

Pitch loader

pitch 方法在 Loader 中便是从左到右执行的,并且可以通过 data 这个变量来进行 pitch 和 normal 之间传递。熔断功能

正常顺序:

1
2
3
4
5
6
7
|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution

设置 b-loader 的 pitch 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// b-loader.js
module.exports = function(source) {
return source
}

module.exports.pitch = function(remainingRequest, precedingRequest, data) {
if (someCondition()) {
return (
'module.exports = require(' +
JSON.stringify('-!' + remainingRequest) +
');'
)
}
}

设置之后的顺序:

1
2
3
|- a-loader `pitch`
|- b-loader `pitch` returns a module
|- a-loader normal execution

raw loader

默认的情况,原文件是以 UTF-8 String 的形式传入给 Loader,而在上面有提到的,module 可使用 buffer 的形式进行处理,针对这种情况,只需要设置 module.exports.raw = true; 这样内容将会以 raw Buffer 的形式传入到 loader 中了

1
2
3
4
5
module.exports = function(source) {
return source
}

module.exports.raw = true

loader context

loader 中的上下文 this

  • data: pitch loader 中可以通过 data 让 pitch 和 normal module 进行数据共享。

  • query: 则能获取到 Loader 上附有的参数。 如 require(“./somg-loader?ls”); 通过 query 就可以得到 “ls” 了。

  • emitFile: emitFile 能够让开发者更方便的输出一个 file 文件,这是 webpack 特有的方法,使用的方法也很直接

1
2
3
4
module.exports = function(source) {
console.log(this)
return source
}

awesome

name description link
html-loader 处理 html 中的资源引用 GitHub
url-loader 处理资源文件,image、video 等 GitHub
file-loader 处理资源文件,image、video 等 GitHub
babel-loader es+ 转 es5 GitHub

plugin

plugin API
webpack plugins

  • webpack 中的 plugin 必须是一个 class,且拥有 apply 方法

  • webpack 打包时会自动调用 plugin 实例的 apply 方法,并传递 compiler 参数

  • compiler 上有个 hooks 属性(钩子函数),plugin 就是基于钩子函数来做处理

server

proxy

简单的代理请求到目标域

1
2
3
4
5
6
7
8
9
10
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'https://localhost',
pathRewrite: { '/api': '' }
}
}
}
}

hook

webpack 中使用 express server

webpack dev server 是基于 express 构建的,它提供一个钩子函数 before 在参数中将 express app 暴露出来,用户可以自定义添加路由,模拟数据等

1
2
3
4
5
6
7
8
9
module.exports = {
devServer: {
before(app) {
app.get('/user', (req, res) => {
res.json({ name: 'hello' })
})
}
}
}

webpack-dev-middleware

webpack/webpack-dev-middleware

用户自定义的 server 中使用 webpack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const webpack = require('webpack')
const middleware = require('webpack-dev-middleware')

const config = require('./webpack.config.js')
const compiler = webpack({
// webpack options
...config
})
const express = require('express')
const app = express()

app.use(
middleware(compiler, {
// webpack-dev-middleware options
})
)

app.listen(3000, () => console.log('Example app listening on port 3000!'))

全局变量

expose-loader

通过 expose-loader 将 import 的模块暴露到全局 window

loader 中配置

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
module: {
rules: [
// import当前模块之后
// 暴露模块到全局对象 window 上
{
test: require.resolve('jquery'),
use: 'expose-loader?$'
}
]
}
}

內联形式

1
import $ from 'expose-loader?$!jquery'

Webpack.ProvidePlugin

通过 new Webpack.ProvidePlugin(), 给每个模块文件直接注入一个模块,其他模块文件无需引用,直接调用

1
2
3
4
5
6
7
module.exports = {
plugins: [
new Webpack.ProvidePlugin({
$: 'jquery'
})
]
}

externals

通过 script 在 html 引用,webpack 中设置 externals 直接使用外部引用,不打包进项目

1
2
3
4
5
6
7
8
9
10
11
// webpack.config.js
module.exports = {
externals: {
jquery: 'jQuery'
}
}

// index.js
import $ from 'jquery'

$('.my-element').animate(/* ... */)

优化

module.noParse

module.noParse

不解析正则匹配的文件内部的 import, require, define

noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析和处理,这样做的好处是能提高构建性能。 原因是一些库例如 jQuery lodash 它们庞大又没有采用模块化标准,让 Webpack 去解析这些文件耗时又没有意义。

1
2
3
4
5
6
module.exports = {
//...
module: {
noParse: /jquery|lodash/
}
}

Webpack.IgnorePlugin

Webpack.IgnorePlugin webpack 自带的插件

比如 moment 库中的语言包很大,使用 IgnorePlugin 在 moment 中任何以 ‘./locale’ 结尾的 require 都将被忽略

1
2
3
4
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/
})

用户需自行引入语言包

1
2
3
4
import moment from 'moment'
import 'moment/locale/zh-cn'

moment.locals('zh-cn')

Webpack.DllPlugin

Webpack.DllPlugin

DllPlugin 是基于 Windows 动态链接库(dll)的思想被创作出来的。这个插件会把第三方库单独打包到一个文件中,这个文件就是一个单纯的依赖库。这个依赖库不会跟着你的业务代码一起被重新打包,只有当依赖自身发生版本变化时才会重新打包。

用 DllPlugin 处理文件,需要两步

  • 基于 dll 专属的配置文件,打包 dll 库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // webpack.dll.js
    const path = require('path')
    const Webpack = require('webpack')

    const resolve = (...dir) => path.resolve(__dirname, ...dir)

    module.exports = {
    mode: 'development',
    entry: {
    react: ['react', 'react-dom']
    },
    output: {
    filename: '_dll_[name].js', // 产生的文件名
    path: resolve('dist'),
    library: '_dll_[name]'
    },
    plugins: [
    // name要等于library里的name
    new Webpack.DllPlugin({
    name: '_dll_[name]',
    path: resolve('dist', 'manifest.json')
    })
    ]
    }
  • 基于 webpack.config.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
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
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const Webpack = require('webpack')

const resolve = (...dir) => path.resolve(__dirname, ...dir)

module.exports = {
mode: 'development',
// 多入口
entry: {
home: './src/index.js'
},
output: {
filename: '[name].js',
path: resolve('dist')
},
devServer: {
port: 3000,
open: true,
contentBase: './dist'
},
module: {
// 不去解析jquery的依赖关系
noParse: /jquery/,
rules: [
{
test: /.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /.js$/,
exclude: /node_modules/,
include: resolve('src'),
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
}
]
},
plugins: [
new Webpack.DllReferencePlugin({
manifest: resolve('dist', 'manifest.json')
}),
new Webpack.IgnorePlugin(/\.\/local/, /moment/),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
})
]
}

happypack

多线程打包 - GitHub

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
// webpack.config.js
const HappyPack = require('happypack')

exports.module = {
rules: [
{
test: /.js$/,
// 1) replace your original list of loaders with "happypack/loader":
// loaders: [ 'babel-loader?presets[]=es2015' ],
use: 'happypack/loader',
include: [
/* ... */
],
exclude: [
/* ... */
]
}
]
}

exports.plugins = [
// 2) create the plugin:
new HappyPack({
// 3) re-add the loaders you replaced above in #1:
loaders: ['babel-loader?presets[]=es2015']
})
]

Tree-Shaking

webpack 自带 Tree-Shaking, scope hosting

1
2
3
4
5
6
7
8
// scope hosting 作用域提升,去除无用代码
const bar = 1
const foo = 2
const foobar = bar + foo
console.log(foobar)

// webpack build file
console.log(3)

基于 import/export 语法,Tree-Shaking 可以在编译的过程中获悉哪些模块并没有真正被使用,这些没用的代码,在最后打包的时候会被去除。适合于处理模块级别的代码,所以尽量使用 es6 的 import/export 语法。

SplitChunksPlugin

webpack - SplitChunksPlugin

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
// 默认设置
module.exports = {
//...
optimization: {
// 分割代码块
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
automaticNameMaxLength: 30,
name: true,
// 缓存组
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
// 抽取的权重值
priority: -10
},
default: {
// 大小 大于 0kb
minSize: 0,
// 使用过两次以上
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
}

dynamic imports

import() 还在草案中,需要 @babel/plugin-syntax-dynamic-import 才能使用

通过 es6 的 import 实现按需加载,在使用 import() 分割代码后,你的浏览器并且要支持 Promise API 才能让代码正常运行, 因为 import() 返回一个 Promise,它依赖 Promise。对于不原生支持 Promise 的浏览器,你可以注入 Promise polyfill。

1
2
3
// dynamic imports
import('./a')
import('./b')

HMR - Hot Module Replacement

模块热替换(HMR - Hot Module Replacement)是 webpack 提供的最有用的功能之一。它允许在运行时替换,添加,删除各种模块,而无需进行完全刷新重新加载整个页面

启用 HRM

  • new webpack.HotModuleReplacementPlugin()

  • devServer 选项中的 hot 字段为 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const Webpack = require('webpack')

const resolve = (...dir) => path.resolve(__dirname, ...dir)

module.exports = {
mode: 'production',
// 多入口
entry: {
index: './src/index.js',
other: './src/other.js'
},
output: {
filename: '[name].js',
path: resolve('dist')
},
devServer: {
// 启用热更新
hot: true,
port: 3000,
open: true,
contentBase: './dist'
},
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
include: resolve('src'),
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-syntax-dynamic-import']
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html',
name: 'index.html'
}),
// 打印更新的模块路径
new Webpack.NamedModulesPlugin(),
// 热更新插件
new Webpack.HotModuleReplacementPlugin()
]
}

Table of Contents

Stack

受限的线性结构,栈顶操作,LIFO (last in first out) 后进先出

可以基于 ArrayList 实现

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
// 基于 Array 的 Stack
class Stack {
get length() {
return this.stack.length
}

constructor() {
this.stack = []
}

pop() {
return this.stack.pop()
}

push(data) {
return this.stack.push(data)
}

peek() {
return this.stack[this.length - 1]
}

isEmpty() {
return !this.length
}

size() {
return this.length
}

toString() {
return this.stack.map(item => item.toString()).join(',')
}
}

Queue

受限的线性结构,FIFO (first in first out) 先入先出

队列可以基于 ArrayList 实现

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
// 基于 Array 的 Queue
class Queue {
get length() {
return this.queue.length
}

constructor() {
this.queue = []
}

dequeue() {
return this.queue.shift()
}

enqueue(data) {
return this.queue.push(data)
}

front() {
return this.queue[0]
}

isEmpty() {
return !this.length
}

size() {
return this.length
}

toString() {
return this.queue.map(item => item.toString()).join(',')
}
}

Priority Queue

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
class Item {
constructor(data, level = 0) {
this.data = data
this.level = level
}

toString() {
return this.data.toString()
}
}

class Queue {
constructor() {
this.queue = []
}

get length() {
return this.queue.length
}

dequeue() {
return this.queue.shift()
}

enqueue(data, level) {
const item = new Item(data, level)
const index = this.queue.findIndex(it => item.level > it.level)
if (index !== -1) {
this.queue.splice(index, 0, item)
} else {
this.queue.push(item)
}
}

front() {
return this.queue[0].data
}

isEmpty() {
return !this.length
}

size() {
return this.length
}

toString() {
return this.queue.map(item => item.toString()).join(',')
}
}

Linked List

单向链表

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
class Node {
constructor(data) {
this.data = data
this.next = null
}

toString() {
return this.data.toString()
}
}
class LinkedList {
constructor() {
this.head = null
this.length = 0
}

append(data) {
const node = new Node(data)

if (!this.head) {
this.head = node
} else {
let current = this.head
while (current.next) {
current = current.next
}
current.next = node
}

this.length += 1
}

insert(position, data) {
if (position < 0 || position > this.length) {
return false
}

const node = new Node(data)
if (position === 0) {
node.next = this.head
this.head = node
} else {
let current = this.head
let prev = null
let index = 0
while (index++ < position) {
prev = current
current = current.next
}
node.next = current
prev.next = node
}
this.length += 1
return true
}

get(position) {
if (position < 0 || position >= this.length) return null
let index = 0
let current = this.head
while (index++ < position) {
current = current.next
}
return current.data
}

indexOf(data) {
let current = this.head
let index = 0

while (current) {
if (current.data === data) {
return index
}
current = current.next
index += 1
}

return -1
}

update(position, data) {
if (position < 0 || position >= this.length) return false
let index = 0
let current = this.head
while (index++ < position) {
current = current.next
}
current.data = data
return true
}

removeAt(position) {
if (position < 0 || position >= this.length) {
return false
}

if (position === 0) {
this.head = this.head.next
} else {
let index = 0
let current = this.head
let prev = null
while (index++ < position) {
prev = current
current = current.next
}

prev.next = current.next
}
this.length -= 1

return true
}

remove(data) {
const position = this.indexOf(data)
return this.removeAt(position)
}

size() {
return this.length
}

isEmpty() {
return !this.length
}

toString() {
let current = this.head
let str = ''
while (current) {
str += current.toString() + ','
current = current.next
}
return str
}
}

双向链表

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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
class Node {
constructor(data) {
this.prev = null
this.data = data
this.next = null
}

toString() {
return this.data.toString()
}
}

class LinkedList {
constructor() {
this.head = null
this.tail = null
this.length = 0
}

append(data) {
const node = new Node(data)

if (!this.length) {
this.head = node
this.tail = node
} else {
this.tail.next = node
node.prev = this.tail
this.tail = node
}

this.length += 1
}

insert(position, data) {
if (position < 0 || position > this.length) {
return false
}

const node = new Node(data)
if (!this.head || !this.tail) {
this.head = node
this.tail = node
this.length += 1
return true
}
if (position === 0) {
this.head.prev = node
node.next = this.head
this.head = node
this.length += 1
return true
}

if (position === this.length) {
this.tail.next = node
node.prev = this.tail
this.tail = node
this.length += 1
return true
}

let current = this.head
let index = 0
while (index++ < position) {
current = current.next
}
node.next = current
node.prev = current.prev
current.prev.next = node
current.prev = node
this.length += 1
return true
}

getNode(position) {
if (position < 0 || position >= this.length) return null

const flag = this.length / 2 > position
if (flag) {
let index = 0
let current = this.head
while (index++ < position) {
current = current.next
}
return current
} else {
let index = this.length
let current = this.tail
while (index-- < position) {
current = current.next
}
return current
}
}

get(position) {
const node = this.getNode(position)
return node ? node.data : null
}

indexOf(data) {
let current = this.head
let index = 0

while (current) {
if (current.data === data) {
return index
}
current = current.next
index += 1
}

return -1
}

update(position, data) {
const node = this.getNode(position)
if (!node) {
return false
}
node.data = data
return true
}

removeAt(position) {
if (position < 0 || position >= this.length) {
return false
}

if (this.length === 1) {
this.head = null
this.tail = null
this.length -= 1
return true
}

if (position === 0) {
this.head.next.prev = null
this.head = this.head.next

this.length -= 1
return true
}

if (position === this.length - 1) {
this.tail.prev.next = null
this.tail = this.tail.prev

this.length -= 1
return true
}

let index = 0
let current = this.head
while (index++ < position) {
current = current.next
}

current.prev.next = current.next
current.next.prev = current.prev

this.length -= 1
return true
}

remove(data) {
const position = this.indexOf(data)
return this.removeAt(position)
}

size() {
return this.length
}

isEmpty() {
return !this.length
}

toString() {
let current = this.head
let str = ''
while (current) {
str += current.toString() + ','
current = current.next
}
return str
}
}

集合

集合常见的实现方式是哈希表

  • 集合通常是由一组无序的,不能重复的元素构成

  • 特殊的数组:没有顺序,不能重复

    • 没有顺序意味着不能通过下标进行访问

    • 不能重复意味着相同的对象在集合中只会存在一份

ES6 中包含了 Set

Dictionary/Map

key-value, key 不允许重复,无序

ES6 中包含了 Map

Hash Table

通过 hash 函数,生成 key

  • 哈希表通常是基于 Array 实现的

    • 提供比数组更快的插入-删除-查找操作

    • 哈希表的速度比树还快

  • 哈希表是无序的,且 key 不允许重复

Hash

ASCII Unicode

  • 哈希化:将大数字(幂的连乘生成的唯一大数字)转化成数组范围内下标的过程

  • 哈希函数:单词转成大数字,大数字进行哈希化的函数

  • 哈希表:最终将数据插入到数组中,对整个结构的封装

冲突

链地址法(拉链法)

开放地址法

  • 线性探测(问题:聚集)

  • 二次探测

  • 再哈希法

    • stepSize = constant - (key % constant)

    • constant 是质数,且小于数组的容量