zh-rocco / fe-notes Goto Github PK
View Code? Open in Web Editor NEW:memo: 前端笔记
Home Page: https://zh-rocco.github.io/
:memo: 前端笔记
Home Page: https://zh-rocco.github.io/
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/
# 运行用户
user nginx;
# 启动进程,通常设置成和cpu的数量相等
worker_processes auto;
# 全局错误日志
error_log /var/log/nginx/error.log;
# PID文件,记录当前启动的nginx的进程ID
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
# 工作模式及连接数上限
events {
# 单个后台worker process进程的最大并发链接数
worker_connections 1024;
}
http {
# 设定日志
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
# 连接超时时间
keepalive_timeout 65;
tcp_nodelay on;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
# gzip压缩开关
gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间
# gzip_comp_level 大于2时效果并不是很明显,所以可以将值设置为1或者2
gzip_comp_level 2;
# 进行压缩的文件类型,只需要为 ttf、otf 和 svg 字体启用 gzip,对其他字体格式进行 gzip 压缩时效果不明显
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;
# 禁用IE 6 gzip
gzip_disable "MSIE [1-6]\.";
# 设定实际的服务器列表
upstream blog {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name singple.com www.singple.com;
return 301 https://singple.com$request_uri;
}
server {
listen 443;
server_name www.singple.com;
return 301 https://singple.com$request_uri;
}
server {
listen 443 ssl http2 default_server;
server_name singple.com;
ssl on;
ssl_certificate ssl/singple.crt;
ssl_certificate_key ssl/singple.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://blog;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
server {
listen 80 default;
return 501;
}
# Settings for a TLS enabled server.
#
# server {
# listen 443 ssl http2 default_server;
# listen [::]:443 ssl http2 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# ssl_certificate "/etc/pki/nginx/server.crt";
# ssl_certificate_key "/etc/pki/nginx/private/server.key";
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 10m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
}
// A.html
localStorage.setItem('message', 'hello');
// B.html
window.onstorage = e => {
// e.key, e.oldValue, e.newValue
};
API 简单直观,兼容性好,除了跨域场景下需要配合其他方案,无其他缺点
和 localStorage
方案基本一致,需要额外初始化
// A.html
const channel = new BroadcastChannel('tabs');
channel.onmessage = e => {
// e.data
};
// B.html
const channel = new BroadcastChannel('tabs');
channel.postMessage('hello');
和 localStorage
方案没特别区别,都是同域、API 简单,BroadcastChannel
方案兼容性差些(chrome > 58),但比 localStorage
方案生命周期短(不会持久化),相对干净些。
net start mysql
。function getDescendantProp(obj, desc) {
var arr = desc.split('.');
while (arr.length) {
obj = obj[arr.shift()];
}
return obj;
}
var obj = { a: { b: { c: 0 } } };
var propPath = getPropPath(); // returns e.g. "a.b.c"
var result = getDescendantProp(obj, propPath);
参考:
function setDescendantProp(obj, desc, value) {
var arr = desc.split('.');
while (arr.length > 1) {
obj = obj[arr.shift()];
}
return (obj[arr[0]] = value);
}
var obj = { a: { b: { c: 0 } } };
var propPath = getPropPath(); // returns e.g. "a.b.c"
var result = setDescendantProp(obj, propPath, 1); // test.a.b.c will now be 1
function isPromise(obj) {
return obj instanceof Object && obj.then instanceof Function;
}
// converting promise or function to flat array response with [error, result]
function awaitWrapper(fn) {
if (fn instanceof Function && !isPromise(fn)) {
throw new Error('Argument must be a function or Promise');
}
const successFn = (value) => [null, value];
const errorFn = (error) => [error];
return isPromise(fn) ? fn.then(successFn, errorFn) : funcWrapper(fn);
}
function funcWrapper(fn) {
try {
return [null, fn()];
} catch (error) {
return [error];
}
}
参考:
/**
* 根绝 File 实例获取图片的像素尺寸
*
* @export
* @param {File} file
* @returns {Promise} resolve({ width: 100, height: 100 }) / reject()
*/
export function getPixelSize(file) {
return new Promise(function(resolve, reject) {
const _URL = window.URL || window.webkitURL;
const img = new Image();
img.onload = function() {
resolve({
width: img.width,
height: img.height,
});
};
img.onerror = function() {
reject();
};
img.src = _URL.createObjectURL(file);
});
}
/**
* 格式化文件大小
*
* @export
* @param {Number} fileSize 文件大小
* @param {Number} [idx=0] 起始索引
* @returns {String} 500KB
*/
function formatFileSize(fileSize, idx = 0) {
const units = ['B', 'KB', 'MB', 'GB'];
if (fileSize < 1024 || idx === units.length - 1) {
return fileSize.toFixed(0) + units[idx];
}
return formatFileSize(fileSize / 1024, ++idx);
}
副作用:(包含但不限于)
定义:
高阶函数至少满足以下一个条件:
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些 跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。
在 JavaScript 中实现 AOP,都是指把一个函数“动态织入”到另外一个函数之中;AOP 是 JavaScript 语言中一种非常特别和巧妙的装饰者模式实现。
Function.prototype.before = function(beforeFn) {
var __self = this; // 保存原函数的引用
return function() {
// 返回包含了原函数和新函数的"代理"函数
beforeFn.apply(this, arguments); // 执行新函数,修正 this
return __self.apply(this, arguments); // 执行原函数
};
};
Function.prototype.after = function(afterFn) {
var __self = this;
return function() {
var ret = __self.apply(this, arguments);
afterFn.apply(this, arguments);
return ret;
};
};
var func = function() {
console.log(2);
};
func = func
.before(function() {
console.log(1);
})
.after(function() {
console.log(3);
});
func();
// 1
// 2
// 3
未完待续...
添加 meta 标签(仅在 HTML4 下生效)
<!-- index.html -->
<head>
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="expires" content="0">
</head>
添加配置
location / {
index index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate"; # HTTP/1.1
add_header Pragma "no-cache"; # HTTP/1.0
expires 0; # Proxies
}
API | Explain |
---|---|
async |
在页面继续解析时执行脚本 |
defer |
在页面完成解析时执行脚本 |
注意:
async
和 defer
只对外部脚本有效async
和 defer
时,<script> 的下载不阻塞 HTML 解析async
和 defer
,应用 async
的执行规则图解:
(function() {
var a = (b = 5);
})();
console.log(b); // 5
console.log(a); // ERROR
因为赋值是从右向左结合:var a = b = c = d = 5;
等价于 var a = (b = (c = (d = 5)));
,其中只有 a 被声明了,b, c 和 d 都是自动解析为全局变量。
内部变量会被屏蔽掉
var b = 10;
console.log(b); // 10
(function b() {
b = 20;
console.log(b); // f b() { ... }
})();
console.log(b); // 10
对比:
var b = 10;
console.log(b); // 10
(function a() {
b = 20;
console.log(b); // 20
})();
console.log(b); // 20
Object.prototype.a = 10;
console.log(a); // 10
解释:
a
变量,未找到window
上查找有无 a
属性,未找到window
的隐式原型上查找有无 a
属性( window
隐式原型的终点是 Object.prototype
),找到# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/
# 运行用户
user nginx;
# 启动进程,通常设置成和cpu的数量相等
worker_processes auto;
# 全局错误日志
error_log /var/log/nginx/error.log;
# PID文件,记录当前启动的nginx的进程ID
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
# 工作模式及连接数上限
events {
# 单个后台worker process进程的最大并发链接数
worker_connections 1024;
}
http {
# 设定日志
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
# 连接超时时间
keepalive_timeout 65;
tcp_nodelay on;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
# gzip压缩开关
gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间
# gzip_comp_level 大于2时效果并不是很明显,所以可以将值设置为1或者2
gzip_comp_level 2;
# 进行压缩的文件类型,只需要为 ttf、otf 和 svg 字体启用 gzip,对其他字体格式进行 gzip 压缩时效果不明显
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;
# 禁用IE 6 gzip
gzip_disable "MSIE [1-6]\.";
}
proxy_pass
) server {
listen 5080;
server_name localhost;
client_max_body_size 600m;
location /api {
proxy_pass http://xxx.xxx.xxx.xxx:3000/api; # remote api server
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
rewrite
) server {
listen 5080;
server_name localhost;
client_max_body_size 600m;
location /api {
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://xxx.xxx.xxx.xxx:3000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
alias
) server {
listen 5080;
server_name localhost;
client_max_body_size 600m;
location /static/ {
alias /path/to/local/direction/;
autoindex on;
}
}
注意:
alias
后面跟的目录最后一定要加 /
符号,如 /Users/me/code/static/
# 设定实际的服务器列表
upstream blog {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name singple.com www.singple.com;
return 301 https://singple.com$request_uri;
}
server {
listen 443;
server_name www.singple.com;
return 301 https://singple.com$request_uri;
}
server {
listen 443 ssl http2 default_server;
server_name singple.com;
ssl on;
ssl_certificate ssl/singple.crt;
ssl_certificate_key ssl/singple.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://blog;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
server {
listen 80 default;
return 501;
}
可以把依赖注入看作一部分“大范围有效的 prop”,并且:
provide
和 inject
绑定并不是可响应的provide
和 inject
主要为高阶插件/组件库提供用例,并不推荐直接用于应用程序代码中// Ancestor.vue
<script>
export default {
name: 'Ancestor',
provide() {
return {
ancestor: this,
someFuncOfAncestor: this.someFuncOfAncestor
}
},
methods: {
someFuncOfAncestor() {
// ...
}
}
}
</script>
// Descendant.vue
<script>
export default {
name: 'Descendant',
inject: ['ancestor', 'someFuncOfAncestor'],
created() {
console.log(this.ancestor) // => Ancestor 组件实例
}
}
</script>
**:性能优化先优化性能瓶颈(短板),具体问题具体分析。
性能优化的最关键点是首屏。
用户交互时也会出现性能瓶颈:大量 dom 更新(react 性能优势在这,vdom 可以减少不必要的 dom 更新),频繁的页面的重排,动画(动画写的不好性能会很差,比如频繁的页面的重排)。
加载优化(网络方面)
渲染和 DOM 操作方面
数据方面
服务器开启 gzip
缓存
负载均衡
D:\Program Files\
目录。D:\Program Files\mysql-5.7.20-winx64\bin
)添加至系统环境变量。新建 mysql.ini,并编辑内容为:
[mysqld]
basedir=D:\Program Files\mysql-5.7.20-winx64\
datadir=D:\Program Files\mysql-5.7.20-winx64\data\
port=3306
skip-grant-tables
# basedir 表示 mysql 安装路径
# datadir 表示 mysql 数据文件存储路径
# port 表示 mysql 端口
# skip-grant-tables 表示忽略密码
以管理员权限启动 CMD 或 PowerShell,并将路径切换至 MySQL 的 bin 目录(D:\Program Files\mysql-5.7.20-winx64\bin
),然后输入 mysqld -install
。
命令行输入 mysqld --initialize
自动生成带随机密码的 root 用户。
命令行输入 net start mysql
启动 mysql 服务。
修改 root 用户的随机密码:
D:\Program Files\mysql-5.7.20-winx64\data
);*.err
格式的文件,打开,搜索 root@localhost:
,复制后面的密码;mysql -u root -p
,键入密码,以 root 身份进入 mysql 管理界面;mysql>
标识)键入 SET PASSWORD FOR "root"@"localhost" = PASSWORD("123456");
(注意不要漏掉最后的 ;
);flush privileges;
刷新权限;clear
清屏,然后输入 quit
退出 mysql 管理界面;修改 mysql.ini 文件删除最后一句 skip-grant-tables
;
命令行输入 net stop mysql
停止 mysql 服务;
命令行输入 net start mysql
重启 mysql 服务;
webpack.config.js
Hot Reloadpackage.json
{
"scripts": {
"dev": "nodemon --watch webpack.config.js --exec webpack-dev-server --hot"
}
}
参考:
fs.watch()
: 监听 filerequire.resolve('filePath')
: 获取 module 的 keydelete require.cache['cacheKey']
: 删除 cachewebpack-proxy.config.js
module.exports = {
target: 'http://localhost:3000/',
};
webpack.config.js
const fs = require('fs');
const proxyConfig = require('./webpack-proxy.config');
let proxyOptions = {
context: '/api',
target: proxyConfig.target,
pathRewrite: proxyConfig.pathRewrite,
changeOrigin: true,
};
fs.watch('./proxy-config.js', () => {
delete require.cache[require.resolve('./webpack-proxy.config')];
try {
const newProxyConfig = require('./webpack-proxy.config');
if (proxyOptions.target !== newProxyConfig.target) {
console.log('Proxy target changed:', newProxyConfig.target);
proxyOptions = {
context: '/api',
target: newProxyConfig.target,
pathRewrite: newProxyConfig.pathRewrite,
changeOrigin: true,
};
}
} catch (e) {
// eslint-disable-line
}
});
module.exports = {
// ...
devServer: {
proxy: [
function proxy() {
return proxyOptions;
},
],
}
}
参考:
webpack.config.js
module.exports = {
output: {
jsonpFunction: 'webpackJsonp' + Date.now(),
},
};
安装依赖
npm i modify-chunk-id-webpack-plugin -D
webpack.config.js
const ModifyChunkIdPlugin = require('modify-chunk-id-webpack-plugin');
module.exports = {
plugins: [
// ...
new ModifyChunkIdPlugin({ random: process.env.NODE_ENV === 'development' }),
],
};
参考:
注意 CSS 的引用顺序, 一定要将外部依赖的 CSS 库先于项目 CSS 引用
以 Vue 为例
main.js
import Vue from 'vue';
// 第三方 CSS 库
import 'path/to/thirdparty.css';
import App from './App';
App.vue
<style lang="less">
// 项目CSS
@import 'path/to/index.less';
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
require.context
3 个参数:要搜索的文件夹目录,是否还应该搜索它的子目录,以及一个匹配文件的正则表达式
require.context(directory, useSubdirectories = false, regExp = /^\.\//);
示例:
require.context('./test', false, /\.test\.js$/);
//(创建了)一个包含了 test 文件夹(不包含子目录)下面的、所有文件名以 `.test.js` 结尾的、能被 require 请求到的文件的上下文。
加载所有的图片
const imageContext = require.context('@/assets/kittens/', false, /\.jpg$/);
const img = imageContext('./name.jpg');
webpack 配置:
module.exports = {
// ...
externals: {
vue: 'Vue',
'element-ui': 'ELEMENT',
},
};
HTML 单独引入:
<head>
<script src="path/to/vue.js"></script>
<script src="path/to/element-ui.js"></script>
</head>
/* webpackChunkName: name */
export default [
{
path: '/',
component: () => import(/* webpackChunkName: "index" */ '@/views/Index.vue'),
children: [
{
path: 'page-a',
component: () => import(/* webpackChunkName: "page" */ '@/views/PageA.vue'),
},
{
path: 'page-b',
component: () => import(/* webpackChunkName: "page" */ '@/views/PageB.vue'),
},
],
},
];
打包后输出 index
和 page
两个 chunk
yarn add babel-plugin-lodash -D
babel.config.js
module.exports = {
plugins: ['lodash'],
};
Transforms
import _ from 'lodash';
import { add } from 'lodash/fp';
const addOne = add(1);
_.map([1, 2, 3], addOne);
roughly to
import _add from 'lodash/fp/add';
import _map from 'lodash/map';
const addOne = _add(1);
_map([1, 2, 3], addOne);
obj?.prop
)yarn add @babel/plugin-proposal-optional-chaining -D
babel.config.js
module.exports = {
plugins: ["@babel/plugin-proposal-optional-chaining"]
};
Transforms
a?.b
roughly to
a == null ? undefined : a.b
参考: Optional Chaining for JavaScript
webpack 编译流程
作用域链是一个 对象列表(list of objects),用以检索上下文代码中出现的 标识符(identifiers)。
标示符[Identifiers] 可以理解为变量名称、函数声明和普通参数。
var x = 10;
function foo() {
var y = 20;
console.log(x + y);
}
foo(); // 30
函数 foo
如何访问到变量 x
(函数能访问一个更高一层上下文的变量对象)?
这种机制是通过函数内部的 [[scope]]
属性来实现的。[[scope]]
是所有父变量对象的层级链,处于当前函数上下文之上,在函数创建时存于其中。
[[scope]]
是函数的一个属性,而不是一个上下文环境。[[scope]]
在函数创建时被存储(静态作用域),直至函数销毁。即:函数可以永不调用,但 [[scope]]
属性已经写入,并存储在函数对象中。[[scope]]
通过函构造函数创建的函数的 [[scope]]
属性总是唯一的全局对象。考虑到这一点,如通过这种函数创建除全局之外的最上层的上下文闭包是不可能的。
var x = 10;
function foo() {
var y = 20;
function barFD() {
// 函数声明
console.log(x);
console.log(y);
}
var barFE = function() {
// 函数表达式
console.log(x);
console.log(y);
};
var barFn = Function('console.log(x); console.log(y);');
barFD(); // 10, 20
barFE(); // 10, 20
barFn(); // 10, "y" is not defined
}
foo();
在 ECMAScript 中,在代码执行阶段有两个声明能修改作用域链。这就是 with
声明和 catch
语句。它们添加到作用域链的 最前端,对象须在这些声明中出现的标识符中查找。
var foo = { x: 10, y: 20 };
with (foo) {
console.log(x); // 10
console.log(y); // 20
}
var x = 10;
var y = 10;
with ({ x: 20 }) {
var x = 30;
var y = 30;
console.log(x); // 30
console.log(y); // 30
}
console.log(x); // 10
console.log(y); // 30
{ x: 20 }
添加到作用域的最前端;with
内部,遇到了 var
声明,当然什么也没创建,因为在进入上下文时,所有变量已被解析添加;x
,实际上对象中的 x
现在被解析,并添加到作用域链的最前端,x
为 20,变为 30;y
的修改,被解析后其值也相应的由 10 变为 30;with
声明完成后,它的特定对象从作用域链中移除(已改变的变量 x
30 也从那个对象中移除),即作用域链的结构恢复到 with
得到加强以前的状态。x
保持同一,y
的值现在等于 30,在 with
声明运行中已发生改变。同样,catch
语句的异常参数变得可以访问,它创建了只有一个属性的新对象:异常参数名
try {
// ...
} catch (ex) {
console.log(ex);
}
作用域链修改为:
var catchObject = {
ex: <exception object>
};
Scope = catchObject + AO|VO + [[Scope]]
在 catch
语句完成运行之后,作用域链恢复到以前的状态
ECMAScript 中,闭包指的是:
var report = function(src) {
var img = new Image();
img.src = src;
};
report('http://xxx.com/getUserInfo');
因为一些低版本浏览器的实现存在 bug,在这些浏览器下使用 report 函数进行数据上报会丢失 30% 左右的数据,也就是说,report 函数并不是每一次 都成功发起了 HTTP 请求。丢失数据的原因是 img 是 report 函数中的局部变量,当 report 函数的调用结束后,img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP 请求,所以此次请求就会丢失掉。
把 img 变量用闭包封闭起来,便能解决请求丢失的问题:
var report = (function() {
var imgs = [];
return function(src) {
var img = new Image();
imgs.push(img);
img.src = src;
};
})();
var
关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁。不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。
跟闭包和内存泄露有关系的地方是,使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些 DOM 节点,这时候就有可能造成内存泄露。但这本身并非闭包的问题,也并非 JavaScript 的问题。在 IE 浏览器中,由于 BOM 和 DOM 中的对象是使用 C++ 以 COM 对象的方式实现的,而 COM 对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用造成的内存泄露在本质上也不是闭包造成的。
如果要解决循环引用带来的内存泄露问题,我们只需要把循环引用中的变量设为 null
即可。将变量设置为 null
意味着切断变量与它此前引用的值之间的连接。
含义: 同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈。
多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句。
在电影的拍摄现场,当导演喊出 "action" 时,主角开始背台词,照明师负责打灯 光,后面的群众演员假装中枪倒地,道具师往镜头里撒上雪花。在得到同一个消息时,每个对象都知道自己应该做什么。如果不利用对象的多态性,而是用面向过程的方式来编写这一段代码,那么相当于在电影开始拍摄之后,导演每次都要走到每个人的面前,确认它们的职业分工(类型),然后告诉他们要做什么。如果映射到程序中,那么程序中将充斥着条件分支语句。
将行为分布在各个对象中,并让这些对象各自负责自己的行为,这正是面向对象设计的优点。
对象的多态性提 示我们,"做什么" 和 "怎么去做" 是可以分开的。
目的: 隐藏信息;具体表现为隐藏数据,隐藏实现细节、设计细节以及隐藏对象的类型等。
在许多语言的对象系统中,封装数据是由语法解析来实现的,这些语言也许提供了 private
、public
、protected
等关键字来提供不同的访问权限。
但 JavaScript 并没有提供对这些关键字的支持,我们只能依赖变量的作用域来实现封装特性,而且只能模拟出 private
和 public
这两种封装性。
var myObject = (function() {
var __name = "sven"; // 私有(private)变量
return {
getName: function() {
return __name;
}
};
})();
console.log(myObject.getName()); // 输出: sven
console.log(myObject.__name); // 输出: undefined
从封装实现细节来讲,封装使得对象内部的变化对其他对象而言是透明的,也就是不可见的。对象对它自己的行为负责。其他对象或者用户都不关心它的内部实现。封装使得对象之间的耦合变松散,对象之间只通过暴露的 API 接口来通信。当我们修改一个对象时,可以随意地修改它的内部实现,只要对外的接口没有变化,就不会影响到程序的其他功能。
JavaScript 基于原型实现继承。
原型模式不单是一种设计模式,也被称为一种编程泛型。
从设计模式的角度讲,原型模式是用于创建对象的一种模式,如果我们想要创建一个对象,一种方法是先指定它的类型,然后通过类来创建这个对象。原型模式选择了另外一种方式,我们不再关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一模一样的对象。
原型模式的实现关键,是语言本身是否提供了
clone
方法。ECMAScript 5 提供了Object.create
方法,可以用来克隆对象。
Object.create
polyfill:
Object.create =
Object.create ||
function(obj) {
var F = function() {};
F.prototype = obj;
return new F();
};
原型模式的真正目的并非在于需要得到一个一模一样的对象,而是提供了一种便捷的方式去创建某个类型的对象,克隆只是创建这个对象的过程和手段。
JavaScript 就是使用原型模式来搭建整个面向对象系统的。在 JavaScript 语言中不存在类的概念,对象也并非从类中创建出来的,所有的 JavaScript 对象都是从某个对象上克隆而来的。
Object
是 Animal
的原型,而 Animal
是 Dog
的原型,它们之间形成了一条原型链。这个原型链是很有用处的,当我们尝试调用 Dog
对象的某个方法时,而它本身却没有 这个方法,那么 Dog
对象会把这个请求委托给它的原型 Animal
对象,如果 Animal
对象也没有这个属性,那么请求会顺着原型链继续被委托给 Animal
对象的原型 Object
对象,这样一来便能得到继承的效果,看起来就像 Animal
是 Dog
的 "父类",Object
是 Animal
的 "父类"。
基于原型链的委托机制就是原型继承的本质。
规则:
按照 JavaScript 设计者的本意,除了 undefined
之外,一切都应是对象。为了实现这一目标,number
、boolean
、string
这几种基本类型数据也可以通过 "包装类" 的方式变成对象类型数据来处理。
JavaScript 中的根对象是 Object.prototype
对象。Object.prototype
对象是一个空的对象。我们在 JavaScript 遇到的每个对象,实际上都是从 Object.prototype
对象克隆而来的,Object.prototype
对象就是它们的原型。
朋友聚会去饭馆,点好菜可以有两种吃法,第一种,等菜上完大家动筷子,第二种,上一道吃一道。如果按第一种吃法,吃饭的程序要等待做饭的程序(饭馆)全部执行完;按第二种吃法,吃饭的程序和做饭的程序就完成了解耦,做饭的做他的,快或者慢,只要上,吃饭的就吃,不依赖一个最终状态。这两种方法不改变菜的品种质量。但流程效益却不同,如果上一道吃一道,吃完的盘子就可以清掉,如果按第一种吃法,上到中间发现桌子不够大,盘子要叠罗汉。而且第一种吃法,喜欢吃某个菜的朋友可以先吃到,不必死等流口水。如果点三道菜,两种吃法或许没太大差异。但是你想像一下,假设点了3000道菜,两种吃法的效率当下立现,如果按第一种吃法,大家都饿死了,即使做好了,桌子(内存)也放不下。
我上面说解耦,并不完全准确,准确得说,吃饭的程序不再依赖于整桌菜,而只依赖于下一道菜,而依赖于前者往往空间和时间效益都低,而依赖于后者较高。
这是
Generator
的实际意义,在值的使用者和值的生产者之间,建立效益更高的依赖,规避效益不高的依赖。
感谢大神 IT浪人 的解释
ES6 中除了新特性外,还有一个新的规范,那就是关于迭代的规范,他包括两部分分别是 “可迭代规范(iterable protocol)” 和 “迭代器规范(iterator protocol)”。任何实现了前者的对象,都可以进行 for…of
循环。
String, Array, Map, Set 等是原生可迭代对象,因为他们都在原型(prototype
)对象中实现了 Symbol.iterator
键对应的方法
for…of
是对象迭代器的遍历,而for…in
是对象中 可枚举 值的遍历
// 1. 迭代器规范
const iter = {
counter: 0,
next(){ // 迭代器是实现了 "next()" 函数的对象
if(++this.counter < 10){
return { // 返回一个含有两个键值对的对象,Object {done => boolean, value => any}
done: false,
value: this.counter
}
}else{
this.counter = 0;
return { // done = true 时,value非必须
done: true
}
}
}
};
// 2. 可迭代规范,实现 "Symbol.iterator => func()" 键值对;而 "func()" 返回一个 迭代器对象
const iterObj = {};
for(var i of iterObj){}; // TypeError: iterObj is not iterable
iterObj[Symbol.iterator] = function() {
return iter;
};
for(var i of iterObj){
console.log(i); // 1,2,3,4,5,6,7,8,9
};
# 查看 SSH keys 是否存在
ls -al ~/.ssh
# 生成新的 SSH key
ssh-keygen -t rsa -C "[email protected]"
# 测试 SSH
ssh -T [email protected]
~/username/.ssh/config
:
# gerrit
Host gerrit_host_url
HostName gerrit_host_url
# gerrit 对应的 email 或者用户名
User email
PreferredAuthentications publickey
# github对应的私钥
IdentityFile ~/.ssh/id_rsa_gerrit
# 查看
git config user.name
git config user.email
#修改
git config --global user.name "rocco"
git config --global user.email "[email protected]"
# 查看本地分支
git branch
# 查看本地和远程分支
git branch -a
# 新建分支
git branch <branchName>
# 切换分支
git checkout <branchName>
# 新建并切换至新建的分支
git checkout -b <branchName>
# 删除本地的某个分支
git branch -D <branchName>
# 删除远程的分支
git branch -r -d origin/<branchName>
git push origin :<branchName>
# 解释:
# git branch -r -d origin/<branchName> 只是删除本地的索引,而不是真正删除远程分支的内容
# 要想真正删除远程分支上的内容,把一个空分支 push 到 server 上,等于删除该分支,git push origin :<branchName>
# 注意:冒号前面的空格不能少
# 该命令撤消上一个commit,但保留add的文件,Git 会暂存所有的因复位带来的差异,但不提交它们
git reset --soft HEAD^
# 强制复位前一个提交
git reset --hard HEAD^
# 清空当前分支
# 注意不要遗漏 -r 后面的 .
git rm --cached -r .
git clean -f -d
# 创建空的 commit
git commit --allow-empty -m "[empty] initial commit"
# 推送空分支
git push
# 新建空分支
# 注意不要遗漏 -r 后面的 .
git branch -b <new_branch>
git rm --cached -r .
git clean -f -d
# 创建空的 commit
git commit --allow-empty -m "[empty] initial commit"
# 推送空分支
git push origin <new_branch>
git config core.ignorecase true
git mv <source> <destination> # 重命名 "source" 为 "destination"
#!/usr/bin/env bash
# set -ex
git filter-branch --env-filter '
an="$GIT_AUTHOR_NAME"
am="$GIT_AUTHOR_EMAIL"
cn="$GIT_COMMITTER_NAME"
cm="$GIT_COMMITTER_EMAIL"
if [ "$GIT_COMMITTER_EMAIL" = "要修改的邮箱地址" ]
then
cn="想要改成的用户名"
cm="想要改成的邮箱地址"
fi
if [ "$GIT_AUTHOR_EMAIL" = "要修改的邮箱地址" ]
then
an="想要改成的用户名"
am="想要改成的邮箱地址"
fi
export GIT_AUTHOR_NAME="$an"
export GIT_AUTHOR_EMAIL="$am"
export GIT_COMMITTER_NAME="$cn"
export GIT_COMMITTER_EMAIL="$cm"
'
Checkout
git checkout --orphan latest_branch
Add all the files
git add -A
Commit the changes
git commit -am "commit message"
Delete the branch
git branch -D master
Rename the current branch to master
git branch -m master
Finally, force update your repository
git push -f origin master --set-upstream origin master
git stash
记录使用 Sourcetree 贮藏工作现场后,误删了这个 stash
git fsck --unreachable
查找所有的 unreachable 记录
git show <sha>
其中 sha 是记录的 key(如下面的:bb3cc162df1270fcabac0dec53effc8166b9563b)
$ git stash drop
Dropped refs/stash@{0} (bb3cc162df1270fcabac0dec53effc8166b9563b)
使用 Sourcetree 删除时,我们是不知道 sha 的,此时回到第一步 git fsck --unreachable
查找所有的 unreachable 记录,例如:
$ git fsck --unreachable
Checking object directories: 100% (256/256), done.
Checking objects: 100% (3730/3730), done.
unreachable tree 3f80ada1c908812f125a5869ca7f77429e42a8c8
unreachable tree 7a409b8150ff5533ab4d22ebfbec520d4ce0bf7d
unreachable blob 8600c5b5609fbb541a2aa492d4eb4ed72c1e43a0
unreachable blob ee006fdb6554149a9f6aa6c3ef3d21853292b44f
unreachable blob b0c14ad2a35149815261ade2f89f3cf8e9fa096c
unreachable blob 57c221b28812aec98f2c44ee226ae75325ef28f3
unreachable blob 2783aa80d2dcdb51ac8cfe2f0cdbfd813ac6bc15
unreachable blob 65439852e70c0649d605a8452d79b4e16d40dada
unreachable blob 4b0472cf3317cbe8a476abd35ead1db1f791ebb7
unreachable tree c8c4ae2a9580bf788b39bf9ad770cc50cdeec734
unreachable tree 178594b9b22143860b6894377bc72b5c0ecbf6ea
unreachable tree 2a45ce0eb08dac0efe095b65eb9e98e3b1e26625
unreachable commit 49863934d99402979b2f43abd77b05e5a19780f2
对所有 unreachable commit 开头的记录,依次执行 git show <sha>
,查看是否为误删的提交,例如:
$ git show bb3cc162df1270fcabac0dec53effc8166b9563b
commit bb3cc162df1270fcabac0dec53effc8166b9563b
Merge: 2800dc1 d7625d4
Author: *** <***@***.com>
Date: Thu Jul 5 11:43:10 2018 +0800
WIP on dev: 2800dc1 style(pc): 样式修改
diff --cc src/common/filters/index.js
index ac9ea48,ac9ea48..1f72657
--- a/src/common/filters/index.js
+++ b/src/common/filters/index.js
@@@ -1,5 -1,5 +1,3 @@@
import currency from './currency'
--export default {
-- currency
--}
++export default { currency }
git stash apply <sha>
找到误删的 stash 后,使用 git stash apply <sha>
即可恢复
git reset --hard HEAD~1
git push --force
git reset commitId # (注: 不要带 --hard)到上个版本
git stash # 暂存修改
git push --force # 强制 push, 远程的最新的一次 commit 被删除
git stash pop # 释放暂存的修改, 开始修改代码
git add .
git commit -m "massage"
git push
#!/usr/bin/env bash
# set -ex
branch=$(git symbolic-ref --short -q HEAD)
if [[ ! -n "$1" ]]; then
echo "You have not input a branch, use the current branch:" $branch
else
branch=$1
fi
echo "push branch: $branch"
git push origin HEAD:refs/for/$branch
exit 0
catch
内一定要加 console.error(error)
,不然 try
块里出现 BUG 时你会想哭的
示例代码:
try {
...
} catch (error) {
console.error(error)
}
谁加谁知道,加了都说好
类似的还有 Promise,Promise 里的函数是被 try...catch
包裹着的
return
in finally
override try
function example() {
try {
return true;
} finally {
return false;
}
}
example(); // false
According to ECMA-262 (5ed, December 2009), in pp. 96:
The production
TryStatement : try Block Finally
is evaluated as follows:Let B be the result of evaluating Block.
Let F be the result of evaluating Finally.
If F.type is normal, return B.
Return F.
And from pp. 36:
The Completion type is used to explain the behaviour of statements (
break
,continue
,return
andthrow
) that perform nonlocal transfers of control. Values of the Completion type are triples of the form (type, value, target), where type is one ofnormal
,break
,continue
,return
, orthrow
, value is any ECMAScript language value or empty, and target is any ECMAScript identifier or empty.
It's clear that return false would set completion type of finally as return, which cause try ... finally to do 4. Return F.
说明:动态添加更多的路由规则
// router.js
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: () => import(/* webpackChunkName: "home" */ './views/Home.vue'),
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
},
{
path: '*',
redirect: '/',
},
],
});
App.vue
<template>
<div id="app">
<div id="nav">
<router-link v-for="r in routes"
:key="r.path"
:to="r.path">{{ r.name }}</router-link>
</div>
<router-view/>
<button @click="addRoutes">add routes</button>
</div>
</template>
<script>
const AsyncComponent = () => import(/* webpackChunkName: "async" */ '@/views/Async');
export default {
name: 'app',
data() {
return {
routes: [{ name: 'Home', path: '/' }, { name: 'About', path: '/about' }],
};
},
computed: {
routeMap() {
return this.routes.reduce((accu, curr) => {
accu[curr.name] = curr.path;
return accu;
}, {});
},
},
methods: {
addRoutes() {
if (this.routeMap.Async) return;
this.$router.addRoutes([
{
path: '/async',
component: AsyncComponent,
},
]);
this.routes.push({ name: 'Async', path: '/async' });
},
},
};
</script>
参数传递策略是求值策略的特殊情况。
ECMAScript 中所有的参数都是按值传递的。 -- 《JavaScript 高级程序设计》 4.1.3
基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样(复制指针)。
对于 基本类型值 的传递是传值调用
特征: 对于传递过来的变量进行修改,不会影响到原变量。
对于 引用类型值 的传递是传引用调用
特征: 对于变量的成员进行修改时,会直接影响原变量;而如果对传递过来的变量进行重新赋值,则不会影响原变量,并且此后再修改变量的成员,也不会影响原变量。
特征: 无论是对于变量成员的修改,还是对变量重新赋值,都会影响到原对象。
特征: 如果实际参数在函数的求值中未被用到,则它永不被求值;如果这个实际参数使用多次,则它每次都被重新求值。
特征: “传需求调用”是传名调用的记忆化版本,如果“函数的实际参数被求值了”,这个值被存储起来已备后续使用。当函数实际参数被使用两次或更多次的时候,传需求调用总是更快。
传需求调用的条件:纯函数。
*
通用选择器:选择所有元素,不参与计算优先级,兼容性:IE6+#X
id 选择器:选择 id 值为 X 的元素,兼容性:IE6+.X
类选择器:选择 class 包含 X 的元素,兼容性:IE6+X Y
后代选择器:选择满足 X 选择器的后代节点中满足 Y 选择器的元素,兼容性:IE6+X
元素选择器:选择标所有签为 X 的元素,兼容性:IE6+:link
,:visited
,:focus
,:hover
,:active
链接状态:选择特定状态的链接元素,顺序 LoVe HAte,兼容性:IE4+X + Y
直接兄弟选择器:在 X 之后第一个兄弟节点中选择满足 Y 选择器的元素,兼容性:IE7+X > Y
子选择器:选择 X 的子元素中满足 Y 选择器的元素,兼容性:IE7+X ~ Y
兄弟:选择 X 之后所有兄弟节点中满足 Y 选择器的元素,兼容性:IE7+[attr]
:选择所有设置了 attr 属性的元素,兼容性:IE7+[attr=value]
:选择属性值刚好为 value 的元素[attr~=value]
:选择属性值为空白符分隔,其中一个的值刚好是 value 的元素[attr|=value]
:选择属性值刚好为 value 或者 value- 开头的元素[attr^=value]
:选择属性值以 value 开头的元素[attr$=value]
:选择属性值以 value 结尾的元素[attr*=value]
:选择属性值中包含 value 的元素[:checked]
:选择单选框,复选框,下拉框中选中状态下的元素,兼容性:IE9+X:after
,X::after
,::after
伪元素,选择元素虚拟子元素(元素的最后一个子元素),CSS3 中 ::
表示伪元素。兼容性 :after
为 IE8+,::after
为 IE9+:hover
:鼠标移入状态的元素,兼容性:a 标签 IE4+, 所有元素 IE7+:not(selector)
:选择不符合 selector 的元素。不参与计算优先级,但是在计算选择器数量时会把其中的选择器当做普通选择器进行计数,兼容性:IE9+::first-letter
:伪元素,选择块元素第一行的第一个字母,兼容性:IE5.5+::first-line
:伪元素,选择块元素的第一行,兼容性:IE5.5+:nth-child(an + b)
:伪类,选择前面有 an + b - 1 个兄弟节点的元素,其中 n >= 0, 兼容性:IE9+:nth-last-child(an + b)
:伪类,选择后面有 an + b - 1 个兄弟节点的元素 其中 n >= 0,兼容性:IE9+X:nth-of-type(an+b)
:伪类,X 为选择器,解析得到元素标签,选择前面有 an + b - 1 个相同标签兄弟节点的元素。兼`容性:IE9+X:nth-last-of-type(an+b)
:伪类,X 为选择器,解析得到元素标签,选择后面有 an+b-1 个相同标签兄弟节点的元素`。兼容性:IE9+X:first-child
:伪类,选择满足 X 选择器的元素,且这个元素是其父节点的第一个子元素。兼容性:IE7+X:last-child
:伪类,选择满足 X 选择器的元素,且这个元素是其父节点的最后一个子元素。兼容性:IE9+X:only-child
:伪类,选择满足 X 选择器的元素,且这个元素是其父元素的唯一子元素。兼容性:IE9+X:only-of-type
:伪类,选择 X 选择的元素,解析得到元素标签,如果该元素没有相同类型的兄弟节点时选中它。兼容性:IE9+X:first-of-type
:伪类,选择 X 选择的元素,解析得到元素标签,如果该元素是此类型元素的第一个兄弟。选中它。兼容性:IE9+:not()
伪类,在优先级计算中不会被看作是伪类,但是在计算选择器数量时会把 其中的选择器 当做普通选择器进行计数color
、line-height
)权重为 0HTML:
<div class="outer" id="app">
<div class="inner">
<p class="highlight">测试文本</p>
</div>
</div>
CSS:
#app .inner:not(#div) .highlight {
color: red;
}
#app .highlight:nth-of-type(1):nth-last-of-type(1) {
color: blue;
}
请问 测试文本 的颜色为?
根据很久之前看过的一篇博文,隐约记得里面有一个权重口诀:
从 0 开始,一个行内样式 +1000,一个 id +100,一个 class / 属性选择器或者伪类 +10,一个元素名或者伪元素 +1,通配符 * 不计权重。
我天真的选择了:测试文本,蓝色。
正确答案为:测试文本,红色!
权重计算:
要特别注意 :not()
伪类,在优先级计算中不会被看作是伪类,但是在计算选择器数量时会把 其中的选择器 当做普通选择器进行计数,MDN
权重为:100 (#app
) + 10 (.inner
) + 100 (#div
) + 10 (.highlight
) = 220
#app .inner:not(#div) .highlight {
color: red;
}
权重为:100 (#app
) + 10 (.highlight
) + 10 (:nth-of-type(1)
) + 10 (:nth-last-of-type(1)
) = 130
#app .highlight:nth-of-type(1):nth-last-of-type(1) {
color: blue;
}
所以最终颜色为红色
根据选择器种类的不同可以分为四类,也决定了四种不同等级的权重值。
行内样式
行内样式包含在你的 html 中 对你的元素产生直接作用,比如:
<h1 style="color: #fff;">header</h1>
ID 选择器
Id 也是元素的一种标识,比如 #div
类,属性选择器和伪类选择器
这一类包括各种 class;属性选择器,比如: [title]
;伪类选择器,比如 :hover
等等
元素和伪元素
元素跟伪元素选择器,比如 div
、::before
、::after
、::first-letter
、::first-line
、::selecton
记忆口诀:从 0 开始,一个行内样式 +1000,一个 id +100,一个 class 属性选择器或者伪类 +10,一个元素名或者伪元素 +1,通配符
*
不计权重,继承来的属性(color
、line-height
)权重为 0。
例如:
body #content .data img:hover {
}
最终权重为:1 (body
) + 100 (#content
) + 10 (.data
) + 1 (img
) + 10 (:hover
) = 122
!important
!important
会覆盖任何其他的样式声明。
这个规则有点毒,除非特殊情况,不然不要使用。
<!-- parent.html -->
<body>
<h1>this is parent page</h1>
<iframe src="./child-a.html" id="child-a" name="child-a"></iframe>
<iframe src="./child-b.html" id="child-b" name="child-b"></iframe>
</body>
<!-- child-a.html -->
<body>
<h1>this is child-a page</h1>
</body>
<!-- child-b.html -->
<body>
<h1>this is child-b page</h1>
</body>
// parent.html
const frameA = document.getElementById('child-a');
frameA.onload = () => {
iFrame.contentWindow.postMessage('some message form parent', '*');
};
// child-a.html
window.addEventListener(
'message',
(event) => {
console.log('received msg:', event);
},
false,
);
// child-a.html
window.parent.postMessage({ msg: 'some message form child-a' }, '*');
// parent.html
window.addEventListener(
'message',
(event) => {
console.log('received msg:', event);
},
false,
);
// child-a.html
window.parent.frames[1].contentWindow.postMessage(
'some message form child-a',
'*',
);
// child-b.html
window.addEventListener(
'message',
(event) => {
console.log('received msg:', event);
},
false,
);
用 iframe.contentWindow.location.replace(newUrl)
的方式设置 iframe URL,不要用 iframe.src = newUrl
,不然 FireFox 下需要点两次返回按钮才可以返回到上一页。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="ie=edge, chrome=1">
<meta name="format-detection" content="telephone=no, email=no">
<link rel="shortcut icon" type="image/x-icon" href="">
<title>标题</title>
<meta name="description" content="不超过150个字符">
<meta name="keywords" content="">
</head>
<body> 这里开始内容 </body>
</html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="ie=edge, chrome=1">
<meta name="format-detection" content="telephone=no, email=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- IOS添加到主屏后的标题和图标 -->
<meta name="apple-mobile-web-app-title" content="标题">
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="">
<link rel="shortcut icon" type="image/x-icon" href="">
<title>标题</title>
<meta name="description" content="不超过150个字符">
<meta name="keywords" content="">
<link rel="stylesheet" href="index.css">
</head>
<body> 这里开始内容 </body>
</html>
1. H5 页面窗口自动调整到设备宽度,并禁止用户缩放页面
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
属性基本含义:
width=device-width
- 控制 viewport
的大小,device-width
为设备的宽度initial-scale
- 初始的缩放比例minimum-scale
- 允许用户缩放到的最小比例maximum-scale
- 允许用户缩放到的最大比例user-scalable
- 用户是否可以手动缩放2. 启用 360 浏览器的极速模式(webkit)
<meta name="renderer" content="webkit">
3. ie=edge 告诉 IE 使用最新的引擎渲染网页,chrome=1 则可以激活 Chrome Frame
<meta http-equiv="X-UA-Compatible" content="ie=edge, chrome=1">
4. 取消微信/百度对网站的自动转码
<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
5. 国产浏览器强制显示
<!-- UC强制竖屏 -->
<meta name="screen-orientation" content="portrait">
<!-- QQ强制竖屏 -->
<meta name="x5-orientation" content="portrait">
<!-- UC强制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ强制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC应用模式 -->
<meta name="browsermode" content="application">
<!-- QQ应用模式 -->
<meta name="x5-page-mode" content="app">
6. 忽略将页面中的数字识别为电话号码
<meta name="format-detection" content="telephone=no">
7. 忽略 Android 平台中对邮箱地址的识别
<meta name="format-detection" content="email=no">
8. 当网站添加到主屏幕快速启动方式,可隐藏地址栏,仅针对 ios 的 safari
<meta name="apple-mobile-web-app-capable" content="yes">
9. 将网站添加到主屏幕快速启动方式,仅针对 ios 的 safari 顶端状态条的样式
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <!– 可选default、black、black-translucent –>
10. 需要在网站的根目录下存放 favicon 图标,防止 404 请求(使用 fiddler 可以监听到),在页面上需加 link 如下:
<link rel="shortcut icon" href="/favicon.ico">
11. QQ 聊天框中发送网站 URL,预览内容修改方法:
<meta name="description" itemprop="description" content="摘要">
<meta itemprop="name" content="标题">
<meta itemprop="image" content="图片">
最近做项目时,发现点击 URL,浏览器始终默认下载文件,而不是预览。
修改文件的响应头
说明:一个可以让客户端下载文件并建议文件名的头部。文件名需要用双引号包裹。
例子:Content-Disposition: attachment; filename="filename.ext"
说明:当前内容的 MIME 类型
例子:Content-Type: text/html; charset=utf-8
router.addRoutes
将子项目的路由动态注册到入口项目中store.registerModule
将子项目的 store module 动态注册到入口项目中# 安装 Vue CLI 3
yarn global add @vue/cli
mkdir projects
cd ./projects
vue create entry-app
vue create sub-app-one
// vue.config.js
module.exports = {
configureWebpack: {
externals: {
vue: 'Vue',
vuex: 'Vuex',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
moment: 'moment',
},
},
};
<!-- index.html -->
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.3.9/theme-chalk/index.css" rel="stylesheet">
</head>
<body>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.1/vue-router.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.4.0/index.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.1/moment.min.js"></script>
</body>
在子项目中用到了这两个实例
// main.js
window.microApp = {};
// store.js
const store = new Vuex.Store({
state: { name: 'entry-app' },
mutations: {},
actions: {},
});
window.microApp.store = store;
// router.js
const router = new Router({ routes });
window.microApp.router = router;
// load-module.js
export default function loadModule(url, options = {}) {
if (!url || typeof url !== 'string') {
return Promise.reject('Illegal parameter: url, expect a not empty string.');
}
if (typeof options !== 'object') {
return Promise.reject('Illegal parameter: options, expect an object.');
}
const noCache = !!options.noCache;
return new Promise((resolve) => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.onload = ({ type }) => resolve({ type, url });
script.onerror = ({ type }) => resolve({ type, url });
script.src = noCache ? addTimestamp(url) : url;
document.body.appendChild(script);
});
}
function addTimestamp(url) {
return url.indexOf('?') > -1 ? `${url}&t=${Date.now()}` : `${url}?t=${Date.now()}`;
}
// main.js
import loadModule, { remove } from 'path/to/load-module.js';
const modules = [
'http://localhost:7200/sub-app-one/dist/sub-app-one.umd.min.js',
'http://localhost:7200/sub-app-two/dist/sub-app-two.umd.min.js',
];
Promise.all(modules.map((url) => loadModule(url))).then((res) => {
console.log(res);
remove();
});
package.json
{
"scripts": {
"build": "vue-cli-service build --target lib --name sub-app-one ./src/module.js"
}
}
这样可以将 sub-app-one 构建成一个库,然后在 entry-app 中引用,参考
// vue.config.js
const webpack = require('webpack');
const APP_NAME = require('./package.json').name;
const IS_DEV = process.env.NODE_ENV === 'development';
module.exports = {
configureWebpack: {
externals: IS_DEV
? {}
: {
vue: 'Vue',
vuex: 'Vuex',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
moment: 'moment',
},
plugins: [
new webpack.DefinePlugin({
'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME),
}),
],
},
};
开发时使用 node_modules 文件,构建后使用外部依赖
为子项目路由添加命名空间
// router-list.js
const IS_DEV = process.env.NODE_ENV === 'development';
const APP_NAME = process.env.VUE_APP_NAME;
export default [
{
path: IS_DEV ? '/' : `/${APP_NAME}`,
name: APP_NAME,
title: APP_NAME,
redirect: { name: `${APP_NAME}.page-a` },
component: () => import(/* webpackChunkName: "index" */ '@/views/Index.vue'),
children: [
{
path: 'page-a',
name: `${APP_NAME}.page-a`,
component: () => import(/* webpackChunkName: "page-a" */ '@/views/PageA.vue'),
},
{
path: 'page-b',
name: `${APP_NAME}.page-b`,
component: () => import(/* webpackChunkName: "page-b" */ '@/views/PageB.vue'),
},
],
},
];
注册路由
// module.js
import routes from 'path/to/router-list.js';
const routerInstance = window.microApp.router; // 后面有解释
routerInstance.addRoutes(routes);
提取 Vuex module,添加命名空间
// store-module.js
export default {
namespaced: true, // namespaced must be true in module app.
state: { name: process.env.VUE_APP_NAME },
mutations: {},
actions: {},
};
注册 Vuex module
// module.js
import store from 'path/to/store-module.js';
const storeInstance = window.microApp.store; // 后面有解释
const moduleName = process.env.VUE_APP_NAME;
storeInstance.registerModule(moduleName, store);
添加插件
yarn add postcss-selector-namespace -D
使用插件
// postcss.config.js
const APP_NAME = require('./package.json').name;
module.exports = {
plugins: {
autoprefixer: {},
'postcss-selector-namespace': { namespace: `.${APP_NAME}` },
},
};
<template>
和 <slot>
元素,用于编写不在页面中显示的标记模板yarn add postcss-selector-namespace -D
postcss.config.js
module.exports = {
plugins: {
'postcss-selector-namespace': { namespace: '.custom-namespace' },
},
};
添加 .custom-namespace
类名
<div class="custom-namespace" id="app"></div>
input.css
h1,
.h1 {
color: red;
}
output.css
.custom-namespace h1,
.custom-namespace .h1 {
color: red;
}
在不想添加 namespace 的样式前增加 :root
input.css
:root #app {
font-weight: bold;
}
output.css
#app {
font-weight: bold;
}
说明:动态注册 vuex module
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
name: 'my app',
},
mutations: {},
actions: {},
modules: {
a: {
namespaced: true,
state: {
name: 'a',
},
},
},
});
component.vue
<template>
<div id="app">
<!-- 注册 template 模块 -->
<button @click="registerModule('template')">register module</button>
</div>
</template>
<script>
export default {
methods: {
registerModule(namespace) {
if (this.$store.state[namespace]) return;
this.$store.registerModule(namespace, {
namespaced: true,
state: { name: namespace },
mutations: {
setName(state, payload) {
state.name = payload;
},
},
});
},
},
mounted() {
// 注册 mounted 模块
this.registerModule('mounted');
},
};
</script>
调用 registerModule
后 Vue Devtools 的 Vuex 面板未更新
解决方法:重启 chrome 开发者工具,参考
{
"name": "Rocco's Blog",
"short_name": "Rocco",
"description": "An front-end engineer who loves and pursues simple.",
"start_url": "/index.html",
"theme_color": "#333",
"background_color": "#fff",
"display": "standalone",
"orientation": "portrait",
"icons": [
{
"src": "/images/icons/icon-64.png",
"sizes": "64x64",
"type": "image/png"
},
{
"src": "/images/icons/icon-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/images/icons/icon-256.png",
"sizes": "256x256",
"type": "image/png"
}
]
}
说明:
name
指定提示用户安装应用时横幅上的文本short_name
指定应用安装后出现在用户主屏幕上的文本start_url
指定当用户从设备的主屏幕开启 Web 应用时所出现的第一个页面,如果你想追踪有多少人是通过主屏幕图标访问网站的,可以这样设置 start_url:/index.html?from=screen
theme_color
指定浏览器地址栏的颜色background_color
指定当用户从设备的主屏幕开启 Web 应用时icons
指定 Web 应用被添加到设备主屏幕时所显示的图标display
指定 Web 应用的显示模式,是可选项,默认为 browser 模式
fullscreen
打开 Web 应用并占用整个可用的显示区域。standalone
打开 Web 应用以看起来像一个独立的原生应用。此模式下,用户代理将排除诸如 URL 栏等标准浏览器 UI 元素,但可以包括诸如状态栏和系统返回按钮的其他系统 UI 元素。minimal-ui
此模式类似于 fullscreen,但为终端用户提供了可访问的最小 UI 元素集合,例如,后退按钮、前进按钮、重载按钮以及查看网页地址的一些方式。browser
使用操作系统内置的标准浏览器来打开 Web 应用。在 HTML 中引入:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Progressive Web Application</title>
<link rel="manifest" href="/manifest.json">
</head>
<body>
</body>
</html>
window.addEventListener('beforeinstallprompt', function(e) {
e.preventDefault();
return false;
});
代码会监听 beforeinstallprompt
事件并防止操作栏的默认行为触发。代码使用了标准 JavaScript 的 preventDefault()
功能来取消事件并返回 false
,两处代码都是需要的,以确保操作栏不会出现。
window.addEventListener('beforeinstallprompt', function(event) {
event.userChoice.then(result => {
console.log(result.outcome);
if (result.outcome == 'dismissed') {
// 发送数据以进行分析
} else {
// 发送数据以进行分析
}
});
});
这可以让用户控制是否要添加你的网站,而不是浏览器决定何时显示横幅。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Progressive Web Application</title>
<link rel="manifest" href="/manifest.json">
</head>
<body>
<!-- 点击此按钮会显示安装横幅 -->
<button id="btnSave" disabled>Click this to show the prompt</button>
</body>
<script>
window.addEventListener('DOMContentLoaded', function () {
let btnSave = document.getElementById('btnSave');
let savedPromptEvent;
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then(registration => {
// 注册成功
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(error => {
// 注册失败 :(
console.log('ServiceWorker registration failed: ', error);
});
}
window.addEventListener('beforeinstallprompt', function (event) {
event.preventDefault();
btnSave.removeAttribute('disabled');
savedPromptEvent = event;
return false;
});
btnSave.addEventListener('click', function () {
if (savedPromptEvent !== undefined) {
savedPrompt.prompt();
savedPrompt.userChoice.then(result => {
if (result.outcome == 'dismissed') {
console.log('User dismissed homescreen install');
} else {
console.log('User added to homescreen');
}
savedPrompt = null;
});
}
});
});
</script>
</html>
如果项目中配置 eslint 的话,在 VSCode settings.json 里添加配置 "prettier.eslintIntegration": true
。
.prettierrc
{
"printWidth": 80, // 换行字符串阈值
"singleQuote": true, // 使用单引号
"semi": true, // 句末加分号
"trailingComma": "all", // 对象 / 数组 多行时最后一行加逗号
"bracketSpacing": true, // 对象 / 数组 边界加空格
"arrowParens": "always" // 箭头函数参数加括号
}
yarn global add prettier
prettier --config ./.prettierrc --write "src/**/*.{js,vue,less}"
.vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch vuejs",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}/src",
"breakOnLoad": true,
"sourceMapPathOverrides": {
"webpack:///src/*": "${webRoot}/*"
}
}
]
}
// vue.config.js
module.exports = {
configureWebpack: {
devtool: 'source-map',
},
};
指代 ”当前的文字颜色“
.text {
color: blue;
border: 1px dashed currentColor;
}
IE9 +
cd path/to/my-project
npm install path/to/my-utils
npm link
模块目录,把模块 link 到全局
cd path/to/my-utils
npm link
项目目录,把模块 link 到当前项目
cd path/to/my-project
npm link my-utils
移除 link
npm unlink my-utils
const scrollTop =
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
const scrollLeft =
window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
API | Chrome 68 | IE 11 | EDGE 17 | Firefox |
---|---|---|---|---|
window.pageYOffset |
true | true | true | true |
document.documentElement.scrollTop |
true | true | false | true |
document.body.scrollTop |
false | false | true | false |
JS 文件:calendar.js
/// <reference path='path/to/calendar.d.ts' />
const calendar = {
// balabala...
};
export default calendar;
添加类型声明文件:calendar.d.ts
export const calendar: any;
使用
<script lang="ts">
import calendar from 'path/to/calendar.js';
</script>
搜索引擎优化(Search engine optimization,简称 SEO),指为了提升网页在搜索引擎自然搜索结果中(非商业性推广结果)的收录数量以及排序位置而做的优化行为。
选择使用子域名还是目录来合理的分配网站内容,对网站在搜索引擎中的表现会有较大的影响。
每个网页的内容都是不同的,每个网页都应该有独一无二的 title。
当你网站上的内容对用户有用时,用户会推荐给别人,推荐的形式可能多种多样:即时通讯工具上发给自己的朋友、在自己常泡的论坛里转帖推荐、在自己网站上做友情链接推荐等等。这些推荐信息,都会被搜索引擎用来判断网页/网站价值的高低。适当的鼓励、引导用户推荐你的网站,对网站在搜索引擎中的表现有很大帮助。
title
中不要加入过多的额外描述,会分散用户注意力;title
中合适的位置,品牌效应会增加用户点击的机率;description
,避免所有网页都使用同样的描述;网站信用度指用户给予你网站的信任程度。用户对网站的信任度是用户在网站上进行活动的基础。
任何利用和放大搜索引擎的策略缺陷,利用恶意手段获取与网页质量不符的排名,引起用搜索结果质量和用户搜索体验下降的行为都会被搜索引擎当做作弊行为。
参照以下步骤:
nofollow
?<meta name="robots" content="nofollow" /> OR <a rel="nofollow" href="url">123</a>
百度支持以上两种写法的 nofollow
,带有 nofollow
属性的 url,不会传递权值。
百度目前只能收录少部分 https 网页,大部分 https 网页无法收录。网站首页和对所有用户都公开的内容页面,建议不要使用 https 协议,如果非用不可,尽量将首页和重要页面做个 http 可访问版,方面百度收录。
title
是极重要的内容。大幅修改,可能会带来大幅波动。所以请慎重对待网页标题。建议按照我们上面所推荐的写法,实事求是的将页面主旨反映在标题中即可,如无必要,尽量不做大幅修改。
meta description
是否会受到惩罚?meta description
只是摘要的一个选择目标,修 meta description
只会影响摘要。我们鼓励大家通过 meta description
来撰写网站的简介。只是过于频繁的修改,未必会及时的反馈在摘要中。
搜索引擎处理不好动态 url,主要是因为动态 url 中参数过多,很容易制造出大量内容相同、url 不同的无限循环的“黑洞”,spider 陷入其中,浪费大量的资源。尽量减少动态 url 中包含的变量参数,一方面可以减短 url 长度,另一方面,也减少把 Baiduspider 带入“黑洞”的风险。
<meta name="robots" content="noarchive">
:防止所有搜索引擎显示您网站的快照,请将此元标记置入网页;<meta name="robots" content="nofollow">
:禁止搜索引擎追踪此网页上的链接,且不传递链接的权重,请将此元标记置入;Baiduspider对站点的抓取方式和普通用户访问一样,只要普通用户能访问到的内容,我们就能抓取到。不管是用什么技术,只要能保证用户能流畅的访问网站,对搜索引擎就没有影响。我们建议尽量选择有实力的服务商和成熟的技术,不成熟的技术容易导致访问不稳定,这就有可能影响搜索引擎的抓取了。
如果是内容发生根本性变化,则理论上会被视为一个全新网站,旧有超链失效。
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('sw.js', { scope: '/' })
.then(function(reg) {
console.log('Service Worker 注册成功,域名: ', reg.scope);
})
.catch(function(err) {
console.log('Service Worker 注册失败: ', err);
});
}
说明:
/sw.js
的 Service Worker。register()
方法,浏览器将会判断 Service Worker 线程是否已注册并做出相应的处理。register()
方法的 scope 参数是可选的,用于指定你想让 Service Worker 控制的内容的子目录,关于 register()
方法的 scope 参数,需要说明一下:/a/b/sw.js
,不传 scope 值的情况下,scope 的值就是 /a/b
,scope 的值的意义在于,如果 scope 的值为 /a/b
,那么 Service Worker 线程只能捕获到 path 为 /a/b
开头的(/a/b/page1,/a/b/page2,...)页面的 fetch 事件,通过 scope 的意义我们也能看出 Service Worker 不是服务单个页面的,所以在 Service Worker 的 js 逻辑中全局变量需要慎用。then()
函数链式调用我们的 Promise,当 Promise resolve 的时候,里面的代码就会执行。catch()
函数,当 Promise rejected 的时候执行。Chrome 地址栏中输入 chrome://serviceworker-internals 可以查看 Service Worker 详情。
生命周期分为这几个状态:installing、installed、activating、activated、redundant。
installing(安装中):这个状态发生在 Service Worker 注册之后,表示开始安装,触发 install 事件回调指定一些静态资源进行离线缓存,
install 事件回调中有两个方法:
event.waitUntil()
:传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。self.skipWaiting()
:self 是当前 context 的 global 变量,执行该方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。installed(安装后):Service Worker 已经完成了安装,并且等待其他的 Service Worker 线程被关闭;
activating(激活中):在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联的旧缓存资源,等待新的 Service Worker 线程被激活,activate 回调中有两个方法:
event.waitUntil()
:传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。self.clients.claim()
:在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存,旧的 Service Worker 脚本不再控制着页面,之后会被停止。activated(激活后):在这个状态会处理 activate 事件回调 (提供了更新缓存策略的机会),并可以处理功能性的事件 fetch (请求)、sync (后台同步)、push (推送)。
redundant(废弃):这个状态表示一个 Service Worker 的生命周期结束,这里特别说明一下,进入 redundant(废弃) 状态的原因可能为这几种:
const VERSION = '180315-01';
const OFFLINE_CACHE = 's-offline-' + VERSION;
// 需要缓存的离线页面
const FILES_TO_CACHE = [
'/',
'/index.html',
'/offline.html',
'/manifest.json',
'/css/style.css',
'/fancybox/jquery.fancybox.css',
'https://cdn.bootcss.com/jquery/2.0.3/jquery.min.js',
'/fancybox/jquery.fancybox.pack.js',
'/js/script.js',
'/images/favicon.ico',
'/images/avatar.jpg',
'/images/offline-image.png',
'/css/images/banner.jpg',
'/css/fonts/fontawesome-webfont.woff'
];
self.addEventListener('install', function(event) {
console.log('[SW]:', '开始安装 Service Worker');
event.waitUntil(
caches
.open(OFFLINE_CACHE)
.then(cache => cache.addAll(FILES_TO_CACHE))
.then(() =>
console.log('[SW]:', '离线资源缓存完毕,当前版本:', OFFLINE_CACHE)
)
.then(() => self.skipWaiting())
);
});
说明:
self
是 Service Worker 线程内的顶级对象,类似于浏览器内的 window
对象。caches.open()
方法可以打开一个缓存,调用后该方法会返回了一个 Promise,当它 resolved 的时候(执行 then()
)就可以调用缓存实例上的 addAll()
方法,addAll()
方法接收一个相对于 origin 的 URL 组成的数组,这些 URL 就是想缓存的资源列表。self.skipWaiting()
方法可以跳过 waiting 状态,然后直接进入 activate 阶段,和 self.clients.claim()
一起使用可以确保更新 Service Worker 时立即生效。任何被 Service Worker 控制的资源被请求到时,都会触发 fetch 事件,这些资源包括指定 scope 内的 html 文档,和这些 html 文档内引用的任何资源(比如 index.html 发起了一个跨域的请求来嵌入一个图片,这个也会通过 Service Worker)。
// 缓存版本
const CACHE_VERSION = 's-data-v1';
self.addEventListener('fetch', function(event) {
const request = event.request;
event.respondWith(
caches.match(request).then(response => {
// 如果匹配到缓存,就直接返回,减少一次 HTTP 请求
if (response) {
console.log('[SW]:', '读取缓存', request.method, request.url);
return response;
}
// 如果未匹配到缓存,就直接发起网络请求
const requestClone = request.clone(); // 拷贝原始请求
return fetch(requestClone).then(httpResponse => {
const responseClone = httpResponse.clone();
caches.open(CACHE_VERSION).then(cache => {
cache.put(request, responseClone);
console.log('[SW]:', '写入缓存', request.method, request.url);
});
return httpResponse;
});
})
);
});
说明:
在 install 事件中缓存静态资源,在 fetch 事件中处理回调来代理页面请求从而实现动态地资源缓存;install 和 fetch 缓存的区别:
接着调用 event 上的 respondWith()
方法来劫持 HTTP 响应。
caches.match()
方法将网络请求的资源和 cache 里可获取的资源进行匹配,查看缓存中是否有相应的资源。
由于请求/响应流只能被读取一次,为了给浏览器返回响应以及把它缓存起来,需要将请求/响应克隆一份。
fetch()
方法必须接受一个参数:资源的路径;无论请求成功与否,它都返回一个 Promise 对象,resolve 时(then()
)返回对应请求的 Response。
使用 caches.open()
方法打开一个缓存,然后调用 cache.put()
方法将克隆的响应存储到缓存中。
最后将原始的响应返回给浏览器(return httpResponse;
)。
// 缓存版本
const CACHE_VERSION = 's-data-v2';
self.addEventListener('activate', function(event) {
console.log('[SW]:', '激活 Service Worker');
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys
.filter(key => key !== CACHE_VERSION) // 过滤出来需要删除的资源
.map(key => {
console.log('[SW]:', '移除过时缓存:', key);
caches.delete(key);
}) // 删除旧版本资源,caches.delete() 返回 Promise 对象
)
)
);
return self.clients.claim();
});
说明:
waitUntil()
的 Promise 会阻塞其他的事件,直到它完成,所以可以确保清理操作(caches.delete()
)会在此次 fetch 事件之前完成。Array.filter()
过滤出来需要删除的资源caches.delete()
方法删除过期的缓存。在计算机科学中,求值策略(Evaluation strategy)是确定编程语言中表达式的求值的一组(通常确定性的)规则。重点典型的位于函数或算子上——求值策略定义何时和以何种次序求值给函数的实际参数,什么时候把它们代换入函数,和代换以何种形式发生。 -- 维基百科
严格求值下,传给函数的实际参数总是在调用这个函数之前被求值。
多数现存编程语言对函数使用严格求值。
非严格求值下,传给函数的实际参数并不会立即求值,是否需要求值依赖于这个实际参数在函数执行中有没有被使用。
注意:对 纯函数 缓存才有意义
memoize.js
const memoize = function(func) {
if (typeof func != 'function') {
throw new TypeError('Expected a function');
}
const cache = new Map();
const memorized = function(...args) {
const key = args.join(',') || 'default';
if (!cache.has(key)) {
cache.set(key, func.apply(this, args));
}
return cache.get(key);
};
memorized.isMemorized = true;
return memorized;
};
示例 1:
const func = function() {
// some expensive operation
console.log('func is executed!');
return 'func';
};
const memorizedFunc = memoize(func);
console.log(memorizedFunc());
console.log(memorizedFunc());
console.log(memorizedFunc());
// console:
// func is executed!
// func
// func
// func
func 只会执行一次
示例 2(斐波那契函数):
let counter = 0;
let fib = function(n) {
counter++;
switch (n) {
case 0:
return 0;
case 1:
return 1;
default:
return fib(n - 1) + fib(n - 2);
}
};
console.log('fib 20:', fib(20));
console.log('counter:', counter, '\n');
counter = 0; // 重置 counter
fib = memoize(fib); // 重写 fib
console.log('memorizedFib 20:', fib(20));
console.log('counter:', counter);
// console:
// fib 20: 6765
// counter: 21891
// memorizedFib 20: 6765
// counter: 21
斐波那契函数时间复杂度为 O(n) = 2^n
,性能极差。
缓存后的 fib 函数只会执行 n 次(0 ~ n)函数体。
问题:
// (1) 商品名转为大写
// (2) 取出三个价格低于 10 的商品
const gems = [
{ name: 'Sunstone', price: 4 },
{ name: 'Amethyst', price: 15 },
{ name: 'Prehnite', price: 20 },
{ name: 'Sugilite', price: 7 },
{ name: 'Diopside', price: 3 },
{ name: 'Feldspar', price: 13 },
{ name: 'Dioptase', price: 2 },
{ name: 'Sapphire', price: 20 }
];
let filterCounter = 0;
let transformCounter = 0;
const filter = item => {
filterCounter++;
console.log('filter is run.');
return item.price < 10;
};
const transform = item => {
transformCounter++;
console.log('transform is run.');
return {
name: item.name.toUpperCase(),
price: item.price
};
};
const result = gems
.filter(filter)
.map(transform)
.slice(0, 3);
console.log('filterCounter:', filterCounter);
console.log('transformCounter:', transformCounter);
console.log(result);
// console:
// filter is run.
// filter is run.
// filter is run.
// filter is run.
// filter is run.
// filter is run.
// filter is run.
// filter is run.
// transform is run.
// transform is run.
// transform is run.
// transform is run.
// filterCounter: 8
// transformCounter: 4
// [
// { name: 'SUNSTONE', price: 4 },
// { name: 'SUGILITE', price: 7 },
// { name: 'DIOPSIDE', price: 3 }
// ]
图示:
let filterCounter = 0;
let transformCounter = 0;
const filter = item => {
filterCounter++;
console.log('filter is run.');
return item.price < 10;
};
const transform = item => {
transformCounter++;
console.log('transform is run.');
return {
name: item.name.toUpperCase(),
price: item.price
};
};
const result = [];
for (let i = 0, len = gems.length; i < len; i++) {
const item = gems[i];
if (filter(item)) {
result.push(transform(item));
}
if (result.length > 2) {
break;
}
}
console.log('filterCounter:', filterCounter);
console.log('transformCounter:', transformCounter);
console.log(result);
// console:
// filter is run.
// transform is run.
// filter is run.
// filter is run.
// filter is run.
// transform is run.
// filter is run.
// transform is run.
// filterCounter: 5
// transformCounter: 3
// [
// { name: 'SUNSTONE', price: 4 },
// { name: 'SUGILITE', price: 7 },
// { name: 'DIOPSIDE', price: 3 }
// ]
图示:
let filterCounter = 0;
let transformCounter = 0;
const filter = item => {
filterCounter++;
console.log('filter is run.');
return item.price < 10;
};
const transform = (item, index, array) => {
transformCounter++;
console.log('transform is run.');
// console.log(item, index);
return {
name: item.name.toUpperCase(),
price: item.price
};
};
let result;
result = _
.chain(gems)
.filter(filter)
.map(transform)
.take(3)
.value();
console.log('filterCounter:', filterCounter);
console.log('transformCounter:', transformCounter);
console.log(result);
// console:
// filter is run.
// transform is run.
// filter is run.
// filter is run.
// filter is run.
// transform is run.
// filter is run.
// transform is run.
// filterCounter: 5
// transformCounter: 3
// [
// { name: 'SUNSTONE', price: 4 },
// { name: 'SUGILITE', price: 7 },
// { name: 'DIOPSIDE', price: 3 }
// ]
TODO: 实现一个简易的 Lazy.js
《JavaScript 面向对象编程指南》2.3.4.3
const func = function() {
console.log('func is executed!');
return 'some value form func.';
};
console.log('a1: start');
const a1 = true || func(); // func 未执行
console.log('a1: end\n');
console.log('a2: start');
const a2 = false || func(); // func 执行了
console.log('a2: end\n');
console.log('b1: start');
const b1 = true && func(); // func 执行了
console.log('b1: end\n');
console.log('b2: start');
const b2 = false && func(); // func 未执行
console.log('b2: end\n');
// console:
// a1: start
// a1: end
// a2: start
// func is executed!
// a2: end
// b1: start
// func is executed!
// b1: end
// b2: start
// b2: end
参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。 -- ECMAScript 6 入门
// 默认参数
// 参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
let counter = 0;
const func = function() {
console.log('func is executed!');
counter++;
return 10;
};
const a = function(val = func()) {
console.log('before use "val"');
console.log('val:', val);
};
console.log('第 1 次执行');
a();
console.log('counter:', counter);
console.log('第 1 次结束\n');
console.log('第 2 次执行');
a();
console.log('counter:', counter);
console.log('第 2 次结束\n');
console.log('第 3 次执行');
a(20);
console.log('counter:', counter);
console.log('第 3 次结束\n');
// console:
// 第 1 次执行
// func is executed!
// before use "val"
// val: 10
// counter: 1
// 第 1 次结束
// 第 2 次执行
// func is executed!
// before use "val"
// val: 10
// counter: 2
// 第 2 次结束
// 第 3 次执行
// before use "val"
// val: 20
// counter: 2
// 第 3 次结束
从第 1 次调用 a
方法可以看出:
func
并不会在 a
方法定义时执行func
在 a
方法体执行前调用,而不是实际参数被使用时调用从第 1、2 次调用 a
方法可以看出(观察 counter 值):
a
方法被调用时,默认参数 func
都会重新执行,即:每次都重新计算默认值表达式的值,属于 Call By Name
求值策略从第 2、3 次调用 a
方法可以看出(观察 counter 值):
a
方法被传入参数后,默认参数 func
不会执行《高性能 JavaScript》 第 8 章 - 避免重复工作
const addHandler = function(target, eventType, handler) {
document.body.addEventListener
? // DOM2 Events
target.addEventListener(eventType, handler, false)
: // IE
target.addEventListener('on' + eventType, handler);
};
const removeHandler = function(target, eventType, handler) {
document.body.removeEventListener
? // DOM2 Events
target.removeEventListener(eventType, handler, false)
: // IE
target.detachEvent('on' + eventType, handler);
};
每次调用时都会判断事件类型,然后再执行 add / remove 事件方法
const addHandler = document.body.addEventListener
? function(target, eventType, handler) {
// DOM2 Events
target.addEventListener(eventType, handler, false);
}
: function(target, eventType, handler) {
// IE
target.attachEvent('on' + eventType, handler);
};
const removeHandler = document.body.removeEventListener
? function(target, eventType, handler) {
// DOM2 Events
target.removeEventListener(eventType, handler, false);
}
: function(target, eventType, handler) {
// IE
target.detachEvent('on' + eventType, handler);
};
脚本加载时会进行条件检测,而不是加载后
function addHandler(target, eventType, handler) {
if (target.addEventListener) {
// DOM2 Events
addHandler = function(target, eventType, handler) {
target.addEventListener(eventType, handler, false);
};
} else {
// IE
addHandler = function(target, eventType, handler) {
target.attachEvent('on' + eventType, handler);
};
}
addHandler(target, eventType, handler);
}
function removeHandler(target, eventType, handler) {
if (target.addEventListener) {
// DOM2 Events
removeHandler = function(target, eventType, handler) {
target.removeEventListener(eventType, handler, false);
};
} else {
// IE
removeHandler = function(target, eventType, handler) {
target.detachEvent('on' + eventType, handler);
};
}
addHandler(target, eventType, handler);
}
调用延时加载函数时,第一次总会消耗较长的时间
Block Formatting Context(块格式化上下文)
是 W3C CSS 2.1
规范中的一个概念,在 CSS3 中被修改为 flow root
。
格式化表明了在这个环境中,元素处于此环境中应当被初始化。元素如果创建了 BFC
,则元素被 BFC
的特性约束。也就是 BFC
提供了一个环境,HTML 元素在这个环境中按照一定的渲染规则进行布局。一个环境中的元素不会影响到其他环境中的布局。
overflow
值不为 visible
的块元素float
不是 none
)position
为 absolute
或 fixed
)display
为 inline-block
)display
为 flex
或 inline-flex
元素的直接子元素)display
为 grid
或 inline-grid
元素的直接子元素)column-count
或 column-width
不为 auto
,包括 column-count
为 1)column-span
为 all
的元素始终会创建一个新的 BFC,即使该元素没有包裹在一个多列容器中(标准变更,Chrome bug)display
为 table-cell
,HTML 表格单元格默认为该值)display
为 table-caption
,HTML 表格标题默认为该值)display
为 table
、table-row
、table-row-group
、table-header-group
、table-footer-group
(分别是 HTML table、row、tbody、thead、tfoot 的默认属性)或 inline-table
)display
值为 flow-root
的元素contain
值为 layout
、content
或 strict
的元素BFC
环境内的盒子会在垂直方向,一个接一个地放置margin
决定。属于同一个 BFC
的两个相邻盒子的 margin
会发生重叠BFC
就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素BFC
的高度时,浮动元素也参与计算IE 浏览器中不支持 BFC
标准,但 IE 中有 Layout
。Layout
和 BFC
基本等价,为了处理 IE 的兼容性,在需要触发 BFC
时,我们除了需要用上面的 CSS 属性来触发 BFC
,还需要针对 IE 浏览器使用 zoom: 1;
来触发 IE 浏览器的 Layout
。
当块容器盒(block container box)不包括任何块级盒(block-level boxes)时,就会创建一个行内格式化上下文(IFC)。
注:在 IFC 的环境中,是不能存在块级元素的,如果将块级元素插入到 IFC 中,那么此 IFC 将会被破坏掉变成 BFC,而块级元素前的元素或文本和块级元素后的元素或文本将会各自自动产生一个匿名块盒其包围。
就一个类而言,应该仅有一个引起它变化的原因。在 JavaScript 中,单一职责原则更多地是被运用在对象或者方法级别上。
单一职责原则体现为: 一个对象(方法)只做一件事情。
要明确的是,并不是所有的职责都应该一一分离。一方面,如果随着需求的变化,有两个职责总是同时变化,那就不必分离他们。另一方面,职责的变化轴线仅当它们确定会发生变化时才具有意义,即使两个职责已经被耦合在一起,但它们还没有发生改变的征兆,那么也许没有必要主动分离它们,在代码需要重构的时候再进行分离也不迟。
优点: 降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他的职责。
缺点: 最明显的是会增加编写代码的复杂度。当我们按照职责把对象分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度。
最少知识原则(LKP)说的是一个软件实体应当尽可能少地与其他实体发生相互作用。
解释:
某军队中的将军需要挖掘一些散兵坑。下面是完成任务的一种方式:将军可以通知上校让他叫来少校,然后让少校找来上尉,并让上尉通知一个军士,最后军士唤来一个士兵,然后命令士兵挖掘一些散兵坑。
等价代码:
general
.getColonel(c)
.getMajor(m)
.getCaptain(c)
.getSergeant(s)
.getPrivate(p)
.digFoxhole();
让代码通过这么长的消息链才能完成一个任务,这就像让将军通过那么多繁琐的步骤才能命令别人挖掘散兵坑一样荒谬!而且,这条链中任何一个对象的改动都会影响整条链的结果。
最少知识原则在设计模式中体现得最多的地方是 中介者模式 和 外观模式。
外观模式容易跟普通的封装实现混淆。这两者都封装了一些事物,但外观模式的关键是定义一个高层接口去封装一组“子系统”。
外观模式主要是为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使子系统更加容易使用。大多数客户都可以通过请求外观接口来达到访问子系统的目的。但在一段使用了外观模式的程序中,请求外观并不是强制的。如果外观不能满足客户的个性化需求,那么客户也可以选择越过外观来直接访问子系统。
封装在很大程度上表达的是数据的隐藏。把变量的可见性限制在一个尽可能小的范围内,这个变量对其他不相关模块的影响就越小,变量被改写和发生冲突的机会也越小。这也是广义的最少知识原则的一种体现。
最少知识原则也叫迪米特法则(Law of Demeter,LoD)
软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。
现在可以引出开放-封闭原则的**:当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码。
过多的条件分支语句是造成程序违反开放-封闭原则的一个常见原因。每当需要增加一个新 的 if
语句时,都要被迫改动原函数。把 if
换成 switch-case
是没有用的,这是一种换汤不换药的做法。实际上,每当我们看到一大片的 if
或者 switch-case
语句时,第一时间就应该考虑,能否利用对象的多态性来重构它们。
环境:腾讯云服务器,64 位 CentOS-7,root 用户
略
输入完成后点击 "确定";
点击确定连接。
参考:
鼠标移到已经连接的服务器 标签(标红处)上,点击右键,选择 复制 SSH 渠道。
yum clean all
yum update
yum upgrade
# 安装 make
yum -y install gcc automake autoconf libtool make
# 安装 g++
yum -y install gcc gcc-c++
yum -y install lrzsz
可以是任何目录,我选择的是 /usr/local/src
。
cd /usr/local/src
# 查看已安装的 SSL 版本
openssl version
# 如果显示 openssl 版本低于 1.02,则需要更新(为了后续开启 http2)
# 切换到源码安装目录
cd /usr/local/src
# 下载源码包
wget https://www.openssl.org/source/openssl-1.0.2l.tar.gz
# 解压
tar -zxvf openssl-1.0.2l.tar.gz
# 切换到源码目录内
cd openssl-1.0.2l
# 添加配置
./config --prefix=/usr/local/openssl
# 安装
make && make install
# 保存老版本的 openssl
mv /usr/bin/openssl /usr/bin/openssl.OFF
mv /usr/include/openssl /usr/include/openssl.OFF
# 创建软链
ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl
ln -s /usr/local/openssl/include/openssl /usr/include/openssl
echo "/usr/local/openssl/lib">>/etc/ld.so.conf
# 检查是否安装成功
openssl version
参考:
# 切换到源码安装目录(如果更换其他目录,下面引用的路径同样需要修改)
cd /usr/local/src
# 下载源码包
wget http://nginx.org/download/nginx-1.12.0.tar.gz
wget https://www.openssl.org/source/openssl-1.0.2l.tar.gz
wget http://zlib.net/zlib-1.2.11.tar.gz
wget https://ftp.pcre.org/pub/pcre/pcre-8.35.tar.gz
# 解压
tar -zxvf nginx-1.12.0.tar.gz
tar -zxvf openssl-1.0.2l.tar.gz
tar -zxvf pcre-8.35.tar.gz
tar -zxvf zlib-1.2.11.tar.gz
# 切换目录
cd nginx-1.12.0
# 创建一个 nginx 目录用来存放运行的临时文件夹
mkdir -p /var/cache/nginx
# 添加配置
# --prefix=/usr/local/nginx --> 指定安装路径
# --conf-path=/etc/nginx/nginx.conf --> 指定配置文件的位置
# --with-http_ssl_module --> 启用 SSL 模块
# --with-http_v2_module --> 启用 http2 模块
# 转义符:"\" 可以实现在多行输入一句命令
./configure --prefix=/usr/local/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=nobody \
--group=nobody \
--with-openssl=/usr/local/src/openssl-1.0.2l \
--with-pcre=/usr/local/src/pcre-8.35 \
--with-zlib=/usr/local/src/zlib-1.2.11 \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_addition_module \
--with-http_sub_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_mp4_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_random_index_module \
--with-http_secure_link_module \
--with-http_stub_status_module \
--with-http_auth_request_module \
--with-mail \
--with-mail_ssl_module \
--with-file-aio \
--with-ipv6 \
--with-http_v2_module \
--with-threads \
--with-stream \
--with-stream_ssl_module \
--with-threads \
--with-debug
# 安装
make && make install
# 重新启动 Xshell,或者重启 Shell 窗口 !!!
# 检查是否安装成功
nginx -V
# 配置 systemctl 服务
vim /usr/lib/systemd/system/nginx.service
# 复制代码
# 按 i 输入以下内容
[Unit]
Description=nginx - high performance web server
Documentation=http://nginx.org/en/docs/
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -c /etc/nginx/nginx.conf
ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
# 按 Esc 推出 vim 编辑模式,输入 :wq 然后点击 Enter,保存并退出
# 重新启动 Xshell,或者重启 Shell 窗口 !!!
# 重启 nginx
systemctl start nginx.service
# 开机自启 nginx
systemctl enable nginx.service
参考:
# 安装 Git
yum install git
# 查看 Git 版本
git --version
# 安装 Node.js
yum install nodejs
# 查看 Node 版本
node -v
# 查看 NPM 版本
npm -v
# 全局安装 PM2
npm i -g pm2
# 我将博客放在了 /home 挂载点内
# 将 Node 静态服务模板克隆到 /home/blog 目录下
git clone https://github.com/no-nothing/server.git /home/blog
# 切换到 blog 目录内
cd /home/blog
# 安装依赖
npm i
# 克隆自己的 Blog 页面到 /home/blog/public 目录下
git clone https://github.com/no-nothing/no-nothing.github.io.git /home/blog/public
或者上传本地的 Blog 页面:
先将本地的网页压缩成 zip 包(Centos 默认支持解压 zip 文件)
# 切换到 blog 目录内
cd /home/blog
# 上传
rz -y
# 解压缩包到 /home/blog/public 目录下
unzip 压缩包名.zip -d /home/blog/public
# 切换到 blog 目录内
cd /home/blog
# 以 blog 为 PM2 进程名称
pm2 start app.js --name blog
# 设置 pm2 启动的服务开机自启
pm2 save
pm2 startup
现在可以通过 IP 地址:3000 访问刚才部署的 Blog 了。
需要先完成域名解析
# 我是通过 yum install nginx 安装的 nginx,所以 nginx 根目录在 /etc/nginx
cd /etc/nginx
# 创建 ssl 文件夹存放证书
mkdir ssl
cd ssl
# 通过 lrzsz (Xshell环境下用于文件上传和下载)上传证书
rz
# 选择 *.crt 和 *.key 两个文件
cd /etc/nginx
sz nginx.conf
...
# 设定实际的服务器列表
upstream blog {
server 127.0.0.1:3000;
}
# http 请求重定向到 https
server {
listen 80;
server_name singple.com www.singple.com; #换成你的域名
return 301 https://www.singple.com$request_uri; #这里的 www.singple.com 换成你的域名或者 $server_name
}
# https 配置
server {
listen 443 ssl http2 default_server;
server_name www.singple.com; #换成你的域名
ssl on;
ssl_certificate ssl/1_www.singple.com_bundle.crt; #证书文件
ssl_certificate_key ssl/2_www.singple.com.key; #秘钥文件
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://blog;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
# 其他的 URL 请求返回 501 (可选配置)
server {
listen 80 default;
return 501;
}
...
补充:
# 修改 nginx.conf 后,上传
rz -y
# 重启 nginx
nginx -s reload
# 开机自启 nginx
systemctl enable nginx.service
# 启用https服务
firewall-cmd --permanent --zone=public --add-service=https
# 开启 443 端口(https)
firewall-cmd --zone=public --add-port=443/tcp --permanent
# 重新加载防火墙,使新添的规则生效
firewall-cmd --reload
参考:
# 查看版本
firewall-cmd --version
# 若提示 command not found,安装 firewalld
yum install firewalld firewall-config
# 启动 firewalld
systemctl start firewalld.service
# 获取 firewalld 状态
firewall-cmd --state
# 开机自启 firewalld
systemctl enable firewalld.service
# firewalld 默认会关闭所有端口访问
# 开启 80 端口(此时只有 80 端口可以访问)
# --permanent 永久
firewall-cmd --zone=public --add-port=80/tcp --permanent
# 启用https服务
firewall-cmd --zone=public --add-service=https --permanent
# 开启 443 端口(https)
firewall-cmd --zone=public --add-port=443/tcp --permanent
# 重新加载防火墙,生效新添的规则
firewall-cmd --reload
# 查看 public 下开启的端口列表
firewall-cmd --zone=public --list-ports
# 立即激活单元
systemctl start <单元>
# 立即停止单元:
systemctl stop <单元>
# 重启单元:
systemctl restart <单元>
# 重新加载配置:
systemctl reload <单元>
# 输出单元运行状态:
systemctl status <单元>
# 检查单元是否配置为自动启动:
systemctl is-enabled <单元>
# 开机自动激活单元:
systemctl enable <单元>
# 取消开机自动激活单元:
systemctl disable <单元>
参考:
# nginx 启动
# 其中参数 -c 指定 nginx 启动时加载的配置文件,当然也可以不指定配置文件,省略 -c,也可以启动,表示使用默认的配置文件
nginx -c /etc/nginx/nginx.conf
# OR
nginx
# nginx 停止
# 例如在我们的编辑环境中已经安装好了 nginx,并且已启动,在命令提示符下直接输入 nginx -s stop 就可以停止了
nginx -s stop
# OR
nginx -s quit
# OR
pkill -9 nginx
# nginx 重载配置
nginx -s reload
# 检查配置文件是否正确
nginx -t
参考:
# 查看版本
firewall-cmd --version
# 获取 firewalld 状态
firewall-cmd --state
# 查看开启的端口列表
firewall-cmd --list-ports
# 查看 public 下开启的端口列表
firewall-cmd --zone=public --list-ports
# 启动 firewalld
systemctl start firewalld.service
# 重启 firewalld
systemctl restart firewalld.service
# 开机自启 firewalld
systemctl enable firewalld.service
# 关闭开机自启 firewalld
systemctl disable firewalld.service
# firewalld 默认会关闭所有端口访问
# 开启 80 端口(此时只有 80 端口可以访问)
# --permanent 永久
firewall-cmd --zone=public --add-port=80/tcp --permanent
# 启用https服务
firewall-cmd --permanent --zone=public --add-service=https
# 开启 443 端口(https)
firewall-cmd --zone=public --add-port=443/tcp --permanent
# 重新加载防火墙,生效新添的规则
firewall-cmd --reload
参考:
# 1. 启动
pm2 start app.js
# 以 process-name 为 PM2 进程名称
pm2 start app.js --name process-name
# 根据 CPU 核数启动进程个数
pm2 start app.js -i 0
# 实时监控 app.js 的方式启动,当 app.js 文件有变动时,PM2 会自动reload
pm2 start app.js --watch
# 2. 查看进程
pm2 list
# 查看进程详细信息,0 为 PM2 进程 id
pm2 show 0
# 或者
pm2 info 0
# 3. 监控
pm2 monit
# 4. 停止
# 停止 PM2 列表中所有的进程
pm2 stop all
# 停止 PM2 列表中进程为 0 的进程
pm2 stop 0
# 5. 重载
# 重载 PM2 列表中所有的进程
pm2 reload all
# 重载 PM2 列表中进程为 0 的进程
pm2 reload 0
# 6. 重启
# 重启 PM2 列表中所有的进程
pm2 restart all
# 重启 PM2 列表中进程为 0 的进程
pm2 restart 0
# 7. 删除 PM2 进程
# 删除 PM2 列表中所有的进程
pm2 delete all
# 删除 PM2 列表中进程为 0 的进程
pm2 delete 0
# 8. 日志操作
# 显示所有进程日志
pm2 logs
# 清除所有日志
pm2 flush
# 重载所有日志
pm2 reloadLogs
# 9. 升级 PM2
# 安装最新的 PM2 版本
npm install pm2@lastest -g
# 升级 PM2
pm2 updatePM2
# 10. 更多命令参数请查看帮助
pm2 --help
参考:
.ellipsis {
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
overflow: hidden;
}
text-overflow
只对 "block container elements" 有效 (block
, inline-block
); flex
不是 "block container elements"
this
是和 执行上下文环境 息息相关的一个特殊对象。因此,它也可以称为 上下文对象[context object] (激活执行上下文的上下文)。
this
是执行上下文环境的一个属性,而不是某个变量对象的属性。
this
的指向。除去不常用的 with
和 eval
的情况,具体到实际应用中,this
的指向大致可以分为以下 4 种:
call
,apply
,bind
调用当函数作为对象的方法被调用时,this
指向该对象
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的 this
总是指向全局对象。在浏览器的 JavaScript 里,这个全局对象是 window
对象。
在 ECMAScript 5 的 strict
模式下,这种情况下的 this
已经被规定为不会指向全局对象,而是 undefined
。
当用
new
运算符调用函数时,该函数 总会返回一个对象,通常情况下,构造器里的this
就指向返回的这个对象。
但用 new
调用构造器时,还要注意一个问题,如果构造器显式地返回了一个 object
类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的 this
。
var MyClass = function() {
this.name = 'sven';
// 显式地返回一个对象
return {
name: 'anne',
};
};
var obj = new MyClass();
alert(obj.name); // 输出: anne
如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成上述问题:
var MyClass = function() {
this.name = 'sven';
return 'anne'; // 返回 string 类型
};
var obj = new MyClass();
alert(obj.name); // 输出: sven
call
,apply
,bind
调用当使用
call
或者apply
的时候,如果我们传入的第一个参数为null
,函数体内的this
会指向默认的宿主对象,在浏览器中则是window
;但如果是在严格模式下,函数体内的this
还是为null
。
有时候我们使用 call
或者 apply
的目的不在于指定 this
指向,而是另有用途,比如借用其他对象的方法。那么我们可以传入 null
来代替某个具体的对象:
Math.max.apply( null, [ 1, 2, 5, 3, 4 ] ) // 输出:5
实现一个 bind
:
简易版
Function.prototype.bind = function(context) {
var self = this; // 保存原函数
// 返回一个新的函数
return function() {
return self.apply(context, arguments); // 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this
};
};
完整版
Function.prototype.bind = function() {
var self = this, // 保存原函数
context = [].shift.call(arguments),
args = [].slice.call(arguments);
// 返回一个新的函数
// 需要绑定的 this 上下文
// 剩余的参数转成数组
return function() {
return self.apply(context, [].concat.call(args, [].slice.call(arguments))); // 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this,并且组合两次分别传入的参数,作为新函数的参数
};
};
var obj = {
name: 'sven',
};
var func = function(a, b, c, d) {
alert(this.name); // 输出: sven
alert([a, b, c, d]); // 输出: [ 1, 2, 3, 4 ]
}.bind(obj, 1, 2);
func(3, 4);
npm | yarn | 功能 |
---|---|---|
npm init |
yarn init |
初始化项目 |
npm i |
yarn install 或 yarn |
安装依赖包 |
rm -rf node_modules && npm i |
yarn upgrade |
重新安装依赖包 |
npm i [package] -S |
yarn add [package] |
安装生产依赖包 |
npm i [package] -D |
yarn add [package] -D |
安装开发依赖包 |
npm i [package] -g |
yarn global add [package] |
全局安装包 |
npm update -g |
yarn global upgrade |
全局更新包 |
npm uninstall [package] -S |
yarn remove [package] |
移除生产依赖包 |
npm uninstall [package] -D |
yarn remove [package] |
移除开发依赖包 |
npm cache clean |
yarn cache clean |
清缓存 |
npm list -g --depth=0 |
yarn global list |
查看全局安装的包 |
npm login/logout/publish |
yarn login/logout/publish |
登录/登出/发布 |
npm config list |
yarn config list |
查看全局配置 |
npm config get registry |
yarn config get registry |
查看源(全局) |
npm config set registry http://r.cnpmjs.org/ |
yarn config set registry https://registry.yarnpkg.com |
设置源(全局) |
npm help |
yarn help |
帮助 |
# 更换淘宝源
npm config set registry https://registry.npm.taobao.org
yarn config set registry https://registry.npm.taobao.org
# 或者 npm 下使用 nrm
npm i nrm -g
# 查看源
nrm ls
# 测试源的延迟
nrm test
# 使用速度最快的源(这里演示使用 taobao)
nrm use taobao
# 或者 yarn 下使用 yrm
yarn global add yrm
# 查看源
yrm ls
# 测试源的延迟
yrm test
# 使用速度最快的源(这里演示使用 taobao)
yrm use taobao
1. npm
项目根目录下增加 .npmrc 配置文件
puppeteer_download_host=https://npm.taobao.org/mirrors/
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/
chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver/
或者,修改 npm 全局配置
# 设置 puppeteer taobao源
npm config set puppeteer_download_host https://npm.taobao.org/mirrors/
# 设置 node-sass taobao源
npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
# 设置 phantomjs taobao源
npm config set phantomjs_cdnurl https://npm.taobao.org/mirrors/phantomjs/
# 查看 config 信息
npm config list
2. yarn
yarn 没有类似于 .npmrc 的配置文件,只能修改 yarn 全局配置
# 设置 puppeteer taobao源
yarn config set puppeteer_download_host https://npm.taobao.org/mirrors/
# 设置 node-sass taobao源
yarn config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
# 设置 phantomjs taobao源
yarn config set phantomjs_cdnurl https://npm.taobao.org/mirrors/phantomjs/
# 查看 config 信息
yarn config list
vim ~/.npmrc # 修改 node 版本
cd path/to/my-project
npm install path/to/my-utils
npm link
1. 模块目录,把模块 link 到全局
cd path/to/my-utils
npm link
2. 项目目录,把模块 link 到当前项目
cd path/to/my-project
npm link my-utils
3. 移除 link
npm unlink my-utils
CommonJS
导出的是变量的一份拷贝; ES6 Module
导出的是变量的绑定(引用)CommonJS
是单个值导出; ES6 Module
可以导出多个CommonJS
是动态语法可以写在判断里; ES6 Module
静态语法只能写在顶层CommonJS
的 this
是当前模块; ES6 Module
的 this
是 undefined
命令行前的 [root@localhost ~]# 是什么
root: 当前登录用户
localhost: 主机名
~: 当前所在目录(家目录)
#: 超级用户的提示符(普通用户的提示符是$)
-rw-r--r-- (其中, r: 读, w: 写, x: 执行)
共10位
第1位: 文件类型(共7种, 常用3种 -: 文件, d: 目录, l: 快捷方式)
第2~4位: 所有者(u)权限
第5~7位: 所属组(g)权限
第8~10位: 其他人(o)权限
ctrl+c
强制终止当前命令;ctrl+l
清屏;ctrl+a
光标移动到命令行首;ctrl+e
光标移动到命令行尾;ctrl+u
从光标所在位置删除到行首;ctrl+z
把命令放入后台;ctrl+r
在历史命令中 搜索;Tab
自动补全目录或者命令history <选项> <历史命令保存文件>
<选项>:
cat .bash_history
可以查看这个文件,默认关机时会执行 history -w
命令;
历史命令默认保存 1000 条,可以在环境变量配置文件 /etc/profile 中进行修改,修改 HISTSIZE
的值;
!n
可以重复执行 history 里第 n 条命令;
!!
重复执行上一条命令;
!字符串
重复执行最后一条已该字符串开头的命令;
date > date.log
将时间信息输入到 date.log 文件中,而不是显示输出;
date >> date.log
将时间信息追加到 date.log 文件中,而 > 是覆盖;
date &>> date.log
同时保存正确和错误信息,如果有错误信息;
date >> date.log 2>> error.log
保存正确信息到 date.log,保存错误信息到 error.log;
wc <选项> <文件名>
<选项>:
例如:wc < date.log
或者直接 wc date.log
统计 date.log 文件里的字节数、单词数、行数;
多命令顺序执行:
命令1 ; 命令2 多个命令顺序执行,命令之间没有任何逻辑联系
命令1 && 命令2 逻辑与,只有当命令1正确执行,则执行命令2
命令1 || 命令2 逻辑或,只有当命令1执行不正确,才执行命令2
管道符:(命令 1 和 2 有数据传递)
# 命令1的正确输出作为命令2的操作对象
命令1 | 命令2
? :匹配一个任意字符;
* :匹配0个或任意多个任意字符,也就是可以匹配任何内容;
[ ] :匹配中括号内的任意一个字符,例如:[abc] 代表一定匹配一个字符,或者是a、或者是b、或者是c;
[ - ] :匹配中括号内的任意一个字符,- 代表一个范围,例如:[a-z] 代表匹配一个小写字母;
[ ^ ] :逻辑非,表示匹配不是中括号内的一个字符,例如:[^0-9] 代表匹配一个不是数字的字符;
a. 首先新建并打开一个文件
vim hello.sh
# 或者
vi hello.sh
b. 然后编辑内容
#!/bin/bash 脚本开头必须内容
#The first program 注释
echo -e "hello"
c. 脚本执行
# 赋予脚本执行权限,直接运行
chmod 755 hello.sh
# 然后相对路径调用
./hello.sh
# 或者绝对路径调用
/root/hello.sh
# 或者通过 Bash 调用执行脚本
bash hello.sh
# 进入当前用户的家目录,命令英文原意:change directory
cd ~
# 或者
cd
# 进入上次目录
cd -
# 进入上一级目录
cd ..
# 进入当前目录(比 cd .. 少一个 .)
cd .
# 进入根目录
cd /
# 查询所在目录位置,命令英文原意:print working directory
pwd
补充:Linux 目录说明
bin
和 sbin
、usr
目录下的 bin
和 sbin
,这四个目录都是用来保存系统命令的;bin
下的命令所用用户都可以执行,而 sbin
下的命令只有超级用户才可以执行。dev
目录内是一些硬件文件,没事别动。etc
目录下是系统的默认配置文件。lib
目录下是函数库,用的时候可以调用,不用就没必要调用,这样可以避免 Linux 变得臃肿。proc
和 sys
目录不能直接操作,这两个目录保存的是内存的挂载点,也就是内存的盘符。tmp
是临时目录。var
保存系统的可变文档目录。ls <选项> <文件或目录>
<选项>:
-a
显示所有文件,包括隐藏文件-l
显示详细信息(ll
= ls -l
)-d
查看目录属性-h
人性化显示文件大小-i
显示 inode,查看 id 号# 获取指定命令的帮助,manual(手册)缩写
man <命令>
# 查询shell命令,如 cd
help <命令>
# 详细信息
w
# 简洁信息
who
# 查询当前登录和过去登陆的用户信息
last
# 查询所有用户的最后一次登录时间
lastlog
mkdir <目录名>
# 递归创建,在没有 xiaomi 目录的前提下创建 xiaomi/redmi 必须加 -p
mkdir -p xiaomi/redmi
# 格式
cp <选项> <原文件或目录> <目标目录>
# 连带文件属性复制
cp -p <原文件或目录> <目标目录>
# 若原文件是链接文件,则复制链接属性
cp -d <原文件或目录> <目标目录>
# 复制目录
cp -r <原文件或目录> <目标目录>
# 相当于 -pdr
cp -a <原文件或目录> <目标目录>
# 把 folder1 里面的文件和文件夹等复制到 folder2 目录下
cp -rf folder1/* folder2
# 把 folder1 里面的文件和文件夹等移动到 folder2 目录下
mv folder1/* folder2
# 把 folder1 重命名为 folder2
mv folder1 folder2
# 删除文件
rm <文件名>
# 删除目录
rm -rf <目录名>
参考:
# 格式
ln -s <原文件> <目标文件>
# 创建软链
ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx
# 删除软链
rm 软链
# 在后台数据库中按文件名搜索,搜索速度快
# 注意:locate命令搜索的是后台数据库,新建的文件需要 updatedb 后才能搜索到(因为,mlocate 数据库并不是实时更新)
locate <文件名>
# 耗费资源,比较慢,但是功能非常强大
# 注意:避免大范围的搜索
find <搜索范围> <搜索条件>
搜索条件:
-name
: 按文件名索搜,只显示文件名一模一样的文件-iname
: 不区分大小写通配符:(放在搜索格式结尾)
*
: 匹配任意内容?
: 匹配任意一个字符[]
: 匹配任意一个中括号内的字符-user <用户名>
: 按照所有者搜索-nouser
: 查找没有所有者的文件-mtime <num>
: 修改文件内容时间-atime <num>
: 文件访问时间-ctime <num>
: 改变文件属性时间
-10
: 10 天内修改的文件10
: 10 天当天修改的文件+10
: 10 天前修改的文件;-size <num>
: 按文件大小查找
-10k
: 小于 10KB 的文件 / -10M
:小于 10MB 的文件(注意大小写)10k
: 等于 10KB 的文件+10k
: 大于 10KB 的文件;-inum <节点数>
: 按 i 节点查找;(ls -i
查看文件的 i 节点)例子:
find /etc -size +20k -a -size -50k -exec ls -lh {} \
解释:
/etc
目录下大于 20KB 小于 50KB 的文件,-a
: and 逻辑与,两个条件都满足,-o
: or 逻辑或,两个条件满足一个即可,-exec <命令> {} \
: 对搜索结果执行操作;# 搜索命令所在路径以及帮助文档所在位置
whereis <选项> <命令名>
选项:
-b
: 只查找可执行文件;-m
: 只查找帮助文件;# 搜索命令的所在位置以及命令的别名(如果有的话,pwd 便没有别名)
# 注意:whereis 和 which 无法搜索到 cd 命令,因为 cd 命令是 shell 自带的命令
which <命令名>
# 在文件当中匹配符合条件的字符串
grep <选项> <字符串> <文件名>
选项:
-i
: 忽略大小写;-v
: 排除指定字符串,也就是取反;# 压缩文件,原文件保留
zip <压缩文件名> <原文件>
# 压缩目录,原目录保留
zip -r <压缩文件名> <原文件夹>
# 解压缩 .zip 文件,原文件保留,解压后默认和压缩文件保存在同一目录
unzip <原文件>
# 压缩为 .gz 格式的文件,原文件会消失
gzip <原文件>
# 压缩为 .gz 格式的文件,原文件保留
gzip -c <原文件> > <压缩文件>
# 例如
gzip -c meizu > meizu.gz
# 压缩目录下所有的子文件,但是不能压缩目录,原文件会消失
gzip -r <目录>
# 解压缩文件,原文件会消失
gzip -d <压缩文件名>
# 解压缩文件,原文件会消失
gunzip <压缩文件名>
# 压缩为 .bz2 格式的文件,原文件会消失
bzip2 <原文件>
# 压缩为 .bz2 格式的文件,原文件保留
bzip2 -k <原文件>
# 解压缩文件,原文件会消失;(加 -k 可以保存)
bzip2 -d <压缩文件名>
# 解压缩文件,原文件会消失;(加 -k 可以保存)
bunzip2 <压缩文件名>
# 打包
tar -cvf <打包文件名> <原文件>
# 例如
tar -cfg meizu.tar meizu
# 解打包
tar -xvf <打包文件名>
# 例如
tar -xvf meizu.tar
选项:
-c
: 打包-v
: 显示过程-f
: 指定打包后的文件名-x
: 解打包其实 .tar.gz
格式是先打包为 .tar
格式,再压缩为 .gz
格式。
# 同时将两个文件打包压缩到一个压缩文件下
tar -zcvf <压缩包名.tar.gz> <原文件> (<另一个原文件>)
# 解压缩 .tar.gz 格式的文件
tar -zxvf <压缩包名.tar.gz>
选项:
-c
: 压缩为 .tar.gz
格式-x
: 解压缩 .tar.gz
格式的文件其实 .tar.bz2 格式是先打包为 .tar 格式,再压缩为 .bz2 格式。
# 压缩
tar -jcvf <压缩包名.tar.bz2> <原文件>
# 解压缩
tar -jxvf <压缩包名.tar.bz2>
# 解压缩到指定目录
tar -jxvf <压缩包名.tar.bz2> -C <其他目录>
# 例如
tar -jxvf meizu.tar.bz2 -C /tmp/;
# 只查看里面的内容,而不解压
tar -jtvf <压缩包名.tar.bz2>
选项:
-j
: 压缩为 .tar.bz2
格式-x
: 解压缩 .tar.bz2
格式的文件-t
: 查看里面的内容# 查询系统中已经挂载的设备
mount
# 依据配置我文件 /etc/fstab 的内容自动挂载
mount -a
mount <-t 文件系统> <-o 特殊选项> <设备文件名> <挂载点>
<选项>:
-t
文件系统:加入文件系统类型来指定挂在的类型,可以 ext3、ext4、iso9660 等文件系统;-o
特殊选项:可以制定挂载的额外选项;# 建立挂载点
mkdir /mnt/cdrom
# 挂载光盘(推荐使用 sr0)
mount -t iso9660 /dev/sr0 /mnt/cdrom
# 或者
# 挂载光盘(cdrom 和 sr0 是软链接关系)
mount -t iso9660 /dev/cdrom /mnt/cdrom
# 或者
# 挂载光盘(简写)
mount /dev/sr0 /mnt/cdrom
umount <设备文件名或挂载点>
# 通过文件名卸载光盘
umount /dev/sr0
# 或者
# 通过挂载点卸载光盘
umount /mnt/cdrom
# 查看U盘设备文件名
fdisk -l
# 建立挂载点
mkdir /mnt/usb
# 挂载U盘
mount -t vfat /dev/ <U盘设备文件名> /mnt/usb
# 安全,关机时会正确保存正在运行的任务
shutdown -h now
# 或者
halt
# 或者
poweroff
# 或者
init 0
shutdown -r now
# 或者
reboot
# 或者
init 6
logout
# 立即激活单元
systemctl start <单元>
# 立即停止单元:
systemctl stop <单元>
# 重启单元:
systemctl restart <单元>
# 重新加载配置:
systemctl reload <单元>
# 输出单元运行状态:
systemctl status <单元>
# 检查单元是否配置为自动启动:
systemctl is-enabled <单元>
# 开机自动激活单元:
systemctl enable <单元>
# 取消开机自动激活单元:
systemctl disable <单元>
参考:
# nginx 启动
# 其中参数 -c 指定 nginx 启动时加载的配置文件,当然也可以不指定配置文件,省略 -c,也可以启动,表示使用默认的配置文件
nginx -c /etc/nginx/nginx.conf
# OR
nginx
# nginx 停止
# 例如在我们的编辑环境中已经安装好了 nginx,并且已启动,在命令提示符下直接输入 nginx -s stop 就可以停止了
nginx -s stop
# OR
nginx -s quit
# OR
pkill -9 nginx
# nginx 重载配置
nginx -s reload
# 检查配置文件是否正确
nginx -t
参考:
# 查看版本
firewall-cmd --version
# 获取 firewalld 状态
firewall-cmd --state
# 查看开启的端口列表
firewall-cmd --list-ports
# 查看 public 下开启的端口列表
firewall-cmd --zone=public --list-ports
# 启动 firewalld
systemctl start firewalld.service
# 重启 firewalld
systemctl restart firewalld.service
# 开机自启 firewalld
systemctl enable firewalld.service
# 关闭开机自启 firewalld
systemctl disable firewalld.service
# firewalld 默认会关闭所有端口访问
# 开启 80 端口(此时只有 80 端口可以访问)
# --permanent 永久
firewall-cmd --zone=public --add-port=80/tcp --permanent
# 启用https服务
firewall-cmd --permanent --zone=public --add-service=https
# 开启 443 端口(https)
firewall-cmd --zone=public --add-port=443/tcp --permanent
# 重新加载防火墙,生效新添的规则
firewall-cmd --reload
使用 Sourcetree 贮藏工作现场后,误删了这个 stash
Google 一通,找到:
git fsck --unreachable
查找所有的 unreachable 记录
git show <sha>
其中 sha 是记录的 key(如下面的:bb3cc162df1270fcabac0dec53effc8166b9563b)
$ git stash drop
Dropped refs/stash@{0} (bb3cc162df1270fcabac0dec53effc8166b9563b)
使用 Sourcetree 删除时,我们是不知道 sha 的,此时回到第一步 git fsck --unreachable
查找所有的 unreachable 记录,例如:
$ git fsck --unreachable
Checking object directories: 100% (256/256), done.
Checking objects: 100% (3730/3730), done.
unreachable tree 3f80ada1c908812f125a5869ca7f77429e42a8c8
unreachable tree 7a409b8150ff5533ab4d22ebfbec520d4ce0bf7d
unreachable blob 8600c5b5609fbb541a2aa492d4eb4ed72c1e43a0
unreachable blob ee006fdb6554149a9f6aa6c3ef3d21853292b44f
unreachable blob b0c14ad2a35149815261ade2f89f3cf8e9fa096c
unreachable blob 57c221b28812aec98f2c44ee226ae75325ef28f3
unreachable blob 2783aa80d2dcdb51ac8cfe2f0cdbfd813ac6bc15
unreachable blob 65439852e70c0649d605a8452d79b4e16d40dada
unreachable blob 4b0472cf3317cbe8a476abd35ead1db1f791ebb7
unreachable tree c8c4ae2a9580bf788b39bf9ad770cc50cdeec734
unreachable tree 178594b9b22143860b6894377bc72b5c0ecbf6ea
unreachable tree 2a45ce0eb08dac0efe095b65eb9e98e3b1e26625
unreachable commit 49863934d99402979b2f43abd77b05e5a19780f2
对所有 unreachable commit 开头的记录,依次执行 git show <sha>
,查看是否为误删的提交,例如:
$ git show bb3cc162df1270fcabac0dec53effc8166b9563b
commit bb3cc162df1270fcabac0dec53effc8166b9563b
Merge: 2800dc1 d7625d4
Author: *** <***@***.com>
Date: Thu Jul 5 11:43:10 2018 +0800
WIP on dev: 2800dc1 style(pc): 样式修改
diff --cc src/common/filters/index.js
index ac9ea48,ac9ea48..1f72657
--- a/src/common/filters/index.js
+++ b/src/common/filters/index.js
@@@ -1,5 -1,5 +1,3 @@@
import currency from './currency'
--export default {
-- currency
--}
++export default { currency }
git stash apply <sha>
找到误删的 stash 后,使用 git stash apply <sha>
即可恢复
next
go version
Go 默认安装在 C:\Go\
路径下
C:\Go\src\
目录下创建 golang.org
文件夹C:\Go\src\golang.org
目录下创建 x
文件夹cd
至 C:\Go\src\golang.org\x
目录git clone https://github.com/golang/tools.git tools
git clone https://github.com/golang/lint.git lint
ms-vscode.Go
插件, 完成后重启 VSCode返回其所包含的脚本中正在被执行的 <script>
元素
值得注意的是,如果当前正在执行的代码是处在某个回调函数或者事件处理函数中的,那么 currentScript
属性不会指向包含那个函数的 <script>
元素,而是会返回 null
。
如果你希望 scoped
样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>>
操作符:
<style scoped>
.a >>> .b { /* ... */ }
</style>
上述代码将会编译成:
.a[data-v-f3f3eg9] .b {
/* ... */
}
像 Sass、Less 之类的预处理器无法正确解析 >>>
。这种情况下可以使用 /deep/
操作符取而代之 ———— 它是 >>>
的别名,同样可以正常工作。
ARP(Address Resolution Protocol)是一种用以解析地址的协议,根据通信方的 IP 地址就可以反查出对应的 MAC 地址。
HTTP 持久连接(HTTP persistent connection,也称作 HTTP keep-alive 或 HTTP connection reuse)是使用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,而不是为每一个新的请求/应答打开新的连接的方法。
多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。
在 HTTP/1.1 协议中 「浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」。
多个静态资源 CDN 域名可以变相解决浏览器针对同一域名的请求限制阻塞问题。
HTTP/2.0 对消息头采用 HPACK(专为 http2 头部设计的压缩格式)进行压缩传输,能够节省消息头占用的网络的流量。而 HTTP/1.x 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。
服务端可以在发送页面 HTML 时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。服务端可以主动推送,客户端也有权利选择接收与否。如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送 RST_STREAM 帧来拒收。主动推送也遵守同源策略,服务器不会随便推送第三方资源给客户端。
简而言之,HTTPS 就是在 HTTP 下加入了 SSL 层,从而保护了交换数据隐私和完整性,提供对网站服务器身份认证的功能,简单来说它就是安全版的 HTTP。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.