merge to master
6
.github/workflows/hexo_deploy.yml
vendored
@ -5,15 +5,9 @@ name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
<<<<<<< HEAD
|
||||
branches: [ backup ]
|
||||
pull_request:
|
||||
branches: [ backup ]
|
||||
=======
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
>>>>>>> master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
9
.gitignore
vendored
@ -1,16 +1,7 @@
|
||||
<<<<<<< HEAD
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
db.json
|
||||
*.log
|
||||
node_modules/
|
||||
public/
|
||||
=======
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
db.json
|
||||
*.log
|
||||
node_modules/
|
||||
public/
|
||||
>>>>>>> master
|
||||
.deploy*/
|
22
CHANGELOG.md
Normal file
@ -0,0 +1,22 @@
|
||||
## [0.0.2-0](https://e.coding.net/Defectink/blog/blog/compare/de59640a4b9a7c779340ceb28000dd0e80f87ac1...v0.0.2-0) (2020-12-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复一篇文章 Front-matter 出错 ([4506a88](https://e.coding.net/Defectink/blog/blog/commits/4506a882ee74a77424bac7980d4275948d14ef8a))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **更新文章:** 更新[构造函数与绑定this] ([0629a61](https://e.coding.net/Defectink/blog/blog/commits/0629a6144701050dfba33a462a549f1e78de6143))
|
||||
* **更新文章:** ajax入门 ([de59640](https://e.coding.net/Defectink/blog/blog/commits/de59640a4b9a7c779340ceb28000dd0e80f87ac1))
|
||||
* **更新文章:** javaScript 的类 ([784eda3](https://e.coding.net/Defectink/blog/blog/commits/784eda3a9f2eb8a221c11cba0ba27dc22f4c71de))
|
||||
* **添加changelog:** 使用 commit 规范 ([d0b704a](https://e.coding.net/Defectink/blog/blog/commits/d0b704ab8c0559c739283d4885c7bc926727d3e0))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **添加changelog:** 修改了npm源
|
||||
|
||||
|
||||
|
12
Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
FROM node:15.0.1-alpine as builder
|
||||
WORKDIR /root
|
||||
COPY ./ ./
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||
&& apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add --no-cache yarn \
|
||||
&& npm install \
|
||||
&& npm run dev
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=builder /root/public/ /usr/share/nginx/html
|
33
Gulpfile.js
Normal file
@ -0,0 +1,33 @@
|
||||
const { series, parallel } = require('gulp');
|
||||
const { src, dest } = require('gulp');
|
||||
const cleanCSS = require('gulp-clean-css');
|
||||
const htmlmin = require('gulp-htmlmin');
|
||||
const uglify = require('gulp-uglify-es').default;
|
||||
const htmlclean = require('gulp-htmlclean');
|
||||
|
||||
function html() {
|
||||
return src('./public/**/*.html')
|
||||
.pipe(htmlclean())
|
||||
.pipe(htmlmin({
|
||||
collapseWhitespace: true,
|
||||
removeComments: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true
|
||||
}))
|
||||
.pipe(dest('./public'))
|
||||
};
|
||||
|
||||
function css() {
|
||||
return src('./public/**/*.css')
|
||||
.pipe(cleanCSS())
|
||||
.pipe(dest('./public'))
|
||||
};
|
||||
|
||||
function js() {
|
||||
return src('public/**/*.js')
|
||||
.pipe(uglify())
|
||||
.pipe(dest('./public'))
|
||||
}
|
||||
|
||||
exports.default = parallel(html, css, js);
|
964
_config.fluid.yml
Normal file
@ -0,0 +1,964 @@
|
||||
#---------------------------
|
||||
# Hexo Theme Fluid
|
||||
# Author: Fluid-dev
|
||||
# Github: https://github.com/fluid-dev/hexo-theme-fluid
|
||||
#
|
||||
# 配置指南: https://hexo.fluid-dev.com/docs/guide/
|
||||
# 你可以从指南中获得更详细的说明
|
||||
#
|
||||
# Guide: https://hexo.fluid-dev.com/docs/en/guide/
|
||||
# You can get more detailed help from the guide
|
||||
#---------------------------
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 全局
|
||||
# Global
|
||||
#---------------------------
|
||||
|
||||
# 用于浏览器标签的图标
|
||||
# Icon for browser tab
|
||||
favicon: /images/img/favicon.webp
|
||||
|
||||
# 用于苹果设备的图标
|
||||
# Icon for Apple touch
|
||||
apple_touch_icon: /images/img/apple-touch-icon.webp
|
||||
|
||||
# 浏览器标签页中的标题分隔符,效果: 文章名 - 站点名
|
||||
# Title separator in browser tab, eg: article - site
|
||||
title_join_string: " - "
|
||||
|
||||
# 强制所有链接升级为 HTTPS(适用于图片等资源出现 HTTP 混入报错)
|
||||
# Force all links to be HTTPS (applicable to HTTP mixed error)
|
||||
force_https: false
|
||||
|
||||
# 代码块的增强配置
|
||||
# Enhancements to code blocks
|
||||
code:
|
||||
# 是否开启复制代码的按钮
|
||||
# Enable copy code button
|
||||
copy_btn: true
|
||||
|
||||
# 代码高亮
|
||||
# Code highlight
|
||||
highlight:
|
||||
enable: true
|
||||
|
||||
# 代码块是否显示行号
|
||||
# If true, the code block display line numbers
|
||||
line_number: false
|
||||
|
||||
# 实现高亮的库,对应下面的设置
|
||||
# Highlight library
|
||||
# Options: highlightjs | prismjs
|
||||
lib: "highlightjs"
|
||||
|
||||
highlightjs:
|
||||
# 在链接中挑选 style 填入
|
||||
# Select a style in the link
|
||||
# See: https://highlightjs.org/static/demo/
|
||||
style: "Github Gist"
|
||||
|
||||
# 是否根据 style 改变代码背景色(如果 style 是深色背景别忘了开启此项)
|
||||
# If true, the code background will change color based on the style (If style has a dark background, don't forget to true)
|
||||
bg_color: false
|
||||
|
||||
prismjs:
|
||||
# 在下方链接页面右侧的圆形按钮挑选 style 填入,也可以直接填入 css 链接
|
||||
# Select the style button on the right side of the link page, you can also set the CSS link
|
||||
# See: https://prismjs.com/
|
||||
style: "default"
|
||||
|
||||
# 设为 true 高亮将本地静态生成(并只支持部分 prismjs 插件),设为 false 高亮将在浏览器通过 js 生成
|
||||
# If true, it will be generated locally (but some prismjs plugins are not supported). If false, it will be generated via JS in the browser
|
||||
preprocess: true
|
||||
|
||||
# 一些好玩的功能
|
||||
# Some fun features
|
||||
fun_features:
|
||||
# 为 subtitle 添加打字机效果
|
||||
# Typing animation for subtitle
|
||||
typing:
|
||||
enable: true
|
||||
|
||||
# 打印速度,数字越大越慢
|
||||
# Typing speed, the larger the number, the slower
|
||||
typeSpeed: 70
|
||||
|
||||
# 游标字符
|
||||
# Cursor character
|
||||
cursorChar: "❤"
|
||||
|
||||
# 是否循环播放效果
|
||||
# If true, loop animation
|
||||
loop: false
|
||||
|
||||
# 为文章内容中的标题添加锚图标
|
||||
# Add an anchor icon to the title on the post page
|
||||
anchorjs:
|
||||
enable: true
|
||||
element: h1,h2,h3,h4,h5,h6
|
||||
placement: right
|
||||
# Options: hover | always | touch
|
||||
visible: hover
|
||||
# Option:§ | # | ❡
|
||||
icon: ""
|
||||
|
||||
# 加载进度条
|
||||
# Progress bar when loading
|
||||
progressbar:
|
||||
enable: true
|
||||
height_px: 3
|
||||
color: "#29d"
|
||||
# See: https://github.com/rstacruz/nprogress
|
||||
options: { showSpinner: false, trickleSpeed: 100 }
|
||||
|
||||
# 主题暗色模式,开启后菜单中会出现切换按钮,用户浏览器会存储切换选项,并且会遵循 prefers-color-scheme 自动切换
|
||||
# Theme dark mode. If enable, a switch button will appear on the menu, each of the visitor's browser will store his switch option
|
||||
dark_mode:
|
||||
enable: true
|
||||
# 默认的选项(当用户手动切换后则不再按照默认模式),选择 `auto` 会优先遵循 prefers-color-scheme,其次按用户本地时间 18 点到次日 6 点之间进入暗色模式
|
||||
# Default option (when the visitor switches manually, the default mode is no longer followed), choosing `auto` will give priority to prefers-color-scheme, and then enter the dark mode from 18:00 to 6:00 in the visitor’s local time
|
||||
# Options: auto | light | dark
|
||||
default: auto
|
||||
|
||||
# 主题颜色配置,其他不生效的地方请使用自定义 css 解决,配色可以在下方链接中获得启发
|
||||
# Theme color, please use custom CSS to solve other colors, color schema can be inspired by the links below
|
||||
# See: https://www.webdesignrankings.com/resources/lolcolors/
|
||||
color:
|
||||
# body 背景色
|
||||
# Color of body background
|
||||
body_bg_color: "#eee"
|
||||
# 暗色模式下的 body 背景色,下同
|
||||
# Color in dark mode, the same below
|
||||
body_bg_color_dark: "#181c27"
|
||||
|
||||
# 顶部菜单背景色
|
||||
# Color of navigation bar background
|
||||
navbar_bg_color: "#9DC8C8"
|
||||
navbar_bg_color_dark: "#1f3144"
|
||||
|
||||
# 顶部菜单字体色
|
||||
# Color of navigation bar text
|
||||
navbar_text_color: "#fff"
|
||||
navbar_text_color_dark: "#d0d0d0"
|
||||
|
||||
# 全局字体色
|
||||
# Color of global text
|
||||
text_color: "#3c4858"
|
||||
text_color_dark: "#c4c6c9"
|
||||
|
||||
# 全局次级字体色(摘要、简介等位置)
|
||||
# Color of global secondary text (excerpt, introduction, etc.)
|
||||
sec_text_color: "#718096"
|
||||
sec_text_color_dark: "#a7a9ad"
|
||||
|
||||
# 文章正文字体色
|
||||
# Color of post text
|
||||
post_text_color: "#2c3e50"
|
||||
post_text_color_dark: "#c4c6c9"
|
||||
|
||||
# 文章正文字体色(h1 h2 h3...)
|
||||
# Color of Article heading (h1 h2 h3...)
|
||||
post_heading_color: "#1a202c"
|
||||
post_heading_color_dark: "#c4c6c9"
|
||||
|
||||
# 文章超链接字体色
|
||||
# Color of post link
|
||||
post_link_color: "#0366d6"
|
||||
post_link_color_dark: "#1589e9"
|
||||
|
||||
# 超链接悬浮时字体色
|
||||
# Color of link when hovering
|
||||
link_hover_color: "#30a9de"
|
||||
link_hover_color_dark: "#30a9de"
|
||||
|
||||
# 超链接悬浮背景色
|
||||
# Color of link background when hovering
|
||||
link_hover_bg_color: "#f8f9fa"
|
||||
link_hover_bg_color_dark: "#364151"
|
||||
|
||||
# 主面板背景色
|
||||
# Color of main board
|
||||
board_color: "#fff"
|
||||
board_color_dark: "#252d38"
|
||||
|
||||
# 主题字体配置
|
||||
# Font
|
||||
font:
|
||||
font_size: 16px
|
||||
font_family:
|
||||
code_font_size: 85%
|
||||
|
||||
# 指定自定义 .js 文件路径,支持列表;路径是相对 source 目录,如 /js/custom.js 对应存放目录 source/js/custom.js
|
||||
# Specify the path of your custom js file, support list. The path is relative to the source directory, such as `/js/custom.js` corresponding to the directory `source/js/custom.js`
|
||||
custom_js: /js/xfy.js
|
||||
|
||||
# 指定自定义 .css 文件路径,用法和 custom_js 相同
|
||||
# The usage is the same as custom_js
|
||||
custom_css: /css/xfy.css
|
||||
|
||||
# 自定义底部 HTML 内容(位于 footer 上方),注意不要和 `post: custom` 配置冲突
|
||||
# Customize the HTML content at the bottom (located above the footer), be careful not to conflict with `post: custom`
|
||||
custom_html: ''
|
||||
|
||||
# 网页访问统计
|
||||
# Analysis of website visitors
|
||||
web_analytics: # 网页访问统计
|
||||
enable: false
|
||||
|
||||
# 百度统计的 Key,值需要获取下方链接中 `hm.js?` 后边的字符串
|
||||
# Baidu analytics, get the string behind `hm.js?`
|
||||
# See: https://tongji.baidu.com/sc-web/10000033910/home/site/getjs?siteId=13751376
|
||||
baidu:
|
||||
|
||||
# Google 统计的 Tracking ID
|
||||
# Google analytics, set Tracking ID
|
||||
# See: https://developers.google.com/analytics/devguides/collection/analyticsjs
|
||||
google:
|
||||
|
||||
# Google gtag.js 的媒体资源 ID
|
||||
# Google gtag.js GA_MEASUREMENT_ID
|
||||
# See: https://developers.google.com/analytics/devguides/collection/gtagjs/
|
||||
gtag:
|
||||
|
||||
# 腾讯统计的 H5 App ID,开启高级功能才有cid
|
||||
# Tencent analytics, set APP ID
|
||||
# See: https://mta.qq.com/h5/manage/ctr_app_manage
|
||||
tencent:
|
||||
sid:
|
||||
cid:
|
||||
|
||||
# 51.la 站点统计 ID
|
||||
# 51.la analytics
|
||||
# See: https://www.51.la/user/site/index
|
||||
woyaola: # 51.la 站点统计 ID,参见
|
||||
|
||||
# 友盟/cnzz 站点统计 web_id
|
||||
# cnzz analytics
|
||||
# See: https://web.umeng.com/main.php?c=site&a=show
|
||||
cnzz:
|
||||
|
||||
# LeanCloud 计数统计,可用于 PV UV 展示,如果 `web_analytics: enable` 没有开启,PV UV 展示只会查询不会增加
|
||||
# LeanCloud count statistics, which can be used for PV UV display. If `web_analytics: enable` is false, PV UV display will only query and not increase
|
||||
leancloud:
|
||||
app_id:
|
||||
app_key:
|
||||
# REST API 服务器地址,国际版不填
|
||||
# Only the Chinese mainland users need to set
|
||||
server_url:
|
||||
|
||||
# 对页面中的图片进行懒加载处理,可见范围外的图片不会加载
|
||||
# Lazy loading of images on the page
|
||||
lazyload:
|
||||
enable: true
|
||||
# 懒加载仅对文章页生效,开启后如果自定义页面需要使用,可以在 Front-matter 里指定 `lazyload: true`
|
||||
# If true, only enable lazyload on the post page. For custom pages, you can set 'lazyload: true' in front-matter
|
||||
onlypost: false
|
||||
|
||||
# 主题版本相关
|
||||
# Theme version
|
||||
version:
|
||||
# 每次生成页面后,检测主题是否为最新版本
|
||||
# If true, check whether Fluid is the latest version after hexo generate
|
||||
check: false
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 页头
|
||||
# Header
|
||||
#---------------------------
|
||||
|
||||
# 导航栏的相关配置
|
||||
# Navigation bar
|
||||
navbar:
|
||||
# 导航栏左侧的标题,为空则按 hexo config 中 `title` 显示
|
||||
# The title on the left side of the navigation bar. If empty, it is based on `title` in hexo config
|
||||
blog_title:
|
||||
|
||||
# 导航栏毛玻璃特效,实验性功能,可能会造成页面滚动掉帧和抖动,部分浏览器不支持会自动不生效
|
||||
# Navigation bar frosted glass special animation. It is an experimental feature
|
||||
ground_glass:
|
||||
enable: true
|
||||
|
||||
# 模糊像素,只能为数字,数字越大模糊度越高
|
||||
# Number of blurred pixel. the larger the number, the higher the blur
|
||||
px: 3
|
||||
|
||||
# 不透明度,数字越大透明度越低,注意透明过度可能看不清菜单字体
|
||||
# Ratio of opacity, 1.0 is completely opaque
|
||||
# available: 0 - 1.0
|
||||
alpha: 0.7
|
||||
|
||||
# 导航栏菜单,可自行增减,key 用来关联 languages/*.yml,如不存在关联则显示 key 本身的值;icon 是 css class,可以省略;增加 name 可以强制显示指定名称
|
||||
# Navigation bar menu. `key` is used to associate languages/*.yml. If there is no association, the value of `key` itself will be displayed; if `icon` is a css class, it can be omitted; adding `name` can force the display of the specified name
|
||||
menu:
|
||||
# - { key: 'home', link: '/', icon: 'iconfont icon-home-fill' }
|
||||
# - {
|
||||
# key: '索引',
|
||||
# icon: 'iconfont icon-books',
|
||||
# submenu: [
|
||||
# { key: 'category', link: '/categories/', icon: 'iconfont icon-category-fill' },
|
||||
# { key: 'tag', link: '/tags/', icon: 'iconfont icon-tags-fill' }
|
||||
# ]
|
||||
# }
|
||||
# - { key: 'archive', link: '/archives/', icon: 'iconfont icon-archive-fill' }
|
||||
# - { key: 'about', link: '/about/', icon: 'iconfont icon-user-fill' }
|
||||
# - { key: '小伙伴', link: '/links/', icon: 'iconfont icon-link-fill' }
|
||||
# - { key: 'PGP', link: '/pgp/', icon: 'iconfont icon-clipcheck' }
|
||||
- { key: '🏠 首页', link: '/', icon: '' }
|
||||
- {
|
||||
key: '📕 索引',
|
||||
icon: '',
|
||||
submenu: [
|
||||
{ key: '🎁 分类 ', link: '/categories/', icon: '' },
|
||||
{ key: '🎐 标签', link: '/tags/', icon: '' }
|
||||
]
|
||||
}
|
||||
- { key: '📂 归档', link: '/archives/', icon: '' }
|
||||
- { key: '🎃 关于', link: '/about/', icon: '' }
|
||||
- { key: '🙆♀️ 小伙伴', link: '/links/', icon: '' }
|
||||
- { key: '🔐 PGP', link: '/pgp/', icon: '' }
|
||||
|
||||
# 搜索功能,基于 hexo-generator-search 插件,若已安装其他搜索插件请关闭此功能,以避免生成多余的索引文件
|
||||
# Search feature, based on hexo-generator-search. If you have installed other search plugins, please disable this feature to avoid generating redundant index files
|
||||
search:
|
||||
enable: true
|
||||
# 搜索索引文件的路径,可以是相对路径或外站的绝对路径
|
||||
# Path for search index file, it can be a relative path or an absolute path
|
||||
path: /xml/local-search.xml
|
||||
|
||||
# 文件生成在本地的位置,必须是相对路径
|
||||
# The location where the index file is generated locally, it must be a relative location
|
||||
generate_path: /xml/local-search.xml
|
||||
|
||||
# 搜索的范围
|
||||
# Search field
|
||||
# Options: post | page | all
|
||||
field: post
|
||||
|
||||
# 搜索是否扫描正文
|
||||
# If true, search will scan the post content
|
||||
content: true
|
||||
|
||||
# 首屏图片的相关配置
|
||||
# Config of the big image on the first screen
|
||||
banner:
|
||||
# 视差滚动,图片与板块会随着屏幕滚动产生视差效果
|
||||
# Scrolling parallax
|
||||
parallax: true
|
||||
|
||||
# 图片最小的宽高比,以免图片两边被过度裁剪,适用于移动端竖屏时,如需关闭设为 0
|
||||
# Minimum ratio of width to height, applicable to the vertical screen of mobile device, if you need to close it, set it to 0
|
||||
width_height_ratio: 1.0
|
||||
|
||||
# 向下滚动的箭头
|
||||
# Scroll down arrow
|
||||
scroll_down_arrow:
|
||||
enable: true
|
||||
|
||||
# 头图高度不小于指定比例,才显示箭头
|
||||
# Only the height of the banner image is greater than the ratio, the arrow is displayed
|
||||
# Available: 0 - 100
|
||||
banner_height_limit: 90
|
||||
|
||||
# 翻页后自动滚动
|
||||
# Auto scroll after page turning
|
||||
scroll_after_turning_page: true
|
||||
|
||||
# 向顶部滚动的箭头
|
||||
# Scroll top arrow
|
||||
scroll_top_arrow:
|
||||
enable: true
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 页脚
|
||||
# Footer
|
||||
#---------------------------
|
||||
footer:
|
||||
# 页脚第一行文字的 HTML,建议保留 Fluid 的链接,用于向更多人推广本主题
|
||||
# HTML of the first line of the footer, it is recommended to keep the Fluid link to promote this theme to more people
|
||||
content: '
|
||||
<a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a>
|
||||
<i class="iconfont icon-love"></i>
|
||||
<a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a>
|
||||
'
|
||||
|
||||
# 展示网站的 PV、UV 统计数
|
||||
# Display website PV and UV statistics
|
||||
statistics:
|
||||
enable: false
|
||||
|
||||
# 统计数据来源,如果使用 leancloud 需要设置 `web_analytics: leancloud` 中的参数;如果使用 busuanzi 可能会有请求失败的情况
|
||||
# Data source. If use leancloud, you need to set the parameter in `web_analytics: leancloud`
|
||||
# Options: busuanzi | leancloud
|
||||
source: "busuanzi"
|
||||
|
||||
# 页面显示的文本,{}是数字的占位符(必须包含),下同
|
||||
# Displayed text, {} is a placeholder for numbers (must be included), the same below
|
||||
pv_format: "总访问量 {} 次"
|
||||
uv_format: "总访客数 {} 人"
|
||||
|
||||
# 国内大陆服务器的备案信息
|
||||
# For Chinese mainland website policy, other areas keep disable
|
||||
beian:
|
||||
enable: true
|
||||
# ICP证号
|
||||
icp_text: 皖ICP备17017808号
|
||||
# 公安备案号,不填则只显示ICP
|
||||
police_text:
|
||||
# 公安备案的编号,用于URL跳转查询
|
||||
police_code: 17017808
|
||||
# 公安备案的图片. 为空时不显示备案图片
|
||||
police_icon: /img/police_beian.webp
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 首页
|
||||
# Home Page
|
||||
#---------------------------
|
||||
index:
|
||||
# 首页 Banner 头图,可以是相对路径或绝对路径,以下相同
|
||||
# Path of Banner image, can be a relative path or an absolute path, the same on other pages
|
||||
banner_img: /images/img/index.webp
|
||||
|
||||
# 头图高度,屏幕百分比
|
||||
# Height ratio of banner image
|
||||
# Available: 0 - 100
|
||||
banner_img_height: 90
|
||||
|
||||
# 头图黑色蒙版的不透明度,available: 0 - 1.0, 1 是完全不透明
|
||||
# Opacity of the banner mask, 1.0 is completely opaque
|
||||
# Available: 0 - 1.0
|
||||
banner_mask_alpha: 0.3
|
||||
|
||||
# 首页副标题的独立设置
|
||||
# Independent config of home page subtitle
|
||||
slogan:
|
||||
enable: true
|
||||
|
||||
# 为空则按 hexo config.subtitle 显示
|
||||
# If empty, text based on `subtitle` in hexo config
|
||||
text:
|
||||
|
||||
# 通过 API 接口作为首页副标题的内容,必须返回的是 JSON 格式,如果请求失败则按 text 字段显示,该功能必须先开启 typing 打字机功能
|
||||
# Subtitle of the homepage through the API, must be returned a JSON. If the request fails, it will be displayed in `text` value. This feature must first enable the typing animation
|
||||
api:
|
||||
enable: false
|
||||
|
||||
# 请求地址
|
||||
# Request url
|
||||
url: ""
|
||||
|
||||
# 请求方法
|
||||
# Request method
|
||||
# Available: GET | POST | PUT
|
||||
method: "GET"
|
||||
|
||||
# 请求头
|
||||
# Request headers
|
||||
headers: {}
|
||||
|
||||
# 从请求结果获取字符串的取值字段,最终必须是一个字符串,例如返回结果为 {"data": {"author": "fluid", "content": "An elegant theme"}}, 则取值字段为 ['data', 'content'];如果返回是列表则自动选择第一项
|
||||
# The value field of the string obtained from the response. For example, the response content is {"data": {"author": "fluid", "content": "An elegant theme"}}, the expected `keys: ['data','content']`; if the return is a list, the first item is automatically selected
|
||||
keys: []
|
||||
|
||||
# 自动截取文章摘要
|
||||
# Auto extract post
|
||||
auto_excerpt:
|
||||
enable: true
|
||||
|
||||
# 打开文章的标签方式
|
||||
# The browser tag to open the post
|
||||
# Available: _blank | _self
|
||||
post_url_target: _self
|
||||
|
||||
# 是否显示文章信息(时间、分类、标签)
|
||||
# Meta information of post
|
||||
post_meta:
|
||||
date: true
|
||||
category: true
|
||||
tag: true
|
||||
|
||||
# 文章通过 sticky 排序后,在首页文章标题前显示图标
|
||||
# If the posts are sorted by `sticky`, an icon is displayed in front of the post title
|
||||
post_sticky:
|
||||
enable: true
|
||||
icon: "iconfont icon-top"
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 文章页
|
||||
# Post Page
|
||||
#---------------------------
|
||||
post:
|
||||
banner_img: /images/img/post.webp
|
||||
banner_img_height: 75
|
||||
banner_mask_alpha: 0.3
|
||||
|
||||
# 文章在首页的默认封面图,当没有指定 index_img 时会使用该图片,若两者都为空则不显示任何图片
|
||||
# Path of the default post cover when `index_img` is not set. If both are empty, no image will be displayed
|
||||
default_index_img:
|
||||
|
||||
# 文章标题下方的元信息
|
||||
# Meta information below title
|
||||
meta:
|
||||
# 作者,优先根据 front-matter 里 author 字段,其次是 hexo 配置中 author 值
|
||||
# Author, based on `author` field in front-matter, if not set, based on `author` value in hexo config
|
||||
author:
|
||||
enable: true
|
||||
|
||||
# 文章日期,优先根据 front-matter 里 date 字段,其次是 md 文件日期
|
||||
# Post date, based on `date` field in front-matter, if not set, based on create date of .md file
|
||||
date:
|
||||
enable: true
|
||||
# 格式参照 ISO-8601 日期格式化
|
||||
# ISO-8601 date format
|
||||
# See: http://momentjs.cn/docs/#/parsing/string-format/
|
||||
format: "LL a"
|
||||
|
||||
# 字数统计
|
||||
# Word count
|
||||
wordcount:
|
||||
enable: true
|
||||
# 显示的文本,{}是数字的占位符(必须包含),下同
|
||||
# Displayed text, {} is a placeholder for numbers (must be included), the same below
|
||||
format: "{} 字"
|
||||
|
||||
# 估计阅读全文需要的时长
|
||||
# Estimate the time required to read the full text
|
||||
min2read:
|
||||
enable: true
|
||||
format: "{} 分钟"
|
||||
# 每分钟阅读多少字,如果是技术文章,建议降低
|
||||
# Words read per minute
|
||||
words: 100
|
||||
|
||||
# 浏览量计数
|
||||
# Number of visits
|
||||
views:
|
||||
enable: false
|
||||
# 统计数据来源
|
||||
# Data Source
|
||||
# Options: busuanzi | leancloud
|
||||
source: "busuanzi"
|
||||
format: "{} 次"
|
||||
|
||||
# 在文章开头显示文章更新时间,该时间默认是 md 文件更新时间,可通过 front-matter 中 `updated` 手动指定(和 date 一样格式)
|
||||
# Update date is displayed at the beginning of the post. The default date is the update date of the md file, which can be manually specified by `updated` in front-matter (same format as date)
|
||||
updated:
|
||||
enable: true
|
||||
|
||||
# 描述文字
|
||||
# Descriptive text before date
|
||||
content: 本文最后水于:
|
||||
|
||||
# 是否使用相对时间表示,比如:"3 天前"
|
||||
# If true, it will be a relative time, such as: "3 days ago"
|
||||
relative: false
|
||||
|
||||
# 文章右侧目录
|
||||
# Table of contents (TOC)
|
||||
toc:
|
||||
enable: true
|
||||
# 目录会选择这些节点作为标题
|
||||
# TOC will select these nodes as headings
|
||||
headingSelector: "h1,h2,h3,h4,h5,h6"
|
||||
# 层级的折叠深度,0 是全部折叠,大于 0 后如果存在下级标题则默认展开
|
||||
# Collapse depth. If 0, all headings collapsed. If greater than 0, it will be expanded by default if there are sub headings
|
||||
collapseDepth: 3
|
||||
|
||||
# 版权声明,会显示在每篇文章的结尾
|
||||
# Copyright, will be displayed at the end of each post
|
||||
copyright:
|
||||
enable: true
|
||||
content: '<a href="https://zh.wikipedia.org/wiki/Wikipedia:CC_BY-SA_3.0%E5%8D%8F%E8%AE%AE%E6%96%87%E6%9C%AC" rel="nofollow noopener">CC BY-SA 3.0❤</a> '
|
||||
|
||||
# 文章底部上一篇下一篇功能
|
||||
# Link to previous/next post
|
||||
prev_next:
|
||||
enable: true
|
||||
|
||||
# 文章底部自定义区域(位于 footer 上方),支持 HTML,可插入赞赏码、公众号这类内容内容
|
||||
# Custom content at the bottom of the post page (located above the footer)
|
||||
custom:
|
||||
enable: false
|
||||
content: '<img src="https://octodex.github.com/images/jetpacktocat.png" class="rounded mx-auto d-block mt-5" style="width:150px; height:150px;">'
|
||||
|
||||
# 文章图片可点击放大
|
||||
# Zoom feature of images
|
||||
image_zoom:
|
||||
enable: true
|
||||
|
||||
# 脚注语法,会在文章底部生成脚注,如果 Markdown 渲染器本身支持,则建议关闭,否则可能会冲突
|
||||
# Support footnote syntax, footnotes will be generated at the bottom of the post page. If the Markdown renderer itself supports it, please disable it, otherwise it may conflict
|
||||
footnote:
|
||||
enable: true
|
||||
# 脚注的节标题,也可以在 front-matter 中通过 `footnote: <h2>Reference</h2>` 这种形式修改单独页面的 header
|
||||
# The section title of the footnote, you can also modify the header of a single page in the form of `footnote: <h2>Reference</h2>` in front-matter
|
||||
header: ''
|
||||
|
||||
# 数学公式,开启之前需要更换 Markdown 渲染器,否则复杂公式会有兼容问题,具体请见:https://hexo.fluid-dev.com/docs/guide/##latex-数学公式
|
||||
# Mathematical formula. If enable, you need to change the Markdown renderer, see: https://hexo.fluid-dev.com/docs/en/guide/#math
|
||||
math:
|
||||
# 开启后文章默认可用,自定义页面如需使用,需在 Front-matter 中指定 `math: true`
|
||||
# If you want to use math on the custom page, you need to set `math: true` in Front-matter
|
||||
enable: true
|
||||
|
||||
# 开启后,只有在文章 Front-matter 里指定 `math: true` 才会在文章页启动公式转换,以便在页面不包含公式时提高加载速度
|
||||
# If true, only set `math: true` in Front-matter will enable math, to load faster when the page does not contain math
|
||||
specific: true
|
||||
|
||||
# Options: mathjax | katex
|
||||
engine: mathjax
|
||||
|
||||
# 流程图,基于 mermaid-js,具体请见:https://hexo.fluid-dev.com/docs/guide/#mermaid-流程图
|
||||
# Flow chart, based on mermaid-js, see: https://hexo.fluid-dev.com/docs/en/guide/#mermaid
|
||||
mermaid:
|
||||
# 开启后文章默认可用,自定义页面如需使用,需在 Front-matter 中指定 `mermaid: true`
|
||||
# If you want to use mermaid on the custom page, you need to set `mermaid: true` in Front-matter
|
||||
enable: true
|
||||
|
||||
# 开启后,只有在文章 Front-matter 里指定 `mermaid: true` 才会在文章页启动公式转换,以便在页面不包含公式时提高加载速度
|
||||
# If true, only set `mermaid: true` in Front-matter will enable mermaid, to load faster when the page does not contain mermaid
|
||||
specific: false
|
||||
|
||||
# See: http://mermaid-js.github.io/mermaid/
|
||||
options: { theme: 'default' }
|
||||
|
||||
# 评论插件
|
||||
# Comment plugin
|
||||
comments:
|
||||
enable: true
|
||||
# 指定的插件,需要同时设置对应插件的必要参数
|
||||
# The specified plugin needs to set the necessary parameters at the same time
|
||||
# Options: utterances | disqus | gitalk | valine | waline | changyan | livere | remark42 | twikoo
|
||||
type: valine
|
||||
|
||||
#---------------------------
|
||||
# 评论插件
|
||||
# Comment plugins
|
||||
#
|
||||
# 开启评论需要先设置上方 `post: comments: enable: true`,然后根据 `type` 设置下方对应的评论插件参数
|
||||
# Enable comments need to be set `post: comments: enable: true`, then set the corresponding comment plugin parameters below according to `type`
|
||||
#---------------------------
|
||||
|
||||
# Utterances
|
||||
# 基于 GitHub Issues
|
||||
# Based on GitHub Issues
|
||||
# See: https://utteranc.es
|
||||
utterances:
|
||||
repo:
|
||||
issue_term: pathname
|
||||
label: utterances
|
||||
theme: github-light
|
||||
theme_dark: github-dark
|
||||
crossorigin: anonymous
|
||||
|
||||
# Disqus
|
||||
# 基于第三方的服务,国内用户直接使用容易被墙,建议配合 Disqusjs
|
||||
# Based on third-party service
|
||||
# See: https://disqus.com
|
||||
disqus:
|
||||
shortname: defectink
|
||||
# 以下为 Disqusjs 支持, 国内用户如果想使用 Disqus 建议配合使用
|
||||
# The following are Disqusjs configurations, please ignore if DisqusJS is not required
|
||||
# See: https://github.com/SukkaW/DisqusJS
|
||||
disqusjs: true
|
||||
apikey: uLPmTxSU1b8Ty8a8tpXZzlBl1NaURmrJFI5RDyIerCl38c7wqJhNDRwPypGaYe8x
|
||||
|
||||
# Gitalk
|
||||
# 基于 GitHub Issues
|
||||
# Based on GitHub Issues
|
||||
# See: https://github.com/gitalk/gitalk#options
|
||||
gitalk:
|
||||
clientID:
|
||||
clientSecret:
|
||||
repo:
|
||||
owner:
|
||||
admin: ['name']
|
||||
language: zh-CN
|
||||
labels: ['Gitalk']
|
||||
perPage: 10
|
||||
pagerDirection: last
|
||||
distractionFreeMode: false
|
||||
createIssueManually: true
|
||||
|
||||
# Valine
|
||||
# 基于 LeanCloud
|
||||
# Based on LeanCloud
|
||||
# See: https://valine.js.org/configuration.html
|
||||
valine:
|
||||
appid: dD9t7mcIBVzJWag5ez6GPy2v-MdYXbMMI
|
||||
appkey: bWG6pmKsEscrH4JjrpNNAAy6
|
||||
placeholder: 嘤嘤嘤???
|
||||
path: window.location.pathname
|
||||
avatar: retro
|
||||
meta: ['nick', 'mail', 'link']
|
||||
pageSize: 10
|
||||
lang: zh-CN
|
||||
highlight: true
|
||||
recordIP: false
|
||||
serverURLs:
|
||||
|
||||
# Waline
|
||||
# 一款从 Valine 衍生的带后端的评论插件
|
||||
# A comment plugin with backend derived from Valine
|
||||
# See: https://waline.js.org/
|
||||
waline:
|
||||
serverURL: ''
|
||||
placeholder: 说点什么
|
||||
path: window.location.pathname
|
||||
avatar: retro
|
||||
meta: ['nick', 'mail', 'link']
|
||||
pageSize: 10
|
||||
lang: zh-CN
|
||||
highlight: true
|
||||
avatarForce: false
|
||||
requiredFields: []
|
||||
emojiCDN: ''
|
||||
emojiMaps: {}
|
||||
|
||||
# 畅言 Changyan
|
||||
# 基于第三方的服务
|
||||
# Based on third-party service, insufficient support for regions outside China
|
||||
# http://changyan.kuaizhan.com
|
||||
changyan:
|
||||
appid: ''
|
||||
appkey: ''
|
||||
|
||||
# 来必力 Livere
|
||||
# 基于第三方的服务
|
||||
# Based on third-party service
|
||||
# See: https://www.livere.com
|
||||
livere:
|
||||
uid: ''
|
||||
|
||||
# Remark42
|
||||
# 需要自己运行后端服务
|
||||
# Need to run the backend service yourself
|
||||
# See: https://remark42.com
|
||||
remark42:
|
||||
host:
|
||||
site_id:
|
||||
max_shown_comments: 10
|
||||
locale: zh
|
||||
|
||||
# Twikoo
|
||||
# 基于腾讯云开发
|
||||
# Based on Tencent CloudBase
|
||||
# See: https://twikoo.js.org
|
||||
twikoo:
|
||||
env_id:
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 归档页
|
||||
# Archive Page
|
||||
#---------------------------
|
||||
archive:
|
||||
banner_img: /images/img/Sensei_sakura.webp
|
||||
banner_img_height: 90
|
||||
banner_mask_alpha: 0.3
|
||||
subtitle:
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 分类页
|
||||
# Category Page
|
||||
#---------------------------
|
||||
category:
|
||||
enable: true
|
||||
banner_img: /images/img/category.webp
|
||||
banner_img_height: 80
|
||||
banner_mask_alpha: 0.3
|
||||
subtitle:
|
||||
|
||||
# 单个分类中折叠展示文章数的最大值,超过限制会显示 More
|
||||
# The maximum number of posts in a single category. If the limit is exceeded, it will be displayed More
|
||||
post_limit: 10
|
||||
|
||||
# 排序字段,前面带减号是倒序,不带减号是正序,可选项:name | length
|
||||
# Sort field, with a minus sign is reverse order
|
||||
# Options: name | length
|
||||
order_by: "-length"
|
||||
|
||||
# 层级的折叠深度,0 是全部折叠,大于 0 后如果存在子分类则默认展开
|
||||
# Collapse depth. If 0, all posts collapsed. If greater than 0, it will be expanded by default if there are subcategories
|
||||
collapse_depth: 0
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 标签页
|
||||
# Tag Page
|
||||
#---------------------------
|
||||
tag:
|
||||
enable: true
|
||||
banner_img: /images/img/tags.webp
|
||||
banner_img_height: 80
|
||||
banner_mask_alpha: 0.3
|
||||
subtitle:
|
||||
tagcloud:
|
||||
min_font: 15
|
||||
max_font: 30
|
||||
unit: px
|
||||
start_color: "#BBBBEE"
|
||||
end_color: "#337ab7"
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 关于页
|
||||
# About Page
|
||||
#---------------------------
|
||||
about:
|
||||
enable: true
|
||||
banner_img: /images/img/about.webp
|
||||
banner_img_height: 90
|
||||
banner_mask_alpha: 0.3
|
||||
subtitle: 嘤嘤嘤
|
||||
avatar: /images/img/avatar.webp
|
||||
name: Defectink
|
||||
introduce: "!@#$%^&*"
|
||||
# 更多图标可从 https://hexo.fluid-dev.com/docs/icon/ 查找,`class` 代表图标的 css class,添加 `qrcode` 后,图标不再是链接而是悬浮二维码
|
||||
# More icons can be found from https://hexo.fluid-dev.com/docs/en/icon/ `class` is the css class of the icon. If adding `qrcode`, The icon is no longer a link, but a hovering QR code
|
||||
icons:
|
||||
- { class: 'iconfont icon-github-fill', link: 'https://github.com/DefectingCat' }
|
||||
- { class: 'iconfont icon-twitter-fill', link: 'https://twitter.com/Defect___' }
|
||||
- { class: 'iconfont icon-telegram-fill', link: 'https://t.me/Defectink' }
|
||||
- { class: 'iconfont icon-mail', link: 'mailto:i@defect.ink' }
|
||||
- { class: 'iconfont icon-wechat-fill', qrcode: '/images/img/qrcode.webp' }
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 自定义页
|
||||
# Custom Page
|
||||
#
|
||||
# 通过 hexo new page 命令创建的页面
|
||||
# Custom Page through `hexo new page`
|
||||
#---------------------------
|
||||
page:
|
||||
banner_img: /images/img/post.webp
|
||||
banner_img_height: 90
|
||||
banner_mask_alpha: 0.3
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 404页
|
||||
# 404 Page
|
||||
#---------------------------
|
||||
page404:
|
||||
enable: true
|
||||
banner_img: /images/img/Sensei_dark.webp
|
||||
banner_img_height: 100
|
||||
banner_mask_alpha: 0.3
|
||||
subtitle: "Page not found"
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 友链页
|
||||
# Links Page
|
||||
#---------------------------
|
||||
links:
|
||||
enable: true
|
||||
banner_img: /images/img/friend.webp
|
||||
banner_img_height: 90
|
||||
banner_mask_alpha: 0.3
|
||||
subtitle: 小伙伴
|
||||
# 友链的成员项
|
||||
# Member item of page
|
||||
items:
|
||||
- {
|
||||
title: '非常医学生',
|
||||
intro: '以医德观人生。',
|
||||
link: 'https://www.cunzher.cn',
|
||||
image: 'https://ypcdnsave.cunzher.cn/img/logo.png'
|
||||
}
|
||||
- {
|
||||
title: 'Zeroの日常',
|
||||
intro: '嘤嘤嘤',
|
||||
link: 'https://mikuac.com/',
|
||||
image: 'https://mikuac.com/images/zero.png'
|
||||
}
|
||||
- {
|
||||
title: 'Feng''blog',
|
||||
intro: '嘤嘤嘤',
|
||||
link: 'https://1984n.win',
|
||||
image: 'https://cdn.v2ex.com/gravatar/bf7a5bd302ccc8021d13d77bd09c9310?s=100&r=g'
|
||||
}
|
||||
- {
|
||||
title: 'Locyoo',
|
||||
intro: '嘤嘤嘤',
|
||||
link: 'https://blog.locyoo.com',
|
||||
image: '/images/img/backup.webp'
|
||||
}
|
||||
- {
|
||||
title: '萌萌哒の柯基',
|
||||
intro: '仰望大佬的菜鸡〒▽〒',
|
||||
link: 'https://www.heroyf.club/',
|
||||
image: 'https://cdn.defectink.com/images/20200924163805.png'
|
||||
}
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 以下是配置 JS CSS 等静态资源的 URL 前缀,可以自定义成 CDN 地址,
|
||||
# 默认的 jsDelivr CDN 可能在部分地区无法访问,如果需要修改,最好使用与默认配置相同的版本,以避免潜在的问题,
|
||||
# ** 如果你不知道如何设置,请不要做任何改动 **
|
||||
#
|
||||
# Here is the url prefix to configure the static assets. Set CDN addresses you want to customize.
|
||||
# Be aware that you would better use the same version as default ones to avoid potential problems.
|
||||
# DO NOT EDIT THE FOLLOWING SETTINGS UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||
#---------------------------
|
||||
|
||||
static_prefix:
|
||||
# 内部静态
|
||||
# Internal static
|
||||
internal_js: /js
|
||||
internal_css: /css
|
||||
internal_img: /images/img
|
||||
|
||||
# 图标库,包含了大量社交类图标,主题依赖的不包含在内,因此可自行修改,详见 https://hexo.fluid-dev.com/docs/icon/
|
||||
# Icon library, which includes many social icons, does not include those theme dependent, so your can modify link by yourself. See: https://hexo.fluid-dev.com/docs/en/icon/
|
||||
iconfont: //at.alicdn.com/t/font_1736178_kmeydafke9r.css
|
||||
|
||||
anchor: https://cdn.jsdelivr.net/npm/anchor-js@4.3.0/
|
||||
|
||||
github_markdown: https://cdn.jsdelivr.net/npm/github-markdown-css@4.0.0/
|
||||
|
||||
jquery: https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/
|
||||
|
||||
bootstrap: https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/
|
||||
|
||||
highlightjs: https://cdn.jsdelivr.net/npm/highlight.js@10.4.0/
|
||||
|
||||
prismjs: https://cdn.jsdelivr.net/npm/prismjs@1.22.0/
|
||||
|
||||
tocbot: https://cdn.jsdelivr.net/npm/tocbot@4.12.0/dist/
|
||||
|
||||
typed: https://cdn.jsdelivr.net/npm/typed.js@2.0.11/lib/
|
||||
|
||||
fancybox: https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.7/dist/
|
||||
|
||||
nprogress: https://cdn.jsdelivr.net/npm/nprogress@0.2.0/
|
||||
|
||||
mathjax: https://cdn.jsdelivr.net/npm/mathjax@3.1.2/es5/
|
||||
|
||||
katex: https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/
|
||||
|
||||
busuanzi: https://busuanzi.ibruce.info/busuanzi/2.3/
|
||||
|
||||
clipboard: https://cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/
|
||||
|
||||
mermaid: https://cdn.jsdelivr.net/npm/mermaid@8.8.3/dist/
|
||||
|
||||
valine: https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/
|
||||
|
||||
waline: https://cdn.jsdelivr.net/npm/@waline/client@0.4.2/dist/
|
||||
|
||||
gitalk: https://cdn.jsdelivr.net/npm/gitalk@1.7.0/dist/
|
||||
|
||||
disqusjs: https://cdn.jsdelivr.net/npm/disqusjs@1.0/dist/
|
||||
|
||||
twikoo: https://cdn.jsdelivr.net/npm/twikoo@0.1.15/dist/
|
||||
|
||||
hint: /lib/hint/
|
136
_config.yml
Normal file
@ -0,0 +1,136 @@
|
||||
# Hexo Configuration
|
||||
## Docs: https://hexo.io/docs/configuration.html
|
||||
## Source: https://github.com/hexojs/hexo/
|
||||
|
||||
# Site
|
||||
title: 🍭Defectink
|
||||
subtitle: '只要心还在跳'
|
||||
description: ''
|
||||
keywords:
|
||||
author: Defectink
|
||||
language: zh-CN
|
||||
timezone: ''
|
||||
|
||||
# URL
|
||||
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
|
||||
url: https://www.defectink.com
|
||||
root: /
|
||||
permalink: defect/:url.html
|
||||
permalink_defaults:
|
||||
pretty_urls:
|
||||
trailing_index: true # Set to false to remove trailing 'index.html' from permalinks
|
||||
trailing_html: true # Set to false to remove trailing '.html' from permalinks
|
||||
|
||||
# Directory
|
||||
source_dir: source
|
||||
public_dir: public
|
||||
tag_dir: tags
|
||||
archive_dir: archives
|
||||
category_dir: categories
|
||||
code_dir: downloads/code
|
||||
i18n_dir: :lang
|
||||
skip_render:
|
||||
|
||||
# Writing
|
||||
new_post_name: :title.md # File name of new posts
|
||||
default_layout: post
|
||||
titlecase: false # Transform title into titlecase
|
||||
external_link:
|
||||
enable: true # Open external links in new tab
|
||||
field: site # Apply to the whole site
|
||||
exclude: ''
|
||||
filename_case: 0
|
||||
render_drafts: false
|
||||
post_asset_folder: false
|
||||
relative_link: false
|
||||
future: true
|
||||
highlight:
|
||||
enable: false
|
||||
line_number: false
|
||||
auto_detect: false
|
||||
tab_replace: ''
|
||||
wrap: true
|
||||
hljs: false
|
||||
|
||||
# Home page setting
|
||||
# path: Root path for your blogs index page. (default = '')
|
||||
# per_page: Posts displayed per page. (0 = disable pagination)
|
||||
# order_by: Posts order. (Order by date descending by default)
|
||||
index_generator:
|
||||
path: ''
|
||||
per_page: 10
|
||||
order_by: -date
|
||||
archive_generator:
|
||||
per_page: 0
|
||||
|
||||
# Category & Tag
|
||||
default_category: uncategorized
|
||||
category_map:
|
||||
tag_map:
|
||||
|
||||
# Metadata elements
|
||||
## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta
|
||||
meta_generator: true
|
||||
|
||||
# Date / Time format
|
||||
## Hexo uses Moment.js to parse and display date
|
||||
## You can customize the date format as defined in
|
||||
## http://momentjs.com/docs/#/displaying/format/
|
||||
date_format: YYYY-MM-DD
|
||||
time_format: HH:mm:ss
|
||||
## Use post's date for updated date unless set in front-matter
|
||||
use_date_for_updated: false
|
||||
|
||||
# Pagination
|
||||
## Set per_page to 0 to disable pagination
|
||||
per_page: 10
|
||||
pagination_dir: page
|
||||
|
||||
# Include / Exclude file(s)
|
||||
## include:/exclude: options only apply to the 'source/' folder
|
||||
include:
|
||||
exclude:
|
||||
ignore:
|
||||
|
||||
# Extensions
|
||||
## Plugins: https://hexo.io/plugins/
|
||||
## Themes: https://hexo.io/themes/
|
||||
theme: fluid
|
||||
|
||||
# Deployment
|
||||
## Docs: https://hexo.io/docs/deployment.html
|
||||
deploy:
|
||||
- type: 'git'
|
||||
repo: git@github.com:DefectingCat/DefectingCat.github.io.git
|
||||
branch: gh-pages
|
||||
message: ❤
|
||||
- type: 'git'
|
||||
repo: git@e.coding.net:Defectink/blog/blog.git
|
||||
branch: gh-pages
|
||||
message: ❤
|
||||
|
||||
# feed
|
||||
feed:
|
||||
type:
|
||||
- atom
|
||||
- rss2
|
||||
path:
|
||||
- /xml/atom.xml
|
||||
- /xml/rss.xml
|
||||
limit: 20
|
||||
hub:
|
||||
content:
|
||||
content_limit: 140
|
||||
content_limit_delim: ' '
|
||||
order_by: -date
|
||||
icon: icon.png
|
||||
autodiscovery: true
|
||||
template:
|
||||
|
||||
# sitemap
|
||||
sitemap:
|
||||
path: /xml/sitemap.xml
|
||||
template: ./source/_data/sitemap_template.xml
|
||||
rel: false
|
||||
tags: true
|
||||
categories: true
|
18024
package-lock.json
generated
Normal file
47
package.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "hexo-site",
|
||||
"version": "0.0.2-0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "hexo generate",
|
||||
"clean": "hexo clean",
|
||||
"deploy": "hexo deploy",
|
||||
"server": "hexo server",
|
||||
"dev": "hexo cl && hexo g && gulp",
|
||||
"go": "hexo cl && hexo g && gulp && hexo d",
|
||||
"cz": "cz",
|
||||
"cl": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
|
||||
},
|
||||
"hexo": {
|
||||
"version": "5.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel": "^6.23.0",
|
||||
"commitizen": "^4.2.2",
|
||||
"conventional-changelog-cli": "^2.1.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean-css": "^4.3.0",
|
||||
"gulp-htmlclean": "^2.7.22",
|
||||
"gulp-htmlmin": "^5.0.1",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"gulp-uglify-es": "^2.0.0",
|
||||
"hexo": "^5.3.0",
|
||||
"hexo-cli": "^4.2.0",
|
||||
"hexo-deployer-git": "^2.1.0",
|
||||
"hexo-generator-archive": "^1.0.0",
|
||||
"hexo-generator-category": "^1.0.0",
|
||||
"hexo-generator-feed": "^3.0.0",
|
||||
"hexo-generator-index": "^2.0.0",
|
||||
"hexo-generator-sitemap": "^2.1.0",
|
||||
"hexo-generator-tag": "^1.0.0",
|
||||
"hexo-renderer-ejs": "^1.0.0",
|
||||
"hexo-renderer-marked": "^3.3.0",
|
||||
"hexo-renderer-stylus": "^2.0.1",
|
||||
"hexo-server": "^2.0.0",
|
||||
"hexo-theme-fluid": "^1.8.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.11"
|
||||
}
|
||||
}
|
4
scaffolds/draft.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: {{ title }}
|
||||
tags:
|
||||
---
|
4
scaffolds/page.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: {{ title }}
|
||||
date: {{ date }}
|
||||
---
|
8
scaffolds/post.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: {{ title }}
|
||||
date: {{ date }}
|
||||
tags:
|
||||
categories:
|
||||
url:
|
||||
index_img: /images/
|
||||
---
|
31
source/_data/Jenkinsfile_同步至bakcupToGithub
Normal file
@ -0,0 +1,31 @@
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('检出') {
|
||||
steps {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: env.GIT_BUILD_REF]],
|
||||
userRemoteConfigs: [[
|
||||
url: env.GIT_REPO_URL,
|
||||
credentialsId: env.CREDENTIALS_ID
|
||||
]]])
|
||||
}
|
||||
}
|
||||
stage('自定义构建过程') {
|
||||
steps {
|
||||
echo '自定义构建过程开始'
|
||||
}
|
||||
}
|
||||
stage('同步至Github') {
|
||||
steps {
|
||||
echo '-- Start push --'
|
||||
sh 'ls'
|
||||
sh 'git remote set-url origin https://DefectingCat:$ID@github.com/DefectingCat/DefectingCat.github.io.git'
|
||||
sh 'git remote -v'
|
||||
sh 'git push origin HEAD:backup'
|
||||
echo '-- All Done --'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
source/_data/Jenkinsfile_部署至云存储
Normal file
@ -0,0 +1,53 @@
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('检出') {
|
||||
steps {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: env.GIT_BUILD_REF]],
|
||||
userRemoteConfigs: [[
|
||||
url: env.GIT_REPO_URL,
|
||||
credentialsId: env.CREDENTIALS_ID
|
||||
]]])
|
||||
}
|
||||
}
|
||||
stage('构建') {
|
||||
steps {
|
||||
echo '-- 构建过程开始 --'
|
||||
sh 'npm -v'
|
||||
sh 'apt install nasm -y'
|
||||
sh 'npm install hexo -g'
|
||||
sh 'npm install'
|
||||
echo '-- hexo环境构建完成 --'
|
||||
}
|
||||
}
|
||||
stage('编译') {
|
||||
steps {
|
||||
echo '-- 开始生成文章 --'
|
||||
sh 'hexo cl'
|
||||
sh 'hexo g'
|
||||
echo '-- 文章生成结束 --'
|
||||
}
|
||||
}
|
||||
stage('同步至Github') {
|
||||
steps {
|
||||
echo '-- Push to Github --'
|
||||
sh 'ls'
|
||||
sh 'git remote set-url origin https://DefectingCat:$ID@github.com/DefectingCat/DefectingCat.github.io.git'
|
||||
sh 'git remote -v'
|
||||
sh 'git push origin HEAD:file'
|
||||
echo '-- All Done --'
|
||||
}
|
||||
}
|
||||
stage('部署至云存储') {
|
||||
steps {
|
||||
echo '-- 开始部署至云存储 --'
|
||||
sh "coscmd config -a ${env.COS_SECRET_ID} -s ${env.COS_SECRET_KEY} -b ${env.COS_BUCKET_NAME} -r ${env.COS_BUCKET_REGION}"
|
||||
sh 'rm -rf .git'
|
||||
sh 'coscmd upload -r ./public/ /'
|
||||
echo '-- 部署完成 --'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
source/_data/backup.md
Normal file
@ -0,0 +1,7 @@
|
||||
## 自定义配置项备份
|
||||
|
||||
`custom.styl`:自定义css,`source/css/_custom`
|
||||
|
||||
`tranquil-heart.min.css`:高亮css,`source/lib/prettify`
|
||||
|
||||
`zh-Hans.yml`:`languages`
|
95
source/_data/custom.styl
Normal file
@ -0,0 +1,95 @@
|
||||
// Custom styles
|
||||
//图片
|
||||
img
|
||||
border-radius 0.8rem
|
||||
transition all .5s
|
||||
-webkit-transition all .5
|
||||
-ms-transition all .5
|
||||
img:hover
|
||||
transition: all 0.5s
|
||||
-webkit-transition: all 0.5s
|
||||
-ms-transition: all 0.5s
|
||||
transform: scale(1.05)
|
||||
|
||||
/*ul and ol*/
|
||||
.markdown-body a
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
color: #00f4e8;
|
||||
transition: all .12s;
|
||||
|
||||
.markdown-body li:hover
|
||||
text-shadow: 3px 3px 2px #2f2f2f57;
|
||||
|
||||
/*ol使用css排序进行定义序号*/
|
||||
.markdown-body ol
|
||||
counter-reset: xxx 0 !important;
|
||||
|
||||
.markdown-body ol li:before
|
||||
content: counter(xxx,decimal) "." !important;
|
||||
counter-increment: xxx 1 !important;
|
||||
position:absolute;
|
||||
font-family:'Comic Sans MS','Open Sans','Microsoft Yahei','Microsoft Yahei',-apple-system,sans-serif !important;
|
||||
color:#000;
|
||||
top:0;
|
||||
left:0;
|
||||
text-align:center;
|
||||
font-size:1.2em;
|
||||
opacity:.5;
|
||||
/*使用行高来调整前缀的上下位置*/
|
||||
line-height:1.33;
|
||||
text-shadow:4px 4px 1px rgba(0,0,0,.1);
|
||||
-webkit-transition:.5s;transition:.5s
|
||||
|
||||
.markdown-body ol li:hover:before
|
||||
-webkit-transform:scale(2);-ms-transform:scale(2);transform:scale(2);
|
||||
opacity:1;
|
||||
text-shadow:2px 2px 1px rgba(0,0,0,.1);
|
||||
-webkit-transition:.1s;transition:.1s
|
||||
|
||||
.markdown-body ol li
|
||||
list-style:none;
|
||||
position:relative;
|
||||
padding:0 0 0 2.1em;
|
||||
margin:0 0 0 10px;
|
||||
text-shadow:0px 0px 0px rgba(0,0,0,.1);
|
||||
-webkit-transition:.12s;transition:.12s;
|
||||
|
||||
/*ul使用插入圆点来模拟无序列表*/
|
||||
.markdown-body ul li:before
|
||||
position:absolute;
|
||||
content:'\2022';
|
||||
font-family:Arial;
|
||||
color:#000;
|
||||
top:0;
|
||||
left:0;
|
||||
text-align:center;
|
||||
font-size:1.5em;
|
||||
opacity:.5;
|
||||
/*使用行高来调整前缀的位置*/
|
||||
line-height:1;
|
||||
text-shadow:4px 4px 1px rgba(0,0,0,.1);
|
||||
-webkit-transition:.5s;transition:.5s
|
||||
|
||||
.markdown-body ul li:hover:before
|
||||
-webkit-transform:scale(2);-ms-transform:scale(2);transform:scale(2);
|
||||
opacity:1;
|
||||
text-shadow:2px 2px 1px rgba(0,0,0,.1);
|
||||
-webkit-transition:.1s;transition:.1s
|
||||
.markdown-body ul li
|
||||
list-style:none;
|
||||
position:relative;
|
||||
padding:0 0 0 1.5em;
|
||||
margin:0 0 0 10px;
|
||||
text-shadow:0px 0px 0px rgba(0,0,0,.1);
|
||||
-webkit-transition:.12s;transition:.12s;
|
||||
|
||||
//CC
|
||||
.note.note-warning
|
||||
background-color: #a3f3f1a1;
|
||||
border-color: #ff81c0 !important;
|
||||
|
||||
//selection伪元素
|
||||
::selection
|
||||
background: #a2f1f1
|
||||
text-shadow: 0px 0px 25px
|
5
source/_data/getTime.js
Normal file
@ -0,0 +1,5 @@
|
||||
let d = new Date();
|
||||
|
||||
let result = d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + ' ' + d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds();
|
||||
|
||||
console.log(result);
|
4
source/_data/m.sh
Normal file
@ -0,0 +1,4 @@
|
||||
git checkout backup
|
||||
git merge master
|
||||
git push
|
||||
git checkout master
|
35
source/_data/p.sh
Normal file
@ -0,0 +1,35 @@
|
||||
echo -e "Do someting:"
|
||||
echo -e "\033[31m 1.Bug \033[0m \033[36m 2.Update \033[0m \033[35m 3.Docs \033[0m \033[29m 4.Heart \033[0m"
|
||||
read git
|
||||
#read input
|
||||
case $git in
|
||||
1) echo -n 'Bug:'
|
||||
read input
|
||||
git add -A
|
||||
git commit -m "🐛$input"
|
||||
git push
|
||||
;;
|
||||
2) echo -n 'Update:'
|
||||
read input
|
||||
git add -A
|
||||
git commit -m "🛠$input"
|
||||
git push
|
||||
;;
|
||||
3) echo -n 'Docs:'
|
||||
read input
|
||||
git add -A
|
||||
git commit -m "📃$input"
|
||||
git push
|
||||
;;
|
||||
4) echo -n 'Heart:'
|
||||
read input
|
||||
git add -A
|
||||
git commit -m "❤$input"
|
||||
git push
|
||||
;;
|
||||
*) echo 'Maybe not today.'
|
||||
;;
|
||||
esac
|
||||
# git add -A
|
||||
# git commit -m "❤$input"
|
||||
# git push
|
38
source/_data/sitemap_template.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
{% for post in posts %}
|
||||
<url>
|
||||
<loc>{{ post.permalink | uriencode }}</loc>
|
||||
{% if post.updated %}
|
||||
<lastmod>{{ post.updated | formatDate }}</lastmod>
|
||||
{% elif post.date %}
|
||||
<lastmod>{{ post.date | formatDate }}</lastmod>
|
||||
{% endif %}
|
||||
</url>
|
||||
{% endfor %}
|
||||
|
||||
<url>
|
||||
<loc>{{ config.url | uriencode }}</loc>
|
||||
<lastmod>{{ sNow | formatDate }}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
|
||||
{% for tag in tags %}
|
||||
<url>
|
||||
<loc>{{ tag.permalink | uriencode }}</loc>
|
||||
<lastmod>{{ sNow | formatDate }}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
{% endfor %}
|
||||
|
||||
{% for cat in categories %}
|
||||
<url>
|
||||
<loc>{{ cat.permalink | uriencode }}</loc>
|
||||
<lastmod>{{ sNow | formatDate }}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
{% endfor %}
|
||||
</urlset>
|
7
source/_data/添加水印.xbs
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><XnView_script version="1.0" name="添加水印">
|
||||
<Watermark filename="C:/Users/xfy/OneDrive/Pictures/Logo/文字logo标题.png" opacity="85" no_alpha="false" position="8" delta_x="-25" delta_y="-25" perc="15" size="3"/>
|
||||
<Output folder="" filename="{Filename}" case="0" startIndex="1" format="WEBP">
|
||||
<Options overwrite="1" orgDate="false" keepMeta="false" keepICC="false" keepFolder="false" keepParentFolder="false" keepExtension="true" delOrg="true" multipage="false" allPages="false" openExplorer="false" openBrowser="false" clearItems="false"/>
|
||||
<WEBP method="0" quality="75" filesize="128" compress="4" strength="60" sharpness="0" preset="0"/>
|
||||
</Output>
|
||||
</XnView_script>
|
6
source/_data/转换webp.xbs
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><XnView_script version="1.0" name="转换webp">
|
||||
<Output folder="" filename="{Filename}" case="0" startIndex="1" format="WEBP">
|
||||
<Options overwrite="1" orgDate="false" keepMeta="false" keepICC="false" keepFolder="false" keepParentFolder="false" keepExtension="true" delOrg="true" multipage="false" allPages="false" openExplorer="false" openBrowser="false" clearItems="false"/>
|
||||
<WEBP method="0" quality="75" filesize="128" compress="4" strength="60" sharpness="0" preset="0"/>
|
||||
</Output>
|
||||
</XnView_script>
|
270
source/_md/DOM.md
Normal file
@ -0,0 +1,270 @@
|
||||
DOM(文档对象模型,Document Object Model),是针对 HTML 和 XML 文档的一个 API。DOM 脱胎于 Netscape 以及微软创始的 DHTML。
|
||||
|
||||
> IE 中所有 DOM 对象都是以 COM 对象的形式实现的,与原生 JavaScript 对象的行为或活动特点不一致。
|
||||
|
||||
## 节点层次
|
||||
|
||||
节点之间的关系构成了层次,而所有页面标记则表现为一个以特点节点为根节点的树形结构。也就是文档树。
|
||||
|
||||
文档节点是每个文档的根节点,文档节点只有一个子节点,称之为文档元素。在 HTML 中,文档元素始终都是`<html>`元素。
|
||||
|
||||
### Node 类型
|
||||
|
||||
总共有 12 中节点类型,这些节点类型都继承自一个基类型。DOM1 级定义了一个 Node 接口,该接口将由 DOM 中所有的节点类型实现。Node 接口在 JavaScript 中是作为 Node 类型实现的;除 IE 外(IE8 及以下),其他所有浏览器都可以访问到这个类型。
|
||||
|
||||
每个节点都有一个`nodeType`属性,用于表明节点类型。在 Node 类型中分别定义了 12 个数值常量来表示:
|
||||
|
||||
**节点类型常量**
|
||||
|
||||
| 常量 | 值 | 描述 |
|
||||
| :--------------------------------- | :--- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Node.ELEMENT_NODE` | `1` | 一个 [`元素`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element) 节点,例如 [`p`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/p) 和 [`div`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/div)。 |
|
||||
| `Node.TEXT_NODE` | `3` | [`Element`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element) 或者 [`Attr`](https://developer.mozilla.org/zh-CN/docs/Web/API/Attr) 中实际的 [`文字`](https://developer.mozilla.org/zh-CN/docs/Web/API/Text) |
|
||||
| `Node.CDATA_SECTION_NODE` | `4` | 一个 [`CDATASection`](https://developer.mozilla.org/zh-CN/docs/Web/API/CDATASection),例如 `<!CDATA[[ … ]]>`。 |
|
||||
| `Node.PROCESSING_INSTRUCTION_NODE` | `7` | 一个用于XML文档的 [`ProcessingInstruction`](https://developer.mozilla.org/zh-CN/docs/Web/API/ProcessingInstruction) ,例如 `<?xml-stylesheet ... ?>` 声明。 |
|
||||
| `Node.COMMENT_NODE` | `8` | 一个 [`Comment`](https://developer.mozilla.org/zh-CN/docs/Web/API/Comment) 节点。 |
|
||||
| `Node.DOCUMENT_NODE` | `9` | 一个 [`Document`](https://developer.mozilla.org/zh-CN/docs/Web/API/Document) 节点。 |
|
||||
| `Node.DOCUMENT_TYPE_NODE` | `10` | 描述文档类型的 [`DocumentType`](https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentType) 节点。例如 `<!DOCTYPE html>` 就是用于 HTML5 的。 |
|
||||
| `Node.DOCUMENT_FRAGMENT_NODE` | `11` | 一个 [`DocumentFragment`](https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment) 节点 |
|
||||
|
||||
**已经弃用的节点类型**
|
||||
|
||||
| 常量 | 值 | 描述 |
|
||||
| ---------------------------- | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Node.ATTRIBUTE_NODE` | 2 | [`元素`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element) 的耦合[`属性`](https://developer.mozilla.org/zh-CN/docs/Web/API/Attr) 。在 [DOM4](https://www.w3.org/TR/dom/) 规范里[`Node`](https://developer.mozilla.org/zh-CN/docs/Web/API/Node) 接口将不再实现这个元素属性。 |
|
||||
| `Node.ENTITY_REFERENCE_NODE` | 5 | 一个 XML 实体引用节点。 在 [DOM4](https://www.w3.org/TR/dom/) 规范里被移除。 |
|
||||
| `Node.ENTITY_NODE` | 6 | 一个 XML `<!ENTITY ...>` 节点。 在 [DOM4](https://www.w3.org/TR/dom/) 规范中被移除。 |
|
||||
| `Node.NOTATION_NODE` | 12 | 一个 XML `<!NOTATION ...>` 节点。 在 [DOM4](https://www.w3.org/TR/dom/) 规范里被移除. |
|
||||
|
||||
通过比较`nodeType`属性中包含的上述常量,就可以确定节点的类型。由于早期的 IE 没有公开 Node 类型,为了兼容所有浏览器,可以使用`nodeType`属性的数值量进行对比:
|
||||
|
||||
```js
|
||||
if (someNode.nodeType === 1) {
|
||||
console.log(`this is an element`)
|
||||
}
|
||||
```
|
||||
|
||||
要详细的查看节点的信息,还有`nodeName`和`nodeValue`属性。这两个属性的值完全取决于节点的类型。对于 Element 节点,`nodeName`就是其标签名,而`nodeValue`始终为`null`。
|
||||
|
||||
```js
|
||||
let c = document.body.appendChild(document.createComment('test'));
|
||||
c.nodeName // "#comment"
|
||||
c.nodeValue // "test"
|
||||
```
|
||||
|
||||
#### 节点关系
|
||||
|
||||
每个节点都有一个`childNodes`属性,其中保存着一个 NodeList 对象,这个对象是一个类数组对象,用于保存一组有序的节点。NodeList 对象的独特之处在于,它实际上是基于 DOM 结构动态查询的结果,而不是静态的。
|
||||
|
||||
`childNodes`会返回所有子节点,不仅仅只是 Element 元素,可以使用`children`来访问子元素。
|
||||
|
||||
```js
|
||||
div.childNodes
|
||||
// NodeList(9) [text, span, text, div, text, div, text, img, text]
|
||||
```
|
||||
|
||||
虽然 NodeList 对象是一个类数组,但是可以通过数组方法`slice()`将其直接转换为数组。
|
||||
|
||||
```js
|
||||
let test = Array.prototype.slice.call(div.childNodes, 0);
|
||||
Object.prototype.toString.call(test)
|
||||
// "[object Array]"
|
||||
```
|
||||
|
||||
但是 IE 早期无效,在早期 IE 中只能将其遍历转换为数组。因为其是类数组,所以 NodeList 是可迭代的(但是我的 IE 11 貌似不能用使用`for/of`)。
|
||||
|
||||
```js
|
||||
function converToArray(nodes) {
|
||||
let arr = null;
|
||||
try {
|
||||
arr = Array.prototype.slice.call(nodes.childNodes, 0);
|
||||
} catch(e) {
|
||||
arr = [];
|
||||
for (let i in nodes) {
|
||||
arr.push(nodes[i]);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
```
|
||||
|
||||
每个节点都有一个父节点,可以使用`parentNodes`属性来访问。父节点是唯一的,所有`childNodes`返回的节点都有同一个父节点。同样的,与`childNodes`相同的是,父节点不一定就是 Element,可以使用`parentElement`返回父元素节点。
|
||||
|
||||
```js
|
||||
let html = document.querySelector('html');
|
||||
html.parentElement;
|
||||
// null
|
||||
html.parentNode;
|
||||
// #document
|
||||
```
|
||||
|
||||
所有的子节点相互之间都是兄弟/同胞节点,可以通过使用`previousSibling`和`nestSibling`属性来访问相邻的同胞节点。
|
||||
|
||||
父节点的`firstChild`和`lastChild`分别指向 childNodes 列表中的第一个和最后一个节点。也就是说,`lastChild`等于`childNodes.length - 1`。
|
||||
|
||||
除了确定`childNodes.length`之外,还可以使用`hasChildNodes()`方法来确定是否拥有子节点。
|
||||
|
||||
所有节点的最后一个属性是`ownerDocument`,该属性指向整个文档的文档节点,也就是`document`。这种关系表示的任何节点都属于它所在的文档,任何节点都不能同时存在与两个或以上的文档中。有了该属性,就不必层层回溯到顶端,而是可以直接访问文档节点。
|
||||
|
||||
> 所有节点类型都继承自 Node,但并不是每种节点都有子节点。
|
||||
|
||||
节点关系的小结:
|
||||
|
||||
* 父节点是唯一的;
|
||||
* `childNodes`返回所有类型的子节点,`children`返回子元素节点;
|
||||
* `childNodes`返回的是类数组;
|
||||
* `parentNodes`返回父节点,`parentElement`返回父元素节点;
|
||||
* `previousSibling`和`nestSibling`属性来访问相邻的同胞节点;
|
||||
* `firstChild`和`lastChild`属性来访问第一个和最后一个子节点;
|
||||
* `hasChildNodes()`方法来确定是否拥有子节点;
|
||||
* `ownerDocument`直达`document`。
|
||||
|
||||
#### 操作节点
|
||||
|
||||
因为节点关系指针都是只读的,所以 DOM 提供了一系列的操作节点的方法。最常用的方法是生成节点:`append()`和`appendChild()`。他们都用于向 childNodes 列表后添加一个节点。
|
||||
|
||||
`append()`和`appendChild()`的区别是:
|
||||
|
||||
* `append()`方法允许添加 DOMString 对象,而`appendChild()`只允许添加 Node 对象;
|
||||
* `append()`可以一次添加多个节点和字符串,而`appendChild()`只能追加一个节点;
|
||||
* `append()`没有返回值,`appendChild()`返回追加的 Node 对象。
|
||||
|
||||
除此之外,两个方法其他部分都是相同的。
|
||||
|
||||
如果添加的节点是已经存存在与文档上,那么结果就是该节点会从文档中原来的位置移动到新的位置。类似于剪切操作,并且被操作的节点的子节点会跟随父节点一起移动。
|
||||
|
||||
还有一些其他的操作方法:
|
||||
|
||||
* `Element.insertBefor()`:在特定子元素前插入一个新的子元素;
|
||||
* `Element.replaceChild()`:替换特定子元素;
|
||||
* `Element.cloneNode()`:创建调用节点的副本,通过一个布尔参数来决定是否执行深复制,浅复制则不包括其子节点。所有类型节点都有该方法;
|
||||
* `normalize()`:唯一的左右就是处理文档树中的文本节点。
|
||||
|
||||
### Document 类型
|
||||
|
||||
JavaScript 通过 Document 类型来表示文档,也就是经常使用的`document`对象。`document`对象是 HTMLDocument 的一个实例。而 HTMLDocument 则继承自 Document 类型。
|
||||
|
||||
```
|
||||
Document --> HTMLDocument.prototype --> document
|
||||
```
|
||||
|
||||
document 对象是 window 的一个属性,所以它可以全局访问。它有一下特点:
|
||||
|
||||
* nodeType 为 9;
|
||||
* nodeName 为`#document`;
|
||||
* 其他值均为`null`;
|
||||
|
||||
它有四种可能的子节点:DocumentType、Element、ProcessingInstruction 或 Comment。
|
||||
|
||||
其中 DocumentType 和 Element 都最多只能有一个,他们就是 HTML 文档最常见的开头:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
```
|
||||
|
||||
> 早期 IE 无法访问 Document 类型的构造函数和原型。
|
||||
|
||||
#### 文档子节点
|
||||
|
||||
* `document.documentElement`始终指向`<HTML>`元素;
|
||||
* `document.body`指向`<body>`元素;
|
||||
* `document.doctype`指向`<!DOCTYPE html>`。
|
||||
|
||||
不同浏览器之间对于`<html>`标签之外的注释有着不同的见解,这也导致了在`<html>`元素外的注释没有什么用处。
|
||||
|
||||
#### 文档信息
|
||||
|
||||
* `document.title`:可以直接获取文档标题。
|
||||
|
||||
使用`document.title`配合两个定时器就可以设置一个经典的 Title 切换的方法:
|
||||
|
||||
```js
|
||||
setInterval(() => {
|
||||
document.title = '小肥羊'
|
||||
setTimeout(() => {
|
||||
document.title = '无敌螺旋小肥羊'
|
||||
}, 500);
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
* `document.URL`:获取当前文档的完整URL;
|
||||
* `document.domain`:获取当前域名;
|
||||
* `document.referrer`:获取来源页面URL。
|
||||
|
||||
使用`document.referrer`就可以轻松判断跳转自页面,可以配合 Live2d 做个简单的识别。
|
||||
|
||||
```js
|
||||
document.referrer
|
||||
// "https://www.baidu.com/link?url=Lx3l1h452xnMy39DdUnl4y2Dl84m7Rb22M2CCwvH6n033Jr7EmLO_LxUYYmg3VMx&wd=&eqid=ba12bd2a000c1485000000055ffdadd3"
|
||||
```
|
||||
|
||||
#### 查找元素
|
||||
|
||||
Document 类型有两个可以取得 DOM 元素的方法:
|
||||
|
||||
* `getElementById()`
|
||||
* `getElementByTagName()`
|
||||
|
||||
`getElementById()`在早期的 IE7 中调用可能会获取到带有`name="test"`的 Attribute 其他元素的 Bug。
|
||||
|
||||
`getElementByTagName()`返回的是一个 HTMLCollection 对象,它是一个动态的集合。该对象与 NodeList 非常类似,他们都可以使用方括号语法或`item()`方法来访问其中的项目。
|
||||
|
||||
```js
|
||||
div[0];
|
||||
div.item(0);
|
||||
// 使用名称
|
||||
div['myDiv'];
|
||||
```
|
||||
|
||||
HTMLDocument 类型也有一个独有的查询 DOM 的方法:
|
||||
|
||||
* `getElementByName()`:返回带有特定 name Attribute 的元素。
|
||||
|
||||
同样的,它返回的也是一个 NodeList 集合。
|
||||
|
||||
#### 特殊集合
|
||||
|
||||
除了一些常见的属性和方法之外,document 对象还提供了一些特殊的集合。这些集合都是 HTMLDocument 对象为访问文档常用的部分提供了快捷方式。
|
||||
|
||||
* `document.anchors`:文档中所有带 name 特性的`<a>`元素;
|
||||
* `document.applets`:文档中所有`<applets>`元素,已经弃用;
|
||||
* `document.forms`:文档中所有`<form>`元素;
|
||||
* `document.images`:文档中所有`<img>`元素,与`document.getElementByTagName('img')`结果相同;
|
||||
* `document.links`:文档中所有带`herf`特性的`<a>`元素。
|
||||
|
||||
这些特殊的集合也都是动态的。
|
||||
|
||||
#### DOM 一致性检测
|
||||
|
||||
DOM 分为多个级别,也包含多个部分,因此检测浏览器实现了 DOM 的哪些部分就十分必要了。`document.implementation`属性就是提供检测方法的对象。
|
||||
|
||||
#### 文档写入
|
||||
|
||||
将输出流写入到网页中的能力已经存在很多年了,这个能力体现在四个方法中:`write()`、`writeIn()`、`open()`和`close()`。
|
||||
|
||||
写入文本:
|
||||
|
||||
* `write()`:接受一个参数,原样写入;
|
||||
* `writeIn()`:接受一个参数,添加换行符`\n`写入。
|
||||
|
||||
```html
|
||||
<body>
|
||||
<p>The current date and time is:</p>
|
||||
</body>
|
||||
<script>
|
||||
document.write(`<strong>${(new Date()).toString()}</strong>`)
|
||||
</script>
|
||||
```
|
||||
|
||||
在页面呈现期间直接使用`document.write()`向页面添加内容是正常的,但是如果等页面渲染完毕了再使用`document.write()`添加内容就会**重写覆盖整个页面**。
|
||||
|
||||
### Element 类型
|
||||
|
||||
除了 Document 类型之外,Element 类型就是 Web 编程中最常用的类型了。它用于表现 XML 或 HTML 元素。
|
||||
|
||||
* nodeType 为 1;
|
||||
* nodeName 为元素的标签名;
|
||||
* nodeValue 为 null;
|
||||
* parentNode 可能是 Document 或 Element;
|
||||
|
5
source/_md/Gitea.md
Normal file
@ -0,0 +1,5 @@
|
||||
```bash
|
||||
docker pull nginx:alpine
|
||||
docker run --rm nginx:alpine cat /etc/nginx/nginx.conf > nginx.conf
|
||||
docker run --rm nginx:alpine cat /etc/nginx/conf.d/default.conf > default.conf
|
||||
```
|
78
source/_md/Map与Set.md
Normal file
@ -0,0 +1,78 @@
|
||||
传统对象的键(key)只能是字符串
|
||||
|
||||
Map 的键(key)可以是任何类型的,不像对象一样只能是字符串。
|
||||
|
||||
```js
|
||||
let map = new Map([
|
||||
[1, 'test'],
|
||||
['1', 'str']
|
||||
])
|
||||
```
|
||||
|
||||
`map[key]`不是使用 Map 的正确方式,虽然`map[key]`也有效,例如我们可以设置`map[key] = 2`,这样会将 Map 视为 JavaScript 的 plain object,因此它暗含了所有相应的限制(没有对象键等)。所以我们应该使用 Map 方法:`set`和`get`等。
|
||||
|
||||
`map.set`方法每次都会返回 Map 本身,所以可以使用“链式”调用:
|
||||
|
||||
```js
|
||||
let map = new Map();
|
||||
|
||||
map.set(obj, 'this is a object')
|
||||
.set('test', 12312313123)
|
||||
.set('obj', obj)
|
||||
```
|
||||
|
||||
## Map 迭代
|
||||
|
||||
Map 有三种迭代方法
|
||||
|
||||
* `map.keys()` —— 遍历并返回所有的键(returns an iterable for keys),
|
||||
* `map.values()` —— 遍历并返回所有的值(returns an iterable for values),
|
||||
* `map.entries()` —— 遍历并返回所有的实体(returns an iterable for entries)`[key, value]`,`for..of`在默认情况下使用的就是这个。
|
||||
|
||||
这三个方法都是能够将 Map 键值迭代出来,同时它们自身也是可迭代的:
|
||||
|
||||
```js
|
||||
for (let i of map.entries()) {
|
||||
console.log(i);
|
||||
}
|
||||
```
|
||||
|
||||
> 迭代的顺序与插入值的顺序相同。与普通的 Object 不同,Map 保留了此顺序。
|
||||
|
||||
### 从对象创建 Map
|
||||
|
||||
对象方法`Object.entries()`的返回格式正好与创建 Map 构造函数相同,因此可以使用该方法使用对象创建 Map。
|
||||
|
||||
```js
|
||||
let obj = {
|
||||
name: 'xfy',
|
||||
age: 18
|
||||
}
|
||||
|
||||
console.log(Object.entries(obj));
|
||||
let map = new Map(Object.entries(obj));
|
||||
console.log(map.get('age'));
|
||||
```
|
||||
|
||||
### 从 Map 创建对象
|
||||
|
||||
`Object.fromEntries()`方法的作用是相反的,它可以从 Map 迭代返回的键值对中创建对象。并且具有各种类型的键会被转换为字符串。
|
||||
|
||||
```js
|
||||
let test = 'xfy';
|
||||
|
||||
let map = new Map([
|
||||
[1, 3],
|
||||
[test, 4]
|
||||
])
|
||||
|
||||
let obj = Object.fromEntries(map.entries());
|
||||
console.log(obj);
|
||||
```
|
||||
|
||||
Set
|
||||
|
||||
Set 是一个特殊的类型集合 —— “值的集合”(没有键),它的每一个值只能出现一次。
|
||||
|
||||
### Set 迭代
|
||||
|
90
source/_md/回首那个jQuery一把梭的年代.md
Normal file
@ -0,0 +1,90 @@
|
||||
## 使用
|
||||
|
||||
在页面中引入jQuery有多种方法,最常用的方法就是下载jQuery库到本地和使用公共CDN。jQuery 库是一个 JavaScript 文件,可以直接使用`script`标签引入到html页面中。
|
||||
|
||||
```html
|
||||
<head>
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.js"></script>
|
||||
</head>
|
||||
```
|
||||
|
||||
另外,现在的jQuery也支持在node上下载和使用。
|
||||
|
||||
```js
|
||||
npm install jquery
|
||||
yarn add jquery
|
||||
```
|
||||
|
||||
[下载]:(https://jquery.com/download/)
|
||||
|
||||
## 语法
|
||||
|
||||
jQuery简化了原生JS对DOM的操作,语法也与原生JS不尽相同。jQuery 语法是通过选取 HTML 元素,并对选取的元素执行某些操作。
|
||||
|
||||
基础语法: $(selector).action()
|
||||
|
||||
* 美元符号定义 jQuery
|
||||
* 选择符(selector)"查询"和"查找" HTML 元素
|
||||
* jQuery 的 action() 执行对元素的操作
|
||||
|
||||
示例:
|
||||
|
||||
* `$(this).hide()` - 隐藏当前元素
|
||||
* `$("p").hide()` - 隐藏所有 <p> 元素
|
||||
* `$("p.test").hide()` - 隐藏所有 class="test" 的 <p> 元素
|
||||
* `$("#test").hide()` - 隐藏 id="test" 的元素
|
||||
|
||||
> jQuery 使用的语法是 XPath 与 CSS 选择器语法的组合。
|
||||
|
||||
### 文档就绪函数
|
||||
|
||||
文档就绪函数是问了防止jQuery在DOM没有加载完之前就开始运行的一个函数,它通常是这样的:
|
||||
|
||||
```js
|
||||
$(document).ready(function() {
|
||||
// code
|
||||
})
|
||||
```
|
||||
|
||||
如果在文档没有完全加载之前就运行函数,操作可能失败。下面是两个具体的例子:
|
||||
|
||||
* 试图隐藏一个不存在的元素
|
||||
* 获得未完全加载的图像的大小
|
||||
|
||||
文档就绪函数还有一个简洁的写法,效果完全一样。
|
||||
|
||||
```js
|
||||
$(function() {
|
||||
// code
|
||||
})
|
||||
```
|
||||
|
||||
原生JS也有类似的入口函数:
|
||||
|
||||
```js
|
||||
window.onload = function () {
|
||||
// code
|
||||
}
|
||||
```
|
||||
|
||||
jQuery 入口函数与 JavaScript 入口函数的区别:
|
||||
|
||||
* jQuery 的入口函数是在 html 所有标签(DOM)都加载之后,就会去执行。
|
||||
* JavaScript 的 window.onload 事件是等到所有内容,包括外部图片之类的文件加载完后,才会执行。
|
||||
|
||||
<p style="text-align: center">load和ready区别<p>
|
||||
|
||||
| | window.onload() | $(document).ready() |
|
||||
| -------- | ---------------------------------------------- | --------------------------------------- |
|
||||
| 执行时机 | 必须等待网页加载完才能执行(包括图片等) | 只需等待网页中的DOM结构加载完毕就能执行 |
|
||||
| 执行次数 | 只能执行一次,如果第二次,那么第一次就会被覆盖 | 可以执行多次,多次都不会被覆盖 |
|
||||
| 简写方式 | 无 | $(function() { <br> // code <br> }) |
|
||||
|
||||
## 选择器
|
||||
|
||||
jQuery 中所有选择器都以美元符号开头:`$()`。选择器基于元素的 id、类、类型、属性、属性值等"查找"(或选择)HTML 元素。 它基于已经存在的 CSS 选择器,除此之外,它还有一些自定义的选择器。
|
||||
|
||||
## 事件
|
||||
|
||||
jQuery 是为事件处理特别设计的。
|
||||
|
205
source/_md/客户端检测.md
Normal file
@ -0,0 +1,205 @@
|
||||
浏览器发展至今,各种主流浏览器都实现了各自的长处,随之而来的就是各种不一致性的问题。
|
||||
|
||||
## 能力检测
|
||||
|
||||
最常用的一种客户端检测形式就是**能力检测**(特征检测)。能力检测不是去识别特定的浏览器,而是去识别浏览器的能力。只要确定了浏览器支持的特定能力,就可以给出特定的解决方案。检测手段也很简单,只需要用到简单的类型转换:
|
||||
|
||||
```js
|
||||
if (Object.propertyInQuestion) {
|
||||
// 使用特定能力
|
||||
}
|
||||
```
|
||||
|
||||
来看一个简单的例子,在IE5.0之前不支持`document.getElementById()`这个DOM方法,但是可以使用非标准的`document.all`属性来实现相同的目的。所以:
|
||||
|
||||
```js
|
||||
function getElementId(id) {
|
||||
if (document.getElementById) {
|
||||
return document.getElementById(id);
|
||||
} else if (document.all) {
|
||||
return document.all(id);
|
||||
} else {
|
||||
throw new Error('No way to get element id.')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里先判断标准方法是否存在,如果存在就直接使用。如果不存在,就使用IE5.0之前的非标准方法。如果二者都没有,则抛出一个错误。
|
||||
|
||||
### 更可靠的能力检测
|
||||
|
||||
仅仅靠简单的类型转换来检测是不够完善的,不仅仅要知道某个属性是否存在,还需要知道它是不是我们所需要的那个方法。如果仅使用类型转换来做判断,那么可能会遇到这样的问题:
|
||||
|
||||
```js
|
||||
function isSortable(obj) {
|
||||
return !!obj.sort;
|
||||
}
|
||||
|
||||
let someObj = {
|
||||
sort: 1
|
||||
}
|
||||
|
||||
isSortable(someObj); //true
|
||||
```
|
||||
|
||||
可以考虑善用`typeof`操作符,例如:
|
||||
|
||||
```js
|
||||
function isSortable(obj) {
|
||||
return typeof obj.sort == 'function';
|
||||
}
|
||||
```
|
||||
|
||||
不过`typeof`操作符也不是完美的解决方案,在早期的IE中,某些DOM方法返回的是`object`而不是`function`。例如`document.createElement()`方法。
|
||||
|
||||
除此之外,IE的ActiveX对象与其他对象的行为差异很大。例如:
|
||||
|
||||
```js
|
||||
let xhr = new ActiveXObject('Microsoft.XMLHttp');
|
||||
if (xhr.open) { //发生错误
|
||||
// do something...
|
||||
}
|
||||
|
||||
typeof (xhr.open); //unknow
|
||||
```
|
||||
|
||||
当然针对IE也是有解决办法的:
|
||||
|
||||
```js
|
||||
//来自 Peter Michaux
|
||||
function isHostMethod(object, property) {
|
||||
let t = object[property];
|
||||
return t == 'function' || (!!(t == 'object' && object[property])) || t == 'unknow';
|
||||
}
|
||||
```
|
||||
|
||||
## 怪癖检测
|
||||
|
||||
怪癖检测,也就是Bug检测。通过确定浏览器以有的Bug来确定某一个特性不能正常工作。
|
||||
|
||||
在IE8以及之前中有个Bug,将某个实例的属性设置为与标记了`[[Enumerbale]]`为`false`的某个原型属性同名,那么该属性就不会被枚举。可以这样来检测:
|
||||
|
||||
```js
|
||||
(function hasEnumerableQuirk() {
|
||||
let obj = {
|
||||
toString: function () {}
|
||||
}
|
||||
for (let i in obj) {
|
||||
if (i == 'toString') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
```
|
||||
|
||||
在Safari 3以前的版本中也有一个Bug,实例会枚举被隐藏的同名的原型属性。
|
||||
|
||||
```js
|
||||
(function hasEnumerShadowQuirk() {
|
||||
let obj = {
|
||||
toString: function () {}
|
||||
}
|
||||
let count = 0;
|
||||
for (i in obj) {
|
||||
if (i == 'toString') {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return (count > 1);
|
||||
})()
|
||||
```
|
||||
|
||||
## 用户代理字符串的历史
|
||||
|
||||
这是一段很有趣的浏览器历史。
|
||||
|
||||
// maybe later
|
||||
|
||||
## 用户代理字符串检测
|
||||
|
||||
用户代理字符串也就是常见的UA(UserAgent)。考虑到各个主流浏览器的发展历史,所以UA的判断也变的比较复杂。当然对于现代更加复杂的浏览器环境来说,识别出详细的浏览器还是需要更多的检测依据。
|
||||
|
||||
### 识别呈现引擎
|
||||
|
||||
呈现引擎,也就是浏览器的内核。每个引擎都有一些自己的特性,但是要正确的识别出引擎,关键还是识别顺序。
|
||||
|
||||
为了不污染全局变量,这里使用局部变量的命名来命名。这个方法最终返回一个对象,这个对象就是根据检测到的引擎版本的键值对。
|
||||
|
||||
```js
|
||||
let client = function () {
|
||||
let engine = {
|
||||
// 主流引擎
|
||||
trident: 0,
|
||||
gecko: 0,
|
||||
webkit: 0,
|
||||
khtml: 0,
|
||||
opera: 0,
|
||||
|
||||
// 具体版本号
|
||||
ver: null
|
||||
}
|
||||
|
||||
return {
|
||||
engine: engine
|
||||
}
|
||||
```
|
||||
|
||||
基本的变量命名都准备好了,接下来就是判断了。我们的第一步就是识别 opera,因为它的用户代理字符串有可能完全模仿其他浏览器。
|
||||
|
||||
判断 opera 很简单,不需要去检测 ua 中的字符串,它有个全局变量`window.opera`供我们检测:
|
||||
|
||||
```js
|
||||
if (window.opera) {
|
||||
engine.ver = window.opera.version();
|
||||
engine.opera = parseFloat(engine.ver);
|
||||
}
|
||||
```
|
||||
|
||||
第二步就是 WebKit 了,WebKit 需要我们通过判断 ua 字符串内的特定内容来识别它。在客户端获取 UA 最好的办法就是通过`navigator.userAgent`属性。
|
||||
|
||||
```js
|
||||
let ua = navigator.userAgent;
|
||||
let webkit = /AppleWebKit\/(\S+)/;
|
||||
|
||||
if (webkit.test(ua)) {
|
||||
engine.ver = ua.match(webkit)[1];
|
||||
engine.webkit = parseFloat(engine.ver);
|
||||
}
|
||||
```
|
||||
|
||||
KHTML 的用户代理字符串中也包含 Gecko,因此在排除 KHTML 之前,无法准确检测基于 Gecko 的浏览器。
|
||||
|
||||
```js
|
||||
let khtml = /KHTML\/(\S+)/;
|
||||
let khtml1 = /Konqueror\/([^;]+)/;
|
||||
|
||||
if (khtml.test(ua) || khtml1.test(ua)) {
|
||||
engine.ver = ua.match(khtml)[1];
|
||||
engine.khtml = parseFloat(engine.ver);
|
||||
}
|
||||
```
|
||||
|
||||
在排除了 KHTML 与 WebKit 之后,就可以去检测 Gecko 了,Gecko 的版本号不一定会出现在 Gecko 关键字后面,而是会出现在`rv:`的后面。
|
||||
|
||||
```js
|
||||
let gecko = /rv:([^\)]+)\) Gecko\/\d{8}/;
|
||||
if (gecko.test(ua)) {
|
||||
engine.ver = ua.match(gecko)[1];
|
||||
engine.gecko = engine.ver;
|
||||
}
|
||||
```
|
||||
|
||||
在所有的大哥都被排除了之后,最后剩下的就是 IE 了。在最新版本的 IE11 中已经没有关键字`MSIE`,取而代之的是`rv:`。并且有个独有的关键字`WOW64`。
|
||||
|
||||
```js
|
||||
let trident = {
|
||||
wow: /WOW64/,
|
||||
rv: /rv:([^\)]+)/
|
||||
};
|
||||
if (trident.wow.test(ua)) {
|
||||
engine.ver = ua.match(trident.rv)[1];
|
||||
engine.trident = engine.ver;
|
||||
}
|
||||
```
|
||||
|
15
source/_md/容器化!nginx与certbot.md
Normal file
@ -0,0 +1,15 @@
|
||||
## Nginx 与反代
|
||||
|
||||
在很久以前,曾今尝试过将自己当时的小破站[所有的服务都使用 Docker 来部署](https://www.defectink.com/defect/docker-container-all.html),在未来迁移也会更加方便。也就是那时,正真的用上了 Docker。
|
||||
|
||||
不过这次水的部分不同,没想到过直接把小破站迁移到了 Hexo。这次准备搭建一些其他的服务,而有些东西对于 SSL 的配置很是复杂,并且多个应用在一起端口也不方便分配。于是就想到了使用 Nginx 来做反代。
|
||||
|
||||
## Nginx 的安装与配置
|
||||
|
||||
### 修改配置文件
|
||||
|
||||
## 使用 certbot 获取证书
|
||||
|
||||
### 为 Nginx 配置 SSL
|
||||
|
||||
### 自动续期
|
70
source/_md/探索Node.js基本概念.md
Normal file
@ -0,0 +1,70 @@
|
||||
为了深入了解同步与异步编程,和充分理解这一理念。打算详细的了解与对比这两种编程模式。
|
||||
|
||||
## Node编程风格
|
||||
|
||||
随便搜寻几篇 Node.js 的文章便会发现,node 主要的编程风格便是使用非阻塞(异步)编码风格。先通过一个日常的举例来了解下阻塞(同步)编码与非阻塞(异步)编码之间的差异。
|
||||
|
||||
## 同步与异步的对比 – 场景一
|
||||
|
||||
Node.js 官网关于阻塞式 I/O 的定义如下:
|
||||
|
||||
> “阻塞表示 Node.js 进程中其他 JavaScript 的执行必须等到非 JavaScript 操作完成后才能继续的情况。发生这种情况的原因在于,当发生阻塞操作时,事件循环无法继续运行 JavaScript。
|
||||
> 在 Node.js 中,由于 CPU 占用率高而不是等待非 JavaScript 操作(如 I/O)导致性能欠佳的 JavaScript 通常并不被称为阻塞。”
|
||||
|
||||
### 同步
|
||||
|
||||
普通的同步风格程序。它在 V8 线程上自上而下运行,仅占用少量 CPU,这是非技术层面的阻塞。
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
console.log(Date.now().toString() + ': 主进程开始;');
|
||||
console.log(Date.now().toString() + ': 创造一个延迟;');
|
||||
let start = Date.now();
|
||||
let end = start;
|
||||
while(end < start + 20) {
|
||||
end = Date.now(); // 阻塞延迟
|
||||
}
|
||||
console.log(Date.now().toString() + ': 主进程结束;');
|
||||
```
|
||||
|
||||
这里使用了`while`通过不断的检查系统时间来创建一个阻塞的延迟动作,根据最后打印的时间戳,总共运行时间在 21~23 毫秒之间。
|
||||
|
||||
### 异步
|
||||
|
||||
使用异步的方式创建了与上述同样效果的实例。
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
console.log(Date.now().toString() + ': 主进程开始;');
|
||||
setTimeout(() => {
|
||||
console.log(Date.now().toString() + ': 事件循环回调');
|
||||
}, 20);
|
||||
console.log(Date.now().toString() + ': 主进程结束;');
|
||||
```
|
||||
|
||||
这里的异步使用了`setTimeout`来将事件超时运行。根据最后打印的时间戳,总共运行时间在 25~30 毫秒之间。
|
||||
|
||||
## 场景一小结
|
||||
|
||||
经过上述的小实验,不难发现同步的运行时间比异步还短那么几毫秒。异步反而更慢,异步编程风格并不关乎单纯的速度,而是关乎可扩展性。
|
||||
|
||||
## 模块系统
|
||||
|
||||
模块化是现代软件开发中的一个关键概念。它使我们能够构建更健壮的代码,并在多处复用代码,而无需重复编写相同的代码。
|
||||
|
||||
Node 模块化不仅为我们提供了上述所有好处,还提供了:
|
||||
|
||||
* 自动封装。默认情况下,一个模块中的所有代码都被打包到函数包装程序中,以便对模块外部的其他 JavaScript 代码隐藏。
|
||||
|
||||
* 一种公开模块接口的方式。模块中的函数、变量和其他构造必须通过 module.exports(或者其简短表示:exports)对模块外部的 JavaScript 模块显式公开。
|
||||
|
||||
### 引入模块
|
||||
|
||||
在 ES6 发布后,module 成为标准。曾经的我们采用的是 CommonJS 规范,使用`require()`来引入模块。目前的 ES6 标准使用`import`来引入模块。
|
||||
|
||||
```js
|
||||
let fs = require('fs');
|
||||
import fs from 'fs';
|
||||
```
|
||||
|
||||
ES2015 的 module
|
28
source/_md/正则表达式.md
Normal file
@ -0,0 +1,28 @@
|
||||
正则,听到这个词大脑就会油然而生一阵疼痛。它是那么的令人头疼,却又是那么强大。无论在什么语言环境下都离不开正则表达式的匹配,而学习它的最佳办法就是多尝试,记住了它的语法就能轻松掌握用法了。
|
||||
|
||||
匹配字符:`[abc]`,匹配`[...]`中的所有字符。
|
||||
|
||||
排除字符:`[^abc]`,匹配除了`[...]`中字符的所有字符。
|
||||
|
||||
区间匹配:`-`表示一个区间,`[0-9]`匹配0到9,`[a-z]`匹配a到z,区分大小写。
|
||||
|
||||
`.`:匹配除换行符(\n、\r)之外的任何单个字符,相等于`[^\n\r]`。
|
||||
|
||||
`\w`:匹配字母、数字、下划线。等价于`[A-Za-z0-9_]`。
|
||||
|
||||
用`\d`可以匹配一个数字,`\w`可以匹配一个字母或数字
|
||||
|
||||
用`*`表示任意个字符(包括0个),用`+`表示至少一个字符,用`?`表示0个或1个字符,用`{n}`表示n个字符,用`{n,m}`表示n-m个字符
|
||||
|
||||
- `[0-9a-zA-Z\_]`可以匹配一个数字、字母或者下划线;
|
||||
- `[0-9a-zA-Z\_]+`可以匹配至少由一个数字、字母或者下划线组成的字符串,比如`'a100'`,`'0_Z'`,`'Py3000'`等等;
|
||||
- `[a-zA-Z\_][0-9a-zA-Z\_]*`可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
|
||||
- `[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}`更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
|
||||
|
||||
`A|B`可以匹配A或B,所以`(P|p)ython`可以匹配`'Python'`或者`'python'`。
|
||||
|
||||
`^`表示行的开头,`^\d`表示必须以数字开头。
|
||||
|
||||
`$`表示行的结束,`\d$`表示必须以数字结束。
|
||||
|
||||
你可能注意到了,`py`也可以匹配`'python'`,但是加上`^py$`就变成了整行匹配,就只能匹配`'py'`了。
|
104
source/_md/路由中的动态组件-keepAlive与路由.md
Normal file
@ -0,0 +1,104 @@
|
||||
## 真正的动态组件
|
||||
|
||||
`<keep-alive>`经常配合`componentIs`来动态的切换组件,当组件再次被切换回来的时候,组件的状态依然被保存。
|
||||
|
||||
```js
|
||||
<keep-alive>
|
||||
<component :is="tabName" :postLists="postLists"></component>
|
||||
</keep-alive>
|
||||
```
|
||||
|
||||
## 在路由中的问题
|
||||
|
||||
在研究动态组件`<keep-alive>`的时候发现,如果配置了默认路由跳转的情况下,再子路由上使用`<keep-alive>`就无法达到预期的效果了。因为就算子组件的状态缓存了,但再次访问该组件时,跳转的还是默认的路由。
|
||||
|
||||
```html
|
||||
<!-- App.vue -->
|
||||
<router-link to="/home" tag="button">首页</router-link>
|
||||
<router-link to="/about" tag="button">关于</router-link>
|
||||
|
||||
<keep-alive>
|
||||
<router-view></router-view>
|
||||
</keep-alive>
|
||||
```
|
||||
|
||||
```js
|
||||
// 默认子路由
|
||||
{
|
||||
path: '/home',
|
||||
component: Home,
|
||||
meta: {
|
||||
title: '首页'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/home/news',
|
||||
},
|
||||
{
|
||||
path: 'news',
|
||||
component: HomeNews,
|
||||
},
|
||||
{
|
||||
path: 'message',
|
||||
component: HomeMessage,
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- Home 组件 -->
|
||||
<router-link to="/home/news" tag="button">新闻</router-link>
|
||||
<router-link to="/home/message" tag="button">消息</router-link>
|
||||
<router-view></router-view>
|
||||
```
|
||||
|
||||
在默认的情况下激活 Home 组件会跳转到`/home/news`。在第一次访问了【消息】这个子组件之后,组件的状态会被缓存下来,如果按照之前动态组件的案例来看,再次访问 Home 组件时,对应的依然是【消息】这个子组件。
|
||||
|
||||
但仅仅只是缓存了组件的状态还不够,再次访问 Home 组件时,URL 还是会被默认的路由覆盖,不会达到动态组件的效果。
|
||||
|
||||
### 手动推送 URL
|
||||
|
||||
目前发现的最佳的解决办法就是使用一个变量来保存离开 Home 组件时的 URL,再次激活 Home 组件时,再将 URL 推送回去。
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
path: "/home/news",
|
||||
};
|
||||
},
|
||||
```
|
||||
|
||||
当使用了`<keep-alive>`时,组件生命周期中会被调用`activated()`和 `deactivated()`两个方法。
|
||||
|
||||
使用`activated()`配合上路由的`beforeRouteLeave()`,就可以达到在离开组件时记录当前的 URL,并在重新激活组件时 push 新的 URL。
|
||||
|
||||
```js
|
||||
activated() {
|
||||
console.log("activated");
|
||||
if (!(location.pathname === this.path)) {
|
||||
this.$router.push(this.path);
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
console.log("leave");
|
||||
(this.path = from.path), next();
|
||||
},
|
||||
```
|
||||
|
||||
另外这个判断是必须的:`if (!(location.pathname === this.path))`。如果不判断当前的 URL 是否与缓存的 URL 一致,那么当组件激活时就会无条件的运行`this.$router.push(this.path);`,导致同一条路由被重写两遍。
|
||||
|
||||
并得到这样的错误:
|
||||
|
||||
```js
|
||||
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: ""
|
||||
```
|
||||
|
||||

|
||||
|
||||
这个错误的主要原因是因为同一条路由被重写两遍:NavigationDuplicated。
|
||||
|
||||
### 不缓存呢
|
||||
|
||||
在组件被创建时,`data`方法也会初始化,其中的值也会跟着初始化,没有办法记录路由离开前的 URL。
|
246
source/_posts/ASCII在线视频流.md
Normal file
@ -0,0 +1,246 @@
|
||||
---
|
||||
title: ASCII 在线视频流
|
||||
date: 2019-06-29 12:12:41
|
||||
tags: Tools
|
||||
categories: 实践
|
||||
url: online-ascii-video
|
||||
index_img: /images/ASCII在线视频流/logo.webp
|
||||
---
|
||||
|
||||
什么是ASCII?
|
||||
|
||||
来自百度百科的解释:
|
||||
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。
|
||||
|
||||
应该很多小伙伴们都非常熟悉ASCII码了,它也是现今最能玩的一套编码了吧(雾💊
|
||||
|
||||
那么ascii视频流又是啥呢?
|
||||
|
||||
这是来自某位大佬胡乱起的名字。🤣
|
||||
|
||||

|
||||
|
||||
## 那么如何安装呢?
|
||||
|
||||
根据[大佬的文章](https://file.aoaoao.me/2018/03/26/e6-9e-84-e5-bb-ba-e4-b8-80-e4-b8-aa-e5-9c-a8-e7-ba-bfascii-e8-a7-86-e9-a2-91-e6-b5-81-e6-9c-8d-e5-8a-a1/#如何搭建这么一个服务?)与开源项目。首先我们需要:
|
||||
|
||||
1. ffmpeg
|
||||
2. [hit9/img2txt](https://github.com/hit9/img2txt)
|
||||
3. [HFO4/plus1s.live](https://github.com/HFO4/plus1s.live)
|
||||
4. node.js/Go/Python运行环境
|
||||
|
||||
### 使用ffmpeg截取视频片段
|
||||
|
||||
安装ffmpeg:
|
||||
|
||||
**CentOS**
|
||||
由于CentOS没有官方FFmpeg rpm软件包。但是,我们可以使用第三方YUM源(Nux Dextop)完成此工作。
|
||||
|
||||
```centos7
|
||||
sudo rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
|
||||
sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
|
||||
sudo rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
|
||||
sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
|
||||
```
|
||||
|
||||
**Ubuntu**
|
||||
Ubuntu的源里默认就有ffmpeg的软件包,所以我们直接安装就ok了。
|
||||
|
||||
```
|
||||
apt install ffmpeg
|
||||
```
|
||||
|
||||
拥有了ffmpeg之后,我们可以使用如下命令:
|
||||
|
||||
```
|
||||
ffmpeg -i demo.mp4 -r 5 -ss 00:01:13 -t 00:00:15 %03d.png
|
||||
```
|
||||
|
||||
将demo视频的第1分13秒后的15秒以每秒5帧的速度保存为图像,图像名格式为001.png 002.png ……
|
||||
效果如下:
|
||||
|
||||

|
||||
|
||||
```bash
|
||||
➜ ~ ls time
|
||||
001.png 005.png 009.png 013.png 017.png 021.png 025.png 029.png 033.png 037.png 041.png 045.png 049.png 053.png 057.png 061.png 065.png 069.png 073.png
|
||||
002.png 006.png 010.png 014.png 018.png 022.png 026.png 030.png 034.png 038.png 042.png 046.png 050.png 054.png 058.png 062.png 066.png 070.png 074.png
|
||||
003.png 007.png 011.png 015.png 019.png 023.png 027.png 031.png 035.png 039.png 043.png 047.png 051.png 055.png 059.png 063.png 067.png 071.png 075.png
|
||||
004.png 008.png 012.png 016.png 020.png 024.png 028.png 032.png 036.png 040.png 044.png 048.png 052.png 056.png 060.png 064.png 068.png 072.png
|
||||
```
|
||||
|
||||
### 使用修改过的hit9/img2txt将图像转换为ASCII画
|
||||
|
||||
> 原版hit9/img2txt只能单张转换,我稍微改了下,可以批量转换并保存为txt。修改后的版本:https://github.com/HFO4/img2txt/blob/gh-pages/img2txt.py
|
||||
|
||||
可能大佬都是说改就改的吧。
|
||||
完事我们clone下来后修改img2txt.py第246行的目录为上一步存放图像的目录:
|
||||
|
||||
```
|
||||
246 imgname = "/root/time/"+str(i).zfill(3)+".png"
|
||||
```
|
||||
|
||||
然后再执行:
|
||||
|
||||
```
|
||||
pip install img2txt.py
|
||||
python img2txt.py h
|
||||
```
|
||||
|
||||
稍等片刻,ASCII字符文件便会存放到与img2txt.py同级的pic目录下。若提示无pic文件夹导致的错误,手动创建一个名为`pic`的文件夹再运行一次即可。
|
||||
|
||||
### 部署在线服务
|
||||
|
||||
最后,使用大佬的[HFO4/plus1s.live](https://github.com/HFO4/plus1s.live)来部署在线播放的服务。
|
||||
|
||||
将上一步使用img2txt的pic文件夹中的图片放到改项目下的pic文件夹内,然后修改stream.go的第13行为你得到的单帧图像的总个数。保存后执行:
|
||||
|
||||
```
|
||||
go build stream.go
|
||||
./stream
|
||||
```
|
||||
|
||||
然后程序会默认开放一个暴力的端口,使用`curl 您的ip:1926`命令即可查看效果。
|
||||
|
||||
## 另一款强大的软件
|
||||
|
||||
> 📺ASCIIPlayer : Golang写的ASCII码播放器
|
||||
|
||||

|
||||
如同作者自己所说的,该软件是Go语言写的一款强大的Ascii码的转码加播放器。
|
||||
|
||||
* [ASCIIPlayer : Golang写的ASCII码播放器](https://segmentfault.com/a/1190000016976239)
|
||||
|
||||
* [asciiplayer](https://github.com/qeesung/asciiplayer)
|
||||
|
||||
### 安装
|
||||
|
||||
```
|
||||
go get -u github.com/qeesung/asciiplayer
|
||||
```
|
||||
|
||||
安装后若提示:
|
||||
|
||||
```
|
||||
zsh: command not found: asciiplayer
|
||||
```
|
||||
|
||||
则在当前目录下会缓存一个`go`文件夹,在`go/bin/`文件夹内会有一个可执行的asciiplayer。我们将其copy至`/usr/bin/`目录下,并重连ssh即可解决。
|
||||
|
||||
```
|
||||
cp -a asciiplayer /usr/bin
|
||||
```
|
||||
|
||||
### 三种工作模式
|
||||
|
||||
该软件强大的地方就是在此了,对于转换为ascii码,它拥有三个工作模式:
|
||||
|
||||
- 输出到一个一般文件中(Encode模式): 这里我们只能逐帧,逐像素的将转化以后的ASCII图像写到文件中去。
|
||||
- 输出到终端(Play模式): 直接将转换以后的图像按照一定的频率输出到终端即可。
|
||||
- 输出到远端客户端(Server模式): 这里和输出到终端的原理类似,只是输出到了远端客户端所在的终端。
|
||||
|
||||
```
|
||||
+---------------+ +---------+
|
||||
| | | |
|
||||
+------> Gif Decoder | +---> Encoder +---> file
|
||||
| | | | | |
|
||||
| +---------------+ | +---------+
|
||||
| +---------------+ +-------------+ | +---------+
|
||||
| | | | | | | |
|
||||
Input File+------> Image Decoder +---> Frames +-->+ Image2ASCII +->ASCII Frames-+---> Player +---> stdout
|
||||
| | | | | | | |
|
||||
| +---------------+ +-------------+ | +---------+
|
||||
| +---------------+ | +---------+
|
||||
| | | | | |
|
||||
+------> Video Decoder | +---> Server +---> socket
|
||||
| | | |
|
||||
+---------------+ +---------+
|
||||
```
|
||||
|
||||
以至于它一款软件就能够直接实现我们是上述将视频中抽去图片再挨个转换为文本的ASCII码的工作了。除了不能将我们需要的输出为文本保存以外,其他都很完美。
|
||||
唯一一个缺点就是目前还不支持直接读取视频文件,只能先使用ffmpeg将视频转换为gif中,在用此软件读取。作者目前也说后续会支持视频的。🎉
|
||||
|
||||
### 常用的命令
|
||||
|
||||
- play
|
||||
|
||||
通过适配屏幕的方式播放GIF
|
||||
|
||||
```
|
||||
asciiplayer play demo.gif
|
||||
```
|
||||
|
||||
缩小为原来的十分之一,然后播放GIF
|
||||
|
||||
```
|
||||
asciiplayer play demo.gif -r 0.1
|
||||
```
|
||||
|
||||
缩放成固定的长和宽,然后播放GIF
|
||||
|
||||
```
|
||||
asciiplayer play demo.gif -w 100 -h 40
|
||||
```
|
||||
|
||||
播放一个PNG图片
|
||||
|
||||
```
|
||||
asciiplayer play demo.png
|
||||
```
|
||||
|
||||
- encode
|
||||
|
||||
将一个GIF文件demo.gif编码为ASCII的Gif文件output.gif
|
||||
|
||||
```
|
||||
asciiplayer encode demo.gif -o output.gif
|
||||
```
|
||||
|
||||
指定输出ASCII字符大小的情况下,讲一个GIF文件demo.gif编码成ASCII的GIF动图文件output.gif
|
||||
|
||||
```
|
||||
asciiplayer encode demo.gif -o output.gif --font_size=5
|
||||
```
|
||||
|
||||
将GIF动图demo.gif缩放为原来的十分之一,然后编码成ASCII的GIF动图文件output.gif
|
||||
|
||||
```
|
||||
asciiplayer encode demo.gif -o output.gif -r 0.1
|
||||
```
|
||||
|
||||
编码一个jpeg文件,然后输出一个ASCII的output.png文件
|
||||
|
||||
```
|
||||
asciiplayer encode demo.jpeg -o output.png
|
||||
```
|
||||
|
||||
- Server
|
||||
|
||||
输入demo.gif,并以默认端口8080启动一个http服务器
|
||||
|
||||
```
|
||||
asciiplayer server demo.gif
|
||||
```
|
||||
|
||||
输入demo.gif,并以自定义端口8888启动一个http服务器
|
||||
|
||||
```
|
||||
asciiplayer server demo.gif --port 8888
|
||||
```
|
||||
|
||||
输入一个demo.png图片,并且启动http 服务器
|
||||
|
||||
```
|
||||
asciiplayer server demo.png
|
||||
```
|
||||
|
||||
## 大佬们
|
||||
|
||||
[ASCIIPlayer : Golang写的ASCII码播放器](https://segmentfault.com/a/1190000016976239#articleHeader0)
|
||||
[构建一个在线ASCII视频流服务](https://file.aoaoao.me/2018/03/26/e6-9e-84-e5-bb-ba-e4-b8-80-e4-b8-aa-e5-9c-a8-e7-ba-bfascii-e8-a7-86-e9-a2-91-e6-b5-81-e6-9c-8d-e5-8a-a1/#如何搭建这么一个服务?)
|
||||
|
||||
## Try it ?
|
||||
|
||||
```
|
||||
curl time.defect.ink:1926
|
||||
```
|
43
source/_posts/AliOssForTypecho.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: AliOssForTypecho
|
||||
date: 2019-06-26 16:42:41
|
||||
tags: typecho
|
||||
categories: 踩坑
|
||||
url: alioss-for-typecho
|
||||
index_img: /images/AliOssForTypecho/logo.webp
|
||||
---
|
||||
|
||||
原作大佬:
|
||||
|
||||
* [Typecho插件](https://zhoujie.ink/AliOssForTypecho.html)
|
||||
|
||||
最近从辣鸡七牛换到了阿里云的oss,对于我们使用阿里云的ECS来说,oss支持直接内网访问还是很友好的。
|
||||
|
||||
存储换了之后,于是找到了大佬的这款插件。可是大佬当初写插件的时候有些地方不太符合个人的使用习惯。比如存储的目录下都会给每张图片单独生成要一个文件夹。
|
||||
|
||||
虽然看到大佬blog下已经有留言了,但是那都是去年的事了。
|
||||
|
||||
当时是因为阿里云还没有检测object是否存在的sdk,大佬估计也是没有时间来跟这阿里云的sdk持续更新。就在18年10月份阿里云才更新了判断文件是否存在的php sdk。
|
||||
|
||||
对于我这种0基础没入门的php玩家,修改太多也太麻烦,也不会。于是只做了一些简单的修改
|
||||
|
||||
- 去除每个图片随机创建一个文件夹,但是没有是否存在的检测,上传时要确保文件不会重名。
|
||||
- 添加图片处理样式,支持自定义规则。
|
||||
- 更新了最新的OssClient(虽然我不知道它怎么用
|
||||
|
||||
|
||||
|
||||
|
||||
为什么不做object存在检测?
|
||||
|
||||
- 当前文件夹是按“年-月-日”来分层的,也就是说存在重名的文件的时间段只有一天内上传的文件才有机会重名。
|
||||
- 不会
|
||||
- 主要是不会
|
||||
|
||||
阿里云的[判断文件是否存在](https://help.aliyun.com/document_detail/88501.html?spm=a2c4g.11186623.6.938.33f015cdQHplrY)文档,有兴趣的大佬可以试试。
|
||||
|
||||
|
||||
|
||||
下载地址:
|
||||
|
||||
* [AliossForTypecho](https://github.com/DefectingCat/AliOssForTypecho-)
|
78
source/_posts/Can't install gifsicle.md
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Can't install gifsicle
|
||||
index_img: /images/Can't%20install%20gifsicle/index.webp
|
||||
date: 2020-08-04 14:03:00
|
||||
tags: network
|
||||
categories: 踩坑
|
||||
url: cant-install-gifsicle
|
||||
---
|
||||
|
||||
## Hexo-all-minifier
|
||||
|
||||
In a long time, i'm used to Hexo-all-minifier to optimization blog. But recently i can't even install it.
|
||||
|
||||
The error logs with `npm install`:
|
||||
|
||||
```bash
|
||||
‼ getaddrinfo ENOENT raw.githubusercontent.com
|
||||
‼ gifsicle pre-build test failed
|
||||
i compiling from source
|
||||
× Error: Command failed: C:\WINDOWS\system32\cmd.exe /s /c "autoreconf -ivf"
|
||||
'autoreconf' <20><><EFBFBD><EFBFBD><EFBFBD>ڲ<EFBFBD><DAB2><EFBFBD><EFBFBD>ⲿ<EFBFBD><E2B2BF><EFBFBD>Ҳ<EEA3AC><D2B2><EFBFBD>ǿ<EFBFBD><C7BF><EFBFBD><EFBFBD>еij<D0B5><C4B3><EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>
|
||||
```
|
||||
|
||||
In the beginning, i thought the problem is my windows can't run autoconf. So, i tried installing cygwin, And that is difficult for me. I never tried to installed cygwin.
|
||||
|
||||
Anyway, i installed successfully. But the problem has not solved. There is still has the errors with npm install .
|
||||
|
||||
## imagemin-gifsicle
|
||||
|
||||
The problem appeared when installing gifsicle, The Hexo-all-minifier used it too. So, the best way is go to the gifsicle issues. As predicted, there is someone got the same errors.
|
||||
|
||||
Be unexpected, It's not a problem with windows or autoconf. That is network problem🌚.
|
||||
|
||||
```bash
|
||||
‼ getaddrinfo ENOENT raw.githubusercontent.com
|
||||
‼ gifsicle pre-build test failed
|
||||
```
|
||||
|
||||
As in above two lines, the problem is can't connect with `githubusercontent.com`.
|
||||
|
||||
## Best way
|
||||
|
||||
Write domain with ip into the hosts. That is best way to connect with github and other domains.
|
||||
|
||||
```
|
||||
52.74.223.119 github.com
|
||||
192.30.253.119 gist.github.com
|
||||
54.169.195.247 api.github.com
|
||||
185.199.111.153 assets-cdn.github.com
|
||||
151.101.76.133 raw.githubusercontent.com
|
||||
151.101.76.133 gist.githubusercontent.com
|
||||
151.101.76.133 cloud.githubusercontent.com
|
||||
151.101.76.133 camo.githubusercontent.com
|
||||
151.101.76.133 avatars0.githubusercontent.com
|
||||
151.101.76.133 avatars1.githubusercontent.com
|
||||
151.101.76.133 avatars2.githubusercontent.com
|
||||
151.101.76.133 avatars3.githubusercontent.com
|
||||
151.101.76.133 avatars4.githubusercontent.com
|
||||
151.101.76.133 avatars5.githubusercontent.com
|
||||
151.101.76.133 avatars6.githubusercontent.com
|
||||
151.101.76.133 avatars7.githubusercontent.com
|
||||
151.101.76.133 avatars8.githubusercontent.com
|
||||
```
|
||||
|
||||
Then, try`npm cache clean -f`and`ipconfig/flushdns`.
|
||||
|
||||
As long as can ping with github domains, the problem will be solved.
|
||||
|
||||
The `Command failed` just write some ips for hosts, then `npm install` will be worked.
|
||||
|
||||
So,
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
enjoy it.
|
596
source/_posts/Docker-全面容器化.md
Normal file
@ -0,0 +1,596 @@
|
||||
---
|
||||
title: Docker-全面容器化!
|
||||
date: 2019-12-19 11:11:33
|
||||
tags: Linux
|
||||
categories: 实践
|
||||
url: docker-container-all
|
||||
index_img: /images/Docker全面容器化/logo.webp
|
||||
---
|
||||
|
||||
自上篇[Docker - 构建属于自己的镜像](https://www.defectink.com/defect/docker-build-own-images.html)以来,发现Docker非常的有意思。主要是非常的方便,并且在可以跨平台的情况下部署环境对于以后迁移也是一件极其有利的事。研究了Dockerfile的编写以及实践。一些基础的实践之后,对于Docker的工作方式以及操作命令都有了一些熟悉。也逐渐了发现了它的一些优点。
|
||||
|
||||
翻开自己的旧机器里的多种环境交杂在一起的配置,时间长了连配置文件在哪都找不到了。管理起来比较复杂。那些服务器的管理面板并不是很喜欢,而且相对于Docker来说,管理面板只是简化了部署的操作,并没有达到方便管理的目的。到最后可能它的软件目录,镜像源都是按照它的想法去放的。对于自己并没有完全的掌控。当然不能完全拿管理面板与Docker来相比,二者完全是两种技术。只是相较于方便管理这方面来对比一下。
|
||||
|
||||
而最近研究的Docker,无疑是最满意的了。在保持宿主机不乱的情况下,可以完全的掌控自己的运行环境。于是就有了将自己目前跑了挺长时间的一套blog环境都迁移到Docker上。对于以后若是迁移机器也会更加的方便。
|
||||
|
||||
## 涉及到的操作
|
||||
|
||||
* Dockerfile
|
||||
* docker-compose.yml
|
||||
* apache virtualhost
|
||||
* php-fpm
|
||||
* http2
|
||||
* apache https
|
||||
* certbot with docker
|
||||
* apache proxy
|
||||
|
||||
## 目前环境
|
||||
|
||||
先来简单看下当前跑在机器上的环境:
|
||||
|
||||
基本的LAMP环境,加上一些自定义的应用,与一个服务器监控软件。其中apache有多个虚拟主机,全部都使用了https。
|
||||
|
||||
咋一看是一套很简单的环境,其中apache配置稍多一点。但是实际在迁移到Docker的操作起来还是比较复杂的。并且为了镜像的最小化,apache基于的镜像都是alpine。配置与常用的Ubuntu略有不同。
|
||||
|
||||
## 容器化
|
||||
|
||||
### 思路
|
||||
|
||||
将多个运行LAMP分别拆分出三个运行环境,使用docker-compose来捆绑运行。
|
||||
|
||||
目录树
|
||||
|
||||
```
|
||||
.
|
||||
├── apache
|
||||
│ ├── Dockerfile
|
||||
│ ├── httpd.conf
|
||||
│ └── sites
|
||||
│ ├── 000-default-ssl.conf
|
||||
│ └── 000-default.conf
|
||||
├── docker-compose.yml
|
||||
├── mysql
|
||||
│ └── backup
|
||||
├── php
|
||||
│ └── Dockerfile
|
||||
└── www
|
||||
└── html
|
||||
└── index.php
|
||||
```
|
||||
|
||||
首先创建一个用于存放整个运行环境的`Docker`父文件夹。然后根据不同的镜像来划分不同的子文件夹,子文件夹内存放的就是各个镜像的`Dockerfile`与配置文件等。将docker-compose.yml存放与父目录下。
|
||||
|
||||
apache与php-fpm通信借助Docker的网络,实现内部的通信。
|
||||
|
||||
## Apahce
|
||||
|
||||
在当前的apache目录下,主要文件夹的划分为`Dockerfile`、`httpd.conf`和sites文件夹。
|
||||
|
||||
### Dockerfile
|
||||
|
||||
虽然httpd有了一个单独的镜像,但是还是需要使用Dockerfile来对其进行自定义配置。为了尽量减小镜像的大小。这里使用基于alpine的apache。
|
||||
|
||||
在Docker hub中的[httpd](https://hub.docker.com/_/httpd)当前支持的tag:
|
||||
|
||||

|
||||
|
||||
整个Dockerfile:
|
||||
|
||||
```dockerfile
|
||||
FROM httpd:alpine
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||
&& apk update \
|
||||
&& apk upgrade
|
||||
COPY sites/ /usr/local/apache2/conf/sites/
|
||||
COPY httpd.conf /usr/local/apache2/conf/httpd.conf
|
||||
```
|
||||
|
||||
所以`FROM`里使用的就是带alpine的tag了。我还尝试过测试使用基于alpine的空载运行apache大概节约了1MB的内存。
|
||||
|
||||
```
|
||||
176d166ee52a testa 0.00% 4.484MiB / 3.607GiB 0.12%
|
||||
3dac39c11385 test 0.00% 5.664MiB / 3.607GiB 0.15%
|
||||
```
|
||||
|
||||
对于跑在国内的机器上,alpine也有国内的源。并且替换的也很简单,一句话就好了。这样在后续的更新源和安装软件就没有那么苦恼了。
|
||||
|
||||
```
|
||||
sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
```
|
||||
|
||||
剩下的`COPY`就是复制自定义的配置文件到容器里去了。
|
||||
|
||||
### 配置文件
|
||||
|
||||
首先,之前的环境中apache是有多个虚拟主机,并且每个主机都启用了ssl以及一些其他的配置。所以第一步是需要修改容器的配置文件。也就是要先获取默认的配置文件。
|
||||
|
||||
优雅的获取apache默认配置文件:
|
||||
|
||||
```
|
||||
docker run --rm httpd:2.4 cat /usr/local/apache2/conf/httpd.conf > httpd.conf
|
||||
```
|
||||
|
||||
默认的ssl配置文件:
|
||||
|
||||
```
|
||||
docker run --rm httpd:2.4 cat /usr/local/apache2/conf/extra/httpd-ssl.conf > ssl.conf
|
||||
```
|
||||
|
||||
容器的配置文件路径:
|
||||
|
||||
```
|
||||
/usr/local/apache2/conf/httpd.conf
|
||||
```
|
||||
|
||||
获取到了默认的配置文件之后,在apache的文件夹内可以先自定义`httpd.conf`。并且尝试启动一次,没用问题后可以继续配置虚拟主机。
|
||||
|
||||
由于不同的站点都交给了虚拟主机的配置文件来处理。所以`httpd.conf`主要是负责一些mod的配置,和一些全局的配置了。还有就是将余下的配置文件`Include`进来了。
|
||||
|
||||
后期还有需要添加更多的虚拟主机的配置文件,到时候一个一个的`Include`操作太过繁琐。所以创建个专门存放配置文件的文件夹,再在`httpd.conf`里将整个文件夹`Include`进去。这样就最简单的解决了操作繁琐的问题。
|
||||
|
||||
创建一个`sites`文件夹用于存放配置文件,`COPY`到容器内相应的目录:
|
||||
|
||||
```
|
||||
COPY sites/ /usr/local/apache2/conf/sites/
|
||||
```
|
||||
|
||||
在`httpd.conf`中相应的引入:
|
||||
|
||||
```
|
||||
Include /usr/local/apache2/conf/sites/*.conf
|
||||
```
|
||||
|
||||
{*}这一操作方法还是学自Ubuntu下的apache,它的配置目录下有两个文件夹`sites-available`和`sites-enabled`。在主要的apache2.conf中引入配置文件。
|
||||
|
||||
```http
|
||||
# Include generic snippets of statements
|
||||
IncludeOptional conf-enabled/*.conf
|
||||
|
||||
# Include the virtual host configurations:
|
||||
IncludeOptional sites-enabled/*.conf
|
||||
```
|
||||
|
||||
`httpd.conf`中的虚拟主机配置不需要修改了。所有的站点可以都在Include中的配置文件中准备。基本上`httpd.conf`就是为引入配置文件和启用mod所准备的。
|
||||
|
||||
### Module
|
||||
|
||||
在基于alpine中的apache,所有的mod加载都写在了配置文件`httpd.conf`里。只需要取消注释就可以加载/启用模组了。
|
||||
|
||||
这次添加的module:
|
||||
|
||||
```
|
||||
LoadModule deflate_module modules/mod_deflate.so
|
||||
LoadModule proxy_module modules/mod_proxy.so
|
||||
LoadModule proxy_connect_module modules/mod_proxy_connect.so
|
||||
LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
|
||||
LoadModule proxy_http_module modules/mod_proxy_http.so
|
||||
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
|
||||
LoadModule setenvif_module modules/mod_setenvif.so
|
||||
LoadModule mpm_event_module modules/mod_mpm_event.so
|
||||
LoadModule http2_module modules/mod_http2.so
|
||||
LoadModule proxy_http2_module modules/mod_proxy_http2.so
|
||||
LoadModule ssl_module modules/mod_ssl.so
|
||||
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
LoadModule headers_module modules/mod_headers.so
|
||||
#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
|
||||
```
|
||||
|
||||
这些mod都是作用于何?
|
||||
|
||||
* mod_deflate是一个压缩算法。
|
||||
|
||||
* mod_socache_shmcb共享对象缓存提供程序。
|
||||
|
||||
* 因为需要配置反代和与php-fpm工作,所以需要启用多个proxy配置文件。
|
||||
|
||||
* 因为需要用到http2,所以工作模式得修改为event。同时注释掉默认的工作模式prefork。自然也需要mod_http2
|
||||
|
||||
* https是不可或缺的,所以mod_ssl不可缺少。
|
||||
|
||||
* 后续的博客需要用到伪静态,mod_rewrite也不可少。
|
||||
|
||||
* 在最近也添加了多个header头,需要用到mod_headers。
|
||||
|
||||
> info:根据自己需要启用module是一个良好的习惯,过多的module会影响性能。
|
||||
|
||||
### 虚拟主机
|
||||
|
||||
前面提到,专门创建了一个sites文件夹来存放虚拟主机的配置文件,目前sites文件夹还是空的。既然`httpd.conf`以及准备就绪,那么接下来就是填满sites文件夹了。
|
||||
|
||||
在还未添加虚拟主机时,默认的站点配置文件全部都写在`httpd.conf`里。默认的根目录在htdocs。所以在第一次启动测试时,访问的时这里的html文件。
|
||||
|
||||
```
|
||||
DocumentRoot "/usr/local/apache2/htdocs"
|
||||
```
|
||||
|
||||
这里的配置可以不用动,全部操作交给虚拟主机就好。
|
||||
|
||||
整个虚拟主机(default.conf)配置文件:
|
||||
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ProtocolsHonorOrder On
|
||||
Protocols h2 h2c
|
||||
Header set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;"
|
||||
Header set Content-Security-Policy "default-src 'self' https://cdn.defectink.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://cdn.defectink.com; img-src *; style-src 'self' 'unsafe-inline' https://cdn.defectink.com https://maxcdn.bootstrapcdn.com https://fonts.googleapis.com/; font-src 'self' https://cdn.defectink.com https://fonts.gstatic.com/ https://maxcdn.bootstrapcdn.com; form-action 'self' https://cdn.defectink.com; upgrade-insecure-requests;"
|
||||
Header set X-Content-Type-Options nosniff
|
||||
Header always set Referrer-Policy "no-referrer-when-downgrade"
|
||||
Header always set Feature-Policy "vibrate 'self'; sync-xhr 'self' https://cdn.defectink.com https://www.defectink.com"
|
||||
# Proxy .php requests to port 9000 of the php-fpm container
|
||||
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000/var/www/html/$1
|
||||
ServerName www.defectink.com
|
||||
DocumentRoot /var/www/html/
|
||||
<Directory /var/www/html/>
|
||||
DirectoryIndex index.php
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
# Send apache logs to stdout and stderr
|
||||
CustomLog /proc/self/fd/1 common
|
||||
ErrorLog /proc/self/fd/2
|
||||
RewriteEngine on
|
||||
RewriteCond %{SERVER_NAME} =www.defectink.com
|
||||
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
#### 虚拟主机优先级
|
||||
|
||||
在apache中,虚拟主机的配置文件是拥有优先级的。优先级的意思就是,当一个域名指向当前机器的ip,而配置文件中没有绑定的ServerName时,默认被引导到的页面。
|
||||
|
||||
优先级的顺序是根据虚拟主机的配置文件名来决定的。名称首字母越靠前,优先级越高。使用数字开头将大于子母开头。
|
||||
|
||||
> 000-default will be the default, because it goes “numbers, then letters”.
|
||||
|
||||
可以使用命令来查看当前的默认站点:
|
||||
|
||||
```bash
|
||||
httpd -S
|
||||
```
|
||||
|
||||
```bash
|
||||
apache2ctl -S
|
||||
```
|
||||
|
||||
### SSL
|
||||
|
||||
这里的ssl配置文件是来自于容器内的默认配置文件,使用上述的方法可以很方便的导出。
|
||||
|
||||
整个ssl(default-ssl.conf)配置文件:
|
||||
|
||||
```
|
||||
Listen 443
|
||||
|
||||
SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
|
||||
SSLProxyCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
|
||||
SSLHonorCipherOrder on
|
||||
SSLProtocol all -SSLv3
|
||||
SSLProxyProtocol all -SSLv3
|
||||
SSLPassPhraseDialog builtin
|
||||
SSLSessionCache "shmcb:/usr/local/apache2/logs/ssl_scache(512000)"
|
||||
SSLSessionCacheTimeout 300
|
||||
|
||||
<VirtualHost *:443>
|
||||
ProtocolsHonorOrder On
|
||||
Protocols h2 h2c
|
||||
Header set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;"
|
||||
Header set Content-Security-Policy "default-src 'self' https://cdn.defectink.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://cdn.defectink.com; img-src *; style-src 'self' 'unsafe-inline' https://cdn.defectink.com https://maxcdn.bootstrapcdn.com https://fonts.googleapis.com/; font-src 'self' https://cdn.defectink.com https://fonts.gstatic.com/ https://maxcdn.bootstrapcdn.com; form-action 'self' https://cdn.defectink.com; upgrade-insecure-requests;"
|
||||
Header set X-Content-Type-Options nosniff
|
||||
Header always set Referrer-Policy "no-referrer-when-downgrade"
|
||||
Header always set Feature-Policy "vibrate 'self'; sync-xhr 'self' https://cdn.defectink.com https://www.defectink.com"
|
||||
# Proxy .php requests to port 9000 of the php-fpm container
|
||||
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000/var/www/html/$1
|
||||
# General setup for the virtual host
|
||||
DocumentRoot "/var/www/html/"
|
||||
ServerName www.defectink.com:443
|
||||
ServerAdmin i@defect.ink
|
||||
<Directory /var/www/html/>
|
||||
DirectoryIndex index.php
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
ErrorLog /proc/self/fd/2
|
||||
TransferLog /proc/self/fd/1
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile "/etc/letsencrypt/live/www.defectink.com/fullchain.pem"
|
||||
SSLCertificateKeyFile "/etc/letsencrypt/live/www.defectink.com/privkey.pem"
|
||||
<FilesMatch "\.(cgi|shtml|phtml|php)$">
|
||||
SSLOptions +StdEnvVars
|
||||
</FilesMatch>
|
||||
<Directory "/usr/local/apache2/cgi-bin">
|
||||
SSLOptions +StdEnvVars
|
||||
</Directory>
|
||||
BrowserMatch "MSIE [2-5]" \
|
||||
nokeepalive ssl-unclean-shutdown \
|
||||
downgrade-1.0 force-response-1.0
|
||||
CustomLog /proc/self/fd/1 \
|
||||
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
|
||||
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
这里的主要配置点就在`SSLCertificateFile`和`SSLCertificateKeyFile`。关于配合certbot申请证书,在前一篇水过。有兴趣的小伙伴可以去了解更多。[Docker - 构建属于自己的镜像](https://www.defectink.com/defect/docker-build-own-images.html#menu_index_8)。
|
||||
|
||||
值得注意的是,在一个`httpd.conf`文件中只能有一个`Listen 443`字段。而默认的ssl配置文件中就包含一个`Listen 443`字段。当复制多个默认的配置文件时,会导致apache运行错误。因为所有的配置文件都会被引入到`httpd.conf`,而一个apache只能监听一次端口,也就是说只能有一个`Listen 443`在配置文件中。
|
||||
|
||||
可以考虑将其写在监听80端口下面:
|
||||
|
||||
```
|
||||
#Listen 12.34.56.78:80
|
||||
Listen 80
|
||||
Listen 443
|
||||
```
|
||||
|
||||
### 日志
|
||||
|
||||
这里虚拟主机的默认配置时将日志发送到stdout和stderr。可以理解为输出到终端上。
|
||||
|
||||
```
|
||||
# Send apache logs to stdout and stderr
|
||||
CustomLog /proc/self/fd/1 common
|
||||
ErrorLog /proc/self/fd/2
|
||||
```
|
||||
|
||||
当然也可以实现日志的持久化保存。将其映射到宿主机的目录下就好了。
|
||||
|
||||
```
|
||||
CustomLog /var/log/access.log common
|
||||
ErrorLog /var/log/error.log
|
||||
```
|
||||
|
||||
## PHP
|
||||
|
||||
PHP这里使用fpm,配合docker-compose的内部网络与apache进行通信。
|
||||
|
||||
### Dockerfile
|
||||
|
||||
```
|
||||
FROM php:7.4-fpm-alpine
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
COPY php.ini $PHP_INI_DIR/conf.d/
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||
&& apk update \
|
||||
&& apk upgrade \
|
||||
&& docker-php-ext-install mysqli \
|
||||
&& docker-php-ext-install pdo_mysql
|
||||
```
|
||||
|
||||
### php.ini
|
||||
|
||||
优雅的获取容器内的php.ini文件:
|
||||
|
||||
```
|
||||
docker cp somephp:/usr/local/etc/php/ /root
|
||||
```
|
||||
|
||||
修改过后的配置文件可以在Dockerfile中copy,也可以在compose.yml中映射。只要到了宿主机的正确位置就可以生效。
|
||||
|
||||
官方的描述是这样的方法:
|
||||
|
||||
```dockerfile
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
COPY php.ini $PHP_INI_DIR/conf.d/
|
||||
```
|
||||
|
||||
### 拓展
|
||||
|
||||
如果需要安装typecho这样的blog程序的话,再连接sql时需要安装mysql的拓展,写在Dockerfile就好了:
|
||||
|
||||
```
|
||||
docker-php-ext-install pdo_mysql
|
||||
```
|
||||
|
||||
如果还需要其他的拓展的话,还可以在Dockerfile里自定义安装。可以使用`pecl`来安装,然后使用`docker-php-ext-enable`来启用它。
|
||||
|
||||
> use `pecl install` to download and compile it, then use `docker-php-ext-enable` to enable it:
|
||||
|
||||
```
|
||||
FROM php:7.4-cli
|
||||
RUN pecl install redis-5.1.1 \
|
||||
&& pecl install xdebug-2.8.1 \
|
||||
&& docker-php-ext-enable redis xdebug
|
||||
```
|
||||
|
||||
或者直接使用`docker-php-ext-install`来安装:
|
||||
|
||||
&& docker-php-ext-install mysqli \
|
||||
&& docker-php-ext-install pdo_mysql
|
||||
至于更多的拓展,还可以编译安装:
|
||||
|
||||
```
|
||||
FROM php:5.6-cli
|
||||
RUN curl -fsSL 'https://xcache.lighttpd.net/pub/Releases/3.2.0/xcache-3.2.0.tar.gz' -o xcache.tar.gz \
|
||||
&& mkdir -p xcache \
|
||||
&& tar -xf xcache.tar.gz -C xcache --strip-components=1 \
|
||||
&& rm xcache.tar.gz \
|
||||
&& ( \
|
||||
cd xcache \
|
||||
&& phpize \
|
||||
&& ./configure --enable-xcache \
|
||||
&& make -j "$(nproc)" \
|
||||
&& make install \
|
||||
) \
|
||||
&& rm -r xcache \
|
||||
&& docker-php-ext-enable xcache
|
||||
```
|
||||
|
||||
## MySql
|
||||
|
||||
mysql主要修改的一些配置使用启动时的环境变量就可以了,不需要修改其配置文件的情况下,便用不到Dockerfile了。直接使用官方的镜像就好了。
|
||||
|
||||
### Docker Secrets
|
||||
|
||||
如果担心密码写在docker-compose.yml里不安全的话,可以考虑使用docker secret。不过secret需要使用swarm集群。单台主机只使用docker-compose可能会更加方便一点。
|
||||
|
||||
并且在compose中生成的是两个虚拟网络,只有apache在前端网络映射了端口。mysql完全放在后端,除了虚拟网络和宿主机都无法与其通信。所以密码的安全并不用太过于担心。如果mysql需要对外提供服务,那就需要多担心一下了。
|
||||
|
||||
```
|
||||
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql-root -d mysql:tag
|
||||
```
|
||||
|
||||
> 映射了目录后,配置的密码都会被持久化保存。再次修改docker-compose.yml中的变量将不会生效。
|
||||
|
||||
### 数据持久化
|
||||
|
||||
对于docker来说,尽量不要在容器内发生写的操作为好。此外,对于数据库来,数据肯定是需要持久化存储的。官方推荐的是:
|
||||
|
||||
> Important note: There are several ways to store data used by applications that run in Docker containers. We encourage users of the `mysql` images to familiarize themselves with the options available, including:
|
||||
>
|
||||
> - Let Docker manage the storage of your database data [by writing the database files to disk on the host system using its own internal volume management](https://docs.docker.com/engine/tutorials/dockervolumes/#adding-a-data-volume). This is the default and is easy and fairly transparent to the user. The downside is that the files may be hard to locate for tools and applications that run directly on the host system, i.e. outside containers.
|
||||
> - Create a data directory on the host system (outside the container) and [mount this to a directory visible from inside the container](https://docs.docker.com/engine/tutorials/dockervolumes/#mount-a-host-directory-as-a-data-volume). This places the database files in a known location on the host system, and makes it easy for tools and applications on the host system to access the files. The downside is that the user needs to make sure that the directory exists, and that e.g. directory permissions and other security mechanisms on the host system are set up correctly.
|
||||
|
||||
说白了就是映射出来为最佳解决方案。如果在compose.yml中使用`-v`映射,而不添加宿主机的目录位置的话。文件将会被映射到一个随机的目录。
|
||||
|
||||
推荐方案:
|
||||
|
||||
```
|
||||
$ docker run --name some-mysql -v /my/own/datadir:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
|
||||
```
|
||||
|
||||
### 备份与恢复
|
||||
|
||||
针对单个或多个数据库都可以导出为sql文件。在docker中当然也是同样。甚至密码可以使用变量`$MYSQL_ROOT_PASSWORD`来代替。
|
||||
|
||||
导入:
|
||||
|
||||
```bash
|
||||
docker exec -i mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" typecho' < /data/docker/typecho.sql
|
||||
```
|
||||
|
||||
导出:
|
||||
|
||||
```bash
|
||||
docker exec mysql sh -c 'exec mysqldump typecho -uroot -p"$MYSQL_ROOT_PASSWORD"' > /data/docker/mysql/backup/typecho.sql
|
||||
```
|
||||
|
||||
## Docker-compose
|
||||
|
||||
整个配置文件:
|
||||
|
||||
docker-compose.yml
|
||||
|
||||
```dockerfile
|
||||
version: "3.2"
|
||||
services:
|
||||
php:
|
||||
container_name: php
|
||||
build: './php/'
|
||||
networks:
|
||||
- backend
|
||||
volumes:
|
||||
- ./www/:/var/www/
|
||||
apache:
|
||||
container_name: apache
|
||||
build: './apache/'
|
||||
depends_on:
|
||||
- php
|
||||
- mysql
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./www/:/var/www/
|
||||
- /etc/letsencrypt/:/etc/letsencrypt/
|
||||
- ./apache/logs/:/var/log/
|
||||
mysql:
|
||||
container_name: mysql
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- ./mysql/sqldata/:/var/lib/mysql
|
||||
networks:
|
||||
- backend
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=password
|
||||
command: ['mysqld', '--character-set-server=utf8mb4']
|
||||
bark:
|
||||
container_name: bark
|
||||
build: './bark'
|
||||
ports:
|
||||
- "8181"
|
||||
depends_on:
|
||||
- apache
|
||||
networks:
|
||||
- backend
|
||||
networks:
|
||||
frontend:
|
||||
backend:
|
||||
```
|
||||
|
||||
### 网络
|
||||
|
||||
## 问题
|
||||
|
||||
```
|
||||
It does not belong to any of this network's subnets
|
||||
```
|
||||
|
||||
这个问题很有意思,在配置文件中设置了网络段。也在服务下写了相同的地址段,它依然说我的网段不一样。
|
||||
|
||||
导致这个问题的原因是在自定义配置网段之前启动过相同的网络,在docker网络下它已经定义过网段了。再次重新手动指定网络地址时,会和之前的不一样。
|
||||
|
||||
只需要删除之前的网络,在重新运行`docker-sompose up`一遍就可以了。
|
||||
|
||||
查看网络:
|
||||
|
||||
```bash
|
||||
docker network ls
|
||||
```
|
||||
|
||||
删除:
|
||||
|
||||
```bash
|
||||
docker network rm docker_backend
|
||||
```
|
||||
|
||||
### 此外
|
||||
|
||||
```
|
||||
- backend
|
||||
ipv4_address: 172.18.0.10
|
||||
```
|
||||
|
||||
不同的语法写在一起也会导致错误。
|
||||
|
||||
## 参考
|
||||
|
||||
* [How to set the default virtual host on Apache 2?](https://www.digitalocean.com/community/questions/how-to-set-the-default-virtual-host-on-apache-2)
|
||||
* [docker-compose up error, Invalid address](https://stackoverflow.com/questions/45648821/docker-compose-up-error-invalid-address)
|
||||
* [Networking in Compose](https://docs.docker.com/compose/networking/)
|
||||
* [Containerize This! How to use PHP, Apache, MySQL within Docker containers](https://www.cloudreach.com/en/insights/blog/containerize-this-how-to-use-php-apache-mysql-within-docker-containers/)
|
||||
|
||||
```
|
||||
docker run -it --rm --name certbot \
|
||||
-v "/etc/letsencrypt:/etc/letsencrypt" \
|
||||
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
|
||||
-v "/data/docker/web/www/:/data/docker/web/www/" \
|
||||
certbot/certbot certonly -n --webroot \
|
||||
-w /data/docker/web/www/test -d test.defectink.com \
|
||||
-w /data/docker/web/www/html -d www.defectink.com \
|
||||
-w /data/docker/web/www/index -d index.defectink.com \
|
||||
-w /data/docker/web/www/api -d api.defectink.com
|
||||
```
|
||||
|
||||
```
|
||||
docker run --rm --name myadmin -d --network docker_backend --link mysql_db_server:mysql -e PMA_HOST=172.20.0.4 -p 3002:80 phpmyadmin/phpmyadmin
|
||||
```
|
||||
|
||||
```
|
||||
certbot certonly -n --webroot -w /data/docker/web/www/html -d www.defectink.com --post-hook "docker restart apache"
|
||||
```
|
||||
|
||||
```
|
||||
certbot certonly -n --webroot -w /data/docker/web/www/html -d www.defectink.com
|
||||
certbot certonly -n --webroot -w /data/docker/web/www/index -d index.defectink.com
|
||||
certbot certonly -n --webroot -w /data/docker/web/www/test -d test.defectink.com
|
||||
certbot certonly -n --webroot -w /data/docker/web/www/api -d api.defectink.com
|
||||
```
|
||||
|
448
source/_posts/Docker-构建属于自己的镜像.md
Normal file
@ -0,0 +1,448 @@
|
||||
---
|
||||
title: Docker-构建属于自己的镜像
|
||||
date: 2019-11-29 09:30:33
|
||||
tags: Linux
|
||||
categories: 实践
|
||||
url: docker-build-own-image
|
||||
index_img: /images/Docker-构建属于自己的镜像/logo.webp
|
||||
---
|
||||
|
||||
以前一直在使用别人构建好的镜像来使用Docker容器,在一次想搭建一个完整的Web环境时,发现使用过多容器非常难以管理。并且容器之间的交互通信变的困难。当然,也可以使用Docker Compose来捆绑多个镜像运行;不过对于运行服务较少的来说,使用Dockerfile来构建成一个镜像也是件好事。
|
||||
|
||||
## 需求
|
||||
|
||||
首先,在构建一个镜像之前,需要先明白这个镜像将会包含哪些东西,运行哪些服务。目前主要是想在当前机器上跑一个hexo的blog。当然可以部署在Github,以前还写过一篇关于部署在Github的[水文](https://www.defectink.com/defect/set-up-the-hexo-blog.html)。不过现在的想法是Github放一份,在本地服务器上也跑一个Server。
|
||||
|
||||
当然跑一个hexo是一件很简单的事情,使用Docker来部署也是为了想体验一下写`Dockerfile`。目前有两个思路:
|
||||
|
||||
1. 把node.js和hexo都部署在当前的宿主机,用Docker的Web服务器来跑宿主机生成的静态文件。
|
||||
|
||||
> 但是这样的话就不需要用到Dockerfile了,直接pull一个http服务的镜像就好了。
|
||||
|
||||
2. 只在宿主机上使用Git来和Github同步文件,每次的生成和运行Web服务都放在Docker容器里。
|
||||
|
||||
> 目前打算尝试的一种方式,可以在每次写完文章后使用Docker构建,并且也可以尝试Dockerfile了。
|
||||
|
||||
具体需要什么使用软件,完全看自己的需求,需要用到什么,就安装什么。就像在当前的宿主机上安装软件一样。只不过是使用Dockerfile来构建时安装的而已。
|
||||
|
||||
## 构建自己的镜像
|
||||
|
||||
好在还可以使用Dockerfile来基于其他的镜像来构建属于自己的镜像。可以在其他的系统基础镜像上来在构建时就安装自己需要的软件服务等,这样就可以构建一个自己需要的镜像了。
|
||||
|
||||
### 使用基础镜像
|
||||
|
||||
构建时使用的第一个命令是`FROM`命令。它会指定一个用于构建的基础镜像。这样就可以在基础镜像中使用自己喜欢的发行版,也解决了继承其他 Docker 镜像的途径 。
|
||||
|
||||
创建一个目录,或者`clone`一个hexo博客等,在目录内编写一个` Dockerfile `。
|
||||
|
||||
```dockerfile
|
||||
#test
|
||||
|
||||
FROM alpine:latest
|
||||
MAINTAINER Defectink <i@defect.ink>
|
||||
```
|
||||
|
||||
这里选择的是alpine系统作为基础镜像,主要原因是alpine是个超级轻量的系统,对于最为基础镜像可以有效的减少构建后镜像的大小。
|
||||
|
||||
除此之外,还有个`MAINTAINER`命令,它是用来著名当前Dockerfile的作者的。Docker支持`#`作为注释,使用起来很方便。
|
||||
|
||||
### 第一次的构建
|
||||
|
||||
编写了一个最基本的` Dockerfile `之后,就是运行第一次的构建测试了。使用`Docker`加上`build`来构建指定的` Dockerfile `为镜像。添加`-t`参数来为构建后的镜像指定一个tag标签,也就是之后的镜像(REPOSITORY)名。最后命令指定的目录是包含刚刚写好的` Dockerfile `文件的目录,被称作为“构建目录”。
|
||||
|
||||
当前系统下没有基础镜像alpine的话,在第一次运行时docker也会进行下载。
|
||||
|
||||
```bash
|
||||
# docker build -t blog /data/github/DefectingCat.github.io/
|
||||
Sending build context to Docker daemon 64kB
|
||||
Step 1/2 : FROM alpine:latest
|
||||
latest: Pulling from library/alpine
|
||||
89d9c30c1d48: Pull complete
|
||||
Digest: sha256:c19173c5ada610a5989151111163d28a67368362762534d8a8121ce95cf2bd5a
|
||||
Status: Downloaded newer image for alpine:latest
|
||||
---> 965ea09ff2eb
|
||||
Step 2/2 : MAINTAINER Defectink <i@defect.ink>
|
||||
---> Running in d572ac48c8f8
|
||||
Removing intermediate container d572ac48c8f8
|
||||
---> b8296646acaa
|
||||
Successfully built b8296646acaa
|
||||
Successfully tagged blog:latest
|
||||
```
|
||||
|
||||
第一次的镜像构建已经完成了,虽然什么都没有进行定制,但已经迈出了第一步。
|
||||
|
||||
### 安装软件
|
||||
|
||||
迈出第一步之后,就可以开始考虑定制属于自己的镜像了。使用`docker images`可以查看当前系统下的docker镜像。也能看到刚刚所构建的第一个镜像。
|
||||
|
||||
```bash
|
||||
# docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
blog latest b8296646acaa 19 minutes ago 5.55MB
|
||||
alpine latest 965ea09ff2eb 5 weeks ago 5.55MB
|
||||
```
|
||||
|
||||
既然是定制属于自己的镜像,那么肯定是需要安装所需求的软件的。这里我想构建一个运行hexo的镜像,所以至少需要3款软件:
|
||||
|
||||
* apache
|
||||
* node.js
|
||||
* hexo
|
||||
|
||||
使用`RUN`命令来在基础镜像上执行命令,像是安装软件等操作。由于alpine默认时区不是国内,还可以顺便修改下时区。可以使用`RUN`来一次安装完所有需要的软件,不需要分开执行。
|
||||
|
||||
使用alpine的另个原因就是在它本身体积小的情况下,它安装软件还可以使用`--no-cache`来减少缓存。
|
||||
|
||||
在容器内使用npm来安装hexo时会出现一个`uid:0`的问题,npm会有生命周期,某个包会有生命周期来执行一些东西,安全起见会自动降级导致没有权限执行一些操作,通过``--unsafe-perm`参数来解锁该限制。
|
||||
|
||||
```dockerfile
|
||||
#install
|
||||
RUN apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add --no-cache \
|
||||
apache2 \
|
||||
nodejs \
|
||||
npm \
|
||||
tzdata \
|
||||
&& cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& mkdir -p /data/DefectingCat.github.io \
|
||||
&& npm config set unsafe-perm true \
|
||||
&& npm install -g hexo
|
||||
```
|
||||
|
||||
因为是基于一个操作系统上构建的镜像,所以在构建完成后可以使用Docker来运行一个“伪终端”,让我们可以直接在终端内进行一些修改和查看。值得注意的是,在“伪终端”里进行的操作只是在当前容器内的,不会被写入镜像。当前被关闭后,任何操作将不复存在。
|
||||
|
||||
在构建完后可以使用“伪终端”进入系统内查看一些信息,测试软件能否正常工作等。
|
||||
|
||||
```bash
|
||||
docker run -it --rm blog
|
||||
```
|
||||
|
||||
关于这里的一些参数:
|
||||
|
||||
* `-i`即使没有附加也保持STDIN 打开。
|
||||
|
||||
* `-t`分配一个伪终端。
|
||||
* `--rm`在退出后立刻删除容器。
|
||||
|
||||
### 缓存
|
||||
|
||||
```bash
|
||||
# docker build -t blog /data/github/DefectingCat.github.io/
|
||||
Sending build context to Docker daemon 64kB
|
||||
Step 1/5 : FROM alpine:latest
|
||||
---> 965ea09ff2eb
|
||||
Step 2/5 : MAINTAINER Defectink <i@defect.ink>
|
||||
---> Using cache
|
||||
---> 92cd04f91315
|
||||
```
|
||||
|
||||
在构建的时候可以在某一步(Step)下看到`Using cache`。 当 Docker 构建镜像时,它不仅仅构建一个单独的镜像;事实上,在构建过程中,它会构建许多镜像。
|
||||
|
||||
输出信息中的每一步(Step),Docker都在创建一个新的镜像。同时它还打印了镜像ID:` ---> 92cd04f91315`。这样的好处在于,我们修改`Dockerfile`后重新构建镜像时,那些没有被修改的部分可以将上次构建的镜像当作缓存,加快构建的速度。
|
||||
|
||||
但是这也会有些小问题,Docker是根据`Dockerfile`来判断构建时的变化的。但如果需要执行更新软件等操作,而`Dockerfile`内的命令是没有变化时,Docker会继续使用以前的缓存,导致旧的软件还是被安装了。
|
||||
|
||||
所有在执行某些必要的操作时,不使用缓存也是极有好处的。在构建镜像时,**使用`--no-cache=True`即可**。
|
||||
|
||||
`RUN`命令推荐使用一条命令完成尽可能多的操作,` Dockerfile `中的每个命令都会被分为构建镜像的一步来执行,这样可以减少构建时的步数(Step)。Docker 镜像类似于洋葱。它们都有很多层。为了修改内层,则需要将外面的层都删掉。
|
||||
|
||||
### 第一次的运行
|
||||
|
||||
将所有的软件都安装、测试完后,就可以构建能够第一次运行的镜像了。在此之前,还需要配置需要运行的软件,例如使用hexo生成静态文件,启动apache等。
|
||||
|
||||
```dockerfile
|
||||
COPY DefectingCat.github.io /data/DefectingCat.github.io
|
||||
WORKDIR /data/DefectingCat.github.io
|
||||
RUN hexo g \
|
||||
&& cp -a public/* /var/www/localhost/htdocs
|
||||
|
||||
EXPOSE 80 443
|
||||
CMD ["/usr/sbin/httpd","-f","/etc/apache2/httpd.conf","-DFOREGROUND"]
|
||||
```
|
||||
|
||||
* `COPY`将宿主机上的文件复制进容器内的目录。在安装软件时就已经使用`RUN`来创建过需要的目录了。
|
||||
* `WORKDIR`切换工作的目录,和`cd`类似;切换后`RUN`等命令都会在当前目录下工作。
|
||||
* `EXPOSE`暴露需要使用到的端口。
|
||||
* `CMD`和`RUN`类似,通常用于来启动容器服务。
|
||||
|
||||
关于`CMD`:
|
||||
|
||||
`CMD`只能存在一条,根据运行的软件,它将占据最后容器输出的终端。因为容器并不像虚拟化或者物理机那样,可以使用守护进程;容器本身就是一个进程,容器内没有后台服务的概念。正确的做法是使用`CMD`直接执行可执行文件,并且要求以前台形式运行。
|
||||
|
||||
当前的操作很简单,就是复制宿主机上git克隆下来的文件到容器的制定文件夹,然后使用`hexo`来生成静态文件,最后复制到`apache`的工作目录下。
|
||||
|
||||
到这里就可以来运行一个一次性的容器测试一下我们的服务是否运行正常了。如果上述都没有任何问题的话,现在打开浏览器就应该能看到hexo的blog了🎉。
|
||||
|
||||
```bash
|
||||
docker run -p 80:80 --rm blog
|
||||
```
|
||||
|
||||
到目前为止,Dockerfile应该是这样的:
|
||||
|
||||
```dockerfile
|
||||
FROM alpine:latest
|
||||
MAINTAINER Defectink <i@defect.ink>
|
||||
|
||||
#install
|
||||
RUN apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add --no-cache \
|
||||
apache2 \
|
||||
nodejs \
|
||||
npm \
|
||||
tzdata \
|
||||
&& cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& mkdir -p /data/DefectingCat.github.io \
|
||||
&& npm config set unsafe-perm true \
|
||||
&& npm install -g hexo
|
||||
|
||||
COPY DefectingCat.github.io /data/DefectingCat.github.io
|
||||
WORKDIR /data/DefectingCat.github.io
|
||||
RUN hexo g \
|
||||
&& cp -a public/* /var/www/localhost/htdocs
|
||||
|
||||
EXPOSE 80 443
|
||||
CMD ["/usr/sbin/httpd","-f","/etc/apache2/httpd.conf","-DFOREGROUND"]
|
||||
```
|
||||
|
||||
安装了一些必要的软件,同时也尽量的减少了镜像构建后的大小。
|
||||
|
||||
## HTTPS
|
||||
|
||||
现代的网站应该都不会少的了SSL,也就是我们常见的https。目前自己的网站用的是最简单的LetsEncrypt,使用他家的工具Certbot来申请证书及其方便。在宿主机的环境下甚至还能自动配置。但是目前用的是Docker环境,在使用Dockefile构建时,是没有交互环境的。自动配置也可能无法生效。
|
||||
|
||||
### 生成证书
|
||||
|
||||
Certbot生成证书很是方便,在Docker环境下也是如此。使用官方的镜像可以很方便的生成:
|
||||
|
||||
```bash
|
||||
sudo docker run -it --rm --name certbot \
|
||||
-v "/etc/letsencrypt:/etc/letsencrypt" \
|
||||
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
|
||||
certbot/certbot certonly
|
||||
```
|
||||
|
||||
配合`certonly`只获取证书,并`-v`来将容器的目录映射到宿主机,这样就能在生成后把证书存到宿主机目录了。
|
||||
|
||||
生成时,也会有两种工作模式选择:
|
||||
|
||||
```bash
|
||||
How would you like to authenticate with the ACME CA?
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
1: Spin up a temporary webserver (standalone)
|
||||
2: Place files in webroot directory (webroot)
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
|
||||
```
|
||||
|
||||
分别是:
|
||||
|
||||
* standalone模式:启动一个临时的webserver;
|
||||
* webroot模式:将验证文件放到当前已有的webserver目录下;
|
||||
|
||||
如果当前没有正在运行的webserver,使用standalone模式是最为方便的。Certbot将自己运行一个临时的webserver完成认证。但是如果使用standalone模式,在运行需要添加一个映射的端口:
|
||||
|
||||
```bash
|
||||
sudo docker run -it -p 80:80 --rm --name certbot \
|
||||
-v "/data/docker/apache/letsencrypt:/etc/letsencrypt" \
|
||||
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
|
||||
certbot/certbot certonly
|
||||
```
|
||||
|
||||
因为Certbot启用了一个临时的webserver来验证域名解析,如果不把容器的`80`端口映射出来的话,将无法完成验证。
|
||||
|
||||
在一切都没有任何问题之后,就能看到Congratulations了:
|
||||
|
||||
```bash
|
||||
IMPORTANT NOTES:
|
||||
- Congratulations! Your certificate and chain have been saved at:
|
||||
/etc/letsencrypt/live/domain/fullchain.pem
|
||||
```
|
||||
|
||||
根据官网的说法,证书均链接在`/etc/letsencrypt/live`目录内。
|
||||
|
||||
> `/etc/letsencrypt/archive` and `/etc/letsencrypt/keys` contain all previous keys and certificates, while `/etc/letsencrypt/live` symlinks to the latest versions.
|
||||
|
||||
### Mod_ssl
|
||||
|
||||
有了证书之后,apache还需要ssl的mod。alpine的镜像安装apache时是没有安装的ssl的mod。所以还需要在Dockerfile内添加一行,手动进行安装,包名为`apache2-ssl`:
|
||||
|
||||
```dockerfile
|
||||
RUN apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add --no-cache \
|
||||
apache2 \
|
||||
apache2-ssl \
|
||||
```
|
||||
|
||||
在重新构建之前,还需要修改apache的`ssl.conf`。如何取得`ssl.conf`呢?我们只需要构建一个临时的alpine镜像,在容器内使用相同的命令安装一个apache与ssl mod,之后在`/etc/apache2/conf.d`目录内就有`ssl.conf`配置文件了。将其copy到宿主机内修改就好了。
|
||||
|
||||
```bash
|
||||
apk add apache2-ssl
|
||||
```
|
||||
|
||||
在启动命令内的`httpd.conf`配置文件会包含`ssl.conf`。所以只需要修改`ssl.conf`,再在构建时将其copy到镜像内就好了。
|
||||
|
||||
`httpd.conf`内的已有配置:
|
||||
|
||||
```
|
||||
IncludeOptional /etc/apache2/conf.d/*.conf
|
||||
```
|
||||
|
||||
那么,如何优雅的将容器内的`ssl.conf`copy出来呢?
|
||||
|
||||
可以在先将容器放在后台运行:
|
||||
|
||||
```bash
|
||||
docker run -id test
|
||||
```
|
||||
|
||||
然后使用docker自带的`docker cp`命令来copy到宿主机的目录:
|
||||
|
||||
```bash
|
||||
docker cp 253d3ca34521:/etc/apache2/conf.d/ssl.conf /root
|
||||
```
|
||||
|
||||
当然也可以直接打开,然后记录文件内容再复制出来。
|
||||
|
||||
有了Mod_ssl组件之后,就可以配合SSL证书来对网站进行加密了。既然能将默认的`ssl.conf`复制出来,就可以对其修改然后在生成镜像时再复制会容器内的原目录。
|
||||
|
||||
剩下对于SSL的配置就和给宿主机配置加密一样了,几乎没有什么不同。主要就是在`ssl.conf`中填上正确的证书目录:
|
||||
|
||||
```
|
||||
SSLCertificateFile /etc/letsencrypt/live/defect.ink/fullchain.pem
|
||||
#SSLCertificateFile /etc/ssl/apache2/server-dsa.pem
|
||||
#SSLCertificateFile /etc/ssl/apache2/server-ecc.pem
|
||||
|
||||
# Server Private Key:
|
||||
# If the key is not combined with the certificate, use this
|
||||
# directive to point at the key file. Keep in mind that if
|
||||
# you've both a RSA and a DSA private key you can configure
|
||||
# both in parallel (to also allow the use of DSA ciphers, etc.)
|
||||
# ECC keys, when in use, can also be configured in parallel
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/defect.ink/privkey.pem
|
||||
```
|
||||
|
||||
Let's Encrypt生成的证书在路径下还会有个`fullchain.pem`,这是一整个证书链。在配置文件中只需要这个证书和一个私钥`privkey.pem`就好。
|
||||
|
||||
### 跳转至443
|
||||
|
||||
在有了https之后,如果不需要80端口还能继续访问。可以使用301跳转来将访问80端口的访客都跳转到443。Apache的mod_rewrite可以轻松的实现针对各种条件的跳转。
|
||||
|
||||
mod_rewrite的作用很多,能设置的条件也可以很复杂。当然配置个简单的跳转不是非常的复杂。
|
||||
|
||||
```
|
||||
RewriteEngine on
|
||||
RewriteCond %{SERVER_NAME} =defect.ink
|
||||
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
|
||||
```
|
||||
|
||||
* `RewriteEngine`打开跳转引擎;
|
||||
* `RewriteCond`跳转的条件;这里设置当域名为`defect.ink`时,执行下面的跳转动作;
|
||||
* `RewriteRule`跳转的动作;当符合上面的条件时,执行添加https`https://%{SERVER_NAME}%{REQUEST_URI}`。而后面的变量保持不动。
|
||||
|
||||
这行配置是来自于certbot的自动配置中的,在配置宿主机的ssl时可以选择全部跳转。然后它就会帮我们自动配置了。对其进行简单的修改就可以作用与其他的配置文件了。
|
||||
|
||||
这几行推荐是写在`httpd.conf`的末尾,也就是`IncludeOptional /etc/apache2/conf.d/*.conf`的上方。虽然ssl.conf也会被include进来,但是还是感觉写在这里要方便一点。
|
||||
|
||||
然后将`httpd.conf`和`ssl.conf`一样在构建时复制到容器内就ok了。
|
||||
|
||||
```dockerfile
|
||||
&& cp -a ssl.conf /etc/apache2/conf.d/ \
|
||||
&& cp -a httpd.conf /etc/apache2/
|
||||
```
|
||||
|
||||
### Renew
|
||||
|
||||
Let's Encrypt的证书虽然很方便,但是一次只能生成三个月有效期的证书。使用和生成差不多的方法renew证书就好了。
|
||||
|
||||
```
|
||||
sudo docker run -it -p 80:80 --rm --name certbot \
|
||||
-v "/data/docker/apache/letsencrypt:/etc/letsencrypt" \
|
||||
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
|
||||
certbot/certbot renew
|
||||
```
|
||||
|
||||
想要自动化执行话,可以使用crontab来定时运行。
|
||||
|
||||
## 全部的Dockerfile
|
||||
|
||||
这时候的配置文件看起来应该是这个样子的:
|
||||
|
||||
```dockerfile
|
||||
#test
|
||||
|
||||
FROM alpine:latest
|
||||
MAINTAINER Defectink <i@defect.ink>
|
||||
|
||||
#install
|
||||
RUN apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add --no-cache \
|
||||
apache2 \
|
||||
apache2-ssl \
|
||||
nodejs \
|
||||
npm \
|
||||
tzdata \
|
||||
&& cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& mkdir -p /data/DefectingCat.github.io \
|
||||
&& npm config set unsafe-perm true \
|
||||
&& npm install -g hexo
|
||||
|
||||
COPY DefectingCat.github.io /data/DefectingCat.github.io
|
||||
WORKDIR /data/DefectingCat.github.io
|
||||
RUN hexo g \
|
||||
&& cp -a public/* /var/www/localhost/htdocs/ \
|
||||
&& cp -a ssl.conf /etc/apache2/conf.d/ \
|
||||
&& cp -a httpd.conf /etc/apache2/
|
||||
|
||||
EXPOSE 80 443
|
||||
CMD ["/usr/sbin/httpd","-f","/etc/apache2/httpd.conf","-DFOREGROUND"]
|
||||
```
|
||||
|
||||
## 启动!
|
||||
|
||||
```bash
|
||||
docker run -id --name="blog" -v /etc/letsencrypt/:/etc/letsencrypt/ -p 80:80 -p 443:443 blog
|
||||
```
|
||||
|
||||
全部操作完了,启动命令也随着操作变得更加的复杂了。
|
||||
|
||||
* `-id`扔到后台;
|
||||
* `--name`容器别名;
|
||||
* `-v`映射之前的ssl证书的目录;
|
||||
* `-p`80和443都需要映射;
|
||||
|
||||
## 优化
|
||||
|
||||
一些比较方便的命令。
|
||||
|
||||
删除所有`<none>`的镜像:
|
||||
|
||||
```bash
|
||||
docker rmi $(docker images -f "dangling=true" -q)
|
||||
```
|
||||
|
||||
停止所有容器,删除所有容器:
|
||||
|
||||
```bash
|
||||
docker kill $(docker ps -q) ; docker rm $(docker ps -a -q)
|
||||
```
|
||||
|
||||
停止所有容器,删除所有容器,**删除所有镜像**:
|
||||
|
||||
```bash
|
||||
docker kill $(docker ps -q) ; docker rm $(docker ps -a -q) ; docker rmi $(docker images -q -a)
|
||||
```
|
||||
|
||||
## 参考
|
||||
|
||||
* [How To Create an SSL Certificate on Apache for CentOS 7](https://www.digitalocean.com/community/tutorials/how-to-create-an-ssl-certificate-on-apache-for-centos-7)
|
||||
|
||||
* [apache2-ssl](https://pkgs.alpinelinux.org/package/edge/main/x86/apache2-ssl)
|
||||
* [Certbot running with Docker](https://certbot.eff.org/docs/install.html#running-with-docker)
|
||||
|
||||
* [Where my Certificate](https://certbot.eff.org/docs/using.html#where-certs)
|
88
source/_posts/Gitlab尝鲜.md
Normal file
@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Gitlab 尝鲜
|
||||
date: 2019-06-19 15:42:41
|
||||
tags: Linux
|
||||
categories: 实践
|
||||
url: try-the-gitlab
|
||||
index_img: /images/Gitlab尝鲜/52152339.webp
|
||||
---
|
||||
|
||||
## Gitlab?
|
||||
|
||||
**GitLab**是由GitLab Inc.开发,使用[MIT许可证](https://zh.wikipedia.org/wiki/MIT許可證)的基于[网络](https://zh.wikipedia.org/wiki/互联网)的[Git](https://zh.wikipedia.org/wiki/Git)[仓库](https://zh.wikipedia.org/wiki/仓库_(版本控制))管理工具,且具有[wiki](https://zh.wikipedia.org/wiki/Wiki)和[issue跟踪](https://zh.wikipedia.org/wiki/事务跟踪管理系统)功能。
|
||||
|
||||
它是一款和常见的Github很像仓库管理工具,大体使用上和Github很像。前端页面也很好看,主要的是安装非常的方便,它集成了自身需要的nginx的服务端。
|
||||
|
||||
起初是由Ruby写成,后来部分由Go语言重写。
|
||||
|
||||
最早,它是完全免费的开源软件,按照 MIT 许可证分发。毕竟人家是公司,后来Gitlab被拆分成GitLab CE(社区版)和 GitLab EE(企业版)。和如今的模式一样,ce是完全免费使用的社区版,而ee是可以进行试用且更多功能的收费版。
|
||||
|
||||

|
||||
|
||||
## 安装部署
|
||||
|
||||
[官方](https://about.gitlab.com/install/)拥有详细的安装操作文档,并且对于不同的Linux发行版也有着不同的软件仓库源。除此之外,我们还可以选择其他的安装方式,如Docker等。
|
||||
|
||||
我当前是部署在Ubuntu上的,系统信息:
|
||||
|
||||

|
||||
|
||||
官方是推荐系统空闲内存在4GB以上的,对于类似我们这样的个人使用的较少的来说,推荐空闲内存是2GB以上。毕竟它会自己运行一套nginx、redis等服务端。
|
||||
|
||||
自家的开源地址:[Gitlab](https://gitlab.com/gitlab-org/gitlab-ce/)
|
||||
|
||||
相对于从源码安装来说,自家提供的相应的软件包更加的方便,也更不会容易出错。我们只需要选择相应的操作系统即可。
|
||||
|
||||
这里仅以Ubunt示例:
|
||||
|
||||
首先安装需要的相关依赖:
|
||||
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y curl openssh-server ca-certificates
|
||||
```
|
||||
|
||||
如果我们不使用外部的SMTP来发邮件的话,Gitlab可以使用postfix来进行发邮件。当然对我们完全不需要发邮件的这个需求的话,这步完全可以跳过。
|
||||
|
||||
```
|
||||
sudo apt-get install -y postfix
|
||||
```
|
||||
|
||||
基本依赖安装完后,随后可以添加Gitlab的源来进行安装软件了:
|
||||
|
||||
```
|
||||
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
|
||||
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh | sudo bash
|
||||
```
|
||||
|
||||
*注意ce和ee的区别*
|
||||
|
||||
接下来,我们就可以使用`apt`来进行安装GItlab-ce了。修改下方命令的`https://gitlab.example.com`为自己Gitlab运行的域名。安装程序将自动配置该网址启动Gitlab
|
||||
|
||||
对于需要启用`https`的小伙伴们,Gitlab可以自动请求[[Let's Encrypt](https://docs.gitlab.com/omnibus/settings/ssl.html#lets-encrypthttpsletsencryptorg-integration)]的证书,方便我们一步到位。当然我们也可以使用自己的证书。
|
||||
|
||||
```
|
||||
sudo EXTERNAL_URL="https://gitlab.example.com" apt-get install gitlab-ce
|
||||
```
|
||||
|
||||
到这里就安装的差不多了,此时我们可以打开自己的Gitlab。第一次访问时会被重定向到设定`root`密码的界面。设置完成后我们的Gitlab就安装完成了。初始管理员的账户就是`root`
|
||||
|
||||
由官方给我们提供的安装方式是不是相对来说非常的简单呢?
|
||||
|
||||
## 使用
|
||||
|
||||

|
||||
|
||||
简洁多彩的界面也时非常的好看的。默认没有配置邮件的情况下是可以随意注册的,我们也可以在后台配置里关闭自动注册,作为一个私人的git仓库。也可以手动添加用户给想尝鲜的小伙伴们。
|
||||
|
||||
当然,Gitlab只是一个仓库源的管理工具,提供了类似与Github的功能。对于我们终端使用git来说,还是和Github一模一样。并且我们可以将其部署在国内的主机上,来提升某些情况到Github速度奇慢无比的问题。
|
||||
|
||||
## 启动与管理
|
||||
|
||||
```
|
||||
$ sudo gitlab-ctl reconfigure
|
||||
$ sudo gitlab-ctl status
|
||||
$ sudo gitlab-ctl stop
|
||||
$ sudo gitlab-ctl restart
|
||||
$ sudo ps aux | grep runsvdir
|
||||
```
|
213
source/_posts/Header实践-得拿下这个A.md
Normal file
@ -0,0 +1,213 @@
|
||||
---
|
||||
title: Header 实践-得拿下这个 A
|
||||
date: 2019-12-18 16:42:53
|
||||
tags: HTML
|
||||
categories: 实践
|
||||
url: header-practice-have-to-win-this-a
|
||||
index_img: /images/Header实践-得拿下这个A/header-security.webp
|
||||
---
|
||||
|
||||
[Header安全检测](https://securityheaders.com/)
|
||||
|
||||
之前在学习HTML时候研究过`X-Frame-Options`,它也是header头中的一个安全策略。用于给浏览器指示是否允许一个页面能否嵌入`<iframe>`等嵌入元素。
|
||||
|
||||
下述所有apache2的操作都需要先启用`header`模块(Model)才能使用。
|
||||
|
||||
所有参考/摘录来自于[MDN](https://developer.mozilla.org/)
|
||||
|
||||
## X-Frame-Options
|
||||
|
||||
https://www.defectink.com/defect/HTML-practice-x-frame-option.html
|
||||
|
||||
## Strict-Transport-Security
|
||||
|
||||
` HTTP Strict Transport Security`(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过HTTPS访问当前资源,而不是HTTP。
|
||||
|
||||
一个网站接受一个HTTP的请求,然后跳转到HTTPS,用户可能在开始跳转前,通过没有加密的方式和服务器对话,比如,用户输入http://foo.com或者直接foo.com。
|
||||
|
||||
这样存在中间人攻击潜在威胁,跳转过程可能被恶意网站利用来直接接触用户信息,而不是原来的加密信息。
|
||||
|
||||
网站通过HTTP Strict Transport Security通知浏览器,这个网站禁止使用HTTP方式加载,浏览器应该自动把所有尝试使用HTTP的请求自动替换为HTTPS请求。
|
||||
|
||||
### Apache配置
|
||||
|
||||
在配置文件中添加:
|
||||
|
||||
```
|
||||
Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;"
|
||||
```
|
||||
|
||||
### 浏览器工作方式
|
||||
|
||||
你的网站第一次通过HTTPS请求,服务器响应`Strict-Transport-Security` 头,浏览器记录下这些信息,然后后面尝试访问这个网站的请求都会自动把HTTP替换为HTTPS。
|
||||
|
||||
当HSTS头设置的过期时间到了,后面通过HTTP的访问恢复到正常模式,不会再自动跳转到HTTPS。
|
||||
|
||||
每次浏览器接收到Strict-Transport-Security头,它都会更新这个网站的过期时间,所以网站可以刷新这些信息,防止过期发生。
|
||||
|
||||
Chrome、Firefox等浏览器里,当您尝试访问该域名下的内容时,会产生一个307 Internal Redirect(内部跳转),自动跳转到HTTPS请求。
|
||||
|
||||
### 语法
|
||||
|
||||
```
|
||||
Strict-Transport-Security: max-age=<expire-time>
|
||||
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
|
||||
Strict-Transport-Security: max-age=<expire-time>; preload
|
||||
```
|
||||
|
||||
* `max-age=<expire-time>`:设置在浏览器收到这个请求后的<expire-time>秒的时间内凡是访问这个域名下的请求都使用HTTPS请求。
|
||||
* `includeSubDomains`:如果这个可选的参数被指定,那么说明此规则也适用于该网站的所有子域名。
|
||||
* `preload`:查看 [预加载 HSTS](https://developer.mozilla.org/zh-CN/docs/Security/HTTP_Strict_Transport_Security#预加载_HSTS) 获得详情。不是标准的一部分。
|
||||
|
||||
## Content-Security-Policy
|
||||
|
||||
内容安全策略 ([CSP](https://developer.mozilla.org/en-US/docs/Glossary/CSP)) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 ([XSS](https://developer.mozilla.org/en-US/docs/Glossary/XSS)) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。
|
||||
|
||||
### Apache配置
|
||||
|
||||
在配置文件中添加:
|
||||
|
||||
```html
|
||||
Header set Content-Security-Policy "default-src 'self' https://cdn.defectink.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://cdn.defectink.com; img-src *; style-src 'self' 'unsafe-inline' https://cdn.defectink.com https://maxcdn.bootstrapcdn.com https://fonts.googleapis.com/; font-src 'self' https://cdn.defectink.com https://fonts.gstatic.com/ https://maxcdn.bootstrapcdn.com; form-action 'self' https://cdn.defectink.com; upgrade-insecure-requests;"
|
||||
```
|
||||
|
||||
说白了就是添加允许加载的脚本、样式等内容的白名单。配置相应的值,以控制用户代理(浏览器等)可以为该页面获取哪些资源。比如一个可以上传文件和显示图片页面,应该允许图片来自任何地方,但限制表单的action属性只可以赋值为指定的端点。一个经过恰当设计的内容安全策略应该可以有效的保护页面免受跨站脚本攻击。
|
||||
|
||||
### 跨站脚本攻击
|
||||
|
||||
CSP 的主要目标是减少和报告 XSS 攻击 ,XSS 攻击利用了浏览器对于从服务器所获取的内容的信任。恶意脚本在受害者的浏览器中得以运行,因为浏览器信任其内容来源,即使有的时候这些脚本并非来自于它本该来的地方。
|
||||
|
||||
CSP通过指定有效域——即浏览器认可的可执行脚本的有效来源——使服务器管理者有能力减少或消除XSS攻击所依赖的载体。一个CSP兼容的浏览器将会仅执行从白名单域获取到的脚本文件,忽略所有的其他脚本 (包括内联脚本和HTML的事件处理属性)。
|
||||
|
||||
作为一种终极防护形式,始终不允许执行脚本的站点可以选择全面禁止脚本执行。
|
||||
|
||||
### 实例
|
||||
|
||||
一个网站管理者想要所有内容均来自站点的同一个源 (不包括其子域名)
|
||||
|
||||
```html
|
||||
Content-Security-Policy: default-src 'self'
|
||||
```
|
||||
|
||||
一个网站管理者允许网页应用的用户在他们自己的内容中包含来自任何源的图片, 但是限制音频或视频需从信任的资源提供者(获得),所有脚本必须从特定主机服务器获取可信的代码.
|
||||
|
||||
```html
|
||||
Content-Security-Policy: default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com
|
||||
```
|
||||
|
||||
在这里,各种内容默认仅允许从文档所在的源获取, 但存在如下例外:
|
||||
|
||||
- 图片可以从任何地方加载(注意 "*" 通配符)。
|
||||
- 多媒体文件仅允许从 media1.com 和 media2.com 加载(不允许从这些站点的子域名)。
|
||||
- 可运行脚本仅允许来自于userscripts.example.com。
|
||||
|
||||
## X-Content-Type-Options
|
||||
|
||||
`X-Content-Type-Options` 响应首部相当于一个提示标志,被服务器用来提示客户端一定要遵循在 [`Content-Type`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type) 首部中对 [MIME 类型](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) 的设定,而不能对其进行修改。
|
||||
|
||||
注意: `nosniff` 只应用于 "`script`" 和 "`style`" 两种类型。事实证明,将其应用于图片类型的文件会导致[与现有的站点冲突](https://github.com/whatwg/fetch/issues/395)。
|
||||
|
||||
### Apache配置
|
||||
|
||||
在配置文件中添加:
|
||||
|
||||
```
|
||||
Header set X-Content-Type-Options nosniff
|
||||
```
|
||||
|
||||
### 语法
|
||||
|
||||
`nosniff`
|
||||
|
||||
下面两种情况的请求将被阻止:
|
||||
|
||||
- 请求类型是"`style`" 但是 MIME 类型不是 "`text/css`",
|
||||
- 请求类型是"`script`" 但是 MIME 类型不是 [JavaScript MIME 类型](https://html.spec.whatwg.org/multipage/scripting.html#javascript-mime-type)。
|
||||
|
||||
## Referrer-Policy
|
||||
|
||||
**`Referrer-Policy`** 首部用来监管哪些访问来源信息——会在 [`Referer`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Referer) 中发送——应该被包含在生成的请求当中。
|
||||
|
||||
### Apache配置
|
||||
|
||||
在配置文件中添加:
|
||||
|
||||
```
|
||||
Header always set Referrer-Policy "no-referrer-when-downgrade"
|
||||
```
|
||||
|
||||
### 语法
|
||||
|
||||
注意 `Referer` 实际上是单词 "referrer" 的错误拼写。`Referrer-Policy` 这个首部并没有延续这个错误拼写。
|
||||
|
||||
```
|
||||
Referrer-Policy: no-referrer
|
||||
Referrer-Policy: no-referrer-when-downgrade
|
||||
Referrer-Policy: origin
|
||||
Referrer-Policy: origin-when-cross-origin
|
||||
Referrer-Policy: same-origin
|
||||
Referrer-Policy: strict-origin
|
||||
Referrer-Policy: strict-origin-when-cross-origin
|
||||
Referrer-Policy: unsafe-url
|
||||
```
|
||||
|
||||
## Feature-Policy
|
||||
|
||||
**这是一个实验中的功能**
|
||||
|
||||
**`Feature-Policy`**响应头提供了一种可以在本页面或包含的iframe上启用或禁止浏览器特性的机制。
|
||||
|
||||
### Apache配置
|
||||
|
||||
在配置文件中添加:
|
||||
|
||||
```
|
||||
Header always set Feature-Policy "vibrate 'self'; sync-xhr 'self' https://cdn.defectink.com https://www.defectink.com"
|
||||
```
|
||||
|
||||
### 语法
|
||||
|
||||
```
|
||||
Feature-Policy: <directive> <allowlist>
|
||||
```
|
||||
|
||||
`<allowlist>`
|
||||
|
||||
- `*`: 允许在当前文档和所有包含的内容(比如iframes)中使用本特性。
|
||||
- `'self'`: 允许在当前文档中使用本特性,但在包含的内容(比如iframes)仍使用原值。
|
||||
- `'src'`: (只在iframe中允许) 只要在[src](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe#Attributes) 中的URL和加载iframe用的URL相同,则本特性在iframe中允许,
|
||||
- `'none'`: 从最上层到包含的内容都禁止本特性。 <origin(s)>: 在特定的源中允许,源URL以空格分割。
|
||||
|
||||
- `*`: 本特性默认在最上层和包含的内容中(iframes)允许。
|
||||
- `'self'`: 本特性默认在最上层允许,而包含的内容中(iframes)使用源地址相同设定。也就是说本特性在iframe中不允许跨域访问。
|
||||
- `'none'`: 本特性默认在最上层和包含的内容中(iframes)都禁止。
|
||||
|
||||
`*`(在所有源地址启用)`或'none'`(在所有源地址禁用)只允许单独使用,而`'self'`和`'src'`可以与多个源地址一起使用。
|
||||
|
||||
所有的特性都有一个如下的默认的allowlist
|
||||
|
||||
- `*`: 本特性默认在最上层和包含的内容中(iframes)允许。
|
||||
- `'self'`: 本特性默认在最上层允许,而包含的内容中(iframes)使用源地址相同设定。也就是说本特性在iframe中不允许跨域访问。
|
||||
- `'none'`: 本特性默认在最上层和包含的内容中(iframes)都禁止。
|
||||
|
||||
## 测试
|
||||
|
||||

|
||||
|
||||
### 为什么没有A+?
|
||||
|
||||
因为CSP的一个报错,拒绝加载内联的JS脚本。可以使用`unsafe-inline`来启用内联脚本。但是启用了`unsafe-inline`之后,就得不到A+了。
|
||||
|
||||

|
||||
|
||||
```
|
||||
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-eval' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://cdn.defectink.com". Either the 'unsafe-inline' keyword
|
||||
```
|
||||
|
||||
## 参考
|
||||
|
||||
* [HTTP Strict Transport Security](https://developer.mozilla.org/zh-CN/docs/Security/HTTP_Strict_Transport_Security)
|
||||
* [内容安全策略( CSP )](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP)
|
||||
* [X-Content-Type-Options](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/X-Content-Type-Options)
|
||||
* [Referrer-Policy](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Referrer-Policy)
|
||||
* [Feature-Policy](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Feature-Policy)
|
112
source/_posts/Hexo-and-Github.md
Normal file
@ -0,0 +1,112 @@
|
||||
---
|
||||
title: Hexo and Github
|
||||
index_img: /images/Hexo-and-Github/index.webp
|
||||
date: 2017-09-14 22:06:12
|
||||
tags: [Linux, HTML]
|
||||
categories: 实践
|
||||
url: hexo-and-github
|
||||
---
|
||||
|
||||
> 这是一篇写于较早期的文章,当时水平有限,文章质量不高。
|
||||
|
||||
上次搭建hexo的时候是直接在服务器上使用hexo -server仍在服务器发布的。这次决定配合github,将hexo生成的静态页面部署至github,不仅有了github.io的域名,还能在github上做备份、版本控制等。
|
||||
|
||||
关于hexo安装的,可以去参考上一篇文章 →
|
||||
|
||||
安装完成后就是将hexo与github关联起来了
|
||||
|
||||
## 创建Repositories
|
||||
|
||||
为了将网页部署到github并发布,需要先创建一个仓库
|
||||
|
||||
在个人资料页面选择仓库(Repositories),并单击New来创建一个新的仓库:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
• Repository name:仓库名称(需要使用格式为"Your_github_name.github.io"。Your_github_name一定要为你的github昵称,否则出现404状况)
|
||||
• Description:仓库描述(选填)
|
||||
• Public/Private:仓库类型(公开/私有)
|
||||
• Initialize this repository with a README:是否生成一个README文件初始化仓库(可选)
|
||||
|
||||
所有选项都填写完成后,点击Create Repository来创建仓库
|
||||
|
||||
随后便能看到自己刚刚创建的仓库了(下图为未创建一个README文件来初始化仓库)
|
||||
|
||||

|
||||
|
||||
## 关联Github
|
||||
|
||||
创建好仓库后,需要配置Github信息,以便于等会部署。
|
||||
|
||||
```bash
|
||||
git config --global user.name "name"
|
||||
git config --global user.email "mail"
|
||||
```
|
||||
|
||||
### 生成SSH密钥
|
||||
|
||||
```bash
|
||||
ssh-keygen -t rsa
|
||||
```
|
||||
|
||||
在生成时可以全部保持默认路径即可。
|
||||
|
||||
生成完毕后可以看到公钥的默认路径为:
|
||||
|
||||
```bash
|
||||
/root/.ssh/id_rsa.pub
|
||||
```
|
||||
|
||||
直接编辑并将所有内容复制至Github
|
||||
|
||||
```bash
|
||||
vim .ssh/id_rsa.pub
|
||||
```
|
||||
|
||||
### 部署至Github
|
||||
|
||||
直接修改_config.yml文件,并找到Deployment,修改为如下内容
|
||||
|
||||
```yml
|
||||
deploy:
|
||||
type: git
|
||||
repo : git@github.com:DefectingCat/DefectingCat.github.io.git
|
||||
branch: master
|
||||
```
|
||||
repo:仓库的路径
|
||||
|
||||
branch:分支(默认为master)
|
||||
|
||||
```bash
|
||||
hexo g
|
||||
hexo d
|
||||
hexo d -g
|
||||
```
|
||||
|
||||
部署前生成静态页面
|
||||
|
||||
Deployer not found:git?
|
||||
|
||||
尝试如下命令:
|
||||
|
||||
```bash
|
||||
npm install hexo-deployer-git --save
|
||||
```
|
||||
|
||||
成功部署
|
||||
|
||||
查看刚刚所部署的仓库
|
||||
|
||||

|
||||
|
||||
访问域名测试
|
||||
|
||||

|
||||
|
||||
到此就成功部署到Github并运行成功了呢。
|
||||
|
||||
## Over
|
||||
|
||||
这个只是最基本的部署发布页面,hexo还有很多高级操作。官方文档写的也非常详细,更多操作可以去参考官方文档 https://hexo.io/zh-cn/
|
414
source/_posts/JavaScript-可迭代对象与for-of.md
Normal file
@ -0,0 +1,414 @@
|
||||
---
|
||||
title: JavaScript-可迭代对象与 for-of
|
||||
date: 2020-10-29 17:15:48
|
||||
tags: JavaScript
|
||||
categories: 笔记
|
||||
url: javascript-iterable-object-and-for-of
|
||||
index_img: /images/JavaScript-可迭代对象与for-of/logo.webp
|
||||
---
|
||||
|
||||
## Iterable object(可迭代对象)
|
||||
|
||||
可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在`for...of`循环中使用的对象。数组是可迭代的。但不仅仅是数组,很多其他的内建对象也是可迭代的。例如字符串就是可迭代的。
|
||||
|
||||
## 总最早开始
|
||||
|
||||
可能十年前或者更加久远的年代,我们遍历一个数组需要这样:
|
||||
|
||||
```js
|
||||
let arr = [1, 2, 3, 4, 5];
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
console.log(arr[i]);
|
||||
}
|
||||
```
|
||||
|
||||
或许也不是很久,在我最初学习js的时候就是这样去尝试理解for循环的。
|
||||
|
||||
后来我们发现这样写或许太复杂了,于是有了`for...in`。我们遍历一个数组就变成了这样:
|
||||
|
||||
```js
|
||||
for (let i in arr) {
|
||||
console.log(arr[i]);
|
||||
}
|
||||
```
|
||||
|
||||
是不是和for循环有点类似,`for...in`便是循环遍历对象的一个方式。
|
||||
|
||||
ES6也给了我们一个专门操作遍历数组的方法:`forEach()`
|
||||
|
||||
```js
|
||||
arr.forEach(element => {
|
||||
console.log(element);
|
||||
});
|
||||
```
|
||||
|
||||
与其他的方法不同的是,`forEach()`同数组的`push()`、`pop()`等方法一样,是在Array对象的原型上的,也就是`Array.prototype.forEach()`。并且它除了抛出异常以外,没有办法中止或跳出`forEach()`循环。如果我们需要中止或跳出循环`forEach()`方法不是应当使用的工具。
|
||||
|
||||
### 弥补不足
|
||||
|
||||
我们有多种可以轻松遍历数组的方法,不过他们各有各的不足之处。`for...of`便是代替`for...in`来循环数组而诞生的。
|
||||
|
||||
首先来看看`for...in`对数组的小问题:
|
||||
|
||||
1. `for...in`是为对象设计的,它遍历的是key,而不是value。
|
||||
2. `for...in`会一直查找可枚举的属性,直至原型链顶端。
|
||||
|
||||
先看第一条,`for...in`和直接for循环遍历数组类似,他们循环的是数组的key,需要使用数组的标准访问写法才能得到值。
|
||||
|
||||
```js
|
||||
let arr = [1, 2, 3, 4, 5];
|
||||
|
||||
for (let i in arr) {
|
||||
console.log(arr[i]);
|
||||
// 1, 2, 3, ,4, 5
|
||||
}
|
||||
|
||||
for (let i in arr) {
|
||||
console.log(i);
|
||||
// 0, 1, 2, 3, ,4
|
||||
}
|
||||
```
|
||||
|
||||
不过这看上去无伤大雅,第二条的问题就不像这么温柔了。在当前数组的原型链上的所有的可枚举的属性都会被遍历出来。
|
||||
|
||||
```js
|
||||
Array.prototype.arrTest = function test() {};
|
||||
|
||||
Object.defineProperty(Array.prototype, 'push', {
|
||||
enumerable: true
|
||||
})
|
||||
|
||||
let arr = [1, 2, 3, 4, 5];
|
||||
|
||||
for (let i in arr) {
|
||||
console.log(arr[i]);
|
||||
}
|
||||
```
|
||||
|
||||
无论是我们自定义的函数,还是修改属性为可枚举,`for...in`一条都不会放过。
|
||||
|
||||

|
||||
|
||||
## 迭代协议
|
||||
|
||||
通常的对象是不可迭代的,它不是数组。通过自己创建一个不可迭代的对象,我们就可以轻松地掌握可迭代的概念。
|
||||
|
||||
首先来看一个最基本的对象,我们尝试使用`for...of`去遍历它,会得到一个其不是可迭代对象的是错误:
|
||||
|
||||
```js
|
||||
let obj = {
|
||||
start: 1,
|
||||
end: 5
|
||||
};
|
||||
|
||||
for (num of obj) {
|
||||
console.log(num);
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
VM155:6 Uncaught TypeError: obj is not iterable
|
||||
```
|
||||
|
||||
这是因为我们的Object对象不是可迭代的对象。而迭代协议可以使其成为一个可迭代的对象。
|
||||
|
||||
迭代协议作为 ECMAScript 2015 的一组补充规范,迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。
|
||||
|
||||
### 迭代器
|
||||
|
||||
为了让`obj`对象可迭代(也就让`for..of`可以运行)我们需要为对象添加一个名为`Symbol.iterator`的方法(一个专门用于使对象可迭代的内置symbol)。
|
||||
|
||||
1. 当 `for..of` 循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 **迭代器(iterator)** —— 一个有 `next` 方法的对象。
|
||||
2. 从此开始,`for..of` **仅适用于这个被返回的对象**。
|
||||
3. 当 `for..of` 循环希望取得下一个数值,它就调用这个对象的 `next()` 方法。
|
||||
4. `next()` 方法返回的结果的格式必须是 `{done: Boolean, value: any}`,当 `done=true` 时,表示迭代结束,否则 `value` 是下一个值。
|
||||
|
||||
```js
|
||||
let obj = {
|
||||
start: 1,
|
||||
end: 5
|
||||
};
|
||||
|
||||
// for..of 调用首先会调用这个:
|
||||
obj[Symbol.iterator] = function () {
|
||||
return {
|
||||
// 这个function还是属于obj,所以this指向obj。
|
||||
//接下来,for..of 仅与此迭代器一起工作,要求它提供下一个值
|
||||
current: this.start,
|
||||
last: this.end,
|
||||
// next() 在 for..of 的每一轮循环迭代中被调用
|
||||
// 所以通常next都带有一个判断语句
|
||||
next() {
|
||||
// Symbol.iterator返回的是一个对象,this不会多级指向,所以这里用到了刚刚定义的属性
|
||||
if (this.current <= this.last) {
|
||||
return {
|
||||
value: this.current++,
|
||||
done: false
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
done: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 可以迭代啦
|
||||
for (let num of obj) {
|
||||
console.log(num);
|
||||
};
|
||||
```
|
||||
|
||||
第一次见到迭代器的时候感觉它还是挺复杂的,但仔细研究过后就会发现,其实它大部分还都是固定搭配的。不过这里的this还是比较容易浑人的。
|
||||
|
||||
仔细观察下其核心的功能,发现迭代器是通过一个名为`Symbol.iterator`的方法返回的对象中的:
|
||||
|
||||
1. `obj` 自身没有 `next()` 方法。
|
||||
2. 相反,是通过调用 `obj[Symbol.iterator]()` 创建了另一个对象,即所谓的“迭代器”对象,并且它的 `next` 会为迭代生成值
|
||||
|
||||
那么,既然都是对象,所以迭代器应该是可以放在`obj`自身的。
|
||||
|
||||
```js
|
||||
let obj = {
|
||||
start: 1,
|
||||
end: 5,
|
||||
// Symbol.iterator负责返回一个对象,其对象中包含next方法,这里直接返回this,在obj中定义一个next方法
|
||||
// this.count用于计数
|
||||
[Symbol.iterator]() {
|
||||
this.count = this.start;
|
||||
return this;
|
||||
},
|
||||
next() {
|
||||
if (this.count <= this.end) {
|
||||
return {
|
||||
value: this.count++,
|
||||
done: false
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
done: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (let num of obj) {
|
||||
console.log(num);
|
||||
};
|
||||
```
|
||||
|
||||
这里的`[Symbol.iterator]()`定义为`obj`的一个属性,同时`[Symbol.iterator]()`需要返回一个带有`next()`方法的对象。所以直接将`next()`方法定义在`obj`身上,`[Symbol.iterator]()`通过返回this来返回这个对象。
|
||||
|
||||
这样的写法会比在外部定义`[Symbol.iterator]()`方法更加简洁,this指向也更加清晰。但是在`[Symbol.iterator]()`方法中定义的属性会被添加到`obj`上。
|
||||
|
||||
```js
|
||||
obj.count // 6
|
||||
```
|
||||
|
||||
并且迭代器只用一个,现在不能在该对象上同时运行多个`for...of`循环了,它们将共享迭代状态,因为只有一个迭代器,即对象本身。但是两个并行的 for..of 是很罕见的,即使在异步情况下。
|
||||
|
||||
> 无穷迭代器
|
||||
> 无穷迭代器也是可能的。例如,将`obj`设置为`obj.to = Infinity`,这时`obj`则成为了无穷迭代器。或者我们可以创建一个可迭代对象,它生成一个无穷伪随机数序列。也是可能的。
|
||||
> `next`没有什么限制,它可以返回越来越多的值,这是正常的。
|
||||
> 当然,迭代这种对象的`for..of`循环将不会停止。但是我们可以通过使用`break`来停止它。
|
||||
|
||||
### 展开语法
|
||||
|
||||
展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。
|
||||
|
||||
字面量也就是常见的`[1, 2, 3]`或者`{name: "mdn"}`这种简洁的构造方式。
|
||||
|
||||
展开语法与`for...of`及其相似,无法迭代的对象也无法使用展开语法。错误信息也是一样:
|
||||
|
||||

|
||||
|
||||
展开语法不仅仅只是和`for...of`行为比较像,它还有更多的用法。不过在此赘述也是没有多少意义了。
|
||||
|
||||
## 可迭代的字符串
|
||||
|
||||
在我最早学习js的基本类型的时候,就被告知字符串可以被循环处理。类似于这样:
|
||||
|
||||
```js
|
||||
let str = 'xfy';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
console.log(str[i]);
|
||||
}
|
||||
// x, f, y;
|
||||
```
|
||||
|
||||
虽然无法理解是什么一回事,但当时就感觉字符串和数组很相似,非常神奇。
|
||||
|
||||
根据包装对象的原理,很容易就联想到字符串可迭代是因为其构造函数`String`可迭代(当然也有length属性)。要验证这非常简单,只需要找下`String`上有没有迭代器必备的`[Symbol.iterator]()`方法就可以了。
|
||||
|
||||
虽然包装对象的过程我们无法看到,但是我们可以对一个字符串的原型链向上寻找就ok了。直接调用其原型链上的方法便会触发包装对象,就像调用`toString()`一样,
|
||||
|
||||
```js
|
||||
let str = 'xfy';
|
||||
str.__proto__[Symbol.iterator];
|
||||
```
|
||||
|
||||
直接访问原型链上的`[Symbol.iterator]()`方法,就会发现有这个方法存在,正是有它的存在,字符串才是可迭代的。
|
||||
|
||||
### 显式调用迭代器
|
||||
|
||||
为了能够更加深入的了解迭代器的工作,我们可以不使用`for...of`,反而使用显式的去操作迭代器:
|
||||
|
||||
```js
|
||||
let str = 'xfy';
|
||||
|
||||
// 接收迭代器
|
||||
let iterator = str[Symbol.iterator]();
|
||||
let res;
|
||||
|
||||
while(true) {
|
||||
res = iterator.next();
|
||||
if (res.done) break;
|
||||
console.log(res.value);
|
||||
}
|
||||
```
|
||||
|
||||
只要弄弄清楚了迭代器的工作方式,就能很轻松的理解显式调用。最终我们根据`next()`方法返回的固定格式的值来判断什么适合需要跳出循环以及取值。
|
||||
|
||||
正常情况下我们不需要显式的去迭代一个对象,但是这样做比`for...of`给了我们更多的控制权。我们可以拆分迭代的步骤,并在中途做一些其他的事情。
|
||||
|
||||
## 可迭代与类数组
|
||||
|
||||
可迭代对象与类数组很相似,但他们是两种不同的对象,有着不同的正式术语:
|
||||
|
||||
- **Iterable** 如上所述,是实现了 `Symbol.iterator` 方法的对象。
|
||||
- **Array-like** 是有索引和 `length` 属性的对象,所以它们看起来很像数组。
|
||||
|
||||
当然也有两种特性都有的对象,例如字符串就是可迭代同时也是类数组(有数值索引和 `length` 属性)。
|
||||
|
||||
当光是类数组的对象是无法迭代的
|
||||
|
||||
```js
|
||||
let obj = {
|
||||
0: 'x',
|
||||
1: 'f',
|
||||
2: 'y',
|
||||
length: 3
|
||||
}
|
||||
// Uncaught TypeError: object is not iterable
|
||||
[...obj];
|
||||
```
|
||||
|
||||
可迭代对象和类数组对象通常都**不是数组**,他们也没有数组的一些方法。不过出了字符串以外,我们手动创建的类数组可以使用`call`来改变数组方法的指向,从而使其能够使用一些数组的方法:
|
||||
|
||||
```js
|
||||
Array.prototype.push.call(obj, 1);
|
||||
obj[3];
|
||||
// 1
|
||||
```
|
||||
|
||||
而包装后的字符串其`length`属性是只读的,所以我们无法通过数组的方法去操作它:
|
||||
|
||||
```js
|
||||
let str = 'xfy';
|
||||
// Uncaught TypeError: Cannot assign to read only
|
||||
Array.prototype.push.call(str, 1);
|
||||
```
|
||||
|
||||
### Array.from
|
||||
|
||||
`Array.from`可以从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。通过创建一个浅拷贝的数组,就可以对其使用数组的方法了。
|
||||
|
||||
```js
|
||||
let obj = {
|
||||
0: 'x',
|
||||
1: 'f',
|
||||
2: 'y',
|
||||
length: 3
|
||||
};
|
||||
|
||||
let arr = Array.from(obj);
|
||||
|
||||
arr.push('嘤嘤嘤');
|
||||
```
|
||||
|
||||
Array.from 方法接受对象,检查它是一个可迭代对象或类数组对象,然后创建一个新数组,并将该对象的所有元素浅拷贝到这个新数组。可迭代的对象也是同理。
|
||||
|
||||
```js
|
||||
let obj = {
|
||||
0: 'x',
|
||||
1: 'f',
|
||||
[Symbol.iterator]() {
|
||||
this.sw = true;
|
||||
return this;
|
||||
},
|
||||
next() {
|
||||
if (this.sw) {
|
||||
this.sw = false;
|
||||
return {
|
||||
value: Object.keys(this),
|
||||
done: false
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
done: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let arr = Array.from(obj);
|
||||
```
|
||||
|
||||
`Array.from`还有一个可选的参数,提供了类似于`forEach`的参数选项。
|
||||
|
||||
```js
|
||||
Array.from(obj[, mapFn, thisArg])
|
||||
```
|
||||
|
||||
可选的第二个参数 `mapFn` 可以是一个函数,该函数会在对象中的元素被添加到数组前,被应用于每个元素,此外 `thisArg` 允许我们为该函数设置 `this`。
|
||||
|
||||
```js
|
||||
Array.from([1, 2, 3], x => x + x);
|
||||
// [2, 4, 6]
|
||||
```
|
||||
|
||||
### 可用于代理对
|
||||
|
||||
对于代理对(surrogate pairs)( UTF-16 的扩展字符),`Array.from`也可以正常识别并拷贝为数组。对于普通的字符串,虽然能够使用`slice()`方法,但是对于代理对的操作会导致乱码,两个不同 UTF-16 扩展字符碎片拼接的结果。
|
||||
|
||||
```js
|
||||
let str = '𝒳😂𩷶';
|
||||
|
||||
console.log(str);
|
||||
// "𝒳😂𩷶"
|
||||
str.slice(1,3)
|
||||
// "<22><>"
|
||||
```
|
||||
|
||||
我们可以利用`Array.from`对代理对的正确操作特性来重写创建代理感知(surrogate-aware)的`slice`方法。
|
||||
|
||||
```js
|
||||
let str = '𝒳😂𩷶';
|
||||
function aSlice(arr, star, end) {
|
||||
return Array.from(arr).slice(star, end).join('');
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
可以应用 `for..of` 的对象被称为 **可迭代的**。
|
||||
|
||||
- 技术上来说,可迭代对象必须实现 `Symbol.iterator`方法。
|
||||
- `obj[Symbol.iterator]` 的结果被称为 **迭代器(iterator)**。由它处理进一步的迭代过程。
|
||||
- 一个迭代器必须有 `next()` 方法,它返回一个 `{done: Boolean, value: any}` 对象,这里 `done:true` 表明迭代结束,否则 `value` 就是下一个值。
|
||||
- `Symbol.iterator` 方法会被 `for..of` 自动调用,但我们也可以直接调用它。
|
||||
- 展开语法的操作结果与`for..of`类似。
|
||||
- 内置的可迭代对象例如字符串和数组,都实现了 `Symbol.iterator`。
|
||||
- 字符串迭代器能够识别代理对(surrogate pair)。
|
||||
|
||||
有索引属性和 `length` 属性的对象被称为 **类数组对象**。这种对象可能还具有其他属性和方法,但是没有数组的内建方法。
|
||||
|
||||
## 参考&推荐
|
||||
|
||||
* [迭代协议](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols)
|
||||
* [迭代器和生成器](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_Generators)
|
||||
* [yield*](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield*)
|
||||
* [展开语法](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax)
|
||||
* [Iterable object(可迭代对象)](https://zh.javascript.info/iterable)
|
290
source/_posts/JavaScript实践-乘法表.md
Normal file
@ -0,0 +1,290 @@
|
||||
---
|
||||
title: JavaScript 实践-乘法表
|
||||
date: 2020-12-11 14:58:46
|
||||
tags: JavaScript
|
||||
categories: 实践
|
||||
url: javascript-practice-multiplication-table
|
||||
index_img: /images/JavaScript实践-乘法表/logo.webp
|
||||
---
|
||||
|
||||
从学习js到现在,也有一段时间了。曾经也尝试过写一些复杂的实践。后来经过测试,就算在入门阶段到了会写,那么距离最优代码还是有一定的差距。可能隔个几天回来自己都看不懂自己写的是什么了,杂乱无章。
|
||||
|
||||
最近打算先开始写一些简单的案例做做练习。无意中想到了经典的乘法口诀表,正好for循环也有过实践了,可以尝试一下。
|
||||
|
||||
> for循环嵌套的一次练习。
|
||||
|
||||
## 思路
|
||||
|
||||
有了想法之后,就要开始构建实现的思路。由于css还没跨过门槛,所以决定就打算输出到console里。
|
||||
|
||||

|
||||
|
||||
而在console输出类似于一个梯形的结构也是需要一个思考的问题。可以打印正确的形状之后,就可以考虑如何输出数字与结果了。
|
||||
|
||||
所以构思分为几步:
|
||||
|
||||
1. 打印正确的形状
|
||||
2. 输出内容
|
||||
|
||||
## 打印正确的形状
|
||||
|
||||
首先先忽略内容,一步一步的打印出所有东西。所以先将内容用`*`代替,只在意形状。
|
||||
|
||||
### 正方形
|
||||
|
||||
要想正确打印出一个正三角,可以先考虑从一个10x10正方形开始。
|
||||
|
||||
第一个想法就是将行和列分别输出。首先定义两个变量,一个用于保存`*`,等会打印再同一行循环打印10次,第一行的`*`就输出完成。另一个变量用于保存第一行输出的内容。
|
||||
|
||||
使用一个简单的for循环就能打印出第一行来:
|
||||
|
||||
```js
|
||||
let x = '*'; //输出“*”
|
||||
let s = ''; //保存列输出
|
||||
for (let i = 0; i < 10; i++){
|
||||
s += x + ' '; //打印10列“*”
|
||||
}
|
||||
console.log(s);
|
||||
```
|
||||
|
||||
此时就能看到第一行的效果:
|
||||
|
||||
```js
|
||||
console.log(s);
|
||||
//输出:
|
||||
* * * * * * * * * *
|
||||
```
|
||||
|
||||
现在已经完成了1x10,也就是说再打印出9行通用的内容就可以打印出一个10x10的正方形了。
|
||||
|
||||
那么接下来的思路和上面的也很相似,只需要**再**定义一个变量用于保存最后10行的结果就可以了。for循环也差不多,只需要将`s`加上换行符打印十次就可以了。
|
||||
|
||||
```js
|
||||
let x = '*'; //输出“*”
|
||||
let s = ''; //保存列输出
|
||||
for (let i = 0; i < 10; i++){
|
||||
s += x + ' '; //打印10列“*”
|
||||
}
|
||||
let t = ''; //保存行输出
|
||||
for (let i = 0; i < 10; i++){
|
||||
t += s + '\n'; //再将10列打印10行
|
||||
}
|
||||
console.log(t);
|
||||
```
|
||||
|
||||
这样一个正方形就被打印出来了:
|
||||
|
||||
```js
|
||||
console.log(t);
|
||||
//输出:
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
```
|
||||
|
||||
### 更好的正方形
|
||||
|
||||
现在离乘法表的三角形应该差切掉一半了。但是在此之前,上述打印一个正方形就用掉了三个变量,并且变量`s`在第一个用完之后便没用了,只是个简单的中间人。思路虽然很清晰,但是最后实现的步骤比较繁琐。
|
||||
|
||||
后来思索了一下,只需要将两个for循环嵌套一下,就可以节省下来两个变量,并且整体更加的简洁。
|
||||
|
||||
此时只需要一个变量`s`来保存最后的输出结果。将两个for循环嵌套在一起时,它们的运算步骤就变成为:
|
||||
|
||||
1. 进入第一层循环
|
||||
2. 第一层循环内的for循环到指定的次数后退出
|
||||
3. 再次执行第一层循环,并重述步骤2,直到第一层循环达到次数
|
||||
|
||||
这样一个步骤就能够更加简洁的打印出一个正方形。步骤总结就是第一个for循环第一次,第二个for循环就循环了10次,正好可以打印以后10个`*`。随后第一个for循环进入第二次,并换行,第二个for循环再次打印10次。这样就两行了,以此类推,就可以打印出10x10。
|
||||
|
||||
```js
|
||||
let s = ''; //保存结果
|
||||
for (let i = 0; i < 10; i++){
|
||||
for (let o=0; o<10; o++){
|
||||
s += '* '; //保存行
|
||||
}
|
||||
s += '\n'; //换行
|
||||
}
|
||||
console.log(s);
|
||||
```
|
||||
|
||||
最后的结果和上述还是一样的,但是整体的代码简洁了很多。
|
||||
|
||||
```js
|
||||
console.log(s);
|
||||
//输出:
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
```
|
||||
|
||||
### 三角形
|
||||
|
||||
有了更好的正方形加持下,就可以轻松的打印出三角形了。
|
||||
|
||||
再看一次上述的10x10的步骤,第一个for循环第一次,第二个for循环就循环了10次,正好可以打印以后10个`*`。那么一个正三角形的第一行是只有一个`*`的,也就是说for循环第一次,第二个for循环就循环了1次,这样第一行就只有一个`*`。
|
||||
|
||||
实现的整体和打印正方形是没有多少区别的,是需要限制第二个循环的次数,循环到指定的次数后跳出,再由第一个循环换行。
|
||||
|
||||
根据“第一行一个`*`,第二行两个`*`”的规律,正好第一个循环的变量`i`是自增的。只需要将内循环的那个限制条件改为第一个循环的变量。就可以正好根据第一个循环打印出一个三角。
|
||||
|
||||
```js
|
||||
let s = '';
|
||||
for (let i = 0; i < 10; i++){
|
||||
for (let o = 0; o <= i; o++){ //条件换成第一个循环的次数,达到“递增换行”的效果
|
||||
s += '* ';
|
||||
}
|
||||
s += '\n';
|
||||
}
|
||||
console.log(s);
|
||||
```
|
||||
|
||||
详细工作方式:当`i`等于1时,第一个循环进入第二个循环,第二个循环限制条件为`i`,只循环一次,输出一个`*`。随后跳出到第一个循环打印换行,然后第一个循环进入第二次,`i`等于2。第二个循环正好沿着`i`的自增依次打印出对应数量的`*`。
|
||||
|
||||
输出:
|
||||
|
||||
```js
|
||||
console.log(s);
|
||||
*
|
||||
* *
|
||||
* * *
|
||||
* * * *
|
||||
* * * * *
|
||||
* * * * * *
|
||||
* * * * * * *
|
||||
* * * * * * * *
|
||||
* * * * * * * * *
|
||||
* * * * * * * * * *
|
||||
```
|
||||
|
||||
## 添加内容
|
||||
|
||||
已经跟着第一次打印的正方形,一步一步的能够打印出需要的正三角了。接下来就只需要将`*`替换为乘法表里的内容就可以了。
|
||||
|
||||
### 乘法表
|
||||
|
||||
要想实现一个有规律的方法,诀窍就是仔细观察其实现的规律。在9行的乘法表中,乘数和被乘数的关系在随着行数和列数增加而增加。被乘数总是与行数相等,而乘数则是与列数相等。
|
||||
|
||||
首先第一次的想法是,将乘法表的两个乘数由两个变量代替。只要控制好变量的变化就可以了。这种方法用到的变量比较多,完全用两个变量来代替乘数和被乘数。
|
||||
|
||||
第一次的想法没有确切的观察乘法表的规律,在打印正三角的基础上。通过对比大小来控制两个变量的变化。乘数随着行的增加时,是从1开始随着列增加的,被乘数一直等于行数。于是在第二个控制行的for循环中添加if语句,当乘数小于被乘数时++。
|
||||
|
||||
被乘数是随着行增加而增加的,于是当第二个for循环结束后,被乘数也++。而乘数则是从头开始,所以重新复制=1。
|
||||
|
||||
```js
|
||||
let a = 1;
|
||||
let b = 1;
|
||||
let s = '';
|
||||
for (let i = 0; i < 9; i++) {
|
||||
for (let o = 0; o <= i; o++) {
|
||||
s += a + '*' + b + '=' + a * b + ' ';
|
||||
if (a < b) {
|
||||
a++;
|
||||
}
|
||||
}
|
||||
b++;
|
||||
a = 1;
|
||||
s += '\n';
|
||||
}
|
||||
```
|
||||
|
||||
这样的方法可以成功的实现所有的效果,但是用的变量还是比较多的。并且语句结构也比较复杂。
|
||||
|
||||
### 更好的乘法表
|
||||
|
||||
前面都是没有仔细去观察乘法表的规律而写出来的,从而使用了好几个变量以及复杂的多的语句。
|
||||
|
||||
只需要详细观察了乘法表里的乘数与被乘数之间的变化关系,就能够发现两次嵌套循环之间变量的关系刚好一样。
|
||||
|
||||
```js
|
||||
let res = '';
|
||||
// i随着行变化(被乘数)
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
// o始终从1开始,小于行数(乘数)
|
||||
for (let o = 1; o <= i; o++) {
|
||||
res += `${o}*${i}=${o * i} `;
|
||||
}
|
||||
res += '\n'
|
||||
}
|
||||
```
|
||||
|
||||
也就是说`i`相当于乘数,它随着每行的增加而增加,并且每行都一样,同时它控制着换行。而变量`o`相当于被乘数,每行从1开始,并且小于等于`i`。
|
||||
|
||||
## 操作DOM
|
||||
|
||||
当时水这篇的时候我还没有学到使用js操作DOM,时隔一年以后,我才想起来这篇还没水完。同时一年后,我还在乘法口诀表(◎﹏◎)
|
||||
|
||||
操作DOM并不是很复杂,打印乘法口诀表的主要方法也还是相同的。
|
||||
|
||||
### 插入节点
|
||||
|
||||
本次操作DOM最主要的一点还是循环添加DOM节点,通过`document.appendChild()`方法来添加生成的节点。HTML结构也非常简单,外部一个`div`,内部通过`span`和`br`来插入内容。就类似于这样:
|
||||
|
||||
```html
|
||||
<div class="wrapper">
|
||||
<span>1 ✖ 1 = 1</span>
|
||||
<br>
|
||||
<span>2 ✖ 1 = 2</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
虽然这是个小测试,这里还是使用了文档片段( [`DocumentFragment`](https://developer.mozilla.org/en-US/docs/DOM/DocumentFragment))来插入节点。
|
||||
|
||||
```js
|
||||
wrapper = document.querySelector('.wrapper');
|
||||
fragment = document.createDocumentFragment();
|
||||
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
for (let o = 1; o <= i; o++) {
|
||||
cont = fragment.appendChild(document.createElement('span'));
|
||||
cont.textContent = `${i} ✖ ${o} = ${i * o}`;
|
||||
}
|
||||
fragment.appendChild(document.createElement('br'));
|
||||
}
|
||||
wrapper.appendChild(fragment);
|
||||
```
|
||||
|
||||
思维也很简单,`appendChild()`方法会将插入的节点返回,返回后就可以直接使用`textContent`来插入生成的内容。同样的换行也是在外部的for循环内插入一个`<br>`标签。
|
||||
|
||||
```js
|
||||
let wrapper,
|
||||
cont,
|
||||
fragment;
|
||||
|
||||
window.onload = function () {
|
||||
wrapper = document.querySelector('.wrapper');
|
||||
fragment = document.createDocumentFragment();
|
||||
|
||||
function x99() {
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
for (let o = 1; o <= i; o++) {
|
||||
cont = fragment.appendChild(document.createElement('span'));
|
||||
cont.textContent = `${i} ✖ ${o} = ${i * o}`;
|
||||
}
|
||||
fragment.appendChild(document.createElement('br'));
|
||||
}
|
||||
wrapper.appendChild(fragment);
|
||||
}
|
||||
x99();
|
||||
}
|
||||
```
|
||||
|
||||
<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="Defectink" data-slug-hash="BaLQZZQ" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Multiplication formula table">
|
||||
<span>See the Pen <a href="https://codepen.io/Defectink/pen/BaLQZZQ">
|
||||
Multiplication formula table</a> by Defectink (<a href="https://codepen.io/Defectink">@Defectink</a>)
|
||||
on <a href="https://codepen.io">CodePen</a>.</span>
|
||||
</p>
|
||||
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
|
280
source/_posts/JavaScript的作用域与链.md
Normal file
@ -0,0 +1,280 @@
|
||||
---
|
||||
title: JavaScript 的作用域与链
|
||||
date: 2020-07-03 15:13:05
|
||||
tags: JavaScript
|
||||
categories: 笔记
|
||||
url: javascript-scope-and-chain
|
||||
index_img: /images/JavaScript的作用域与链/index.webp
|
||||
---
|
||||
|
||||
JavaScript是一门动态语言,也常称呼为弱类型/解释型的语言。除了不需要明确写出数据类型外,JavaScript的作用域也和其他类型的语言不同。并且作用域还以链的方式互相连接。
|
||||
|
||||
## 预编译
|
||||
|
||||
要想彻底的了解作用域与变量提升,就得先了解js语言的执行过程。
|
||||
|
||||
JavaScript语言会分为三步来解释执行代码,分别是:
|
||||
|
||||
1. 语法分析
|
||||
2. 预编译
|
||||
3. 解释执行
|
||||
|
||||
语法分析主要是检查整体代码中有没有明显的会导致程序无法执行的语法错误,在解释执行之前找出并提示错误。语法分析只是餐前甜点,真正发生变化的还是在预编译阶段。
|
||||
|
||||
由于预编译的阶段,存在了函数以及变量的提升。所谓的预编译就是在代码真正执行之前做好一些准备,例如声明变量,声明函数等。
|
||||
|
||||
函数的预编译需要经过几个阶段:
|
||||
|
||||
1. 创建AO(执行期上下文,Activation Object)
|
||||
|
||||
```js
|
||||
// 创建AO对象
|
||||
AO {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
2. 寻找形参和变量声明,将变量和形参名作为AO的属性名,值为undefined。(**变量声明提升**)
|
||||
|
||||
```js
|
||||
// 创建AO对象
|
||||
AO {
|
||||
a: undefined,
|
||||
b: undefined
|
||||
}
|
||||
```
|
||||
|
||||
3. 寻找函数声明,赋值为函数体(**函数整体提升**)
|
||||
|
||||
```js
|
||||
// 创建AO对象
|
||||
AO {
|
||||
a : function a() {},
|
||||
b : undefined
|
||||
}
|
||||
```
|
||||
|
||||
> 预编译阶段不会为变量赋值。
|
||||
|
||||
预编译阶段结束后就会进入解释执行阶段,此时函数就会真正准备执行了。
|
||||
|
||||
```js
|
||||
function test(a) {
|
||||
console.log(a);
|
||||
// 结果:function a() {},
|
||||
var a = 123;
|
||||
// let a = 123;变量不会提升
|
||||
console.log(a);
|
||||
// AO对象中的a被变量赋值,结果:123
|
||||
function a() {}
|
||||
console.log(a);
|
||||
// 函数声明已经被提升,结果:123
|
||||
let b = function() {}
|
||||
console.log(b);
|
||||
// b是函数表达式,刚刚被赋值为函数体。结果:function() {}
|
||||
}
|
||||
test(1);
|
||||
```
|
||||
|
||||
## 作用域
|
||||
|
||||
JavaScript也有作用域。不过和其他强类型语言相比,JavaScript并不是以花括号`{}`来产生作用域的。
|
||||
|
||||
作用域可以直接的理解为一块独立的区域,在该区域内声明的函数、变量等都属于这个区域。使得在不同区域内的同重名变量和函数等不会冲突。在通常情况下作用域内的变量等不会被外部所访问到。
|
||||
|
||||
在预编译环境中,函数会创建一个执行期上下文的内部对象,这个AO就定义了一个函数执行时的环境。AO就可以理解为函数的作用域。
|
||||
|
||||
全局环境下也有一个执行环境:GO(Global Object),它存储了全局环境下的变量和函数等。也就是全局作用域。
|
||||
|
||||
执行期上下文:当函数运行时,会创建一个执行期上下文的内部对象(AO,Activation Object)。一个执行期上下文定义了一个函数执行时的环境。函数每次执行时对应的执行上下文是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文。当函数执行完毕,它所产生的执行期上下文会被销毁。
|
||||
|
||||
在函数的AO中保存的**经过声明的变量**等,除了特意保存到外部,否则无论函数执行与否,在AO外部是无法被访问的:
|
||||
|
||||
```js
|
||||
function test() {
|
||||
var a = 123;
|
||||
let b = 333;
|
||||
function c() {
|
||||
console.log('xfy');
|
||||
}
|
||||
}
|
||||
console.log(a); // a is not defined.
|
||||
c(); // c is not defined.
|
||||
```
|
||||
|
||||
> 未经声明而直接赋值的变量将会转化为全局变量。
|
||||
|
||||
### 变量声明提升
|
||||
|
||||
除了新的关键字`let`,`cont`之外,JavaScript只在函数中产生作用域。并且和全局作用域一样,在函数作用域内使用`var`声明和函数声明也会提升。
|
||||
|
||||
```js
|
||||
(function () {
|
||||
a = 123; // 未经声明前使用
|
||||
console.log(a);
|
||||
var a; // 声明提升
|
||||
}())
|
||||
```
|
||||
|
||||
变量仅仅只是声明会提升到作用域顶部,变量的赋值还是在原有的位置被赋值。因为预编译环境不会为变量赋值。
|
||||
|
||||
### 函数声明提升
|
||||
|
||||
函数的声明提升与变量提升类似,都是经过预编译阶段将函数的声明提升到作用域的顶端。所以和使用`var`声明的变量一样,可以将调用写在函数声明之前。
|
||||
|
||||
```js
|
||||
someFun() //声明前调用
|
||||
function someFun() {
|
||||
console.log('xfy');
|
||||
}
|
||||
```
|
||||
|
||||
但必须是声明函数体才会在预编译中提升,将函数赋值给变量并不属于函数声明。此时在声明前调用函数就会得到`ReferenceError: Cannot access 'foo' before initialization`的提示。
|
||||
|
||||
```js
|
||||
foo();
|
||||
let foo = function () {
|
||||
console.log('xfy');
|
||||
}
|
||||
```
|
||||
|
||||
### 全局作用域
|
||||
|
||||
最外层的作用域,在代码的任何地方都能访问到的作用域。
|
||||
|
||||
```js
|
||||
let a = 123; // 全局作用域内的变量
|
||||
function foo() {
|
||||
let b = 333; // 函数作用域内的变量
|
||||
return b;
|
||||
}
|
||||
```
|
||||
|
||||
在window/global的属性也是全局作用域。
|
||||
|
||||
如果变量没有使用关键字声明,那么将会创建出一个全局变量。并且值得注意的是在连等赋值时,容易产生没有被声明的变量,因为关键字只能声明第一个变量,等号后的变量将不会被关键字声明。
|
||||
|
||||
```js
|
||||
function foo() {
|
||||
a = 123; // 全局变量
|
||||
let b = c = 100; // c没有被声明
|
||||
}
|
||||
foo();
|
||||
console.log(a); // 123
|
||||
// console.log(b);
|
||||
console.log(c); // 100
|
||||
```
|
||||
|
||||
### 块级作用域
|
||||
|
||||
新增的`let`和`const`关键字可以产生块级作用域。在其他的强类型语言中可能会经常容易见到块级作用域,常见的就是在大括号`{}`中的代码为一个作用域。
|
||||
|
||||
js的大括号`{}`是不会产生作用域的,也就是在没有这两个新增的关键字之前,js的块级作用域并不是那么容易产生。
|
||||
|
||||
块级作用域在日常的代码中也有很多方便的用途,例如最常见的`for`循环就很容易因为没有块级作用域而导致在循环中调用的是同一个计数器的值。
|
||||
|
||||
在下面这段代码中,计数器`i`使用`var`来声明的,并不会产生块级作用域。也就是说:**无论循环多少次,最后作用域里保存的`i`只有一个值,那就是最后一次循环的值`10`。**所以在这个数组内保存的十个函数在执行时,读取的到作用域里的`i`都是10
|
||||
|
||||
```js
|
||||
let arr = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
arr[i] = function () {
|
||||
console.log(i);
|
||||
}
|
||||
}
|
||||
arr[2](); // 10
|
||||
```
|
||||
|
||||
而将`var`换成`let`之后,由于块级作用域的存在,每次循环,都能将`i`的值保存在作用域里。等到数组内的函数执行时,它所访问的`i`就是那一次循环所保存在作用域里的值。所以使用了`let`就不会出现所有的`i`都是10的情况了。
|
||||
|
||||
```js
|
||||
let arr = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
arr[i] = function () {
|
||||
console.log(i);
|
||||
}
|
||||
}
|
||||
arr[2](); // 2
|
||||
```
|
||||
|
||||
因为js中的函数是可以产生作用域的,所以除了使用`let`来生成块级作用域,还能使用函数来模拟块级作用域。上述的例子中,可以使用立即执行函数来模拟出一个块级作用域。
|
||||
|
||||
由于立即执行函数是立即执行的,所以每次循环它都会执行一次,并产生一个AO(作用域)来保存参数。将`i`作为参数传递给立即执行函数,就能把每次循环`i`的值给保存下来。
|
||||
|
||||
```js
|
||||
let arr = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
(function (j) {
|
||||
arr[j] = function () {
|
||||
console.log(j);
|
||||
}
|
||||
}(i))
|
||||
}
|
||||
arr[2]() // 2
|
||||
```
|
||||
|
||||
> 目前官方也是推荐使用`let`替换`var`。
|
||||
|
||||
## 链
|
||||
|
||||
[[scope]]:每个js函数都是一个对象,对象中有些属性可以访问,但有些不可以。这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个。
|
||||
|
||||
[[scope]]中所存储的**执行期上下文对象的集合**,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
|
||||
|
||||
通俗的来说,作用域链就是将多个函数的AO包括GO呈链式的保存起来,并按照一定的顺序来访问。
|
||||
|
||||
作用域链将函数与其内部的函数和全局作用域串接在一起,呈一条链式的连接。在需要访问其变量或其他属性时,函数会顺着作用域依次向上查找,直到找到顶部GO。
|
||||
|
||||
```js
|
||||
let a = 123;
|
||||
function x() {
|
||||
let b = 333;
|
||||
console.log(a, b);
|
||||
function y() {
|
||||
let c = 321;
|
||||
console.log(a, b, c);
|
||||
function z() {
|
||||
let d = 'xfy';
|
||||
console.log(a, b, c, d);
|
||||
}
|
||||
z();
|
||||
}
|
||||
y();
|
||||
}
|
||||
x();
|
||||
```
|
||||
|
||||

|
||||
|
||||
在作用域链内部的函数可以向上方位外部函数作用域内的变量,直至全局作用域。而作用域外的函数不能访问内部函数的变量。
|
||||
|
||||
### 父级作用域中的变量
|
||||
|
||||
由于作用域链的特性,子作用域内可以任意访问外部作用域,也就是父级作用域内的变量。但它也有一些值得注意的地方。
|
||||
|
||||
与`this`的指向不同的是,它并不是谁调用就会去访问谁的作用域。下述代码,虽然**fun调用了foo**,但是foo并不在fun作用域链内,也就是说foo并不能访问fun内的变量。
|
||||
|
||||
```js
|
||||
let a = 123;
|
||||
function foo() {
|
||||
console.log(a);
|
||||
}
|
||||
function fun() {
|
||||
let a = 'xfy';
|
||||
foo();
|
||||
}
|
||||
fun(); // 123
|
||||
```
|
||||
|
||||
```js
|
||||
let a = 123;
|
||||
function foo() {
|
||||
console.log(b);
|
||||
}
|
||||
function fun() {
|
||||
let b = 'xfy';
|
||||
foo();
|
||||
}
|
||||
fun(); // b is not defined.
|
||||
```
|
||||
|
616
source/_posts/JavaScript的函数.md
Normal file
@ -0,0 +1,616 @@
|
||||
---
|
||||
title: JavaScript 的函数
|
||||
index_img: /images/JavaScript的函数/index.webp
|
||||
date: 2020-08-07 11:26:10
|
||||
tags: JavaScript
|
||||
categories: 笔记
|
||||
url: function-of-javascript
|
||||
---
|
||||
|
||||
函数表达式是JavaScript中一个强大同时容易令人困惑的特性。
|
||||
|
||||
定义函数的方式有两种:函数声明和函数表达式。
|
||||
|
||||
函数声明首先是`function`关键字,其后是函数的名字,这就是指定函数名字的方式。
|
||||
|
||||
```js
|
||||
// 函数声明
|
||||
function test() {
|
||||
console.log('Hi, there');
|
||||
}
|
||||
```
|
||||
|
||||
关于函数声明,其一个重要特征就是**函数声明体提升**。它和用`var`声明提升类似,但他是整体提升,所以可以在声明前使用。
|
||||
|
||||
```js
|
||||
// 函数声明提升
|
||||
test();
|
||||
function test() {
|
||||
console.log('Hi, there');
|
||||
}
|
||||
```
|
||||
|
||||
函数表达式创建函数有一点不同,它类似于声明一个变量,使用`var`或者`let`来声明一个函数体。
|
||||
|
||||
`let`关键字后的变量名就是函数的名字,而函数后没有名称,这个函数称为匿名函数。可以理解为将这个匿名函数赋值给了这个变量。
|
||||
|
||||
```js
|
||||
// 函数表达式
|
||||
let test = function () {
|
||||
console.log('Yo, there');
|
||||
}
|
||||
```
|
||||
|
||||
函数表达式与其他表达式一样,需要先声明才能使用。尽管`var`创建的变量会声明提升,但是赋值并不会提升。
|
||||
|
||||
```js
|
||||
test(); // 函数还未声明
|
||||
let test = function () {
|
||||
console.log('something...');
|
||||
}
|
||||
```
|
||||
|
||||
在早期的环境里,函数声明和if语句可能还不能工作的和契合。不过现在多数浏览器与运行环境都能正常执行如下代码:
|
||||
|
||||
```js
|
||||
if (1) {
|
||||
function hi() {
|
||||
console.log('hi');
|
||||
}
|
||||
} else {
|
||||
function hi() {
|
||||
console.log('yo');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
我的猜测可能是和函数声明提升有关,因为js没有块级作用域,所以在if语句里根据条件的函数声明都提升了,导致就算条件满足,最后声明的还是下面的函数声明。
|
||||
|
||||
如果真的有不能运行的环境,可以做这样的尝试。因为提前声明了变量,在将匿名函数赋值给变量,所以就不存在函数声明提升这个特性了。
|
||||
|
||||
```js
|
||||
let hi;
|
||||
if (1) {
|
||||
hi = function () {
|
||||
console.log('hi');
|
||||
}
|
||||
} else {
|
||||
hi = function () {
|
||||
console.log('yo');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 递归
|
||||
|
||||
递归函数是在一个函数通过名字调用自身的情况下构成的:
|
||||
|
||||
```js
|
||||
function add(num) {
|
||||
if (num <= 1) {
|
||||
return 1;
|
||||
}
|
||||
return num + add(num - 1);
|
||||
}
|
||||
```
|
||||
|
||||
这是一个经典的递归实例,但是如果将函数名更换一下。并将前一个函数名给解除引用,就会导致函数出错。这是因为函数内部的递归是通过函数名来引用这个函数的。如果更换了函数名,就会导致这种错误。
|
||||
|
||||
```js
|
||||
function add(num) {
|
||||
if (num <= 1) {
|
||||
return 1;
|
||||
}
|
||||
return num + add(num - 1);
|
||||
}
|
||||
|
||||
let minus = add;
|
||||
add = null;
|
||||
console.log(minus(5));
|
||||
|
||||
// return num + add(num - 1);
|
||||
// ^
|
||||
// TypeError: add is not a function
|
||||
|
||||
```
|
||||
|
||||
但这并不影响递归,可以换种方式来继续引用函数自身。`arguments.callee`就是指向正在执行的函数的指针,因此可以用它来实现对函数递归的调用。从而与函数名拜托关系。
|
||||
|
||||
```js
|
||||
function add(num) {
|
||||
if (num <= 1) {
|
||||
return 1;
|
||||
}
|
||||
return num + arguments.callee(num - 1);
|
||||
}
|
||||
|
||||
let minus = add;
|
||||
add = null;
|
||||
console.log(minus(5));
|
||||
```
|
||||
|
||||
但`arguments.callee`不能在strict下使用。不过可以通过命名函数表达式来达到相同的效果。
|
||||
|
||||
在使用函数表达式时,使用小括号可以为函数体添加一个任然有效的名称。再将这个函数赋值给这个变量,无论函数怎么更换引用,函数名任然有效。这种方式再严格模式和非严格模式都行得通。
|
||||
|
||||
```js
|
||||
let add = (function a(num) {
|
||||
if (num <= 1) {
|
||||
return 1;
|
||||
}
|
||||
return num + a(num - 1);
|
||||
})
|
||||
```
|
||||
|
||||
## 闭包
|
||||
|
||||
闭包就是在一个函数的内部返回另一个函数,返回的函数将还能访问外部函数的AO。
|
||||
|
||||
在compare这个函数里返回了另一个内部函数(一个匿名函数),这个匿名函数使用了compare函数的`prep`参数。即使这个函数被返回到了外部使用,依然能访问到prep变量。这涉及到作用域链的细节。
|
||||
|
||||
当一个函数运行时(被调用时),会创建一个执行环境(execution context)及相应的作用域链,然后使用arguments和其他参数的值来初始化函数的活动对象(Activation Object)。这个AO会随着作用域链,链给内部的函数,并使内部函数可以访问外部函数的变量。
|
||||
|
||||
[JavaScript的作用域与链](https://www.defectink.com/defect/javascript-scope-and-chain.html)
|
||||
|
||||
```js
|
||||
function compare(prep) {
|
||||
return function (obj1, obj2) {
|
||||
let o1 = obj1[prep];
|
||||
let o2 = obj2[prep];
|
||||
if (obj1[prep] < obj2[prep]) {
|
||||
return -1;
|
||||
} else if (obj1[prep] > obj2[prep]) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当创建compare函数时,会创建一个包含全局对象的作用域链,这个作用域链被保存在内部的[[scope]]属性中。当调用`compare()`函数时,会为函数创建一个执行环境,然后赋值[[scope]]属性中的对象构建起执行环境的作用域链。
|
||||
|
||||
作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
|
||||
|
||||
无论什么时候访问函数中的一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来说,当函数执行完后,局部活动对象就会被销毁,内存中仅保留全局作用域(全局执行环境的活动对象GO)。
|
||||
|
||||
但闭包的情况有所不同,当外部函数执行完后,其活动对象也不会被销毁,内部函数依然引用着外部函数的作用域链,。在将内部函数返回后,其执行环境的作用域链会被销毁,但是它的活动对象依然会留在内存中,直到匿名函数被销毁。
|
||||
|
||||
> 由于闭包会携带着包含它的函数的作用域,所以会占用更多的内存。虽然2020年不差内存,且像v8等js引擎会尝试回收闭包的内存,但还是推荐谨慎使用。
|
||||
|
||||
### 闭包与变量
|
||||
|
||||
作用域链的机制以及没有块级作用域的特性引出了一个副作用,即闭包只能取得外部函数AO中变量任何最后一个值。
|
||||
|
||||
```js
|
||||
function cycle() {
|
||||
let arr = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
arr[i] = function () {
|
||||
console.log(i);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
let test = cycle();
|
||||
test[1]();
|
||||
```
|
||||
|
||||
数组随着for循环将函数赋值到自身,变量i会随着循环的增加而增加。但是函数被赋值时并没有执行,等到函数被返回后在外部被执行时,访问到的i已经是AO里的10了。
|
||||
|
||||
可以使用立即执行函数,让i变量成为实参传入数组的函数内,因为**参数是按值传递的**,这样在外部执行时,每个匿名函数中的变量都有一个副本。
|
||||
|
||||
立即执行函数在执行后会被销毁,但是它的AO依然被内部的函数所引用。所以对应次数循环的函数内j就对应立即执行函数AO中的j,且每个立即执行函数AO中的j都对应i,因为每次的立即执行函数都不同。
|
||||
|
||||
可以使用立即执行函数来为内部的匿名函数再封装一个AO来解决此问题。
|
||||
|
||||
```js
|
||||
function cycle() {
|
||||
let arr = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
(function (o) {
|
||||
arr[o] = function () {
|
||||
console.log(o);
|
||||
}
|
||||
})(i);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
function cycle() {
|
||||
let arr = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
arr[i] = (function (o) {
|
||||
return function () {
|
||||
console.log(o);
|
||||
}
|
||||
})(i);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
let test = cycle();
|
||||
```
|
||||
|
||||
或者直接使用let来声明变量,产生块级作用域,在根本上解决问题。
|
||||
|
||||
```js
|
||||
function cycle() {
|
||||
let arr = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
arr[i] = function () {
|
||||
console.log(i);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
```
|
||||
|
||||
### 关于this对象
|
||||
|
||||
在闭包中使用this对象可能会出现一些小问题。由于闭包返回的匿名函数是在外部执行的,所以匿名函数的执行环境具有全局性,因此它的this对象通常指向window。
|
||||
|
||||
```js
|
||||
global.name = 'xfy';
|
||||
// window.name = 'xfy';
|
||||
let obj = {
|
||||
name: 'xxxfy',
|
||||
age: 18,
|
||||
say: function () {
|
||||
return function () {
|
||||
console.log(this.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj.say()();
|
||||
```
|
||||
|
||||
这个例子在非严格模式下返回的是全局对象的属性。那为什么匿名函数没有取得外部函数的this的值呢?
|
||||
|
||||
每个函数在被调用时都会自动取得两个特殊的变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量(在匿名函数没有定义形参时可以访问外部函数的实参)。
|
||||
|
||||
可以将this实体化,保持在外部函数的AO中,再由匿名函数去访问外部AO中的变量。即使在函数返回之后,that也任然引用着obj。
|
||||
|
||||
```js
|
||||
global.name = 'xfy';
|
||||
// window.name = 'xfy';
|
||||
let obj = {
|
||||
name: 'xxxfy',
|
||||
age: 18,
|
||||
say: function () {
|
||||
let that = this;
|
||||
return function () {
|
||||
console.log(that.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj.say()();
|
||||
```
|
||||
|
||||
有几种特殊的情况,会导致this发生意外的变化。
|
||||
|
||||
```js
|
||||
let obj = {
|
||||
name: 'xfy',
|
||||
feature: 'handsome',
|
||||
say: function () {
|
||||
console.log(this.name);
|
||||
}
|
||||
}
|
||||
global.name = 'dfy'; // 非严格模式下;
|
||||
// let name = 'dfy';
|
||||
```
|
||||
|
||||
第一行就是平时最平常的调用了对象的方法,this得到正确的引用。第二行在方法执行前加上了括号,虽然加上了括号之后好像是在引用一个函数,但this的值得到了维持。而第三行代码先执行了一条赋值语句,然后再调用赋值后的结果。因为这个赋值表达式的值是函数本身,所以this的值不能得到维持,返回的就是全局对象里的属性了。
|
||||
|
||||
```js
|
||||
obj.say(); // xfy;
|
||||
(obj.say)(); // xfy;
|
||||
(obj.say = obj.say)(); // 'dfy';
|
||||
```
|
||||
|
||||
### 内存泄漏
|
||||
|
||||
在高贵的IE9之前的版本,JScript对象和COM对象使用不同的辣鸡收集程序例程。那么在这些版本中的ie使用闭包就会导致一些特殊的问题。如果闭包的作用域中保存着一个HTML元素,那么就意味着该元素无法被销毁。
|
||||
|
||||
这样的一个简单的示例为element元素事件创建了一个闭包,而这个闭包又创建了一个循环引用。由于匿名函数保存了对getid函数的活动对象(AO)的引用,因此就会导致无法减少element的引用数。只要匿名函数存在,element引用数至少为1。所以它占用的内存就不会被回收。
|
||||
|
||||
```js
|
||||
function getid() {
|
||||
let element = docuemnt.getElementById('test');
|
||||
element.onclick = function () {
|
||||
alert(element.id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
不过只需要稍微改下就能够解决这个问题。将`element.id`保存在一个变量中,并且在闭包中引用这个变量消除循环引用。但仅仅还不能解决内存泄漏的问题。闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭不引用element,包含函数的活动对象中也仍然会保存一个引用。因此,有必要把element设置为null。
|
||||
|
||||
```js
|
||||
function getid() {
|
||||
let element = docuemnt.getElementById('test');
|
||||
let id = element.id;
|
||||
element.onclick = function () {
|
||||
alert(id);
|
||||
}
|
||||
element = null;
|
||||
}
|
||||
```
|
||||
|
||||
## 模仿块级作用域
|
||||
|
||||
JavaScript是没有块级作用域的(`let`与`const`声明的变量/常量是有的)。这就意味着块语句中定义的变量,实际上是在包含函数中而非语句中创建的。
|
||||
|
||||
在C艹、Java等语言中,变量i只会在for循环的语句块中有定义,一旦循环结束,变量i就会被销毁。而在JS中,变量i是由函数的活动对象所定义的,使用`var`声明的变量将会在函数的AO里。并且在函数的任何位置都能访问它,即使重新声明一次,它的值也不会变。
|
||||
|
||||
```js
|
||||
function test(num) {
|
||||
for (var i = 0; i < num; i++) {
|
||||
console.log(i);
|
||||
}
|
||||
var i;
|
||||
console.log(i); // 10
|
||||
console.log(i); // 10
|
||||
}
|
||||
test(10);
|
||||
```
|
||||
|
||||
在现在看来,当然是推荐使用`let`来解决这个问题了。在for循环中使用`let`声明变量就会产生块级作用域。
|
||||
|
||||
```js
|
||||
function test(num) {
|
||||
for (let i = 0; i < num; i++) {
|
||||
console.log(i);
|
||||
}
|
||||
console.log(i); // i is not defined
|
||||
}
|
||||
test(10);
|
||||
```
|
||||
|
||||
不过在没有`let`关键字的时候,可以使用立即执行函数来模拟块级作用域(私有作用域)。与`let`的效果一样!
|
||||
|
||||
```js
|
||||
function test(num) {
|
||||
(function () {
|
||||
for (var i = 0; i < num; i++) {
|
||||
console.log(i);
|
||||
}
|
||||
})()
|
||||
console.log(i); // i is not defined
|
||||
}
|
||||
test(10);
|
||||
```
|
||||
|
||||
JS将`function`关键字当作一个函数声明的开始,而函数声明后面不能跟园括号。然而函数表达式后面可以跟圆括号,要将函数声明转换为函数表达式,只需要加一对括号即可。
|
||||
|
||||
```js
|
||||
function () {
|
||||
//
|
||||
}(); // 语法错误;
|
||||
|
||||
(function () {
|
||||
//
|
||||
})();
|
||||
```
|
||||
|
||||
模仿的块级作用域通常作用于函数外部的全局作用域,从而向全局作用域添加变量和函数,而不污染全局作用域。
|
||||
|
||||
```js
|
||||
let name = 'xfy';
|
||||
(function () {
|
||||
let name = 'yyy';
|
||||
console.log(name);
|
||||
})() // yyy
|
||||
```
|
||||
|
||||
> 这种做法可以减少闭包占用内存的问题,没有匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域了。
|
||||
|
||||
## 私有变量
|
||||
|
||||
严格来说,JS是没有私有成员的概念;所有对象属性都是公开的。不过可以使用函数来实现私有变量的概念。任何在函数中定义的变量,都可以认为是私有化变量,因为在函数外部无法访问这些变量。私有变量包含函数的参数、局部变量和在函数内定义的其他函数。
|
||||
|
||||
在这个函数内部,有两个参数和一个局部变量。在函数的内部可以访问这些变量,而在函数外部除了主动返回之外则不能访问他们。如果在这个函数内创建一个闭包,那么这个闭包可以通过作用域链来访问到这些变量。利用这一点,就可以创建访问私有变量的共有方法。
|
||||
|
||||
```js
|
||||
function add(num1, num2) {
|
||||
let result = num1 + num2;
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
可以利用构造函数的模式来创建私有和公有属性。带有this的属性将因为构造函数被返回到外部,从而形成闭包。而构造函数内部其他的没有this的属性则不会被赋予到实例上。此时就只能通过定义的公有方法来访问私有的属性。
|
||||
|
||||
这种有权访问私有属性的公有方法称之为**特权方法**(privileged method)。
|
||||
|
||||
```js
|
||||
function Person(name) {
|
||||
// 私有变量
|
||||
let age = 18;
|
||||
// 私有方法
|
||||
function pv() {
|
||||
age++;
|
||||
console.log(age);
|
||||
}
|
||||
// 公有方法
|
||||
this.say = function () {
|
||||
console.log(name);
|
||||
// 通过公有方法执行私有方法
|
||||
pv();
|
||||
}
|
||||
}
|
||||
let xfy = new Person('xfy');
|
||||
console.log(xfy.name);
|
||||
xfy.say();
|
||||
```
|
||||
|
||||
因为必须要使用构造函数,且还需要在构造函数内定义方法,所以这种方法一样有着和构造函数一样的缺点,每个实例都会创建一组同样的新方法。
|
||||
|
||||
### 静态私有变量
|
||||
|
||||
直接使用构造函数会导致方法被重新定义到每个实例。解决构造函数的这个问题的方法就是将方法定义在其原型上,而为了使原型上的方法能够访问到私有化的变量,可以通过在私有作用域中定义方法。
|
||||
|
||||
这个方法通过在私有作用域中创建一个全局的构造函数,并且将其方法定义在原型上。当构造函数在全局时,其原型的方法依然能访问私有作用域内的私有变量。
|
||||
|
||||
未经声明的创建变量可以提升到全局作用域,但是在严格模式下未经声明的变量则会报错。在目前的版本,可以在全局作用域中将变量先声明,再在私有作用域中赋值。可以到达同样的效果,其原型上的方法依然继承私有作用域的活动对象。
|
||||
|
||||
```js
|
||||
let Person;
|
||||
(function () {
|
||||
'use strict'
|
||||
let v = 10;
|
||||
function pv() {
|
||||
v++;
|
||||
console.log(v);
|
||||
}
|
||||
|
||||
Person = function () {};
|
||||
Person.prototype.say = function () {
|
||||
console.log(v);
|
||||
}
|
||||
Person.prototype.cha = function () {
|
||||
pv();
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
let xfy = new Person();
|
||||
xfy.say();
|
||||
```
|
||||
|
||||
除了上述的静态私有变量,还有一种写法就是在构造函数内为私有变量赋值。这样创建的实例就能够共享私有变量,并且在创建是就为其赋值。
|
||||
|
||||
这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己私有变量。要使用实例变量,还是静态私有变量。最终视需求而定。
|
||||
|
||||
```js
|
||||
let Person;
|
||||
(function () {
|
||||
let name;
|
||||
Person = function (value) {
|
||||
name = value;
|
||||
}
|
||||
Person.prototype.get = function () {
|
||||
console.log(name);
|
||||
}
|
||||
Person.prototype.set = function (value) {
|
||||
name = value;
|
||||
}
|
||||
})()
|
||||
|
||||
let xfy = new Person('xfy');
|
||||
xfy.get();
|
||||
xfy.set('dfy');
|
||||
xfy.get();
|
||||
```
|
||||
|
||||
### 模块模式
|
||||
|
||||
前面所述的方法都是为自定义类型创建私有变量和特权方法的。而道格拉斯所说的模块模式(module pattern)是为单例创建私有变量和特权方法的。所谓单例(singleton),指的就是只有一个实例的对象。
|
||||
|
||||
通常,JS都是以对象字面量来创建单例对象的。
|
||||
|
||||
```js
|
||||
let singleton = {
|
||||
name: value,
|
||||
method: function () {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
模块模式通过为单例添加私有变量和特权方法能够让其得到增强。
|
||||
|
||||
这个模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部定义了私有变量和函数。然后,将一个对象字面量作为函数的值返回。在返回的字面量中定义了公开的属性和方法。因为这个字面量是在匿名函数内定义的,所以它有权访问匿名的方法和属性。从本质上来讲,这个对象字面量定义的是单例的公共接口。
|
||||
|
||||
```js
|
||||
let singleton = function () {
|
||||
let pv = 10;
|
||||
function pf() {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
pubp: true,
|
||||
prip: function () {
|
||||
pv++;
|
||||
return pv;
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
console.log(singleton);
|
||||
console.log(singleton.prip());
|
||||
```
|
||||
|
||||
这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时时非常有用的。
|
||||
|
||||
```js
|
||||
let app = function () {
|
||||
// 私有变量和函数
|
||||
let con = [];
|
||||
// 初始化
|
||||
con.push(new BaseCon());
|
||||
// 公共方法
|
||||
return {
|
||||
getCon: function () {
|
||||
return con;
|
||||
},
|
||||
regCon: function (con) {
|
||||
if (typeof con == 'obejct') {
|
||||
con.push(con)
|
||||
}
|
||||
}
|
||||
};
|
||||
}();
|
||||
```
|
||||
|
||||
### 增强的模块模式
|
||||
|
||||
有人改进了模块模式,即在返回对象之前就加入对其增强的代码。这种增强的模块模式适合那些单例必须是某种类型的实例。同时还必须添加某些属性/方法对其加以增强的情况。
|
||||
|
||||
```js
|
||||
let app = function () {
|
||||
// 私有变量和函数
|
||||
let con = [];
|
||||
// 初始化
|
||||
con.push(new BaseCon());
|
||||
// 公共方法
|
||||
return {
|
||||
getCon: function () {
|
||||
return con;
|
||||
},
|
||||
regCon: function (con) {
|
||||
if (typeof con == 'obejct') {
|
||||
con.push(con)
|
||||
}
|
||||
}
|
||||
};
|
||||
}();
|
||||
```
|
||||
|
||||
## 小结
|
||||
|
||||
在JavaScript编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无须对函数命名,从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用JavaScript函数的强大方式。以下总结了函数表达式的特点。
|
||||
|
||||
* 函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫匿名函数。
|
||||
* 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂;
|
||||
* 递归函数应该始终使用`arguments.callee`来递归地调用自身,不要使用函数名———函数名可能会发生变化。
|
||||
|
||||
当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理如下。
|
||||
|
||||
* 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。
|
||||
* 通常,当函数返回一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。
|
||||
|
||||
使用闭包可以在JavaScript中模仿块级作用域(JavaScript本身没有块级作用域的概念),要点如下。
|
||||
|
||||
* 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。
|
||||
* 结果就是函数内部的所有变量都会被立即销毁——除非某些变量赋值给了包含作用域(即外部作用域)中的变量。
|
||||
|
||||
闭包还可以用于在对象中创建私有变量,相关概念和要点如下。
|
||||
|
||||
* 即使JavaScript中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。
|
||||
* 有权访问私有变量的公有方法叫做特权方法。
|
||||
* 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法。
|
||||
|
||||
JavaScript中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。不过,因为创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。
|
1099
source/_posts/JavaScript笔记-引用类型.md
Normal file
1203
source/_posts/JavaScript面向对象的程序设计.md
Normal file
158
source/_posts/Node.js之旅.md
Normal file
@ -0,0 +1,158 @@
|
||||
---
|
||||
title: Node.js 之旅
|
||||
index_img: /images/Node.js之旅/2020-09-03-16-16-04.webp
|
||||
date: 2020-09-03 16:09:29
|
||||
tags: [JavaScript, Node]
|
||||
categories: 笔记
|
||||
url: get-starting-for-node-js
|
||||
---
|
||||
|
||||
> Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
|
||||
|
||||
Node.js不仅仅是服务器上的JavaScript。
|
||||
|
||||

|
||||
|
||||
## 并不熟悉的JavaScript
|
||||
|
||||
虽然说Node直接般来了个V8来运行JavaScript,但它毕竟不运行在浏览器上,并且是由事件驱动的异步程序,它的本来的目的就是用来搭建高性能的Web服务器。在极少情况下,编写的 JavaScript 代码没有按期望的方式运行。
|
||||
|
||||
在客户端的运行环境下,只要是在同一个浏览器窗口运行的JS环境就属于同一个全局环境。无论是在哪里引入的JS文件,都属于同一个运行环境,全局变量能够正常工作,例如有两个js被引入:
|
||||
|
||||
```html
|
||||
<script src="a.js"></script>
|
||||
<script src="b.js"></script>
|
||||
```
|
||||
|
||||
他们分别有一段代码:
|
||||
|
||||
```js
|
||||
// a.js
|
||||
let a = 'xxxxxfy!';
|
||||
|
||||
// b.js
|
||||
alert(a);
|
||||
```
|
||||
|
||||
正常情况下全局变量`a`是可以被正常访问的,而在node中,a.js和b.js分别是两个文件,不做其他操作的情况下,他们分别是两个全局变量。
|
||||
|
||||
## 基础架构
|
||||
|
||||
Node 运行时的基础架构由两大组件构成:
|
||||
|
||||
* JavaScript 引擎
|
||||
* 非阻塞 I/O 库
|
||||
|
||||
### JavaScript引擎
|
||||
|
||||
众所周知,node使用的是Chrome大排自吸V8,它可以运行任何JS代码。启动时,它会启动一个V8的引擎实例,且node可以充分的利用这个引擎实例。
|
||||
|
||||
V8可嵌入(/绑定)到任何C++程序中,。这意味着,除了纯 JavaScript 库外,还可以扩展 V8 来创建全新的函数(或函数模板),方法是将其与 V8 绑定在一起。并且node还支持使用编译好的二进制的C++程序。
|
||||
|
||||
### 事件循环
|
||||
|
||||
JavaScript是一门单线程的语言,无论换到什么地方运行,它也是单线程语言。单线程语言面临的最大的问题就是阻塞,在前面的代码没有被执行完,后面的代码都将处于等待状态。
|
||||
|
||||
node使用[libuv](http://libuv.org/)来实现事件循环。要使用事件循环可以使用异步API,可将回调函数作为参数传递到该 API 函数,在事件循环期间会执行该回调函数。node异步编程的直接体现就是回调函数。异步编程基于回调函数来实现,但不能说是使用了回调后程序就异步化了。Node 所有 API 都支持回调函数。
|
||||
|
||||
```
|
||||
┌───────────────────────────┐
|
||||
┌─>│ timers │
|
||||
│ └─────────────┬─────────────┘
|
||||
│ ┌─────────────┴─────────────┐
|
||||
│ │ pending callbacks │
|
||||
│ └─────────────┬─────────────┘
|
||||
│ ┌─────────────┴─────────────┐
|
||||
│ │ idle, prepare │
|
||||
│ └─────────────┬─────────────┘ ┌───────────────┐
|
||||
│ ┌─────────────┴─────────────┐ │ incoming: │
|
||||
│ │ poll │<─────┤ connections, │
|
||||
│ └─────────────┬─────────────┘ │ data, etc. │
|
||||
│ ┌─────────────┴─────────────┐ └───────────────┘
|
||||
│ │ check │
|
||||
│ └─────────────┬─────────────┘
|
||||
│ ┌─────────────┴─────────────┐
|
||||
└──┤ close callbacks │
|
||||
└───────────────────────────┘
|
||||
```
|
||||
|
||||
例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。
|
||||
|
||||
事件循环包含多个调用回调函数的阶段:
|
||||
|
||||
* 计时器阶段:将运行 setInterval() 和 setTimeout() 过期计时器回调函数
|
||||
* 轮询阶段:将轮询操作系统以查看是否完成了所有 I/O 操作,如果已完成,将运行回调函数
|
||||
* 检查阶段:将运行 setImmediate() 回调函数
|
||||
|
||||
一个常见的误区是,认为 V8 和事件循环回调函数在不同线程上运行。但事实并非如此。V8 在同一个线程上运行所有 JavaScript 代码。
|
||||
|
||||
## 非阻塞I/O
|
||||
|
||||
已经简单的了解过node基于v8的单线程来使用回调函数实现异步编程,从而达到高性能与高并发。
|
||||
|
||||
### 异步I/O
|
||||
|
||||
异步I/O能够大大的提升程序的工作效率,且不会影响剩下的代码执行。
|
||||
|
||||
#### 同步读取文件
|
||||
|
||||
首先来看下同步读取文件
|
||||
|
||||
```js
|
||||
let fs = require('fs');
|
||||
console.log('starting process...');
|
||||
let data = fs.readFileSync('test.txt');
|
||||
console.log(data);
|
||||
console.log('the end');
|
||||
```
|
||||
|
||||
同步读取文件会按照从上到下的顺序来执行代码,到遇到读取I/O时,剩下的代码将处于等待状态。并且在读取I/O时遇到错误(例如文件不存在),将会直接返回错误,剩下的代码将不执行完。
|
||||
|
||||
#### 异步读取文件
|
||||
|
||||
```js
|
||||
let fs = require('fs');
|
||||
console.log('starting process...');
|
||||
fs.readFile('test.txt', (err, data) => {
|
||||
if (err) {
|
||||
console.log('something was wrong: ' + err);
|
||||
} else {
|
||||
console.log(data);
|
||||
}
|
||||
})
|
||||
console.log('the end');
|
||||
```
|
||||
|
||||
异步读取文件调用了回调函数,它设置了一个事件循环,在等待读取I/O的同时,将继续执行剩下的代码。当文件读取完成之后,将会返回回调函数,并输出结果。并且当读取文件遇到错误时,在等待读取期间执行的代码将正常执行。
|
||||
|
||||
异步的结果看起来像是这样的:
|
||||
|
||||
```
|
||||
starting process...
|
||||
the end
|
||||
<Buffer 64 66 6a 61 73>
|
||||
```
|
||||
|
||||
在等待其读取`test.txt`的期间,JS将剩下的`console.log`语句先执行了。
|
||||
|
||||
### 同步I/O
|
||||
|
||||
node的设计理念就是使用异步编程,使用阻塞编程可无法写出一个高性能的web服务器。
|
||||
|
||||
但这不代表着同步I/O就一无是处,在某些情况下,同步 I/O 通常比异步 I/O 更快,原因在于设置和使用回调函数、轮询操作系统来获取 I/O 状态等操作都涉及到一定的开销。
|
||||
|
||||
如果需要写一个一次性的使用程序,仅用于处理一个文件,从命令行中启动 Node,并向其传递 JavaScript 实用程序的文件名。此时,该实用程序是唯一运行的程序,因此,即使它阻塞了 V8 线程,也没有任何影响。在此情况下,适合使用同步 I/O。
|
||||
|
||||
只要是谨慎使用同步 Node API 调用,就不会出现什么问题。
|
||||
|
||||
## 小结
|
||||
|
||||
* V8是有八个气缸的V型发动机.
|
||||
* JavaScript是单线程语言,它通过V8 引擎提供的异步执行回调接口实现了异步编程。
|
||||
|
||||
全文均是笔记
|
||||
|
||||
* [Node.js 之旅](https://developer.ibm.com/zh/tutorials/learn-nodejs-tour-node/)
|
||||
* [The Node.js Event Loop](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/)
|
||||
* [Node.js 回调函数](https://www.runoob.com/nodejs/nodejs-callback.html)
|
||||
* [V8发动机](https://zh.wikipedia.org/wiki/V8%E5%8F%91%E5%8A%A8%E6%9C%BA)
|
219
source/_posts/QinQ基础操作.md
Normal file
@ -0,0 +1,219 @@
|
||||
---
|
||||
title: QinQ 基础操作
|
||||
date: 2019-05-29 16:21:15
|
||||
tags: Network
|
||||
categories: 网络
|
||||
url: basic-knowledge-of-qinq
|
||||
index_img: /images/QinQ基础操作/qinq.webp
|
||||
---
|
||||
|
||||
> QwQ♥
|
||||
|
||||
## QinQ简介
|
||||
|
||||
QinQ技术(或称为IEEE 802.1ad、Vlan stacking)。是802.1q协议(Virtual Bridged Local Area Networks)为基础衍生出的一种通讯协议。
|
||||
|
||||
它是一项拓展vlan空间的技术,通过在原有的以太网帧中再堆叠一个802.1q的报头来达到拓展vlan空间的功能。使其vlan数量最多可以达4094(inner)*4094(outer)。即802.1Q-in-802.1Q,所以称之为QinQ协议。
|
||||
|
||||
### 目的
|
||||
|
||||
随着当前的以太网技术的发展,利用传统802.1q vlan来对用户进行隔离和标识收到很大限制。因为IEEE802.1Q中定义的VLAN Tag域只有12个比特,仅能表示4096个VLAN,无法满足以太网中标识大量用户的需求,于是QinQ技术应运而生。
|
||||
|
||||
而运用了QinQ协议之后,可以在原有的vlan标签中再堆叠一层vlan标签,使其vlan的数量达到翻倍,极大的拓展了vlan的空间。
|
||||
|
||||
### 优点
|
||||
|
||||
- 扩展VLAN,对用户进行隔离和标识不再受到限制。
|
||||
- QinQ内外层标签可以代表不同的信息,如内层标签代表用户,外层标签代表业务,更利于业务的部署。
|
||||
- QinQ封装、终结的方式很丰富,帮助运营商实现业务精细化运营。
|
||||
|
||||
1. 解决日益紧缺的公网VLAN ID 资源问题
|
||||
2. 用户可以规划自己的私网VLNA ID
|
||||
3. 提供一种较为简单的二层VPN解决方案
|
||||
4. 使用户网络具有较高的独立性
|
||||
|
||||
### 实现方式
|
||||
|
||||
QinQ拥有两种实现方式:
|
||||
|
||||
1. 基本QinQ
|
||||
|
||||
1. 如果收到的是带有VLAN Tag的报文,该报文就成为带双Tag的报文。
|
||||
2. 如果收到的是不带VLAN Tag的报文,该报文就成为带有本端口缺省VLAN Tag的报文。
|
||||
|
||||
2. 灵活QinQ
|
||||
|
||||
1. 为具有不同内层VLAN ID的报文添加不同的外层VLAN Tag。
|
||||
2. 根据报文内层VLAN的802.1p优先级标记外层VLAN的802.1p优先级和添加不同的外层VLAN Tag。通过使用灵活QinQ技术,在能够隔离运营商网络和用户网络的同时,又能够提供丰富的业务特性和更加灵活的组网能力。
|
||||
|
||||
|
||||
* 基本QinQ
|
||||
|
||||
基本QinQ是基于接口的封装,是指进入一个接口的所有流量都将全部封装为一个相同的外层Vlan tag。封装方式不够灵活,用户业务区分不够细致。这种封装方式称之为基本QinQ。
|
||||
|
||||
* 灵活QinQ
|
||||
|
||||
* 基于Vlan ID的QinQ封装:这种封装方式可以针对不同的vlan tag来决定是否封装外层vlan tag、封装何种vlan tag。这种封装方式称之为灵活QinQ。
|
||||
* 基于802.1q优先级的QinQ封装:这种封装方式可以针对用户不同优先级的数据流来决定是否封装外层vlan tag、封装何种vlan tag。这种封装方式亦称之为灵活QinQ。
|
||||
|
||||
## 终结子接口
|
||||
|
||||
“终结”意思为设备对传过来的报文tag进行识别,然后根据后续的转发行为来对单层或双层的tag进行玻璃或继续传输。
|
||||
|
||||
“终结”一般作用于子接口上,故称之为:终结子接口
|
||||
|
||||
QinQ技术在和MPLS/IP核心网连接时,根据不同的情况,会用到不同的终结方法:
|
||||
|
||||
* 如果路由子接口是对报文的单层Tag终结,那么该子接口称为Dot1q终结子接口;
|
||||
* 如果路由子接口是对报文的双层Tag终结,那么该子接口称为QinQ终结子接口。
|
||||
|
||||
Tips:Dot1q终结子接口和QinQ终结子接口不支持透传不带VLAN的报文,收到不带VLAN的报文会直接丢弃。
|
||||
|
||||
## 帧格式
|
||||
|
||||
QinQ报文有着固定的格式,就是在802.1Q的标签上再堆叠一层802.1Q标签。QinQ报文比普通的vlan标签多4个字节。vlan帧最小帧长为68字节。
|
||||
|
||||

|
||||
|
||||
| 字段 | 长度 | 含义 |
|
||||
| ------------------- | ----------- | ------------------------------------------------------------ |
|
||||
| Destination address | 6字节 | 目的MAC地址。 |
|
||||
| Source address | 6字节 | 源MAC地址。 |
|
||||
| Type | 2字节 | 长度为2字节,表示帧类型。取值为0x8100时表示802.1Q Tag帧。如果不支持802.1Q的设备收到这样的帧,会将其丢弃。对于内层VLAN tag,该值设置为0x8100;对于外层VLAN tag,有下列几种类型0x8100:思科路由器使用0x88A8:Extreme Networks switches使用0x9100:Juniper路由器使用0x9200:Several路由器使用 |
|
||||
| PRI | 3比特 | Priority,长度为3比特,表示帧的优先级,取值范围为0~7,值越大优先级越高。用于当交换机阻塞时,优先发送优先级高的数据包。 |
|
||||
| CFI | 1比特 | CFI (Canonical Format Indicator),长度为1比特,表示MAC地址是否是经典格式。CFI为0说明是经典格式,CFI为1表示为非经典格式。用于区分以太网帧、FDDI(Fiber Distributed Digital Interface)帧和令牌环网帧。在以太网中,CFI的值为0。 |
|
||||
| VID | 12比特 | LAN ID,长度为12比特,表示该帧所属的VLAN。在VRP中,可配置的VLAN ID取值范围为1~4094。 |
|
||||
| Length/Type | 2字节 | 指后续数据的字节长度,但不包括CRC检验码。 |
|
||||
| Data | 42~1500字节 | 负载(可能包含填充位)。 |
|
||||
| CRC | 4字节 | 用于帧内后续字节差错的循环冗余检验(也称为FCS或帧检验序列)。 |
|
||||
|
||||
### 报文示例
|
||||
|
||||

|
||||
|
||||
### TPID(Tag Protocol Identifier)
|
||||
|
||||
TPID:标签协议标识ID(Tag Protocol Identifier)是Vlan tag中的一个字段,标识该vlan tag的协议类型。IEEE 802.1Q协议规定QinQ的外层vlan标签的type值为:(0x8100)。
|
||||
|
||||

|
||||
|
||||
> IEEE802.1Q协议定义的以太网帧的VLAN Tag。802.1Q Tag位于SA(Source Address)和Length/Type之间。通过检查对应的TPID值,设备可确定收到的帧承载的是运营商VLAN标记还是用户VLAN标记。接收到帧之后,设备将配置的TPID值与帧中TPID字段的值进行比较。如果二者匹配,则该帧承载的是对应的VLAN标记。例如,如果帧承载TPID值为0x8100的VLAN标记,而用户网络VLAN标记的TPID值配置为0x8200,设备将认为该帧没有用户VLAN标记。也就是说,设备认为该帧是Untagged报文。
|
||||
> 另外,不同运营商的系统可能将QinQ帧外层VLAN标记的TPID设置为不同值。为实现与这些系统的兼容性,可以修改TPID值,使QinQ帧发送到公网时,承载与特定运营商相同的TPID值,从而实现与该运营商设备之间的互操作性。以太网帧的TPID与不带VLAN标记的帧的协议类型字段位置相同。为避免在网络中转发和处理数据包时出现问题,不可将TPID值设置为下表中的任意值:
|
||||
|
||||
| 协议类型 | 对应值 |
|
||||
| :------: | :------------------: |
|
||||
| ARP | 0x0806 |
|
||||
| RARP | 0x8035 |
|
||||
| IP | 0x0800 |
|
||||
| IPV6 | 0x86DD |
|
||||
| PPPoE | 0x8863/0x8864 |
|
||||
| MPLS | 0x8847/0x8848 |
|
||||
| IPX/SPX | 0x8137 |
|
||||
| LACP | 0x8809 |
|
||||
| 802.1x | 0x888E |
|
||||
| HGMP | 0x88A7 |
|
||||
| 设备保留 | 0xFFFD/0xFFFE/0xFFFF |
|
||||
|
||||
## 基本QinQ配置
|
||||
|
||||
拓扑:
|
||||
|
||||

|
||||
|
||||
如图示,SW2和SW3用于模拟运营商之间的Internet,SW1和SW4为客户内网。基本QinQ的配置就作用于SW2和SW3之间,将客户内网内的vlan10与vlan20封装上一层vlan100,用于再SW2和SW3之间传输。
|
||||
|
||||
* SW1和SW4配置相同:
|
||||
|
||||
```
|
||||
sysname SW1
|
||||
#
|
||||
vlan batch 10 20
|
||||
#
|
||||
interface GigabitEthernet0/0/1
|
||||
port link-type trunk
|
||||
port trunk allow-pass vlan 10 20
|
||||
#
|
||||
interface GigabitEthernet0/0/2
|
||||
port link-type access
|
||||
port default vlan 10
|
||||
#
|
||||
interface GigabitEthernet0/0/3
|
||||
port link-type access
|
||||
port default vlan 20
|
||||
```
|
||||
|
||||
SW1和SW4只需做基本配置,用作普通二层交换。
|
||||
|
||||
* SW2和SW3配置相同:
|
||||
|
||||
```
|
||||
sysname SW2
|
||||
#
|
||||
vlan batch 100
|
||||
#
|
||||
interface GigabitEthernet0/0/1
|
||||
port link-type dot1q-tunnel //开启基本二层QinQ功能
|
||||
port default vlan 100 //并划分为vlan100
|
||||
#
|
||||
interface GigabitEthernet0/0/2
|
||||
port link-type trunk //普通trunk
|
||||
port trunk allow-pass vlan 100
|
||||
```
|
||||
|
||||
SW1的`G 0/0/1`为trunk接口,对应连接的SW2的`G 0/0/1`为基本二层QinQ接口,划分vlan为vlan 100。
|
||||
|
||||
使用PC1发送ICMP包到PC3,数据包内容为:
|
||||
|
||||

|
||||
|
||||
其中,可以看到内层的802.1q的vlan标签ID为10,type为(0x0800);外层的,也就是SW2封装的vlan标签ID为100,type为(0x8100)。
|
||||
|
||||
## 灵活QinQ配置
|
||||
|
||||
拓扑和上述一样:
|
||||
|
||||

|
||||
|
||||
我们在模拟internet的SW2和SW3之间添加了一个vlan 200,用于配置灵活的QinQ的vlan 20堆叠一个vlan 200的tag。
|
||||
|
||||
SW1和SW4的配置与普通的QinQ的配置相同,无需改变。
|
||||
|
||||
* SW2与SW3配置相同:
|
||||
|
||||
```
|
||||
sysname SW2
|
||||
#
|
||||
vlan batch 100 200
|
||||
#
|
||||
interface GigabitEthernet0/0/1
|
||||
port link-type hybrid //必须是hybrid接口模式
|
||||
qinq vlan-translation enable //开启vlan转换
|
||||
port hybrid untagged vlan 100 200 //出方向时剥离vlan100和200的标签
|
||||
port vlan-stacking vlan 10 stack-vlan 100 //vlan10堆叠vlan100tag
|
||||
port vlan-stacking vlan 20 stack-vlan 200 ////vlan20堆叠vlan200tag
|
||||
#
|
||||
interface GigabitEthernet0/0/2
|
||||
port link-type trunk
|
||||
port trunk allow-pass vlan 100 200
|
||||
```
|
||||
|
||||
从vlan10和vlan20的PC分别向各自的vlan发包,可以看到数据包内容:
|
||||
|
||||
* vlan10
|
||||
|
||||

|
||||
|
||||
* vlan20
|
||||
|
||||

|
||||
当两台PC机正常通信的时候,可以看到不同vlan封装的外层vlan tag也是不一样的。这就是基于vlan的灵活QinQ。
|
||||
|
||||
## 上述拓扑
|
||||
|
||||
* [灵活QinQ](http://cloud.defect.ink/s/s9l4h9rx)
|
||||
* [接口QinQ](http://cloud.defect.ink/s/5ttw9ett)
|
||||
|
||||
## 参考
|
||||
|
||||
* [QinQ帧格式](http://www.023wg.com/message/message/cd_feature_eth_qinq.html)
|
||||
* [QinQ基础知识](https://blog.csdn.net/qq_38265137/article/details/80404320)
|
343
source/_posts/Resilio-Sync多平台实时同步.md
Normal file
@ -0,0 +1,343 @@
|
||||
---
|
||||
title: Resilio Sync 多平台实时同步
|
||||
date: 2019-05-18 12:31:45
|
||||
tags: Tools
|
||||
categories: 实践
|
||||
url: multi-platform-real-time-synchronization-by-resilio-sync
|
||||
index_img: /images/Resilio-Sync多平台实时同步/res.webp
|
||||
---
|
||||
|
||||
> 备份,同步一步到位。🏹
|
||||
|
||||
## 什么是Resilio Sync
|
||||
|
||||
[Resilio Sync](https://www.resilio.com/individuals/)是一款多平台的文件同步工具,能够实现几乎实时的同步效果。原名BitTorrent Sync,看到的原名就能想到,它的运作原理类似于我们的BitTorrent。
|
||||
|
||||
它是由[BitTorrent](https://en.wikipedia.org/wiki/BitTorrent_(company))开发的一款基于P2P的协议来进行传输文件的。
|
||||
|
||||
它和我以前用过的一款运作于Linux发行版之上的同步软件[lsyncd](https://github.com/axkibe/lsyncd)类似,都能达到几乎实时监控指定文件夹变动的一款同步工具。
|
||||
|
||||
相比较二者之间的不同的是,Resilio Sync在Windows平台是拥有GUI的(亦是使用WebUI),但是在其他服务端的操作系统上,类似于各种Linux发行版与Windows Server。它的GUI是以WebUI的方式提供给我们使用的。
|
||||
|
||||
这也是它的优点之一了,在不用反复的修改配置文件的情况下,我们可以使用基于WebUI的一种交互方式来对其进行操作。
|
||||
|
||||
这也是我很喜欢它的地方之一了❤
|
||||
|
||||
### 优点
|
||||
|
||||
如果我们用作同步工具来使用的话,它还是有很多的优点的:
|
||||
|
||||
1. 可以纯内网工作(P2P)
|
||||
2. 多人实时同步,原理是人越多速度越快(BitTorrent)
|
||||
3. 存储空间及流量等不受限制
|
||||
4. 多平台、多网络环境同步
|
||||
5. Free for home
|
||||
6. WebUI
|
||||
|
||||
### 缺点
|
||||
|
||||
事物都是拥有两面性的,既然它拥有不少优点。那么缺点肯定也不会缺席:
|
||||
|
||||
1. 闭源软件(Free for home)
|
||||
2. 公共网络可能会使用中转服务器
|
||||
3. 类似BT的原理,机器需要保持在线才能保证传输
|
||||
|
||||
### 多平台
|
||||
|
||||
- Windows
|
||||
- Mac OS
|
||||
- Linux
|
||||
- FreeBSD
|
||||
- NAS设备
|
||||
- [Android](https://play.google.com/store/apps/details?id=com.resilio.sync&hl=en_US)
|
||||
- [IOS](https://itunes.apple.com/us/app/resilio-sync/id1126282325?mt=8)
|
||||
|
||||
两个移动设备上目前还是一款免费软件,不知道功能是什么样的。但是身为一个闭源软件在多平台都有免费使用的方案还是很欣慰的。
|
||||
|
||||
## 同类
|
||||
|
||||
[Syncthing](https://syncthing.net/)几乎就是Resilio Sync的另一面了,有人说它就是Resilio Sync的替代品。它不但能够实现相应的实时同步的所有功能,并且还是一款开源的自由软件。
|
||||
|
||||
对于我们来说,一款开源软件不仅仅是不收费这么简单。当一款开源软件由社区进行驱动时,人人都可以完全查看它的源代码以及改进。因此,一款开源软件对我们最大的益处就是真正安全与尊重隐私。
|
||||
|
||||
但闭源软件也有它存在的合理处,如果我们未来需要可靠稳定的支持,与更多的付费服务。Resilio Sync肯定也是一个不错的选择。毕竟顾客即是上帝🍡
|
||||
|
||||
总之各个实时同步的软件之间都是各有各的优缺点,具体想要使用哪一款,完全根据我们自身的需要与心情就OK了。
|
||||
|
||||
## 安装
|
||||
|
||||
上述我们有提到Resilio Sync支持多种平台,这里的我只使用到了Windows与Linux的两个发行版之间。所以其他平台的安装没有相应的记录。
|
||||
|
||||
### Windows
|
||||
|
||||
Windows是我们平常接触最多的一款GUI操作系统了,对于安装软件来说,差距都不会太大的。Resilio Sync也是同样。直接前往[官网](https://www.resilio.com/individuals/#hero)选择合适的授权下载安装即可。
|
||||
|
||||
安装界面仅仅只需要一步
|
||||
|
||||

|
||||
合适的选择后,对于较新的Windows平台可是直接打开一个软件窗口
|
||||
|
||||

|
||||
|
||||
接受了几个隐私政策与EULA之后(有空还是要多留意条款之类的),我们就可以正常使用了。
|
||||
|
||||
打开后的简洁的界面
|
||||
|
||||

|
||||
|
||||
配置之类的稍后再说,再将自己的其他平台的机器也给完成安装。
|
||||
|
||||
### Linux
|
||||
|
||||
对于在各个Linux平台的安装,官方有给一个完整的[帮助文档](https://help.resilio.com/hc/en-us/articles/206178924-Installing-Sync-package-on-Linux)其中有各个版本的不同数位版的下载链接。以及一些发行版的手动添加源的二进制安装方式。
|
||||
|
||||
- 下载安装包
|
||||
|
||||
对于网络环境不好的时候,可以考虑使用离线安装包的方式来安装。官方给了两个包,分别是deb和rpm。
|
||||
|
||||
DEB:
|
||||
|
||||
```
|
||||
sudo dpkg -i <resilio-sync.deb>
|
||||
```
|
||||
|
||||
RPM
|
||||
|
||||
```
|
||||
sudo rpm -i <resilio-sync.rpm>
|
||||
```
|
||||
|
||||
由于我的服务器因为网络环境无法与其官方源通信,所以只好选择使用下载离线安装包的方式来进行安装了。
|
||||
|
||||
相比较之下,个人感觉Linux平台的安装甚至比带有GUI的Windows更加方便。对于此软件,至需要一条命令即可。速度也很快。
|
||||
|
||||
- 使用软件仓库安装
|
||||
|
||||
使用软件仓库安装,我们需要简单的三个步骤:
|
||||
|
||||
1. 添加仓库源
|
||||
2. 添加GPG公钥用于验证
|
||||
3. 安装✨
|
||||
|
||||
对于Debian-based Linux(Debian, Ubuntu, Mint, Zorin, Elementary)
|
||||
|
||||
创建用于安装的仓库源列表:
|
||||
|
||||
```
|
||||
echo "deb http://linux-packages.resilio.com/resilio-sync/deb resilio-sync non-free" | sudo tee /etc/apt/sources.list.d/resilio-sync.list
|
||||
```
|
||||
|
||||
添加公钥:
|
||||
|
||||
```
|
||||
curl -LO http://linux-packages.resilio.com/resilio-sync/key.asc && sudo apt-key add ./key.asc
|
||||
```
|
||||
|
||||
更新源与安装:
|
||||
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get install resilio-sync
|
||||
```
|
||||
|
||||
对于RPM-based Linux(Red Hat, Fedora, CentOS)
|
||||
|
||||
创建用于安装的仓库源列表:
|
||||
|
||||
```
|
||||
printf "[resilio-sync]\nname=Resilio Sync\nbaseurl=https://linux-packages.resilio.com/resilio-sync/rpm/\$basearch\nenabled=1\ngpgcheck=1\n" | sudo tee /etc/yum.repos.d/resilio-sync.repo
|
||||
```
|
||||
|
||||
添加公钥:
|
||||
|
||||
```
|
||||
sudo rpm --import https://linux-packages.resilio.com/resilio-sync/key.asc
|
||||
```
|
||||
|
||||
安装:
|
||||
|
||||
```
|
||||
sudo yum install resilio-sync
|
||||
```
|
||||
|
||||
更新:
|
||||
|
||||
```
|
||||
sudo yum check-update
|
||||
sudo yum update resilio-sync
|
||||
```
|
||||
|
||||
## 运行与配置
|
||||
|
||||
Windows上的打开软件不需要任何多嘴了,双击快捷方式打开就好了。
|
||||
|
||||
对于Linux发行版上,几款目前常用的较新的发行版运行软件的方式都是差不多的。
|
||||
|
||||
可以使用Systemd进行控制
|
||||
|
||||
自动运行服务:
|
||||
|
||||
```
|
||||
sudo systemctl enable resilio-sync
|
||||
```
|
||||
|
||||
启动与停止:
|
||||
|
||||
```
|
||||
sudo systemctl start resilio-sync
|
||||
sudo systemctl stop resilio-sync
|
||||
```
|
||||
|
||||
对于在没有GUI的机器的环境下,是可以使用WebUI通过其他机器的浏览器访问来进行控制操作。
|
||||
|
||||
所以我们可以修改其配置文件来进行启动操作。
|
||||
|
||||
### 默认的配置文件
|
||||
|
||||
```
|
||||
/etc/resilio-sync/config.json
|
||||
```
|
||||
|
||||
而对于基本的使用,配置文件也是非常的简单易读的。
|
||||
|
||||
```
|
||||
{
|
||||
"storage_path" : "/var/lib/resilio-sync/",
|
||||
"pid_file" : "/var/run/resilio-sync/sync.pid",
|
||||
|
||||
"webui" :
|
||||
{
|
||||
"listen" : "127.0.0.1:8888"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
对于,官方文档称为了安全选择,软件默认监听`127.0.0.1`。如果需要在其他机器上直接访问的话,我们需要修改监听地址为:
|
||||
|
||||
```
|
||||
"listen" : "0.0.0.0:8888"
|
||||
```
|
||||
|
||||
并且可以根据自己的需求修改监听的端口。
|
||||
|
||||
其中`storage_path`为软件的一些设置,log等文件的存放目录。也可以根据自身的需要修改。
|
||||
|
||||
```
|
||||
╰─# ls /var/lib/resilio-sync/
|
||||
debug.txt history.dat http.port settings.dat.old storage.db-wal sync.dat.old sync.log
|
||||
FileDelayConfig history.dat.old settings.dat storage.db sync.dat sync.lng
|
||||
```
|
||||
|
||||
### 示例配置文件
|
||||
|
||||
上述运行的配置文件那么的简单易懂,同时可自定义的功能也就是更少了。而对于软件的一些其他功能在配置文件中的写法,官方是有留给我们示例的配置文件的。
|
||||
|
||||
我们只需要使用一条命令就可以将示例的配置文件导出到当前目录下:
|
||||
|
||||
```
|
||||
rslsync --dump-sample-config > sync.conf
|
||||
```
|
||||
|
||||
并且其中注释说明等都是非常完善的:
|
||||
|
||||
```
|
||||
╰─# cat sync.conf
|
||||
{
|
||||
"device_name": "My Sync Device",
|
||||
// "listening_port" : 0, // 0 - randomize port
|
||||
|
||||
/* storage_path dir contains auxilliary app files if no storage_path field: .sync dir created in current working directory */
|
||||
// "storage_path" : "/home/user/.sync",
|
||||
|
||||
/* set location of pid file */
|
||||
// "pid_file" : "/var/run/resilio/resilio.pid",
|
||||
|
||||
/* use UPnP for port mapping */
|
||||
"use_upnp" : true,
|
||||
|
||||
/* 这里只截取部分配置 */
|
||||
```
|
||||
|
||||
当我们修改的差不多的使用,我们就可以使用上述命令启动了。启动后,可以在进程中看到默认读取的配置文件的路径:
|
||||
|
||||
```
|
||||
╰─# ps -aux | grep rslsync
|
||||
rslsync 88730 0.5 1.5 772560 15316 ? Ssl 01:12 0:05 /usr/bin/rslsync --config /etc/resilio-sync/config.json
|
||||
```
|
||||
|
||||
### 用户
|
||||
|
||||
同样是为了安全原因,软件默认是使用最小权限的**rslsync**的用户来运行的。这样就会导致一个问题,在**rslsync**这个用户没有权限的目录就无法打开,就会导致无法使用该目录了。
|
||||
|
||||
最简便的解决办法就是将需要的文件夹给予用户**rslsync**可使用的权限,我们可以将**rslsync**添加到当前的用户组,并保证需要同步的文件夹用于上述组的权限:
|
||||
|
||||
```
|
||||
sudo usermod -aG user_group rslsync
|
||||
sudo chmod g+rw synced_folder
|
||||
```
|
||||
|
||||
亦或者直接修改rslsync用户为当前使用的用户来运行程序,编辑文件**/usr/lib/systemd/user/resilio-sync.service**将文件中的:
|
||||
|
||||
```
|
||||
WantedBy = multi-user.target
|
||||
```
|
||||
|
||||
修改为:
|
||||
|
||||
```
|
||||
WantedBy = default.target
|
||||
```
|
||||
|
||||
随后需要reload systemd并重启程序:
|
||||
|
||||
```
|
||||
systemctl daemon-reload
|
||||
systemctl --user start resilio-sync
|
||||
```
|
||||
|
||||
此处的`--user`与普通启动可以分别守护两个进程,默认的分别为:
|
||||
|
||||
```
|
||||
rslsync 27519 0.4 0.8 670144 16928 ? Ssl 15:36 0:01 /usr/bin/rslsync --config /etc/resilio-sync/config.json
|
||||
root 29385 0.3 0.4 589864 9676 ? Ssl 15:43 0:00 /usr/bin/rslsync --config /root/.config/resilio-sync/config.json
|
||||
```
|
||||
|
||||
且`--user`的用户成功被更改为当前用户root。当然配置文件也是独立的。
|
||||
|
||||
### 除此之外
|
||||
|
||||
软件还可以直接在终端中使用`rslsync`命令来进行运行等其他控制。上述的打印示例配置文件就是一个例子。关于该方式的更多可以在官方的帮助文档[Guide To Linux, And Sync Peculiarities](https://help.resilio.com/hc/en-us/articles/204762449-Guide-to-Linux)中找到。
|
||||
|
||||
成后运行后的界面与Windows完全一样。毕竟二者是同一种方式展示的UI界面。
|
||||
|
||||

|
||||
|
||||
## 同步
|
||||
|
||||
安装均已经完成,接下来就是简单从操作来实现需要的同步效果了。
|
||||
|
||||
在保证权限都是正常的情况下,添加在A机器添加我们需要进行同步的文件夹。选择好了之后就是三种分享链接的方式。
|
||||
|
||||

|
||||
|
||||
分别是“链接”、“密钥”和二维码三种方式。
|
||||
|
||||
随后我们就可以使用三种方式的其中一种,例如使用我最喜欢的密钥,选择好读写权限后,在需要同步的B机器上输入复制过来的密钥。
|
||||
|
||||

|
||||
|
||||
然后等着他们自己开始同步就OK了。在多平台的环境下也是不会影响正常工作的。
|
||||
|
||||
除此之外,对于同步的文件夹还有一些其他的选项可以配置。具体就看自己的需要来配置了🌭
|
||||
|
||||

|
||||
|
||||
## 参考&推荐阅读
|
||||
|
||||
* [Installing Sync Package On Linux](https://help.resilio.com/hc/en-us/articles/206178924-Installing-Sync-package-on-Linux)
|
||||
* [Legal](https://www.resilio.com/legal/eula/)
|
||||
* [BitTorrent (company)](https://en.wikipedia.org/wiki/BitTorrent_(company))
|
||||
* [Resilio Sync](https://en.wikipedia.org/wiki/Resilio_Sync)
|
||||
* [Resilio Sync Android](https://play.google.com/store/apps/details?id=com.resilio.sync&hl=en_US)
|
||||
* [Resilio Sync IOS](https://itunes.apple.com/us/app/resilio-sync/id1126282325?mt=8)
|
||||
* [Resilio Sync Help Center](https://help.resilio.com/hc/en-us)
|
270
source/_posts/Teamspeak-Server.md
Normal file
@ -0,0 +1,270 @@
|
||||
---
|
||||
title: 开黑之路-Teamspeak Server 搭建
|
||||
date: 2019-05-29 8:05:00
|
||||
tags: Player
|
||||
categories: Player
|
||||
url: teamspeak-server
|
||||
index_img: /images/Teamspeak-Server/logo.jpg
|
||||
---
|
||||
|
||||
> 与小伙伴的开黑之路🤞
|
||||
|
||||
## Teamspeak?
|
||||
|
||||

|
||||
|
||||
Teamspeak是一套专有的VoIP软件。所谓VoIP软件,就是基于网络协议的语音通话。而Teamspeak就是和现在市面上大多数即时通讯软件差不多,可以发送即时消息以及多人语音通话。
|
||||
|
||||
它是以S(erver) -> C(lient)架构基于Internet的运作方式,但是与其他多数软件不同的是,它允许自己搭建服务器。也就是说它的服务端也是可以下载安装的。
|
||||
|
||||
而且Teamspeak是一款多平台的软件,这就意味着,如果我们想,可以让在任何机器上运行成为服务端。它甚至支持离线/局域网的通信。
|
||||
|
||||
除此之外,它对自己的通信频道有着完全自定义的控制。设置官方称可以为整个服务器或只是特定通道启用基于AES的加密。
|
||||
|
||||
我主要喜欢它的地方在于,完全不需要注册账号。可以建立在私人服务器上并有着很完成的控制权限。
|
||||
|
||||
## 服务端
|
||||
|
||||
废话了半天,我们了解它是一款可以搭建在自己私人服务器上的一款即时通讯软件。所以接下来就是安装操作了。
|
||||
|
||||
### 服务器环境
|
||||
|
||||
首先,我用做服务端的环境:
|
||||
|
||||
* Server:阿里云ECS
|
||||
* Bandwidth:1M
|
||||
* OS:Ubuntu 18.04 bionic
|
||||
|
||||
```
|
||||
./+o+- root@Cruiser
|
||||
yyyyy- -yyyyyy+ OS: Ubuntu 18.04 bionic
|
||||
://+//////-yyyyyyo Kernel: x86_64 Linux 4.15.0-041500-generic
|
||||
.++ .:/++++++/-.+sss/` Uptime: 26d 11h 31m
|
||||
.:++o: /++++++++/:--:/- Packages: 1031
|
||||
o:+o+:++.`..```.-/oo+++++/ Shell: zsh 5.4.2
|
||||
.:+o:+o/. `+sssoo+/ WM:
|
||||
.++/+:+oo+o:` /sssooo. CPU: Intel Xeon E5-2682 v4 @ 2.494GHz
|
||||
/+++//+:`oo+o /::--:. GPU: cirrusdrmfb
|
||||
\+/+o+++`o++o ++////. RAM: 588MiB / 1993MiB
|
||||
.++.o+++oo+:` /dddhhh.
|
||||
.+.o+oo:. `oddhhhh+
|
||||
\+.++o+o``-````.:ohdhhhhh+
|
||||
`:o+++ `ohhhhhhhhyo++os:
|
||||
.o:`.syhhhhhhh/.oo++o`
|
||||
/osyyyyyyo++ooo+++/
|
||||
````` +oo+++o\:
|
||||
`oo++.
|
||||
```
|
||||
|
||||
(主要是内存有点多的空闲……
|
||||
|
||||
### 下载地址
|
||||
|
||||
既然是安装,首先是下载用于作为服务端的软件。据我所知,TS是不开源的。
|
||||
|
||||
→[官方下载地址](https://www.teamspeak.com/zh-CN/downloads/)
|
||||
|
||||
客户端平台:
|
||||
|
||||
* LINUX
|
||||
* MACOS
|
||||
* WINDOWS
|
||||
* ANDROID
|
||||
* IOS
|
||||
|
||||
听说两个移动端都不怎么样。
|
||||
|
||||
当然,我们主要搭建的是服务端,服务端的平台也不少:
|
||||
|
||||
* MACOS
|
||||
* WINDOWS
|
||||
* FREEBSD
|
||||
* LINUX
|
||||
|
||||
这些都是我们常见的服务器操作系统。也就是说搭建服务端对系统没有多大挑剔性了。
|
||||
|
||||
### 开始动手
|
||||
|
||||
从上述的下载地址中,我们下将服务端的软件下载到我们的服务器上:
|
||||
|
||||
```
|
||||
wget https://files.teamspeak-services.com/releases/server/3.6.1/teamspeak3-server_linux_amd64-3.6.1.tar.bz2
|
||||
```
|
||||
|
||||
(这条命令的软件版本可能会随着TS的更新而失效)
|
||||
|
||||
由于下载下来的是`tar.bz2`的压缩格式,所以我们使用`-xjvf`来进行解压操作:
|
||||
|
||||
```
|
||||
tar -xjvf teamspeak3-server_linux_amd64-3.6.1.tar.bz2
|
||||
```
|
||||
|
||||
解压之后的文件夹目录:
|
||||
|
||||
```
|
||||
CHANGELOG libts3db_mariadb.so libts3_ssh.so LICENSE-THIRDPARTY serverquerydocs ts3server ts3server_startscript.sh
|
||||
doc libts3db_sqlite3.so LICENSE redist sql ts3server_minimal_runscript.sh tsdns
|
||||
```
|
||||
|
||||
我们可以了解到它有一堆各种各样的文件,我们不需要去一个个的了解。据说是专有软件,也就是不开源。所以我们也更不需要去编译等操作了。这些文件肯定都是已经编译好的了。
|
||||
|
||||
要运行服务端的软件,首先我们要同意它的许可协议。(如果有人愿意看的话
|
||||
|
||||
```
|
||||
touch .ts3server_license_accepted
|
||||
```
|
||||
|
||||
`touch`这样一个文件在刚刚解压出的目录就意味着我们同意License了。
|
||||
|
||||
同意过后,我们执行它的启动脚本:
|
||||
|
||||
```
|
||||
./ts3server_startscript.sh start
|
||||
```
|
||||
|
||||
如果是root用户运行的话,会在启动时提示为了”安全起见,不要使用root用户运行“:
|
||||
|
||||
```
|
||||
WARNING ! For security reasons we advise: DO NOT RUN THE SERVER AS ROOT
|
||||
!!!!!!!!!!!
|
||||
```
|
||||
|
||||
当然等最后几个感叹号出现完之后,服务端软件就会正常运行了。就可以继续快乐的当root敢死队了🎉。当然使用什么用户运行取决于你自己。我这边是和自己的小伙伴开黑用的,几乎就是私用,不会将服务器做为公用。所以我并不怕死😈。
|
||||
|
||||
警告⚠我们过后,就是正常启动了
|
||||
|
||||
```
|
||||
Starting the TeamSpeak 3 server
|
||||
TeamSpeak 3 server started, for details please view the log file
|
||||
|
||||
------------------------------------------------------------------
|
||||
I M P O R T A N T
|
||||
------------------------------------------------------------------
|
||||
Server Query Admin Account created
|
||||
loginname= "serveradmin", password= "dxxxxxxAa"
|
||||
------------------------------------------------------------------
|
||||
|
||||
○
|
||||
------------------------------------------------------------------
|
||||
I M P O R T A N T
|
||||
------------------------------------------------------------------
|
||||
ServerAdmin privilege key created, please use it to gain
|
||||
serveradmin rights for your virtualserver. please
|
||||
also check the doc/privilegekey_guide.txt for details.
|
||||
|
||||
token=HxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxRL
|
||||
------------------------------------------------------------------
|
||||
|
||||
```
|
||||
|
||||
机会将等会会在客户端用的admin账户以及token显示在我们的终端上了。serveradmin账户和token都是TS的最高权限账户,可修改服务器设置。下文会用到
|
||||
|
||||
进程:
|
||||
|
||||
```
|
||||
○ ps -aux | grep ts3
|
||||
root 4031 0.4 1.1 689336 22820 pts/0 Sl 10:44 0:07 ./ts3server
|
||||
```
|
||||
|
||||
### 防火墙
|
||||
|
||||
如果我们的服务器有启用防火墙来限制网络的话,ts需要一些TCP/UDP的端口来和客户端进行通信。
|
||||
|
||||
目前的Teamspeak 3需要这些端口:
|
||||
|
||||
* UDP: 9987
|
||||
* TCP: 10011
|
||||
* TCP: 30033
|
||||
|
||||
可以在`iptables`中添加:
|
||||
|
||||
```
|
||||
iptables -A INPUT -p udp --destination-port 9987 -j ACCEPT
|
||||
iptables -A INPUT -p tcp --dport 10011 -j ACCEPT
|
||||
iptables -A INPUT -p tcp --dport 30033 -j ACCEPT
|
||||
```
|
||||
|
||||
亦或者使用`firewalld`:
|
||||
|
||||
```
|
||||
rewall-cmd --zone=public --add-port=9987/udp --permanent
|
||||
firewall-cmd --zone=public --add-port=10011/tcp --permanent
|
||||
firewall-cmd --zone=public --add-port=30033/tcp --permanent
|
||||
firewall-cmd --reload
|
||||
```
|
||||
|
||||
对于一些特殊的发行版可能需要特殊操作,例如我当前的ECS机器,它的防火墙(iptables)的工作方式不是以进程方式运行的。(虽然我也不懂……
|
||||
|
||||
但是新建防火墙的规则与上述不同,具体可以了解我很早以前[水过的一篇文章](https://www.defectink.com/defect/23.html)
|
||||
|
||||
### 开机启动
|
||||
|
||||
当前已经添加过了端口并且已经成功启动了,我们基本上就可以正常连接了。当然还少了一个重要的一步,那就是开机自启了。
|
||||
由于是使用脚本启动了,而没有使用`systemd`来进行控制,所以服务端需要使用`Crontab`来进行开机启动的控制
|
||||
|
||||
```
|
||||
@reboot /root/teamspeak/teamspeak3-server_linux_amd64/ts3server_startscript.sh start
|
||||
```
|
||||
|
||||
在crontab文件中添加对应的ts脚本启动位置,来实现开机启动ts服务端的效果。
|
||||
添加完成后我们可以使用`crontab -l`来查看添加完成后的crontab文件,确认是否添加成功。
|
||||
至此,运行于Ubuntu上的Teamspeak服务端就运行成功了。
|
||||
|
||||
## 客户端
|
||||
|
||||
上述我们介绍过客户端软件支持的平台以及服务端的安装搭建。客户端的[下载地址](https://www.teamspeak.com/en/downloads/)于服务端是同一个地址,可以找的适用于自己的平台的安装包来进行下载安装。
|
||||
|
||||
### 连接服务端
|
||||
|
||||
前面有介绍过TS使用的是C/S架构,我们搭建好了服务端当然是为了连接它。连接它比我们想象的要简单的多,打开软件后直接在工具栏就能找到连接这一选项。
|
||||
单击连接,就可以根据服务器地来连接我们搭建好的服务端了。
|
||||
|
||||

|
||||
|
||||
我们可以看到有三个选项框,第一个是服务器地址,其次是服务器密码,最后是用于展示给其他人的昵称
|
||||
默认新安装的服务端是没有密码的,如果我们是连接一个新服务器的话,是可以将密码留空登陆。
|
||||
**这里的昵称只用于展示于其他人,不等于我们的用户名。**
|
||||
|
||||
### Token
|
||||
|
||||
在上述我们的安装服务端的操作中,第一次启动后会给我们一个serveradmin账户一段token码。ts这个软件不是那么的强调用户。我们登陆至服务器是不需要创建一个拥有密码的账户的。这和我们目前常用的微信、QQ等软件是略有不同的。
|
||||
|
||||
但是ts它也是有用户身份的,因为ts大部分对于服务器的控制操作都是可以在客户端完成的。这时候我们就需要一个有高等级权限的用户身份来完成这个操作。同样需要识别出其他连接进来的用户没有修改服务器的权限。
|
||||
|
||||
如果是第一次连接至ts的新服务器,那么我们连接成功后就会立马弹出一个用于输入token的对话框。我们将刚刚创建服务器时给我们的token填入即可。这样就可以直接在客户端修改自己的服务器了。
|
||||
|
||||

|
||||
|
||||
因为ts默认在使用客户端时会自动创建一个用户身份,每个身份都是不相同的。**那如果我们更换电脑连接自己的服务器时,或者想给其他人一个修改服务器的权限时该怎么办呢?**
|
||||
|
||||
- 导出当前的用户身份
|
||||
|
||||
在工具栏的“工具-身份”这个标签中,我们可以看到自己当前账户身份。直接右击便可以执行导出操作。在其他地方使用相同方法导入就可以继续使用这个身份了。
|
||||

|
||||
|
||||
- 新建token码
|
||||
|
||||
当我们想给其他身份的用户修改服务器的权限的时候,我们可以使用新建权限码的方式来提升其他用户的权限。
|
||||
在工具栏的“权限-权限码清单”中就可以找的新建权限码的按钮以及已经新建过的权限码清单。新建时也可以选择不同的权限来进一步控制。使用权限码就和我们第一次使用时一样操作即可。
|
||||

|
||||
|
||||
### 翻译插件
|
||||
|
||||
如何安装软件这里就不再做赘述。ts默认是英文版本的,可以自己在其他地方下载拥有中文汉化的第三方做的包来使用。也可以自己在原版的基础上添加汉化文件。亦或者是自己安装中文翻译的插件(目前中文插件仅有繁体中文)。
|
||||
|
||||
打开软件后,打开“工具-选项”(Alt+P),找到“插件(add-ons)”这一选项卡。
|
||||
|
||||

|
||||
|
||||
默认看到的是当前本地的插件,我们可以选择“Browse online"来查看在线可以下载安装的插件。选择筛选器为”翻译“然后输入”Chinese“就可以找到一款繁体中文的插件。点击进入插件的详情页面就可以看到”install“。单击安装即可。
|
||||
|
||||

|
||||
|
||||
Install完成之后重新打开软件就会应用上翻译了。如果没有成功应用,可以再去刚刚插件地方看看有没有启用。
|
||||
除了翻译插件之外,TS还有很多种类的插件,以及界面皮肤等。和刚刚安装翻译插件的方法一摸一样。
|
||||
|
||||

|
||||
|
||||
## 参考
|
||||
|
||||
* [Teamspeak 3 Server Ports](https://forum.teamspeak.com/threads/46486-Teamspeak-3-Server-Ports)
|
476
source/_posts/Vue.js-起步!.md
Normal file
@ -0,0 +1,476 @@
|
||||
---
|
||||
title: Vue.js-起步!
|
||||
date: 2020-10-22 11:21:22
|
||||
tags: [JavaScript, Vue]
|
||||
categories: 笔记
|
||||
url: Vue-js-get-started
|
||||
index_img: /images/Vue.js-起步!/logo.webp
|
||||
---
|
||||
|
||||
在我打算学习vue的时候,正是其3.0版本发布不久的时候。很庆幸生活在这个时代,但困扰我的是是否应该由旧版本的2.x开始学习?一向选择困难的我最终打算两个版本一起学习,从2.x开始入门,顺便还能一睹其与3.0版本的变化。
|
||||
|
||||
## 起步
|
||||
|
||||
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
|
||||
|
||||
### 安装
|
||||
|
||||
从最基础的开始,可以在单html文件中引入vue。
|
||||
|
||||
```js
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
|
||||
```
|
||||
|
||||
### 声明式渲染
|
||||
|
||||
Vue的核心是采用简介的模板语法来声明式地将数据渲染进DOM系统:
|
||||
|
||||
```html
|
||||
<div class="app">
|
||||
{{ message }}
|
||||
</div>
|
||||
```
|
||||
|
||||
```js
|
||||
let app = new Vue({
|
||||
el: '.app',
|
||||
data: {
|
||||
message: 'Hello world!'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
不得不说Vue的教程确实简单易懂,也可能是因为Vue本身的语法简洁,第一次看到教程里的这个实例时,大部分都是能够理解的。目前为止,已经成功的创建了第一个Vue应用。
|
||||
|
||||
现在数据和DOM已经被建立的关联,所有的东西都是响应式的,刚刚新建的`app`实例拥有一个`app.message`的值,在console中修改就能实时的看到相应的属性更新。
|
||||
|
||||
目前就不再和HTML直接进行交互了,一个Vue应用会将其挂在到一个DOM元素上:`el: '.app'`,然后对其进行完全控制。那个 HTML 是我们的入口,但其余都会发生在新创建的 Vue 实例内部。
|
||||
|
||||
除此之外,Vue还能直接对DOM元素attribute进行绑定
|
||||
|
||||
```html
|
||||
<div class="app">
|
||||
<img v-bind:src='src' v-bind:alt='alt' v-bind:title='message'>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let app = new Vue({
|
||||
el: '.app',
|
||||
data: {
|
||||
src: 'https://cdn.defectink.com/images/file_4963947.png',
|
||||
alt: 'ヽ(✿゚▽゚)ノ',
|
||||
message: 'ヽ(✿゚▽゚)ノ'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
这样的操作方法被称之为**指令**。指令带有`v-`前缀,以表示他们是Vue提供的特殊attribute。它们会在渲染的 DOM 上应用特殊的响应式行为。
|
||||
|
||||
### 条件与循环
|
||||
|
||||
Vue提供了一个类似于条件语句的指令,切换一个元素的显示也非常的简单,使用`v-if`语句。
|
||||
|
||||
```html
|
||||
<div class="app">
|
||||
<img v-if='seen' v-bind:src='src' v-bind:alt='alt' v-bind:title='message'>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let app = new Vue({
|
||||
el: '.app',
|
||||
data: {
|
||||
src: 'https://cdn.defectink.com/images/file_4963947.png',
|
||||
alt: 'ヽ(✿゚▽゚)ノ',
|
||||
message: 'ヽ(✿゚▽゚)ノ',
|
||||
seen: true
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
继上一个例子,添加一个和绑定DOM attribute类似的指令:`v-if`。相应的,它也类似于常见的if语句,当值为`true`时,则显示这个DOM,反之亦然。当然,所有的内容还都是动态的,在console中继续使用`app.seen = false`时,DOM元素将会隐藏。
|
||||
|
||||
既然有了if语句,那自然是不能少了for循环的。
|
||||
|
||||
```html
|
||||
<ol class="app">
|
||||
<li v-for="todo in items">
|
||||
{{ todo.txt }}
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<script>
|
||||
let app = new Vue({
|
||||
el: '.app',
|
||||
data: {
|
||||
items: [
|
||||
{ txt: '小' },
|
||||
{ txt: '小小' },
|
||||
{ txt: '小小小肥羊' }
|
||||
]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
Vue里的for循环可以用来创建列表等,并且是以数组的方式对其DOM进行控制的。指令`v-for="todo in items"`中的`items`就对应了`data`中的`items`数组,而DOM里的参数`{{ todo.txt }}`就相当于`items[i].txt`。
|
||||
|
||||
并且后续可以使用数组方法对DOM进行直接的操作:
|
||||
|
||||
```js
|
||||
app.items.push({txt: 'test'});
|
||||
// 4
|
||||
app.items.shift();
|
||||
// {__ob__: Observer}
|
||||
```
|
||||
|
||||
### 处理用户输入
|
||||
|
||||
Vue可以使用指令`v-on`来对DOM绑定一个事件监听器,通过它来调用在实例中定义的方法
|
||||
|
||||
```html
|
||||
<div class="app">
|
||||
<input type="button" v-on:click="disableImage" value="切换!"><br>
|
||||
<img v-bind:src="src" alt="">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let app = new Vue({
|
||||
el: '.app',
|
||||
data: {
|
||||
src: 'https://cdn.defectink.com/images/file_4963947.png'
|
||||
},
|
||||
methods: {
|
||||
disableImage: function () {
|
||||
if (this.src) {
|
||||
this.src = '';
|
||||
} else {
|
||||
this.src = 'https://cdn.defectink.com/images/file_4963947.png';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
这是对事件监听器的一个实例,通过在`input`上绑定一个事件监听器来触发对实例中定义的方法。在实例中的方法中的`this`指向于当前实例。
|
||||
|
||||
在实例方法中,我们更新了应用状态,但没有触碰DOM——所有的 DOM 操作都由 Vue 来处理,我们编写的代码只需要关注逻辑层面即可。
|
||||
|
||||
Vue还提供了`v-model`指令,它能够轻松实现对表单的双向绑定
|
||||
|
||||
```html
|
||||
<div id="app">
|
||||
<input type="text" v-model="message"><br>
|
||||
<p>
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
message: 'Input something...'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
## 组件化应用构建
|
||||
|
||||
组件系统是Vue的另一个重要概念,它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。一个大型的页面应用将由几个可重复利用的组件构成。
|
||||
|
||||

|
||||
|
||||
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:
|
||||
|
||||
```js
|
||||
Vue.component('todo-item', {
|
||||
template: '<li>这是一个测试</li>'
|
||||
});
|
||||
|
||||
let app = new Vue(...)
|
||||
```
|
||||
|
||||
注册完成后就可以使用使用它来构建一个模板:
|
||||
|
||||
```html
|
||||
<ol id="app">
|
||||
<todo-item></todo-item>
|
||||
</ol>
|
||||
<script>
|
||||
Vue.component('todo-item', {
|
||||
template: '<li>这是一个测试</li>'
|
||||
});
|
||||
let app = new Vue({
|
||||
el: '#app'
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
这样一个组件简而易懂,定义一个特定内容的组件,然后在html中渲染出来其内容。但这样还不够,内容都是特定的,每次渲染的都是同样的文本。我们应该能从父作用域将数据传到子组件才对。
|
||||
|
||||
稍微修改一下定义的组件,使其能够接收一个prop。这类似于一个自定义的attribute。
|
||||
|
||||
```js
|
||||
Vue.component('todo-item', {
|
||||
// 这个 prop 名为 todo。
|
||||
props: ['todo'],
|
||||
template: '<li>{{ todo.text }}</li>'
|
||||
})
|
||||
```
|
||||
|
||||
现在,我们可以使用 v-bind 指令将待办项传到循环输出的每个组件中:
|
||||
|
||||
```html
|
||||
<div id="app">
|
||||
<ol>
|
||||
<todo-item v-for="item in list" v-bind:todo="item" v-bind:key="item.id"></todo-item>
|
||||
</ol>
|
||||
</div>
|
||||
<script>
|
||||
Vue.component('todo-item', {
|
||||
props: ['todo'],
|
||||
template: '<li>{{ todo.text }}</li>'
|
||||
});
|
||||
let app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
list: [
|
||||
{ id: 0, text: '蔬菜' },
|
||||
{ id: 1, text: '奶酪' },
|
||||
{ id: 2, text: '随便其它什么人吃的东西' }
|
||||
]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
这个实例中,父作用域中的数据通过组件的`prop`接口进行了良好的解耦。在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。类似于这样:
|
||||
|
||||
```html
|
||||
<div id="app">
|
||||
<app-nav></app-nav>
|
||||
<app-view>
|
||||
<app-sidebar></app-sidebar>
|
||||
<app-content></app-content>
|
||||
</app-view>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 与自定义元素的关系
|
||||
|
||||
Vue组件非常类似于**自定义元素**——它是 Web 组件规范的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 Slot API 与 is attribute。但是,还是有几个关键差别:
|
||||
|
||||
1. Web Components 规范已经完成并通过,但未被所有浏览器原生实现。目前 Safari 10.1+、Chrome 54+ 和 Firefox 63+ 原生支持 Web Components。相比之下,Vue 组件不需要任何 polyfill,并且在所有支持的浏览器 (IE9 及更高版本) 之下表现一致。必要时,Vue 组件也可以包装于原生自定义元素之内。
|
||||
|
||||
2. Vue 组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流、自定义事件通信以及构建工具集成。
|
||||
|
||||
虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,依然有很好的互操作性。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。
|
||||
|
||||
## Vue 3!
|
||||
|
||||
对于一个初学者来说,同时学习两个版本可能有些吃力。但我依然想从最基本的开始时就了解了它的变化,并且还发现了一些有意思的收获。
|
||||
|
||||
### 更简洁的声明
|
||||
|
||||
第一次学习2.x版本时,发现确实如其介绍的那样:
|
||||
|
||||
> Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
|
||||
|
||||
用最直白的方式来看,2.x使用的方式是类似于构造函数来声明一个实例,并且有着固定的搭配:`el`为DOM的element,`data`为数据,后续还能继续添加方法:
|
||||
|
||||
```js
|
||||
let app = new Vue({
|
||||
el: '.app',
|
||||
data: {
|
||||
message: 'Hello world!'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
而Vue3使用了另一种方法:
|
||||
|
||||
```html
|
||||
<div id="test">
|
||||
<p>
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
<script>
|
||||
let count = {
|
||||
data() {
|
||||
return {
|
||||
message: 'Hello world!'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.createApp(count).mount('#test');
|
||||
</script>
|
||||
```
|
||||
|
||||
Vue3首先使用一个对象字面量创建一个带有`data()`函数的变量,该函数使用的是一种更简短的[定义方法的方法](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Method_definitions)。`data()`函数的返回值就是实例的数据。
|
||||
|
||||
当变量声明完成后,使用Vue的一个`createApp()`方法传入,并接着使用`mount()`方法传入DOM。这样一个Vue的实例就创建挂载完成了,相比较之下,我觉得这种方式对其生命周期有着更清晰的显示。
|
||||
|
||||
当然,也可以跳过创建变量这一步,直接传参,这样看上去更像Vue2。
|
||||
|
||||
```html
|
||||
<div id="app">
|
||||
<p>
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
<script>
|
||||
let app = Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
message: 'xfy!'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let id = setInterval(() => {
|
||||
this.message = 'x' + this.message;
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
clearInterval(id);
|
||||
}, 10000)
|
||||
}
|
||||
}).mount('#app')
|
||||
</script>
|
||||
```
|
||||
|
||||
就拿其官方文档的实例来看,再创建实例时还能方便的为其的添加其他方法,并且其方法名就是生命周期名。例如在挂载后执行`mounted()`。
|
||||
|
||||
```html
|
||||
<div id="test">
|
||||
<p>
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
<script>
|
||||
let count = {
|
||||
data() {
|
||||
return {
|
||||
message: 'Hello world!'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let id = setInterval(() => {
|
||||
this.message += this.message;
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
clearInterval(id);
|
||||
},9000)
|
||||
}
|
||||
}
|
||||
|
||||
Vue.createApp(count).mount('#test');
|
||||
```
|
||||
|
||||
> 入门到这里时,2和3目前接触到的只是写法不同。后续以3为基础学习,并和2做比较。
|
||||
|
||||
## 应用实例
|
||||
|
||||
所有的 Vue 组件都是实例,并且接受相同的选项对象。
|
||||
|
||||
### 创建一个实例
|
||||
|
||||
每个 Vue 应用都是通过用`createApp`函数创建一个新的应用实例开始的,而2.x则是以一个构造函数开始的。
|
||||
|
||||
```js
|
||||
Vue.createApp(...);
|
||||
```
|
||||
|
||||
创建实例后,我们可以挂载它,将容器传递给`mount`方法。`mount`方法接收DOM的选择器(class、ID等)。
|
||||
|
||||
```js
|
||||
Vue.createApp(...).mount('#id');
|
||||
```
|
||||
|
||||
### 根组件
|
||||
|
||||
`Vue.createApp()`方法用于创建一个根组件,当我们挂载一个应用程序时,该组件将为渲染起点。
|
||||
|
||||
一个应用需要被挂载到一个DOM节点上。例如我们需要挂载实例到`<div id="app"></div>`上,通常的步骤如下:
|
||||
|
||||
```js
|
||||
let rootComponent = { /* some data */ };
|
||||
let app = Vue.createApp(rootComponent);
|
||||
let vm = app.mount('#app');
|
||||
```
|
||||
|
||||
不像大多数的应用程序方法,`mount`不会返回应用。相反,它会返回根节点实例。也就说变量`vm`是根节点的实例。
|
||||
|
||||
Vue2和3虽然都没有完全遵循MVVM模型,但是 Vue 的设计也受到了它的启发。
|
||||
|
||||
一个 Vue 应用由一个通过 createApp 创建的根实例,以及可选的嵌套的、可复用的组件树组成。举个例子,一个 todo 应用的组件树可以是这样的:
|
||||
|
||||
```
|
||||
根实例
|
||||
└─ TodoList
|
||||
├─ TodoItem
|
||||
│ ├─ DeleteTodoButton
|
||||
│ └─ EditTodoButton
|
||||
└─ TodoListFooter
|
||||
├─ ClearTodosButton
|
||||
└─ TodoListStatistics
|
||||
```
|
||||
|
||||
### 节点实例属性
|
||||
|
||||
前面我们遇到了`data`属性,`data`中定义的属性通过节点实例暴露出来:
|
||||
|
||||
```js
|
||||
let app = Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
meg: 'greeting something...'
|
||||
}
|
||||
}
|
||||
});
|
||||
let vm = app.mount('#app');
|
||||
console.log(vm.meg) // 'greeting something...'
|
||||
```
|
||||
|
||||
`data`中暴露出的属性都会加如其响应式系统,整个实例会被设置一个Proxy代理拦截其行为,从而监听数据的变化并实时渲染到DOM上。
|
||||
|
||||
还有其他各种用户自定义属性的组件选项能够添加到实例,例如`methods`, `props`, `computed`, `inject`和`setup`。
|
||||
|
||||
Vue同样也暴露了一些内建的属性,例如`$attrs`和`$emit`。他们都有`$`前缀与用户自定义的属性区分开来。
|
||||
|
||||
## 生命周期钩子
|
||||
|
||||
每个组件在创建后都要经历一系列初始化的步骤,例如,它需要设置数据监控,编译模板,挂载实例到DOM节点和当数据变化时更新DOM。这一系列操作也被称之为生命周期钩子。
|
||||
|
||||
简单来说,就是一个实例在从最初始的声明到最后的卸载期间不同阶段对其操作的API。
|
||||
|
||||
例如,调用`create()`钩子,在实例被创建后运行的操作:
|
||||
|
||||
```js
|
||||
let app = Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
meg: 'test'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// `this`指向当前实例
|
||||
console.log('instance created!' + this.meg);
|
||||
}
|
||||
});
|
||||
let vm = app.mount('#app');
|
||||
```
|
||||
|
||||
所有的一系列钩子,他们的`this`都指向当前调用的活动实例。
|
||||
|
||||
> 注意,不要在一个组件属性或回调中使用箭头函数。例如:`created: () => {console.log(this.a);};`或者`vm.$watch('a', newValue => this.myMethod());`。箭头函数没有自己的`this`,`this`会和其他变量一样,向上层作用域中查找,直到找到为止。通常会遇到这样的报错:`Uncaught TypeError: Cannot read property of undefined`或`Uncaught TypeError: this.myMethod is not a function.`
|
||||
|
||||
## 生命周期图
|
||||
|
||||
下图可以很清晰的看到Vue3的实例的一个生命周期。
|
||||
|
||||

|
163
source/_posts/Vue3中的响应数据.md
Normal file
@ -0,0 +1,163 @@
|
||||
---
|
||||
title: Vue3 中的响应数据
|
||||
date: 2020-11-2 10:01:55
|
||||
tags: [JavaScript, Vue]
|
||||
categories: 笔记
|
||||
url: response-data-in-Vue3
|
||||
index_img: /images/Vue3中的响应数据/logo.webp
|
||||
---
|
||||
|
||||
## 实时渲染
|
||||
|
||||
在学习 Vue2.x 的过程中,做过一个更改数据从而触发实时渲染 DOM 的小实例。期间很顺利,而后在同样方法测试 Vue3 的时候发现遇到了一些不同的行为。根据查阅了一些文档以及源码,做出了一些推测。
|
||||
|
||||
## 数据与方法
|
||||
|
||||
当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
|
||||
|
||||
在 Vue2.x 中,可以创建一个数据对象,为实例提供数据。虽然这样的写法和直接在实例中为`data`添加属性没有多少差别:
|
||||
|
||||
```html
|
||||
<div id="app">
|
||||
<p>
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
<script>
|
||||
let data = {
|
||||
message: 'Hello World!'
|
||||
}
|
||||
let app = new Vue({
|
||||
el: '#app',
|
||||
data: data
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
这时我们单独创建的`data`对象与实例中的`data`成立了引用关系:
|
||||
|
||||
```js
|
||||
app.$data.message === data.message
|
||||
// true
|
||||
```
|
||||
|
||||
并且他们三者是互等的:
|
||||
|
||||
```js
|
||||
app.message === app.$data.message
|
||||
app.$data.message === data.message
|
||||
```
|
||||
|
||||
并且我们单独创建的`data`对象也被转换成了检测数据变化的 Observer 对象
|
||||
|
||||

|
||||
|
||||
因此,我们在修改`data`对象的内容时,app 实例的属性也会被改变,从而实时渲染到 DOM 上。
|
||||
|
||||

|
||||
|
||||
但在 Vue3 上发生了一些小小的改变。在 Vue3 上,我们将实例的`data`函数直接 return 为我们在父作用域中创建的对象,这个对象不会被修改为检测属性数据变化的对象。
|
||||
|
||||
```html
|
||||
<div id="app">
|
||||
<p>
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
<script>
|
||||
let data = {
|
||||
message: 'Hello World!'
|
||||
};
|
||||
let app = Vue.createApp({
|
||||
data() {
|
||||
return data;
|
||||
}
|
||||
});
|
||||
let vm = app.mount('#app');
|
||||
</script>
|
||||
```
|
||||
|
||||
这里的app是我们创建的实例,但最终挂载 DOM 后返回的实例为 vm。不同于 2.x 的地方是,这里我们在父作用域中创建的对象并没用任何的变化,它还是一个普通的对象。
|
||||
|
||||

|
||||
|
||||
并且,他们也互相建立了引用的关系;
|
||||
|
||||
```js
|
||||
vm.message === data.message
|
||||
// true
|
||||
```
|
||||
|
||||
虽然他们已经是互相引用,但是`data`还是一个普通的对象。这里就会发现一个有意思的现象,只更新`data.message`的值,`vm.message`或者说`vm.$data.message`的值会同样更新,保持和`data`对象一样。但是DOM却没用被实时渲染。
|
||||
|
||||

|
||||
|
||||
这一点 2 和 3 有着很大的差距,在 Vue2 中,我们是可以通过`data`对象来实时更新 DOM 的。而在 3 中就不行了。
|
||||
|
||||
据我的猜测,主要是 Vue3 没有对父作用域的`data`对象设置 Proxy 代理的原因。虽然二者已经是互相引用,修改一个对象值,另一个对象也会被修改。**但是通过修改`data`的属性,并不会触发`vm.$data`对象的`set()`方法。**
|
||||
|
||||
## 模仿行为
|
||||
|
||||
我使用了一个小例子,模仿了一下 Vue3 的行为:
|
||||
|
||||
```js
|
||||
// 这是在父作用域中的data对象,它是一个普通对象
|
||||
let data = {
|
||||
message: 'xfy'
|
||||
}
|
||||
|
||||
// 这是模拟set方法,成功set时会打印一条信息
|
||||
let handler = {
|
||||
set: (obj, prop, value) => {
|
||||
obj[prop] = value;
|
||||
console.log('set success: ' + value);
|
||||
}
|
||||
}
|
||||
|
||||
// 通过proxy创建一个继承自data属性的实例
|
||||
let vm = new Proxy(data, handler);
|
||||
```
|
||||
|
||||
这是一个很简单的例子,我们为`vm`对象设置了一个来自`data`对象的代理。现在二者就是互相引用的关系了,就和 Vue3 一样。
|
||||
|
||||
```js
|
||||
data.message === vm.message
|
||||
// true
|
||||
```
|
||||
|
||||
我在代理的拦截中配置了一个 setter,当`vm`对象成功设置了值后,就会触发这个 setter ,并在控制台打印一则信息。用来模拟更新 DOM。也就是说,现在的`vm`实例就相当于 Vue 实例,当我更新其属性时,会在控制台动态的打印信息,就相当于实时更新了 DOM。就和 Vue 实例一样。
|
||||
|
||||
现在我们直接对`vm.message`赋值,则会成功触发预先设置的 setter 函数,成功的更新了值并且在控制打印了消息。
|
||||
|
||||
```js
|
||||
vm.message
|
||||
// "xfy"
|
||||
vm.message = 'hello xfy';
|
||||
// set success: hello xfy
|
||||
// "hello xfy"
|
||||
```
|
||||
|
||||
并且`data`对象也同样的被修改了。
|
||||
|
||||
```js
|
||||
data.message
|
||||
// "hello xfy"
|
||||
```
|
||||
|
||||
直接设置`data.message`可以成功修改`vm.message`的值,但是却不会触发`vm`对象的 setter 方法。
|
||||
|
||||
```js
|
||||
data.message = '嘤嘤嘤';
|
||||
// "嘤嘤嘤"
|
||||
vm.message
|
||||
// 属性被修改,但是没有触发setter
|
||||
// "嘤嘤嘤"
|
||||
```
|
||||
|
||||
这里的小例子最简化的模拟了 Vue3 的实例行为,在真正的 Vue3 的实例上,我们也可以很清晰的看到其 Proxy 属性:
|
||||
|
||||

|
||||
|
||||
## 总结
|
||||
|
||||
总的来说就是因为 data 对象修改时不会触发实例的 set 方法,但数据依然会改变,只是 DOM 不会实时更新。
|
42
source/_posts/hello-world.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Hello World
|
||||
tags:
|
||||
date: 1999-06-05 17:42:58
|
||||
url: hello-world
|
||||
index_img: /images/hello-world/index.webp
|
||||
---
|
||||
Welcome to [Hexo](https://hexo.io/)! This is your very first post. Check [documentation](https://hexo.io/docs/) for more info. If you get any problems when using Hexo, you can find the answer in [troubleshooting](https://hexo.io/docs/troubleshooting.html) or you can ask me on [GitHub](https://github.com/hexojs/hexo/issues).
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Create a new post
|
||||
|
||||
``` bash
|
||||
$ hexo new "My New Post"
|
||||
```
|
||||
|
||||
More info: [Writing](https://hexo.io/docs/writing.html)
|
||||
|
||||
### Run server
|
||||
|
||||
``` bash
|
||||
$ hexo server
|
||||
```
|
||||
|
||||
More info: [Server](https://hexo.io/docs/server.html)
|
||||
|
||||
### Generate static files
|
||||
|
||||
``` bash
|
||||
$ hexo generate
|
||||
```
|
||||
|
||||
More info: [Generating](https://hexo.io/docs/generating.html)
|
||||
|
||||
### Deploy to remote sites
|
||||
|
||||
``` bash
|
||||
$ hexo deploy
|
||||
```
|
||||
|
||||
More info: [Deployment](https://hexo.io/docs/one-command-deployment.html)
|
177
source/_posts/hexo.md
Normal file
@ -0,0 +1,177 @@
|
||||
---
|
||||
title: hexo
|
||||
index_img: /images/hexo/index.webp
|
||||
date: 2017-08-30 11:08:19
|
||||
tags: [Linux, HTML]
|
||||
categories: 实践
|
||||
url: hexo
|
||||
---
|
||||
|
||||
> 这是一篇写于较早期的文章,当时水平有限,文章质量不高。
|
||||
|
||||
好久不见
|
||||
|
||||
## Hexo?
|
||||
|
||||
Hexo是一款博客页面框架。特点是简洁、高效。使用markdown语法渲染文章。
|
||||
|
||||
其大概原理将本地markdown编写的.md文件经过多次渲染为html等静态页面文件。再由hexo服务发布。
|
||||
|
||||
可参考:
|
||||
|
||||
* https://www.zhihu.com/question/51588481
|
||||
* http://coderunthings.com/2017/08/20/howhexoworks/
|
||||
* 中文官网: https://hexo.io/zh-cn/
|
||||
|
||||
## 安装
|
||||
|
||||
Ubuntu
|
||||
|
||||
hexo的安装过程简便,工作原理与其他blog程序都大不相同。
|
||||
|
||||
安装前提
|
||||
|
||||
安装 Hexo 相当简单。然而在安装前,必须检查电脑中是否已安装下列应用程序:
|
||||
|
||||
• Node.js
|
||||
• Git
|
||||
|
||||
官方文档中提到安装所需要的上述应用程序,在ubuntu环境下Git在默认情况下为已经安装的。
|
||||
|
||||
若无git,可使用如下命令进行安装
|
||||
|
||||
```bash
|
||||
apt install git
|
||||
```
|
||||
|
||||
首先安装npm与node.js
|
||||
|
||||
```bash
|
||||
apt install npm nodejs-legacy
|
||||
npm install -g hexo-cli
|
||||
```
|
||||
|
||||
界面可能会如下
|
||||
|
||||
|
||||
等待npm install -g hexo-cli命令完成后,hexo就已经安装在系统中了
|
||||
|
||||
接下来就是使用hexo建立站点了,可参考官方文档: https://hexo.io/zh-cn/docs/setup.html
|
||||
|
||||
```bash
|
||||
$ hexo init <folder>
|
||||
$ cd <folder>
|
||||
$ npm install
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
```bash
|
||||
hexo init web
|
||||
```
|
||||
|
||||

|
||||
|
||||
过程略长,稍等即可
|
||||
|
||||
init文件夹完成后,进入并安装
|
||||
|
||||
```bash
|
||||
cd web && npm install
|
||||
```
|
||||
|
||||
完成后文件目录为如下。
|
||||
|
||||
新建完成后,指定文件夹的目录如下:
|
||||
|
||||
```
|
||||
.
|
||||
├── _config.yml
|
||||
├── package.json
|
||||
├── scaffolds
|
||||
├── source
|
||||
| ├── _drafts
|
||||
| └── _posts
|
||||
└── themes
|
||||
```
|
||||
|
||||
* _config.yml:站点的配置文件
|
||||
* package.json:应用程序信息
|
||||
* scaffolds:模版文件夹
|
||||
* source:用户资源存放文件夹
|
||||
* themes:主题文件夹
|
||||
|
||||
关于_config.yml的详细配置说明可参考官方文档: https://hexo.io/zh-cn/docs/configuration.html
|
||||
|
||||
## 服务端hexo-server
|
||||
|
||||
Hexo 3.0 把服务器独立成了个别模块,需要安装才能够使用。
|
||||
|
||||
```bsah
|
||||
npm install hexo-server --save
|
||||
```
|
||||
|
||||
安装完毕后,启动服务。(_config.yml需要先配置好)
|
||||
|
||||
```bash
|
||||
hexo server -p 80 -s
|
||||
```
|
||||
|
||||
正常情况下便可以直接进行访问,为如下页面。
|
||||
|
||||

|
||||
|
||||
Cannot GET / ?
|
||||
|
||||
如出现运行服务端后访问提示为"Cannot GET / ",请尝试:
|
||||
|
||||
• 确保在init的目录下运行过npm install
|
||||
• 添加了-s 参数,需要运行"hexo generate"命令生成静态文件。
|
||||
|
||||
|
||||
无法打开?
|
||||
|
||||
• Hexo server默认端口号为4000,可用-p参数进行修改
|
||||
• 需要在_config.yml配置文件中配置域名。
|
||||
|
||||
• 也可以使用-i命令修改监听的ip地址。默认为0.0.0.0
|
||||
• 需要在所init的目录下运行server命令
|
||||
|
||||
Hexo server会占用整个shell,对于ssh连接来说,断开后便会中断服务。
|
||||
|
||||
我们可以使用screen命令搭配运行hexo server
|
||||
|
||||
```
|
||||
screen hexo s -p 80 -s
|
||||
```
|
||||
|
||||
运行后按下`Ctrl+a+d`将当前窗口放置后台运行
|
||||
|
||||
查看`screen -ls`
|
||||
|
||||
重新连接会话`screen -r 25211`
|
||||
|
||||
## 写作
|
||||
可以执行下列命令来创建一篇新文章。
|
||||
|
||||
```bash
|
||||
$ hexo new [layout] <title>
|
||||
```
|
||||
|
||||
可以直接使用命令来创建文章,默认文件名为title
|
||||
|
||||
```bash
|
||||
hexo new TEST
|
||||
```
|
||||
|
||||
可以看到创建的文章会被创建为source目录下的.md文件,可以直接使用markdown语法写作修改。
|
||||
|
||||
> 若是静态运行,每次修改文章后需要使用hexo generate命令渲染。
|
||||
|
||||
更多方式可以了解官方说明: https://hexo.io/zh-cn/docs/writing.html
|
||||
|
||||
## 结尾
|
||||
|
||||
Hexo是一款不同与其他的博客框架,其简洁、高效和多样性的用法也是吸引人的一大特点。无需数据库,所有文章都在统一文件夹内,真正纯静态化的站点。新颖、特殊的工作方式让人眼前一亮。由于与众不同的特点,hexo与markdown的方式还得日后慢慢学习使用。
|
||||
|
||||
此文完全参照与官方说明文档。
|
154
source/_posts/minecraft-bedrock服务端.md
Normal file
@ -0,0 +1,154 @@
|
||||
---
|
||||
title: Minecraft bedrock服务端🥂
|
||||
date: 2019-05-16 14:21:37
|
||||
tags: Minecraft
|
||||
categories: 实践
|
||||
url: minecraft-bedrock-server
|
||||
index_img: /images/Minecraft-bedrock服务端/217940907.webp
|
||||
---
|
||||
|
||||
## Minecraft
|
||||
|
||||
**MINECRAFT 是什么?**
|
||||
|
||||
> **这是一个有关放置方块与探险的游戏。**
|
||||
游戏设定在一片可以无限生成的世界里,这里有广袤而开阔的土地——由冰雪覆盖的山峰、潮湿的河口、辽阔的牧场等等组成——它们充满着奥秘、奇迹与危险。
|
||||
|
||||
这个游戏应该是多数玩家都已不陌生,开发商为Mojang AB(Mojäng Aktiebolag),是瑞典的一家电子游戏开发商。他家也发行过其他的小游戏,但是名声都不怎么样。最后Minecraft一炮走红。2014年9月,财大气粗的软软以25亿美元收购Mojang以及游戏的知识产权。
|
||||
|
||||
最早的时候我们在PC上接触的应该都是开发商的Java版本,Java版本的好处就是它有各种各样的Mod,以及材质包等。对于玩家来说使用也非常的方便。虽然性能不是那么的卓越,但是现在依然是很受玩家的欢迎了。
|
||||
|
||||
在被微软收购以后,微软结合自家的Win10应用商店也出了一个Bedrock版本,与其不同的是,这次使用C语言写出来的。在自家的应用商店内,玩家们安装也变的更加的方便了。除此之外,性能方面肯定是要比Java好的多的。
|
||||
|
||||
## Bedrock Server
|
||||
|
||||
相比较以前的Java版本自建服务器来说,Bedrock版本的Server要比以前方便简单的多。虽然目前还处于Bate版本,但是支持的功能也比较完善了。
|
||||
|
||||
主要是简单的多,对于以前的Java,服务端搭建起来非常的麻烦,得力于Java,还非常吃服务器的资源。目前已经编译好的C语言来说,不仅省了很多事,还降低了对服务器的要求。当然,唯一一个缺点就是跨平台肯定没有Java方便了。
|
||||
|
||||
## 搭建服务端
|
||||
|
||||
服务端我们可以免费的在[官方网站](<https://www.minecraft.net/en-us/download/server/bedrock/>)下载,目前只支持Windows与Ubuntu版本。在Windows上运行推荐使用Windows10/Windows Server 2016及以后的版本。
|
||||
|
||||
![2019-05-13T05:16:24.png][1]
|
||||
|
||||
[1]: ../images/Minecraft-bedrock服务端/4100402335.png
|
||||
|
||||
Windows版本与Ubuntu版本的文件几乎差不多,下载后直接解压,我们就能够看到一个可执行的`bedrock_server.exe`文件。当没有任何需求时,直接执行它就可以启动并正常使用服务端了。
|
||||
|
||||
启动后我们可以看到一个类似这样的命令提示符的界面:
|
||||
|
||||
![2019-05-15T13:18:43.png][2]
|
||||
|
||||
[2]: ../images/Minecraft-bedrock服务端/3200057918.png
|
||||
|
||||
此时的服务器端就已经启动完成了,若能直接访问服务器,就可以直接开始游戏了🥓。
|
||||
|
||||
*需要使用在Windows商店下载的Minecarft作为客户端*
|
||||
|
||||
### 配置文件
|
||||
|
||||
当然,默认的配置文件往往大多数时间都是不能满足我们的需求的。还有很多种情况需要我们根据自己的想法去自定义。例如修改个监听的端口。
|
||||
|
||||
目前的Bedrock服务端也有个较为完善的配置文件,可读性也非常的高。官方下载的包里不仅有个“How to”,并且配置文件内都有着很详细的注释,例如修改游戏模式:
|
||||
|
||||
```
|
||||
gamemode=survival
|
||||
# Sets the game mode for new players.
|
||||
# Allowed values: "survival", "creative", or "adventure"
|
||||
```
|
||||
|
||||
配置文件的修改入门很低,只要能打开几乎针对服务器的一些选项都可以自定义。除此之外,我们还可以在服务器允许中的命令行/shell内直接使用一些命令,例如提出某个在线的玩家:
|
||||
|
||||
```
|
||||
kick <player name or xuid> <reason>
|
||||
```
|
||||
|
||||
或者是关闭服务器:
|
||||
|
||||
```
|
||||
stop
|
||||
```
|
||||
|
||||
*当然你喜欢的话也可以直接终止这个进程,当然不推荐这么做。*
|
||||
|
||||
压缩包内的“bedrock_server_how_to.html”写的非常的详细,功能也非常的全。对于一些日常的使用,或者是入门的话,可以多参考参考该文件。
|
||||
|
||||
为了方便,我还放了个在线版本的[📢bedrock_server_how_to](<https://www.defectink.com/defect/bedrock_server_how_to.html>)。是当前最新的1.11.2.1版本的,后续可能不会持续更新。当前写的非常的详细了。
|
||||
|
||||
### 权限级别
|
||||
|
||||
配置文件可以修改大多数服务端的配置,主要是针对游戏服务器的修改。像是对于游戏内的具体修改并没有写在配置文件内,例如对玩家的修改以及修改世界的选项。这些操作选项需要我们手动赋予一个玩家“操作员”的权限,这样,该玩家就会有对目前游戏的整个世界的完整操作权限。
|
||||
|
||||
![2019-05-15T15:27:50.png][3]
|
||||
|
||||
[3]: ../images/Minecraft-bedrock服务端/2032314932.png
|
||||
|
||||
在游戏的目录下有个名为`permissions.json`的json文件,在默认情况下它是空的,我们可以根据帮助文件提供的格式直接赋予某个玩家权限:
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"permission": "operator",
|
||||
"xuid": "451298348"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
*需要注意的是,我们需要对应的玩家的`xuid`值,这在玩家连接时会显示在终端上。*
|
||||
|
||||
一次赋予多个玩家的格式:
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"permission": "operator",
|
||||
"xuid": "451298348"
|
||||
},
|
||||
{
|
||||
"permission": "member",
|
||||
"xuid": "52819329"
|
||||
},
|
||||
{
|
||||
"permission": "visitor",
|
||||
"xuid": "234114123"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
当玩家被赋予`operator`的权限时,重启服务端,再次进入游戏时该玩家就拥有了对世界的完整控制。
|
||||
|
||||
*某些世界生成后就不能修改的选项除外*
|
||||
|
||||
## 备份世界
|
||||
|
||||
最简单也是最直接的备份方式就是直接备份当前服务端的整个文件夹,如果这个操作太过于麻烦的话,或者说文件夹已经达到了臃肿的地步了。我们可以使用预留的备份命令,可以生成`.db`的文件用于copy。文档的详细解释:
|
||||
|
||||
| Command | Description |
|
||||
| ----------- | ------------------------------------------------------------ |
|
||||
| save hold | This will ask the server to prepare for a backup. It’s asynchronous and will return immediately. |
|
||||
| save query | After calling `save hold` you should call this command repeatedly to see if the preparation has finished. When it returns a success it will return a file list (with lengths for each file) of the files you need to copy. The server will not pause while this is happening, so some files can be modified while the backup is taking place. As long as you only copy the files in the given file list and truncate the copied files to the specified lengths, then the backup should be valid. |
|
||||
| save resume | When you’re finished with copying the files you should call this to tell the server that it’s okay to remove old files again. |
|
||||
|
||||
我们可以直接使用`save hold`来生成备份文件,然后再使用`save query`来查询文件的位置。*注意:当我们的世界名称使用中文时,可能会出现在终端中文乱码的情况*,例如:
|
||||
|
||||
![2019-05-15T15:50:09.png][4]
|
||||
|
||||
[4]: ../images/Minecraft-bedrock服务端/1339714059.png
|
||||
|
||||
此时最佳解决办法就是换个世界名称。但是直接在配置文件中更换名称后,会导致重新创建一个新的世界。为了避免这个现象,达到给旧世界更换名称的操作。我们需要同时修改三个地方的名称,并保持一致:
|
||||
|
||||
* `server.properties`文件内的名称
|
||||
* `world`文件夹内的世界文件夹名称
|
||||
* 世界文件夹内的`levelname.txt`内的文字
|
||||
|
||||
当这三处名称统一修改时,再重新启动服务器就会达到修改世界名称的效果了。
|
||||
|
||||
目前推荐的最佳的备份方法就是直接备份`world`文件夹的所有内容,在服务器运行时对文件夹进行复制操作不会影响服务器的正常运行。
|
||||
|
||||
## 网易代理
|
||||
|
||||
网易代理的Minecraft对游戏的本质来说或许没有太多的不同,对于收费模式也是符合目前国内的游戏较为普遍的免费进入游戏,后续再根据自己的选择购买一些增值服务。也就是非买断制。但是网易拥有租赁服务器的服务,可能是想方便提供给玩家们一个更加方便的购买与启用服务器的渠道,但是他直接禁止使用其他的第三方服务器。对于某些玩家来说可能不是个好的选择。
|
||||
|
||||
虽然微软在Xbox上也有这类似的操作,但是Windows商店的Minecraft也不是天价,对于不想玩个mc就把身份证给网易的小伙伴们,Windows商店可能是个更好的选择。
|
||||
|
234
source/_posts/systemd的基础操作.md
Normal file
@ -0,0 +1,234 @@
|
||||
---
|
||||
title: systemd 的基础操作
|
||||
date: 2019-06-14 14:42:41
|
||||
tags: Linux
|
||||
categories: Linux
|
||||
url: basic-knowledge-of-systemd
|
||||
index_img: /images/systemd的基础操作/logo.webp
|
||||
---
|
||||
|
||||
## 什么是systemd?
|
||||
|
||||
Systemd是我们常用的一些Linux发行版中常见的一套中央化系统及设置管理程序(init),包括有[守护进程](https://zh.wikipedia.org/wiki/%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B)、[程序库](https://zh.wikipedia.org/wiki/%E7%A8%8B%E5%BC%8F%E5%BA%AB)以及应用软件。
|
||||
|
||||
我们经常使用`systemctl start apache2`来启动一些服务或者应用软件时,使用到的就是Systemd的一部分。
|
||||
|
||||
当前绝大多数的Linux发行版都已采用systemd代替原来的[System V](https://zh.wikipedia.org/wiki/UNIX_System_V)。
|
||||
|
||||
## 学习它的作用?
|
||||
|
||||
它能够方便的对一些软件运行进行管理,经常使用`systemctl`的同学们可能会比较了解,譬如查看运行状态,设置开机自启等操作。
|
||||
|
||||
最近尝鲜了Ubuntu,但是遇到个新的问题。在Ubuntu 18.10中,已经将以前的开机自启动的脚本`/etc/rc.local`去掉了。无意中看到使用systemd来控制开机自启应用程序。
|
||||
|
||||
后来就顺便尝试进一步了解一下systemd,毕竟还是比较有用的。
|
||||
|
||||
## 开机运行启动的原理
|
||||
|
||||
一些支持systemd的软件,在安装时会在`/usr/lib/systemd/system`目录下添加一个支持systemd的配置文件。当我们使用`systemctl enable apache2`时,就相当于将`/usr/lib/systemd/system`目录下的配置文件添加一个符号链接,链接到`/etc/systemd/system`目录。
|
||||
|
||||
当我们的系统开机时,systemd会执行`/etc/systemd/system`目录下的配置文件,以达到开机自启的效果。
|
||||
|
||||
最近发现当前较新的发行版,使用enable命令时,创建的链接目录为:
|
||||
|
||||
```
|
||||
~ systemctl enable httpd
|
||||
Created symlink from /etc/systemd/system/multi-user.target.wants/httpd.service to /usr/lib/systemd/system/httpd.service.
|
||||
```
|
||||
|
||||
## 配置文件
|
||||
|
||||
几条常用且熟悉的sysemctl的命令这里就不在详细介绍了。这里直接了解一下最核心的部分,配置文件。
|
||||
|
||||
早在很久以前,对于[Nyncat](https://www.defectink.com/defect/12.html)这篇文章,里面就使用到自己编写systemd的配置文件来达到对nyancat这个服务的详细控制。(虽然当时我不理解配置文件说的啥…
|
||||
|
||||
上述了解到,配置文件一般情况下出现在两个地方:`/usr/lib/systemd/system`目录和`/etc/systemd/system/multi-user.target.wants`目录。对于完全不了解配置文件的情况下,我们可以先在这两个目录找个案例了解一下。
|
||||
|
||||
**某些Ubuntu的发行版可能在`/lib/systemd/system`目录下保存配置文件**
|
||||
|
||||
例如,CentOS的httpd:
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=The Apache HTTP Server
|
||||
After=network.target remote-fs.target nss-lookup.target
|
||||
Documentation=man:httpd(8)
|
||||
Documentation=man:apachectl(8)
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
EnvironmentFile=/etc/sysconfig/httpd
|
||||
ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND
|
||||
ExecReload=/usr/sbin/httpd $OPTIONS -k graceful
|
||||
ExecStop=/bin/kill -WINCH ${MAINPID}
|
||||
# We want systemd to give httpd some time to finish gracefully, but still want
|
||||
# it to kill httpd after TimeoutStopSec if something went wrong during the
|
||||
# graceful stop. Normally, Systemd sends SIGTERM signal right after the
|
||||
# ExecStop, which would kill httpd. We are sending useless SIGCONT here to give
|
||||
# httpd time to finish.
|
||||
KillSignal=SIGCONT
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
我们可以了解到,它基本上分为三个区块:`Unit`、`Service`和`Install`区块。
|
||||
|
||||
### Unit区域
|
||||
|
||||
Unit区域用于解释启动顺序与依赖关系。可以直观的看到它有几个字段:
|
||||
|
||||
- Description
|
||||
- After
|
||||
- Documentation
|
||||
|
||||
第一眼看上去可能都是比较乱七八糟的,根本不知道它在说啥。但是我们拆开来一个一个了解,就会发现它意义非常的简单。
|
||||
|
||||
**Description**:用于给出当前服务的简单描述。通常我们查看服务状态时,都会在第一行看到这么一句话:
|
||||
|
||||
```
|
||||
httpd.service - The Apache HTTP Server
|
||||
```
|
||||
|
||||
这就是`Descipton`字段的作用,一句话对当前服务的简单介绍。
|
||||
|
||||
**After**:该字段是有关于启动顺序的字段,从字面意思我们就应该大概了解到,这个值应该是定义当前服务应该启动在哪些服务之后。
|
||||
|
||||
上述配置文件该值解释就是:当`network.target remote-fs.target nss-lookup.target`这些服务需要启动,那么当前的`httpd.service`应该在他们之后启动。
|
||||
|
||||
相对的,和`After`对应的还有个`Before`字段。了解了`After`这个`Before`就应该很容易理解了。完全和`After`相对的意思,定义`httpd.service`应该在哪些服务之前启动。
|
||||
|
||||
After和Before只关乎到服务的启动顺序,并不关乎到依赖关系。
|
||||
|
||||
**Documentation**:该字段比较简单,和`Descripton`作用差不多。它的值用于给出当前服务的文档的位置。
|
||||
|
||||
当前配置文件中并没有说明依赖关系的字段。依赖关系和启动顺序都是写在当前这个Unit区域的,它俩非常像象,但是作用不同。
|
||||
|
||||
依赖关系有两个字段进行控制:`Wants`和`Requiers`。
|
||||
|
||||
**Wants**:表示弱依赖关系,即使该值内的服务启动失败,也不影响当前服务的继续运行。
|
||||
|
||||
**Requires**:表示强依赖关系,如果该值内的服务无法运行,那么当前服务也将停止。
|
||||
|
||||
打个比方:
|
||||
|
||||
当前的httpd.service需要依赖mysql来存储数据。如果在配置文件中它只定义了在mysql之后启动。而没定义依赖关系,那么当mysql出现错误停止时,在重新启动期间,当前的httpd将无法与mysql建立链接。
|
||||
|
||||
**这里只是打个比方帮助我们更好的了解,实际情况下httpd在通常和mysql是没有这样的依赖关系的🍥。**
|
||||
|
||||
### Service区域
|
||||
|
||||
Service区域是主要的一部分,主要控制软件的启动停止等,都是在此部分声明的。也就是定义了如何启动当前的服务。
|
||||
|
||||
许多软件都有环境参数文件,使用`EnvironmentFile`字段便可以定义环境参数。
|
||||
|
||||
`EnvironmentFile`字段:指定当前服务的环境参数文件。该文件内部的`key=value`键值对,可以用`$key`的形式,在当前配置文件中获取。
|
||||
|
||||
例如,启动`sshd`,执行的命令是`/usr/sbin/sshd -D $OPTIONS`,其中的变量`$OPTIONS`就来自`EnvironmentFile`字段指定的环境参数文件。
|
||||
|
||||
除此之外,Service区域还有一些关于控制软件行为的一些字段:
|
||||
|
||||
* `ExecStart`字段:定义启动进程时执行的命令。
|
||||
|
||||
- `ExecReload`字段:重启服务时执行的命令
|
||||
- `ExecStop`字段:停止服务时执行的命令
|
||||
- `ExecStartPre`字段:启动服务之前执行的命令
|
||||
- `ExecStartPost`字段:启动服务之后执行的命令
|
||||
- `ExecStopPost`字段:停止服务之后执行的命令
|
||||
|
||||
所有的启动设置之前,都可以加上一个连词号(`-`),表示"抑制错误",即发生错误的时候,不影响其他命令的执行。比如,`EnvironmentFile=-/etc/sysconfig/sshd`(注意等号后面的那个连词号),就表示即使`/etc/sysconfig/sshd`文件不存在,也不会抛出错误。
|
||||
|
||||
此外,Service中还有几个比较重要的字段
|
||||
|
||||
**Type**字段,它有如下一些值:
|
||||
|
||||
- simple(默认值):`ExecStart`字段启动的进程为主进程
|
||||
- forking:`ExecStart`字段将以`fork()`方式启动,此时父进程将会退出,子进程将成为主进程
|
||||
- oneshot:类似于`simple`,但只执行一次,Systemd 会等它执行完,才启动其他服务
|
||||
- dbus:类似于`simple`,但会等待 D-Bus 信号后启动
|
||||
- notify:类似于`simple`,启动结束后会发出通知信号,然后 Systemd 再启动其他服务
|
||||
- idle:类似于`simple`,但是要等到其他任务都执行完,才会启动该服务。一种使用场合是为让该服务的输出,不与其他服务的输出相混合
|
||||
|
||||
### Install区域
|
||||
|
||||
`Install`区块,定义如何安装这个配置文件,即怎样做到开机启动。
|
||||
|
||||
`WantedBy`字段:表示该服务所在的 Target。
|
||||
|
||||
`Target`的含义是服务组,表示一组服务。`WantedBy=multi-user.target`指的是,sshd 所在的 Target 是`multi-user.target`。
|
||||
|
||||
这个设置非常重要,因为执行`systemctl enable sshd.service`命令时,`sshd.service`的一个符号链接,就会放在`/etc/systemd/system`目录下面的`multi-user.target.wants`子目录之中。
|
||||
|
||||
Systemd 有默认的启动 Target。
|
||||
|
||||
```bash
|
||||
$ systemctl get-default
|
||||
multi-user.target
|
||||
```
|
||||
|
||||
一般来说,常用的 Target 有两个:一个是`multi-user.target`,表示多用户命令行状态;另一个是`graphical.target`,表示图形用户状态,它依赖于`multi-user.target`。官方文档有一张非常清晰的 [Target 依赖关系图](https://www.freedesktop.org/software/systemd/man/bootup.html#System Manager Bootup)。
|
||||
|
||||
Target 也有自己的配置文件。
|
||||
|
||||
```bash
|
||||
$ systemctl cat multi-user.target
|
||||
|
||||
[Unit]
|
||||
Description=Multi-User System
|
||||
Documentation=man:systemd.special(7)
|
||||
Requires=basic.target
|
||||
Conflicts=rescue.service rescue.target
|
||||
After=basic.target rescue.service rescue.target
|
||||
AllowIsolate=yes
|
||||
```
|
||||
|
||||
## 详细的字段解释
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description : 服务的简单描述
|
||||
Documentation : 服务文档
|
||||
Before、After:定义启动顺序。Before=xxx.service,代表本服务在xxx.service启动之前启动。After=xxx.service,代表本服务在xxx.service之后启动。
|
||||
Requires:这个单元启动了,它需要的单元也会被启动;它需要的单元被停止了,这个单元也停止了。
|
||||
Wants:推荐使用。这个单元启动了,它需要的单元也会被启动;它需要的单元被停止了,对本单元没有影响。
|
||||
```
|
||||
|
||||
```
|
||||
[Service]
|
||||
Type=simple(默认值):systemd认为该服务将立即启动。服务进程不会fork。如果该服务要启动其他服务,不要使用此类型启动,除非该服务是socket激活型。
|
||||
Type=forking:systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon),除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile=,以便systemd能够跟踪服务的主进程。
|
||||
Type=oneshot:这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态。
|
||||
Type=notify:与 Type=simple 相同,但约定服务会在就绪后向 systemd 发送一个信号。这一通知的实现由 libsystemd-daemon.so 提供。
|
||||
Type=dbus:若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。
|
||||
Type=idle: systemd会等待所有任务(Jobs)处理完成后,才开始执行idle类型的单元。除此之外,其他行为和Type=simple 类似。
|
||||
PIDFile:pid文件路径
|
||||
ExecStartPre:停止服务时执行的命令
|
||||
ExecStart:指定启动单元的命令或者脚本,ExecStartPre和ExecStartPost节指定在ExecStart之前或者之后用户自定义执行的脚本。Type=oneshot允许指定多个希望顺序执行的用户自定义命令。
|
||||
ExecReload:指定单元停止时执行的命令或者脚本。
|
||||
ExecStop:指定单元停止时执行的命令或者脚本。
|
||||
ExecStopPost:停止服务之后执行的命令
|
||||
ExecStartPost:启动服务之后执行的命令
|
||||
PrivateTmp:True表示给服务分配独立的临时空间
|
||||
Restart:这个选项如果被允许,服务重启的时候进程会退出,会通过systemctl命令执行清除并重启的操作。
|
||||
RemainAfterExit:如果设置这个选择为真,服务会被认为是在激活状态,即使所以的进程已经退出,默认的值为假,这个选项只有在Type=oneshot时需要被配置。
|
||||
```
|
||||
|
||||
```
|
||||
[Install]
|
||||
Alias:为单元提供一个空间分离的附加名字。
|
||||
RequiredBy:单元被允许运行需要的一系列依赖单元,RequiredBy列表从Require获得依赖信息。
|
||||
WantBy:单元被允许运行需要的弱依赖性单元,Wantby从Want列表获得依赖信息。
|
||||
Also:指出和单元一起安装或者被协助的单元。
|
||||
DefaultInstance:实例单元的限制,这个选项指定如果单元被允许运行默认的实例。
|
||||
```
|
||||
|
||||
## 重启
|
||||
|
||||
```bash
|
||||
# 重新加载配置文件
|
||||
$ sudo systemctl daemon-reload
|
||||
|
||||
# 重启相关服务
|
||||
$ sudo systemctl restart foobar
|
||||
```
|
82
source/_posts/使typecho支持emoji.md
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
title: 使 typecho 支持 emoji🎈
|
||||
date: 2019-05-12 13:41:57
|
||||
tags: typecho
|
||||
categories: 实践
|
||||
url: make-typecho-support-the-emoji
|
||||
index_img: /images/使typecho支持emoji/1118031526.jpg
|
||||
---
|
||||
|
||||
## Emoji?
|
||||
|
||||
emoji是我们身边常见的且神奇的表情符号,它被称为绘文字(えもじ *emoji*)。最初是日本在无线通信中所使用的视觉情感符号。与我们常发的表情包不同的是,它并不是图片。
|
||||
|
||||
Emoji的编码是Unicode字符集中的一部分,特定形象的Emoji表情符号对应到特定的Unicode字节。也就是说emoji是unicode编码。好处是无论在什么地方使用都不像是图片那么难处理,以及可以直接写在数据库内。
|
||||
|
||||
![emoji_unicode.webp][1]
|
||||
|
||||
### 词语发音
|
||||
|
||||
絵文字/えもじ(emoji)的发音是 /emoꜜdʑi/(此处为国际音标)。 [2]
|
||||
|
||||
- /e/:即汉语拼音 y**e**中**ê**的发音,英语单词 b**e**d 中**e**的发音。
|
||||
- /dʑ/:与汉语拼音 j 对应的浊音。与潮州话拼音方案的 r 相似,如潮州话“字”(ri⁷)字的声母。 [3]
|
||||
- /mo/ 为重读音节。
|
||||
|
||||
在英语中,emoji 常被读作 /ɪˈmoʊdʒi/。
|
||||
|
||||
## 在typecho中使用emoji
|
||||
|
||||
现在多数的软件、网站等都已经广泛的支持emoji表情了。自己也是非常的喜欢这类表情,特别喜欢微软家的,那种扁平的风格真的很招人喜爱。
|
||||
|
||||
![1557663679707.webp][2]
|
||||
|
||||
但是最近使用typecho的时候遇到点小问题,发现新安装的typecho居然不支持使用emoji。在文章等页面使用了emoji之后,保存会提示数据库查询错误。
|
||||
|
||||
这是因为数据库默认使用的是`utf8`编码,在`utf8`的编码中最多只支持3个字节,而我们可爱的emoji是4个字节,如上述所说的,emoji并非图片,是直接存储在数据库内的。所以就出现了数据库查询错误导致无法使用emoji的问题。
|
||||
|
||||
### 修改数据库
|
||||
|
||||
解决办法也非常的简单,我们直接使用phpMyAdmin或者sql,修改数据库`charset`为`utf8mb4`就ok了
|
||||
|
||||
```
|
||||
alter table typecho_comments convert to character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||
alter table typecho_contents convert to character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||
alter table typecho_fields convert to character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||
alter table typecho_metas convert to character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||
alter table typecho_options convert to character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||
alter table typecho_relationships convert to character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||
alter table typecho_users convert to character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||
```
|
||||
|
||||
如果有没有涉及的表,按照同样的语句修改就可以了。
|
||||
|
||||
修改后就可以看到表的‘排序规则’(charset)为可以使用emoji的`utf8mb4`了。
|
||||
|
||||
![2019-05-12T12:27:33.webp][3]
|
||||
|
||||
### 修改typecho配置文件
|
||||
|
||||
当数据库修改完成之后,到typecho的目录下找到其配置文件`config.inc.php`。并且修改为刚刚设置编码就ok了
|
||||
|
||||
```
|
||||
$db->addServer(array (
|
||||
'host' => localhost,
|
||||
'user' => 'root',
|
||||
'password' => 'my_password',
|
||||
'charset' => 'utf8mb4', //修改这一行
|
||||
'port' => 3306,
|
||||
'database' => '喵喵喵'
|
||||
)
|
||||
```
|
||||
|
||||
全部修改完成后就能正常的在typecho中使用emoji了
|
||||
|
||||
![2019-05-12T12:31:18.webp][4]
|
||||
|
||||
|
||||
|
||||
[1]: ../images/使typecho支持emoji/2721696195.webp
|
||||
[2]: ../images/使typecho支持emoji/1431864746.webp
|
||||
[3]: ../images/使typecho支持emoji/175217384.webp
|
||||
[4]: ../images/使typecho支持emoji/4188132525.webp
|
48
source/_posts/修改Windows端iCloud云盘存储位置.md
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
title: 修改 Windows 端 iCloud 云盘存储位置
|
||||
date: 2019-11-18 12:53:53
|
||||
tags: Windows
|
||||
categories: 踩坑
|
||||
url: modify-icloud-storage-location-on-windows
|
||||
index_img: /images/修改Windows端iCloud云盘存储位置/logo.webp
|
||||
---
|
||||
|
||||
|
||||
|
||||
自从有了水果之后,除了天天被人嘲讽之外,还花了大手笔买了一个月6块的iCloud 50GB存储空间。但是光有手机来用还是略微有点大。以前一直以为iCloud就是手机上存个照片的,都忘了它其实也是一个云盘来着。于是决定利用一下剩下的空间,在Windows也用它来同步。
|
||||
|
||||
果然在试用了一段时间过后,水果没有让我失望过。iCloud照片可以跟着Windows系统的【图片】文件夹跑,但是iCloud云盘就不那么听话了,它只会在`User`目录下直接怼一个目录,并且**无法修改**,连个设置都没有。对于我这种C盘小的可怜的来说,就非常难受了。
|
||||
|
||||
所以只能用最基本的方法,创建符号链接 : 软链接来解决问题了。
|
||||
|
||||
## 操作
|
||||
|
||||
1. 找到当前的iCloud云盘文件夹位置,并剪切到目的位置。当然先复制更加稳妥。
|
||||
2. 关掉不听话的iCloud。
|
||||
3. 以管理员身份运行cmd,创建符号链接到新的存储位置。需要删除已经存在c盘的文件夹。
|
||||
|
||||
```cmd
|
||||
C:\WINDOWS\system32>mklink /D C:\Users\Defectink\iCloudDrive D:\iCloudDrive
|
||||
为 C:\Users\Defectink\iCloudDrive <<===>> D:\iCloudDrive 创建的符号链接
|
||||
```
|
||||
|
||||
4. 重新运行iCloud。
|
||||
|
||||
没有退出按钮?
|
||||
|
||||

|
||||
|
||||
> 如果先退出再复制文件可能会导致意外
|
||||
|
||||

|
||||
|
||||
## 启动文件夹
|
||||
|
||||
我的iCloud不知道为什么就是无法开机自启,无奈只好放到Windows的启动文件夹里了。~~效果还可以~~
|
||||
|
||||
```cmd
|
||||
shell:startup
|
||||
```
|
||||
|
||||
|
||||
|
246
source/_posts/入坑IRC.md
Normal file
@ -0,0 +1,246 @@
|
||||
---
|
||||
title: 入坑 IRC
|
||||
index_img: /images/入坑IRC/logo.webp
|
||||
date: 2020-08-22 18:37:36
|
||||
tags: [tools,IRC,Linux]
|
||||
categories: 日常
|
||||
url: irc-getting-started
|
||||
---
|
||||
|
||||
## IRC
|
||||
|
||||
IRC的全称为Internet Relay Chat,是一种应用层的协议。主要用于聊天,是早期互联网中主流的聊天工具,在今天依然也有不少人活跃。要使用它需要使用客户端来连接到服务器。
|
||||
|
||||
## IRC的组成
|
||||
|
||||
### 服务器
|
||||
|
||||
IRC是一个分布式的C/S架构。通过连接到一个服务器,就可以访问其连接的其他服务器上的频道。目前常见的有`irc.freenode.net`。
|
||||
|
||||
### 频道
|
||||
|
||||
频道存在于一个IRC服务器上。一个频道类似于一个聊天室,频道名称必须以#符号开始,例如#irchelp。
|
||||
|
||||
### 客户端
|
||||
|
||||
客户端用于连接至服务器,目前有很多种基于字符/GUI的跨平台软件。我用的是HexChat,一款基于GUI的软件。
|
||||
|
||||
## 使用
|
||||
|
||||
简单的了解了IRC是一款用于聊天的应用协议之后,就是开始使用了。既然是基于C/S架构的,那么首先是准备好自己的客户端。我挑了一个常见的GUI客户端:HexChat。
|
||||
|
||||
### 昵称注册
|
||||
|
||||
IRC并不像现代的聊天软件一样,需要先注册账号才能使用。它可以输入一个昵称后就进入服务器的频道内与人聊天。而昵称任然是需要唯一的,所以想要使用自己的昵称而不被别人占用,就需要注册昵称。类似于注册账号,基于邮箱与密码。
|
||||
|
||||
注册过程很简单,首先需要在聊天窗口中输入注册的命令:
|
||||
|
||||
```
|
||||
/msg nickserv register password email
|
||||
```
|
||||
|
||||
因为是在聊天框里输入命令,所以一定要注意命令格式,否则一不小心可能就会将明文的密码发到频道里。在注册昵称时推荐不加入任何频道,这样就不会不小心发出去了。
|
||||
|
||||
输入注册命令后就会收到认证的邮件,邮件大概是这样的(freenode):
|
||||
|
||||

|
||||
|
||||
将邮件里的内容再输入一遍就注册完成了。
|
||||
|
||||
```
|
||||
/msg NickServ VERIFY REGISTER Defectink hr*********
|
||||
```
|
||||
|
||||
### 认证
|
||||
|
||||
当注册昵称过后,下次再使用这个昵称登录的时候就需要认证了。使用同样的`/msg`来进行认证:
|
||||
|
||||
```
|
||||
/msg nickserv identify password
|
||||
```
|
||||
|
||||
另一种说明身份的方法是设置服务器密码为您注册时提供的密码。
|
||||
|
||||
还可以对昵称设置进行保护,即在登录认证时,必须在30秒内向服务器表明身份,否则就强制改为其他昵称,并在一段时间内禁止此人使用此昵称(即便是在说明身份后)。在任意窗口中键入:`/msg nickserv set enforce on`。如果你登陆时在30秒内未能表明身份且被改为其他名字,请在改回原有名称前,输入`/msg nickserv release username password`以解除。
|
||||
|
||||
### SASL
|
||||
|
||||
不少 IRC 客户端都支持使用 SASL 自动登录。只需找到相应的选项,在 SASL 用户名密码部分分别填入自己的昵称和NickServ密码,就可以获得自动登录的效果。
|
||||
|
||||
不同的客户端有不同的设置方法,文档也很多。这是对于HexCaht的:
|
||||
|
||||
1. Open the Network List (Ctrl + S)
|
||||
2. The freenode network may already exist; find it in the list then click on Edit
|
||||
3. In the `User name` field, enter your primary nick
|
||||
4. Select `SASL (username + password)` for the `Login method` field
|
||||
5. In the `Password` field, enter your NickServ password
|
||||
|
||||

|
||||
|
||||
## 频道
|
||||
|
||||
以#号开头的字符串就是频道名,可以使用命令`/join`来加入频道。
|
||||
|
||||
`/join #archlinux-cn`
|
||||
|
||||
### 建立频道
|
||||
|
||||
创建频道同样也是使用`/join`来创建,如果创建的频道已经存在,则直接进入。建立频道可用于与自己的小伙伴聊天,也可以用于熟悉频道命令。
|
||||
|
||||
如果频道成功建立,那么我们就会成为频道的管理员。因为频道名和昵称一样需要具有唯一性,所以频道也需要注册。
|
||||
|
||||
`/msg ChanServ REGISTER <#channel> <passwd>`
|
||||
|
||||
一般频道都通过ChanServ这个机器人管理.
|
||||
|
||||
`/msg ChanServ SET <频道名> GUARD ON`
|
||||
|
||||
在已注册的频道上委任管理员OP
|
||||
|
||||
`/msg ChanServ op #xfy Defectink`
|
||||
|
||||
设置频道简介
|
||||
|
||||
`/topic <your_topic>`
|
||||
|
||||
### 频道模式
|
||||
|
||||
频道的模式用于设置频道的一些功能与限制。通过`/mode #channel`来列出频道模式,通过`/msg chanserv info #channel`来列出有MLOCK的模式。
|
||||
|
||||
使用`/mode #channel +(mode)`或者`/mode #channel -(mode)`来增加或删除模式。带有MLOCK的模式需要使用`/msg ChanServ SET #foo MLOCK +c`
|
||||
|
||||
一些常用的模式:
|
||||
|
||||
| Mode(name) | Description |
|
||||
| :------------------------ | ------------------------------------------------------------ |
|
||||
| b (channel ban) | Prevent users from joining or speaking. Sending /mode #channel +b alone will return the current ban list. While on the channel, banned users will be unable to send to the channel or change nick. The most common form for a ban is +b nick!user@host. The wildcards * and ? are allowed, matching zero-or-more and exactly-one characters, respectively. Bans set on IP addresses will apply even if the affected user joins with a resolved or cloaked hostname. CIDR notation is supported in bans. The second form can be used for bans based on user data. You can append $#channel to any ban to redirect banned users to another channel. |
|
||||
| q (quiet) | Works like +b (ban user), but allows matching users to join the channel. |
|
||||
| c (colour filter) | Strip colour and formatting codes from channel messages. |
|
||||
| C (block CTCPs) | Blocks CTCP commands (other than /me actions). |
|
||||
| i (invite only) | Users are unable to join invite-only channels unless they are invited or match a +I entry. |
|
||||
| k (password) | To enter the channel, you must specify the password on your /join command. Keep in mind that modes locked with ChanServ's MLOCK command can be seen by anyone recreating the channel; this includes keys. Also keep in mind that users being on the channel when +k is set will see the key as well. |
|
||||
| n (prevent external send) | Users outside the channel may not send messages to it. Keep in mind that bans and quiets will not apply to external users. |
|
||||
| l (join limit) | Takes a positive integer parameter. Limits the number of users who can be in the channel at the same time. |
|
||||
| m (moderated) | Only opped and voiced users can send to the channel. This mode does not prevent users from changing nicks. |
|
||||
| t (ops topic) | Only channel operators may set the channel topic. |
|
||||
|
||||
### 管理员
|
||||
|
||||
频道管理员称为operation channel。通常为:
|
||||
|
||||
1. sop (super operator) 频道的註册者,拥有操作频道所有权限,包括踢人。
|
||||
2. aop (auto operator) 频道註册者信任的共同管理者,拥有部分权限,和第三个的差异在於,离开频道后再进入还是能拥有管理权限。
|
||||
3. op 普通管理者,可能一旦离开频道就失去op。
|
||||
|
||||
## weechat
|
||||
|
||||
weechat是一款基于命令行的客户端。
|
||||
|
||||
### 添加服务器
|
||||
|
||||
```
|
||||
/server add freenode chat.freenode.net
|
||||
```
|
||||
|
||||
### 服务器选项
|
||||
|
||||
weechat的一些选项加油默认值,昵称默认为终端用户名。
|
||||
|
||||
```
|
||||
/set irc.server.freenode.nicks "mynick,mynick2,mynick3,mynick4,mynick5"
|
||||
```
|
||||
|
||||
设置用户和真实姓名:
|
||||
|
||||
```
|
||||
/set irc.server.freenode.username "My user name"
|
||||
/set irc.server.freenode.realname "My real name"
|
||||
```
|
||||
|
||||
在启动时启用自动连接到服务器:
|
||||
|
||||
```
|
||||
/set irc.server.freenode.autoconnect on
|
||||
```
|
||||
|
||||
使用SSL连接:
|
||||
|
||||
```
|
||||
/set irc.server.freenode.addresses "chat.freenode.net/7000"
|
||||
/set irc.server.freenode.ssl on
|
||||
```
|
||||
|
||||
SASL:
|
||||
|
||||
```
|
||||
/set irc.server.freenode.sasl_username "mynick"
|
||||
/set irc.server.freenode.sasl_password "xxxxxxx"
|
||||
```
|
||||
|
||||
nickserv:
|
||||
|
||||
```
|
||||
/set irc.server.freenode.command "/msg nickserv identify xxxxxxx"
|
||||
```
|
||||
|
||||
autojoin:
|
||||
|
||||
```
|
||||
/set irc.server.freenode.autojoin "#channel1,#channel2"
|
||||
```
|
||||
|
||||
### 连接服务器
|
||||
|
||||
```
|
||||
/connect freenode
|
||||
```
|
||||
|
||||
```
|
||||
/disconnect freenode
|
||||
```
|
||||
|
||||
### 窗口/缓冲区管理
|
||||
|
||||
```
|
||||
/buffer
|
||||
/window
|
||||
```
|
||||
|
||||
例如,将屏幕垂直分割为一个小窗口(1/3宽度)和一个大窗口(2/3),使用命令:
|
||||
|
||||
```
|
||||
/window splitv 33
|
||||
```
|
||||
|
||||
删除分割:
|
||||
|
||||
```
|
||||
/window merge
|
||||
```
|
||||
|
||||
## 一些常用的命令
|
||||
|
||||
| 示例命令 | 备注 |
|
||||
|:---------------------------------------------|:---------------------------------------------|
|
||||
| `/server irc.freenode.net` | 连接到 `freenode` 网络 |
|
||||
| `/nick myName` | 更换昵称为 myName |
|
||||
| `/msg nickserv register password me@163.com` | 注册昵称, 密码为 password, 邮箱为 me@163.com |
|
||||
| `/join #java` | 进入 #java 聊天室 |
|
||||
| `/exit` | 退出账户 |
|
||||
| `/nick zhijia` | 登陆或切换用户名 |
|
||||
| `/msg NickServ identify <password>` | 切换用户后登陆验证用户身份 |
|
||||
| `/help` | 帮助 |
|
||||
| `/quit` | 退出服务器 |
|
||||
| `/whois 昵称` | 查看某人的资料 |
|
||||
| `/part` | 离开频道 |
|
||||
| `/query 昵称` | 和某人开小窗口私聊 |
|
||||
| `/away 原因` | 离开 |
|
||||
| `/away` | 取消离开。当您不写原因时,就会取消离开状态 |
|
||||
|
||||
## 坑中
|
||||
|
||||
在今天日异月新的IM中,IRC肯定是较小众。虽然几乎就是上个世纪流行的沟通方式了,不过在今天回味一下也很棒,如果有人在一起聊天的话就更棒了。
|
||||
|
||||
freenode有个web版,入门体验很好。[webchat](https://webchat.freenode.net/)
|
||||
|
||||
另外,#xfy on freenode.
|
147
source/_posts/公开密钥密码学.md
Normal file
@ -0,0 +1,147 @@
|
||||
---
|
||||
title: 公开密钥密码学🔑
|
||||
date: 2019-06-05 18:51:32
|
||||
tags: Linux
|
||||
categories: 实践
|
||||
url: public-key-cryptgraphy
|
||||
index_img: /images/公开密钥密码学/logo.webp
|
||||
---
|
||||
|
||||
GPG/PGP 赛高!
|
||||
|
||||
## 什么是非对称加密?
|
||||
|
||||
人类的历史上加密走了很长的一段路程。想尽了各种办法来保护自己那不想让不该知道的人知道的东西。 加密这东西,在密码学中最直白的解释就是将一般的明文信息改变为难以读取的内容,使其不可读的过程只有拥有解密方法的对象,经由解密过程,才能将密文还原为正常可读的内容。
|
||||
|
||||
大概在 1970 年代中期,所谓的“强加密”的使用开始从政府保密机构延申至公共的领域了,也就是说开始到我们大众都开始接触了。当今世界,加密已经是我们的日常生活中常常见到的东西了。
|
||||
|
||||
例如我们常常访问的带有 SSL/TLS 的网站,这也是非对称加密的一种。所谓的对称加密,它也是密码学中的一种。但他与对称加密不同的是,它需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密,另一个则用作解密。使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文;甚至连最初用来加密的密钥也不能用作解密。
|
||||
|
||||
由于加密和解密需要两个不同的密钥,故被称为非对称加密; 不同于加密和解密都使用同一个密钥的对称加密。虽然两个密钥在数学上相关,但如果知道了中一个,并不能凭此计算出另外一个;因此其中一个可以公开,称为公钥,任意向外发布;不公开的密钥为私钥,必须由用户自行严格秘密保管,绝不透过任途径向任何人提供,也不会透露给被信任的要通信的另一方。
|
||||
|
||||
### 加密
|
||||
|
||||
如果任何人使用公钥加密明文,得到的密文可以透过不安全的途径(如网络)发送,只有对应的私钥持有者才可以解密得到明文;其他人即使从网络上窃取到密文及加密公钥,也无法(在数以年计的合理时间内)解密得出明文。
|
||||
|
||||
典型例子是在网络银行或购物网站上,因为客户需要输入敏感消息,浏览器连接时使用网站服务器提供的公钥加密并上传数据,可保证只有信任的网站服务器才能解密得知消息,不必担心敏感个人信息因为在网络上传送而被窃取。
|
||||
|
||||
在现实世界上可作比拟的例子是,一个传统保管箱,开门和关门都是使用同一条钥匙,这是对称加密;而一个公开的邮箱,投递口是任何人都可以寄信进去的,这可视为公钥;而只有信箱主人拥有钥匙可以打开信箱,这就视为私钥。
|
||||
|
||||
常见的公钥加密算法有:RSA、ElGamal、背包算法、Rabin(RSA的特例)、迪菲-赫尔曼密钥交换协议中的公钥加密算法、椭圆曲线加密算法(英语:Elliptic Curve Cryptography, ECC)。使用最广泛的是 RSA 算法(由发明者Rivest、Shmir和Adleman姓氏首字母缩写而来)是著名的公开秘钥加密算法,ElGamal 是另一种常用的非对称加密算法。
|
||||
|
||||
#### 加密过程
|
||||
|
||||
直白的解释:Tom 和 Jerry 想发送一些消息/文件,而不被隔壁的 Spike 知道文件的内容。于是它们机智的采用了非对称加密来保证内容的安全性。
|
||||
|
||||
1. Tom 先生成非对称的两个密钥,分别为公钥 A,私钥 B
|
||||
2. 为了能让 Jerry 发过来的消息被加密了,Tom 先将可公开的公钥 A 发给 Jerry
|
||||
3. 因为公钥 A 是完全可公开的,所以 Spike 知道也没关系
|
||||
4. Jerry 收到 Tom 发的公钥 A,并将自己的文件 X 使用公钥 A 进行加密
|
||||
5. 随后 Jerry 就可以将加密的文件 A(X) 正大光明的发送给 Tom 了
|
||||
6. 此时的 Spike 就算截取到加密过的文件 A(X) 也没有用
|
||||
7. 因为 Tom 收到的加密文件 A(X) 只有它自己的私钥 B 能够解密,于是它收到后可以使用私钥 B 正常解密
|
||||
8. 所以如果 Tom 丢失了它的私钥 B,那么 Tom and Jerry 都无法读取加密的文件 A(X) 了
|
||||
9. (没有私钥就无法解开公钥加密过的信息)
|
||||
10. 相反,Jerry 也可以将自己的公钥发给 Tom,使其加密要发给自己的信息。
|
||||
|
||||
#### 数字签名
|
||||
|
||||
如果某一用户使用他的私钥加密明文,任何人都可以用该用户的公钥解密密文;由于私钥只由该用户自己持有,故可以肯定该文件必定出自于该用户。
|
||||
|
||||
公众可以验证该用户发布的数据或文件是否完整、中途有否曾被篡改,接收者可信赖这条信息确实来自于该用户,该用户亦无法抵赖,这被称作数字签名。 所以我们常常见到提示一定要保护好自己的私钥,因为不仅仅会使得加密失效,还会直接影响签名验证。
|
||||
|
||||
## 非对称加密的软件
|
||||
|
||||
对于软件来说,我们可能经常听说到 GPG 这一词。GPG 的全称是 GNU Privacy Guard(GnuPG 或 GPG)。它是一款非对称加密的软件,是 PGP 加密软件的满足 GPL 的替代物。 也就是说它相对于 PGP 加密来说,它是一款开源软件。
|
||||
|
||||
因为 PGP 的非对称的算法是开源的,所以 GPG 和 PGP 原理是完全一样的。通常我们会见到 GPG/PGP。 所以 PGP 就可以简单了解到它是一款非开源的非对称加密软件了。 PGP(英语:Pretty Good Privacy,中文翻译“优良保密协议”)是一套用于讯息加密、验证的应用程序,采用 IDEA 的散列算法作为加密和验证之用。
|
||||
|
||||
### 多平台的安装与使用
|
||||
|
||||
既然上述已经介绍了它是自由软件,那么它跨平台的几率就很大了,支持的平台也非常的多。在官方网站里,我们可以看到它支持很多平台。
|
||||
|
||||

|
||||
|
||||
#### Windows GPG4win
|
||||
|
||||
安装就不再多说,GPG4win 的官网有打包好的 exe 可执行程序,我们直接下载双击安装就好,安装过程也非常的简单,不需要进行任何配置。也就是常说的“无脑 next☀”。
|
||||
|
||||
[Download](<https://www.gpg4win.org/get-gpg4win.html>)
|
||||
|
||||
GPG4win 是 GPG 在 Windows 平台的一款可视化的非对称加密软件。对于可视化的软件来说,使用也非常的简单明了。 几乎常用的一些功能都非常直白的写在了开打的页面中。基本上只要使用者了解大概的非对称加密的运作原理,就可以很轻松的使用该软件了。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### Ubuntu & CentOS
|
||||
|
||||
目前最新的 Ubuntu 与 CentOS 的发行版中都带有 GnuPrivacyGuard。也就是 GPG 的一种,所以使用的方法也是大同小异了。 以 Ubuntu 为例: * 创建密钥
|
||||
|
||||
```
|
||||
gpg --gen-key
|
||||
```
|
||||
|
||||
不知道为啥我的机器在生成密钥的时候会卡住很长时间,导致我没有生成出来。等以后再考虑填这个坑吧。
|
||||
|
||||
- 查看秘钥
|
||||
|
||||
```
|
||||
查看公钥:gpg --list-key
|
||||
查看私钥:gpg --list-secret-keys
|
||||
```
|
||||
|
||||
- 提取秘钥
|
||||
|
||||
```
|
||||
提取公钥:gpg -a --export newkey > newkey.asc
|
||||
提取私钥:gpg -a --export-secret-keys newkey > newkey_pirv.asc
|
||||
```
|
||||
|
||||
- 导入秘钥
|
||||
|
||||
```
|
||||
导入公钥或私钥:gpg --import newkey
|
||||
```
|
||||
|
||||
- 使用公钥加密
|
||||
|
||||
```
|
||||
gpg -ea -r newkey filename
|
||||
```
|
||||
|
||||
- 解密并导出为文件
|
||||
|
||||
```
|
||||
gpg -d test.asc > test
|
||||
```
|
||||
|
||||
- 设置密钥信任程度
|
||||
|
||||
```
|
||||
gpg --edit-key [导入的密钥ID]
|
||||
trust
|
||||
您是否相信这位用户有能力验证其他用户密钥的有效性(查对身份证、通过不同的渠道检查
|
||||
指纹等)?
|
||||
1 = 我不知道或我不作答
|
||||
2 = 我不相信
|
||||
3 = 我勉强相信
|
||||
4 = 我完全相信
|
||||
5 = 我绝对相信
|
||||
m = 回到主菜单
|
||||
```
|
||||
|
||||
## 我的公钥
|
||||
|
||||
如果有小伙伴想和我扮演 Tom and Jerry 的话,或者想校验我的签名的文件的话。欢迎使用下述公钥
|
||||
|
||||
[我的公钥🔒!](https://1drv.ms/u/s!ArC4gW7Dc7wWhd5PD8R_o6Mmhp2LxA?e=Ivpa8X)
|
||||
|
||||
## 参考
|
||||
|
||||
* [传输层安全性协议](https://zh.wikipedia.org/wiki/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E6%80%A7%E5%8D%94%E5%AE%9A)
|
||||
* [对称密钥加密](https://zh.wikipedia.org/wiki/對稱密鑰加密)
|
||||
* [公开密钥加密](https://zh.wikipedia.org/wiki/公开密钥加密)
|
||||
* [GnuPG](https://zh.wikipedia.org/wiki/GnuPG)
|
||||
* [PGP](https://zh.wikipedia.org/wiki/PGP)
|
||||
* [GnuPrivacyGuardHowto](https://help.ubuntu.com/community/GnuPrivacyGuardHowto)
|
369
source/_posts/写作与协作.md
Normal file
@ -0,0 +1,369 @@
|
||||
---
|
||||
title: 写作与协作
|
||||
date: 2020-06-29 12:12:41
|
||||
tags: Tools
|
||||
categories: 实践
|
||||
url: write-and-cooperation
|
||||
index_img: /images/写作与协作/index.webp
|
||||
---
|
||||
|
||||
出于对速度无理的追求,最终还是放弃了使用动态内容。转战静态blog。以前也稍微尝试过hexo,所以决定还是主要为hexo为主了。
|
||||
|
||||
在之前试过的typecho、wordpress之中,越是臃肿复杂的程序,1M的带宽越是不够。再详细的折腾了hexo之后,发现了最佳的解决方案。
|
||||
|
||||
在早期的一些常识之后,我也学会了很多。在刚入坑hexo的时候是盯上了阿里云的ECS+OSS和CDN的。虽然部署还是比较麻烦,但起码已经有了一套比较完善的流程了。
|
||||
|
||||
## 从前的写作流程✍
|
||||
|
||||
Typora + OneDrive + VScode
|
||||
|
||||
以前买过软软的365,于是就用Onedrive来同步写的东西了。Onedrive对windows用起来还好,虽然有时会出些莫名其的问题,但基本上的备份与同步都是正常的。
|
||||
|
||||
但对于这套流程的问题不只是Onedrive它卡,这三个软件的契合度并不是很高。写一段代码要切到VScode,写完了再切回来复制到Typora。Onedrive在后台的实时同步还占用一定的性能。
|
||||
|
||||
相比较而下,我的部署流程就更为复杂了。因为我的Markdown文件都是放在Onedrive里的,而Hxeo因为`node_modules`的原因并不在Onedrive里,我需要写完了之后再将图片和MD放到Hexo的目录。之后再手动执行生成文章的命令。随后再将生成好的文章手动上传到ECS里挂载的OSS目录。
|
||||
|
||||
为什么要通过ECS再传到OSS呢?因为ECS和OSS再同一个区域是不会产生流量费用的。😅
|
||||
|
||||
Hexo --> ECS --> OSS
|
||||
|
||||
## 现在的流程
|
||||
|
||||
VScode + git
|
||||
|
||||
将步骤的简化带来的不只是效率,从之前的两个编辑器来回切换到现在的只需要一个编辑器,在文字多的情况下也能保证一定的性能。此外,整体流程的步骤也更加契合,写完了之后可以直接在VScode里打开Terminal进行push。
|
||||
|
||||
配上CI持续集成,只需要写完push,之后就可以等着全新的文章上线。
|
||||
|
||||
Hexo --> CI --> COS
|
||||
|
||||
Hexo的仓库直接push,之后CI持续集成就会按照预设好的步骤来进行部署,除了可以部署到几个仓库的Pages外,还可以直接部署到云存储。
|
||||
|
||||
### VS Code - 不只是代码
|
||||
|
||||
软软的Visual Studio Code是一款很棒的编辑器。很早之前我就用它来尝试写一些东西,但只是用作于编辑器,主要功能就是代码补全和着色。
|
||||
|
||||
在早期的时候我比较喜欢用Typora来写作,它的风格我很喜欢,还能换主题,整体看上去很漂亮。但最近发现了一些比较难以容忍的毛病;就是当它的一篇文章字符超过10K的时候,性能略差一点的电脑就会很卡,打字都不出来的那种。我猜想可能和它是electron写的有关系。
|
||||
|
||||
就在我还没放弃它的时候,我无意中找到了VScode的Markdown插件。反正装插件也不是很麻烦是事情,于是就是尝试了一下。
|
||||
|
||||
没想到一时间我便爱上了它,虽然整体界面没有以前那么整洁、那么清新脱俗。但整体给人给感觉没有非常杂乱,反而看上去倒有点像剪辑软件?
|
||||
|
||||
除了外观从清新脱俗到繁重了一点,余下就只剩方便了。对于我这种才转到hexo的写作半吊子,一直很想找个与hexo契合度高的写作姿势。之前需要在Typora中写完,然后再将文章和单独的图片文件夹复制到hexo的`souce/_post`目录。像我这种半年产出一篇文章的还好,要是天天写,那样会被麻烦死。况且,如果有某一篇文章出了点小差错需要改。那就要同时动两个md文件和两组图片文件夹,对着资源管理翻来覆去的找,极为麻烦!🌚
|
||||
|
||||

|
||||
|
||||
#### 粘贴图片🖼
|
||||
|
||||
之前在使用Typora写作的时候,最为方便的莫非是粘贴图片了。目前Windows上的Typora也支持将粘贴的图片复制到指定的路径或者是云存储。我的图片存放路径都是相对于文章的目录下的`images`目录:
|
||||
|
||||
```bash
|
||||
../images/postName/
|
||||
```
|
||||
|
||||
Typora可以设置将图片复制到指定文件夹,还能创建文件夹。不过好在在VScode里也有插件能够实现同样的操作。使得插图剩下了一大笔麻烦的操作。
|
||||
|
||||
我用的是开源的[Paste Images](https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image)。
|
||||
|
||||
只需要将插件稍微改下设置,将path修改下就能达到想要的效果。
|
||||
|
||||
```bash
|
||||
../images/${currentFileNameWithoutExt}/
|
||||
```
|
||||
|
||||
#### 一些小设置
|
||||
|
||||
**Quick Suggestions**
|
||||
|
||||
Editor > Suggest: Snippets Prevent Quick Suggestions
|
||||
|
||||
在代码段中依然显示建议。
|
||||
|
||||
**自定义Suggestions**
|
||||
|
||||
```js
|
||||
{
|
||||
// Place your snippets for javascript here. Each snippet is defined under a snippet name and has a prefix, body and
|
||||
// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
|
||||
// same ids are connected.
|
||||
// Example:
|
||||
"Print to console": {
|
||||
"prefix": "cl",
|
||||
"body": [
|
||||
"console.log($1);",
|
||||
],
|
||||
"description": "Log output to console"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Coding - 持续构建⏰
|
||||
|
||||
之前最大的问题还是手动部署hexo的繁琐操作,每新增一篇文章都是几个重复的机械性操作。对于我这种半年才写一篇文章的咸鱼来说都感觉到烦了。
|
||||
|
||||
使用CI持续部署的好处就是,可以完全专心与创作,而不用再去管部署之类的问题。只需要第一次写好流程,剩下的就全部交给自动化吧。
|
||||
|
||||

|
||||
|
||||
之前的我从来没有用过Coding,对CI/CD也没有什么了解,从来没考虑过自动化部署这类操作。后来在研究静态化网站时发现了新大陆,完全可以将复杂重复的工作交给机器。并且随着后面文章的增加,渲染markdown文件肯定会越来越慢,于其手动繁琐的操作,不如完全交给CI。
|
||||
|
||||
### Github Action
|
||||
|
||||
在我研究CI姿势的这段时间里,Github也推出了自己的CI(钞能力)。无论是谁家的CI,除了部署步骤不一样,其结果肯定是相同的。Github action也是能达到同样的效果,对于各个厂家的云存储,action也有同样的解决方法,甚至是比coding的jenkins还要灵活一点。
|
||||
|
||||

|
||||
|
||||
## Hexo插件📥
|
||||
|
||||
### 压缩
|
||||
|
||||
我用的是[Hexo-all-minifier](https://github.com/chenzhutian/hexo-all-minifier),可以静态文件以及图片。还可以分别设置压缩等级来权衡质量与大小。
|
||||
|
||||
```bash
|
||||
npm install hexo-all-minifier --save
|
||||
```
|
||||
|
||||
不过它使用到了已经编译好的二进制包gifsicle等,在安装时需要走个脚本编译一下,在网络不好的情况下大概率会安装失败。为此我还特地水过一次:[Can't install gifsicle](https://www.defectink.com/defect/cant-install-gifsicle.html)
|
||||
|
||||
Hexo-all-minifier用到的也是gulp和一些图片压缩的工具,相对于gulp来说,它的配置更简单,更适合像我这种比较懒的咸鱼。
|
||||
|
||||
只需要在站点配置文件添加一段聚合好的配置文件就好了,像这样:
|
||||
|
||||
```yml
|
||||
# minifier
|
||||
all_minifier: ture
|
||||
|
||||
html_minifier:
|
||||
enable: true
|
||||
ignore_error: false
|
||||
silent: false
|
||||
exclude:
|
||||
|
||||
css_minifier:
|
||||
enable: true
|
||||
silent: false
|
||||
exclude:
|
||||
- '*.min.css'
|
||||
|
||||
js_minifier:
|
||||
enable: true
|
||||
mangle: true
|
||||
silent: false
|
||||
output:
|
||||
compress:
|
||||
exclude:
|
||||
- '*.min.js'
|
||||
|
||||
image_minifier:
|
||||
enable: true
|
||||
interlaced: false
|
||||
multipass: false
|
||||
optimizationLevel: 2
|
||||
webpquant: false
|
||||
progressive: false
|
||||
silent: false
|
||||
```
|
||||
|
||||
### Sitemap与RSS
|
||||
|
||||
两个小功能吧,虽然也不会有人来订阅我的小破站🤣。
|
||||
|
||||
```bash
|
||||
npm install hexo-generator-sitemap --save
|
||||
npm install hexo-generator-feed --save
|
||||
```
|
||||
|
||||
和压缩插件一样,都是在站点的`_config.yml`里添上相应的配置文件就好了。
|
||||
|
||||
```yml
|
||||
# feed
|
||||
feed:
|
||||
type:
|
||||
- atom
|
||||
- rss2
|
||||
path:
|
||||
- /xml/atom.xml
|
||||
- /xml/rss.xml
|
||||
limit: 20
|
||||
hub:
|
||||
content:
|
||||
content_limit: 140
|
||||
content_limit_delim: ' '
|
||||
order_by: -date
|
||||
icon: icon.webp
|
||||
autodiscovery: true
|
||||
template:
|
||||
|
||||
# sitemap
|
||||
sitemap:
|
||||
path: /xml/sitemap.xml
|
||||
template: ./source/_data/sitemap_template.xml
|
||||
rel: false
|
||||
tags: true
|
||||
categories: true
|
||||
```
|
||||
|
||||
另外,可以来试一下:
|
||||
|
||||
* [Atom](/xml/atom.xml)
|
||||
* [RSS](/xml/rss.xml)
|
||||
* [Sitemap](/xml/sitemap.xml)
|
||||
|
||||
## Git加速
|
||||
|
||||
使用Linux主机或者在Windows中使用git bash时,修改(新建)在用户目录下的`~/.ssh/config`文件,新加如下内容。
|
||||
|
||||
```bash
|
||||
host github.com
|
||||
HostName github.com
|
||||
PreferredAuthentications publickey
|
||||
IdentityFile ~/.ssh/id_rsa
|
||||
ProxyCommand connect -S 127.0.0.1:1080 %h %p
|
||||
```
|
||||
|
||||
macos的connect可以用brew安装,而windows的git bash中已有:
|
||||
|
||||
```bash
|
||||
brew install connect
|
||||
```
|
||||
|
||||
对于Ubuntu:
|
||||
|
||||
```bash
|
||||
apt-get install connect-proxy
|
||||
```
|
||||
|
||||
某些发行版可能没有connect软件包,这个地址下载源码编译一下就好了。
|
||||
|
||||
```
|
||||
https://bitbucket.org/gotoh/connect/src/default/
|
||||
```
|
||||
|
||||
对于http:
|
||||
|
||||
```bash
|
||||
git config --global http.proxy "socks5://127.0.0.1:1080"
|
||||
git config --global https.proxy "socks5://127.0.0.1:1080"
|
||||
```
|
||||
|
||||
node的淘宝源
|
||||
|
||||
```bash
|
||||
npm config set registry https://registry.npm.taobao.org
|
||||
```
|
||||
|
||||
```bash
|
||||
npm config set registry https://registry.npmjs.org
|
||||
```
|
||||
|
||||
## 早期计划
|
||||
|
||||
### 内容分发网络
|
||||
|
||||
之前就有在使用cdn来加速图片的访问,效果也还是不错的。但是整体blog的内容还都是有1M服务器上的php生成的。所以就算异地图片加载速度再快,终端也需要连接到身在华南的服务器。无论是人多还是人少,速度总是不理想。
|
||||
|
||||
最后打算使用纯静态的blog,直接部署到cdn上,速度肯定是无可比拟的。但是还是有一点弊端的,例如cdn节点可能更新不及时等问题。
|
||||
|
||||
### 解决方案
|
||||
|
||||
正好手头有个1M出口的ECS,嫌它太慢。而它的真正作用在于和阿里云的oss进行通信,因为走的是阿里云的内网,所以通过ECS上传文件到oss是不需要收流量费用的。而ECS仅仅只是出口1M而已,入口是不限速的。如果需要最大化节约的上传文件到oss,可以通过ECS传。
|
||||
|
||||
阿里云的oss在linux上有个可以连接oss的软件,但是那个操作并不是我想需要的。好在阿里云还有一款[ossfs](https://help.aliyun.com/document_detail/32196.html?spm=a2c4g.11186623.6.749.72b73adfsKCqTT)软件。它可以将对象存储OSS的存储空间(Bucket)挂载到本地文件系统中,能够像操作本地文件一样操作OSS的对象(Object),实现数据的共享。
|
||||
|
||||
这对于写静态blog来说实在是太方便了,只需要将Bucket挂载到本地文件夹,就像挂载磁盘那样操作。随后就可以不用流量的将静态文件上传到oss。
|
||||
|
||||
开启OSS的静态网站托管,将hexo生成的静态blog都放到挂载的目录下即可。非常的方便,文章内的图片也可以使用相对路径,而不需要一张一张的插入外链那么麻烦了。
|
||||
|
||||
需要注意一点的是:使用OSS默认域名访问时,Response Header中会自动加上` Content-Disposition:'attachment=filename;'`。即从浏览器访问网页类型文件时,不会显示文件内容,而是以附件形式进行下载。也就是说需要绑定自己的域名才能静态托管。
|
||||
|
||||
### 多重备份
|
||||
|
||||
这种方案解决的好处就是,可以在多个地方实现多重的数据备份。且不需要在备份数据库了。
|
||||
|
||||
* Github一份备份
|
||||
* CDN一份备份
|
||||
* ECS一份备份
|
||||
* 自己的电脑还可以有多份备份
|
||||
|
||||
一步直接实现异地多备份。
|
||||
|
||||
### OSSFS
|
||||
|
||||
目前有多个发行版的安装包:
|
||||
|
||||
| Linux发行版 | 下载 |
|
||||
| :----------------- | :------ |
|
||||
| Ubuntu 18.04 (x64) | [ossfs_1.80.6_ubuntu18.04_amd64.deb](http://gosspublic.alicdn.com/ossfs/ossfs_1.80.6_ubuntu18.04_amd64.deb) |
|
||||
| Ubuntu 16.04 (x64) | [ossfs_1.80.6_ubuntu16.04_amd64.deb](http://gosspublic.alicdn.com/ossfs/ossfs_1.80.6_ubuntu16.04_amd64.deb) |
|
||||
| Ubuntu 14.04 (x64) | [ossfs_1.80.6_ubuntu14.04_amd64.deb](http://gosspublic.alicdn.com/ossfs/ossfs_1.80.6_ubuntu14.04_amd64.deb) |
|
||||
| CentOS 7.0 (x64) | [ossfs_1.80.6_centos7.0_x86_64.rpm](http://gosspublic.alicdn.com/ossfs/ossfs_1.80.6_centos7.0_x86_64.rpm) |
|
||||
| CentOS 6.5 (x64) | [ossfs_1.80.6_centos6.5_x86_64.rpm](http://gosspublic.alicdn.com/ossfs/ossfs_1.80.6_centos6.5_x86_64.rpm) |
|
||||
|
||||
下载到主机内后,根据不同的发行版进行安装就好了。而对于Ubuntu需要使用gdebi:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install gdebi-core
|
||||
sudo gdebi your_ossfs_package
|
||||
```
|
||||
|
||||
#### 配置账号信息
|
||||
|
||||
成功了安装了之后就可以配置oss的账号信息来登陆。使用AccessKeyId/AccessKeySecret来代替账号密码进行访问。如果担心安全问题还可以使用阿里云的子账号只赋予oss的访问权限,来最大程度的保护账户资产。在阿里云的[RAM访问控制](https://ram.console.aliyun.com/overview)中可以进行添加子账户并赋予特定的权限。
|
||||
|
||||

|
||||
|
||||
AccessKeyId/AccessKeySecret信息存放在`/etc/passwd-ossfs`文件中。并且文件的权限必须正确设置,建议设为640。
|
||||
|
||||
格式为:`bucket名:AccessKeyId:AccessKeySecret`
|
||||
|
||||
```bash
|
||||
echo my-bucket:my-access-key-id:my-access-key-secret > /etc/passwd-ossfs
|
||||
chmod 640 /etc/passwd-ossfs
|
||||
```
|
||||
|
||||
如果需要配置多个账号或者多个bucket,可以直接将账号信息写在后面,ossfs会根据挂载的存储空间名称匹配到正确的账号上。
|
||||
|
||||
```bash
|
||||
echo my-second-bucket:my-access-key-id:my-access-key-secret >> /etc/passwd-ossfs
|
||||
```
|
||||
|
||||
#### 挂载到指定目录
|
||||
|
||||
账号信息填写的非常简单,写到指定文件里就可以了。填完之后就可以将oss挂载到本地的指定目录上。
|
||||
|
||||
格式为:`ossfs bucket名 本地挂载点 -ourl=oss url`
|
||||
|
||||
```
|
||||
ossfs my-bucket my-mount-point -ourl=my-oss-endpoint
|
||||
```
|
||||
|
||||
如果正好使用的是阿里云的ECS机器,可以走oss的内网,在上传文件时就不会产生多余的流量费用。通常oss的内网域名包含internal。例如:
|
||||
|
||||
```
|
||||
oss-cn-beijing-internal.aliyuncs.com
|
||||
```
|
||||
|
||||
只需要将近4条左右的命令就可以将oss成功挂载到本地上,就如同一个文件夹。
|
||||
|
||||
#### 开机自动挂载
|
||||
|
||||
和开机自动挂载分区一样,Ubuntu需要在`/etc/fstab`中进行操作。
|
||||
|
||||
```bash
|
||||
ossfs#bucket_name mount_point fuse _netdev,url=url,allow_other 0 0
|
||||
```
|
||||
|
||||
对应的填入了信息之后,可以使用`mount -a`进行测试。如果没有任何报错,即代表成功。
|
||||
|
||||
### 阿里云子目录
|
||||
|
||||
阿里云oss的默认配置是不会去访问子目录下的首页的,子目录下的`index.html`必须访问全部的静态链接。否则会跳转回主页的`index.html`。
|
||||
|
||||
网上的大多数解决办法就是修改hexo的配置,把所有的子目录的绝对路径都生成出来。这是一种解决办法,但不能从根源上解决所有问题。
|
||||
|
||||
并且阿里云也早就支持了子目录首页了。只需要简单开一下就能解决这个问题。
|
||||
|
||||

|
||||
|
319
source/_posts/想起来当年还折腾过hexo.md
Normal file
@ -0,0 +1,319 @@
|
||||
---
|
||||
title: 想起来当年还折腾过 hexo
|
||||
date: 2018-06-29 12:12:41
|
||||
tags: [Linux, HTML]
|
||||
categories: 实践
|
||||
url: hexo-again
|
||||
index_img: /images/想起来当年还折腾过hexo/index.webp
|
||||
---
|
||||
|
||||
## Hexo
|
||||
|
||||
曾经的水文:
|
||||
|
||||
[Hexo](/defect/hexo.html)✔
|
||||
[Hexo and Github](/defect/hexo-and-github.html)✔
|
||||
|
||||
谁还不喜欢水呢(小声
|
||||
|
||||
hexo估计了解的人有很多了,在业界也是很知名的一款blog框架。说到blog程序,可能很多人都听说过知名的wordpress、typecho等。
|
||||
|
||||
那么hexo相对于他们的优势有什么呢?
|
||||
|
||||
- 全静态化站点
|
||||
- 可部署于GitHub
|
||||
- 一键部署
|
||||
- 同样有丰富的插件
|
||||
- 原生支持Markdown
|
||||
|
||||
曾经的曾经,那时的我刚开始研究hexo,还不够了解它的工作机制。谁让我以前比较笨。
|
||||
|
||||
以前我以为它是和部署普通的blog程序一样的,只是不需要用到数据库而已。于是我直接将其装在了自己的VPS上,虽然它也支持使用hexo-server来启用网页服务,直接部署在当前机器上。但是在我那个卡的要死的机器上使用ssh+vim来写markdown肯定不好受。
|
||||
|
||||
就算是在本地写完再上传也是比较麻烦的,尤其是后来研究了hexo与GitHub共同工作之后。发现它完全就可以部署在本地计算机上。写起来也更加的方便。
|
||||
这就是这篇文章的作用了。
|
||||
|
||||
## 部署于Windows
|
||||
|
||||
所需:
|
||||
|
||||
- [Gitbash](https://gitforwindows.org/)(git for windows)
|
||||
- [Node.js](https://nodejs.org/en/)
|
||||
- Github仓库
|
||||
|
||||
> Windows 用户
|
||||
> 对于windows用户来说,建议使用安装程序进行安装。安装时,请勾选Add to PATH选项。
|
||||
> 另外,您也可以使用Git Bash,这是git for windows自带的一组程序,提供了Linux风格的shell,在该环境下,您可以直接用上面提到的命令来安装Node.js。打开它的方法很简单,在任意位置单击右键,选择“Git Bash Here”即可。由于Hexo的很多操作都涉及到命令行,您可以考虑始终使用Git Bash来进行操作。
|
||||
|
||||
Gitbash与Node.js均有在Windows下的独立安装程序,就和安装其他软件一般,非常简单。不再赘述。当然也可以使用gitbash来安装node.js,都是同样的简单。
|
||||
|
||||
当git与node.js全部安装完成后,我们就可以使用一条命令直接安装hexo了。
|
||||
|
||||
```npm
|
||||
$ npm install -g hexo-cli
|
||||
```
|
||||
|
||||
## 建站🏘
|
||||
|
||||
当hexo以及其他所需要的环境都安装在我们的Windows上后,就可以开始配合GitHub来搭建一个托放在GitHub上的静态化blog了。
|
||||
|
||||
```bash
|
||||
$ hexo init <folder>
|
||||
$ cd <folder>
|
||||
$ npm install
|
||||
```
|
||||
|
||||
仅需三条命令,就可以部署一个文件夹为我们的站点跟目录了。当然这个文件夹需要是空的,必须要是新建的一个全空的文件夹,才能正常执行 hexo init 。
|
||||
正常安装完成后,可以在目录下看到如下的文件树了。
|
||||
|
||||
```bash
|
||||
.
|
||||
├── _config.yml
|
||||
├── package.json
|
||||
├── scaffolds
|
||||
├── source
|
||||
| ├── _drafts
|
||||
| └── _posts
|
||||
└── themes
|
||||
```
|
||||
|
||||
日后我们新建的文章都会存放在source/_posts中,以便于hexo的渲染。
|
||||
|
||||
### _config.yml
|
||||
|
||||
该配置文件用于修改一些站点的配置。可以修改大多数站点的参数。例如:站点标题,时区等。
|
||||
|
||||
```
|
||||
# Site
|
||||
title: Defectink
|
||||
subtitle: Another Defectink?
|
||||
description: Just Blog
|
||||
keywords:
|
||||
author: DefectingCat
|
||||
```
|
||||
|
||||
更多:[配置](https://hexo.io/zh-cn/docs/configuration)
|
||||
|
||||
### 部署至GitHub🛰
|
||||
|
||||
部署至GitHub是非常简单且方便的一个操作了。相对于自己建设于VPS上的站点来说,优势于:
|
||||
|
||||
- 免费
|
||||
- 利用多个分支实现备份
|
||||
- 版本控制
|
||||
|
||||
安装用于部署至GitHub的工具 [hexo-deployer-git](https://github.com/hexojs/hexo-deployer-git)
|
||||
|
||||
```bash
|
||||
$ npm install hexo-deployer-git --save
|
||||
```
|
||||
|
||||
修改_config.yml中的deploy配置。
|
||||
|
||||
```
|
||||
deploy:
|
||||
type: git
|
||||
repo: <repository url> #https://bitbucket.org/JohnSmith/johnsmith.bitbucket.io
|
||||
branch: [branch] #published
|
||||
message: [message]
|
||||
```
|
||||
|
||||
如果要利用多个分支实现,一个分支用于存放hexo的文件,一个分支用于部署hexo生成的网页。那么就需要修改branch中的分支了。hexo会根据配置文件中的分支来创建并提交到分支中。
|
||||
|
||||
> 这一切是如何发生的?
|
||||
> 当初次新建一个库的时候,库将自动包含一个master分支。请在这个分支下进行写作和各种配置来完善您的网页。当执行hexo deploy时,Hexo会创建或更新另外一个用于部署的分支,这个分支就是_config.yml配置文件中指定的分支。Hexo会将生成的站点文件推送至该分支下,并且完全覆盖该分支下的已有内容。因此,部署分支应当不同于写作分支。(一个推荐的方式是把master作为写作分支,另外使用public分支作为部署分支。)值得注意的是,hexo deploy并不会对本地或远程的写作分支进行任何操作,因此依旧需要手动推送写作分支的所有改动以实现版本控制。此外,如果您的Github Pages需要使用CNAME文件自定义域名,请将CNAME文件置于写作分支的source_dir目录下,只有这样hexo deploy才能将CNAME文件一并推送至部署分支。
|
||||
|
||||
首先需要满足:
|
||||
|
||||
- 仓库名(用户名.github.io)✔
|
||||
- 用于存放网页的必须是master分支✔
|
||||
|
||||
如果不满足呢?当然也可以。
|
||||
|
||||
但是当你使用其他仓库名来创建网页(GitHub Pages),也可以使用“用户名.github.io”这个域名。但是会在域名后面添加一个仓库名。例如:“defectingcat.github.io/test”。 就好像是子目录一样。且不知道为什么分支只能使用gh-pages。
|
||||
|
||||
当所有条件都准备好了,配置文件也准备好了。那么现在就可以部署了。
|
||||
|
||||
```bash
|
||||
hexo clean && hexo deploy
|
||||
```
|
||||
|
||||
前者清除站点文件,后者重新生成站点文件并将之推送到指定的库分支。
|
||||
每次都需要这么长的命令吗?不,通过markdown写完文章后。直接使用
|
||||
|
||||
```
|
||||
hexo g -d
|
||||
```
|
||||
|
||||
来部署至GitHub。
|
||||
|
||||
> INFO Deploy done: git
|
||||
|
||||
看到这条消息,就说明我们已经向GitHub部署成功了。
|
||||
此时访问GitHub的域名就可以打开刚刚部署好的hexo了。
|
||||
|
||||
### 恢复
|
||||
|
||||
部署至GitHub最大的好处就在于这里了,那就是恢复。刚刚上述有说过,我们利用两个分支,将生成的静态站点放在master分支,再额外创建一个分支用于存放hexo的核心文件。并使用git同步。
|
||||
|
||||
这样,当我们的本地的hexo的核心文件遭受损坏,或者误删的时候,我们就可以使用git。很轻松的获取一份曾经的备份。
|
||||
例:
|
||||
|
||||
```
|
||||
git clone https://github.com/DefectingCat/DefectingCat.github.io/tree/file
|
||||
```
|
||||
|
||||
另外,hexo默认是没有后台的面板的。毕竟是纯静态化的站点。貌似使用某些插件可以实现拥有后台面板。没有后台就意味着我们不在自己的电脑环境下更新自己的hexo文章就比较麻烦了。或者说更换电脑、操作系统等。我们的环境都会被更改。
|
||||
|
||||
此时,亦可以使用git恢复备份的文件。并再通过上述几个简单的步骤安装hexo。值得注意的是,使用以前的命令若安装不成功,可以试试:
|
||||
|
||||
```
|
||||
npm install hexo --save
|
||||
```
|
||||
|
||||
## 主题⛺
|
||||
|
||||
无论是什么程序,那可能离不开主题。hexo也有很多很棒的主题,其中大部分都是开源主题。用起来也是很方便。
|
||||
大红大紫的NexT应该是很多人都有了解了。找了半天也没有找到啥合适心意的主题。就决定试试这款主题了。
|
||||
主题官方网站也有很完善的安装文档?[使用文档](http://theme-next.iissnan.com/getting-started.html)
|
||||
|
||||
### 安装与启用
|
||||
|
||||
开源主题,直接clone。
|
||||
|
||||
```
|
||||
$ cd your-hexo-site
|
||||
$ git clone https://github.com/iissnan/hexo-theme-next themes/next
|
||||
```
|
||||
|
||||
亦或者使用其他方法:
|
||||
|
||||
```
|
||||
curl -s https://api.github.com/repos/iissnan/hexo-theme-next/releases/latest | grep tarball_url | cut -d '"' -f 4 | wget -i - -O- | tar -zx -C themes/next --strip-components=1
|
||||
```
|
||||
|
||||
ok,无论是什么方法下载下来。都会在同一个文件夹`themes/next`文件夹下。若不在,还是要主动移动到指定的文件较内。
|
||||
|
||||
安装完成后,可以通过修改配置文件来进行启用了。hexo很多的操作都是通过修改配置文件来实现的。虽然时修改配置文件,但是配置文件都是很人性化的,修改起来也非常的简单。并且NexT这款主题也有很完善的配置文档。
|
||||
|
||||
> 在 Hexo 中有两份主要的配置文件,其名称都是 _config.yml。 其中,一份位于站点根目录下,主要包含 Hexo 本身的配置;另一份位于主题目录下,这份配置由主题作者提供,主要用于配置主题相关的选项。
|
||||
|
||||
与其他所有hexo主题一样,启用方法都是:在站点配置文件中找到`theme`字段,如下修改:
|
||||
|
||||
```
|
||||
theme: next
|
||||
```
|
||||
|
||||
**完成只后推荐使用hexo clean来清除下缓存**
|
||||
|
||||
### 菜单
|
||||
|
||||
> 菜单配置包括三个部分,第一是菜单项(名称和链接),第二是菜单项的显示文本,第三是菜单项对应的图标。 NexT 使用的是 Font Awesome 提供的图标, Font Awesome 提供了 600+ 的图标,可以满足绝大的多数的场景,同时无须担心在 Retina 屏幕下 图标模糊的问题。
|
||||
|
||||
也就相当于我们常见的独立页面了。
|
||||
编辑主题配置文件,修改如下:
|
||||
|
||||
设定菜单内容,对应的字段是 menu。 菜单内容的设置格式是:item name: link。其中 item name 是一个名称,这个名称并不直接显示在页面上,她将用于匹配图标以及翻译。
|
||||
|
||||
```
|
||||
menu:
|
||||
home: / || home
|
||||
about: /about/ || user
|
||||
tags: /tags/ || tags
|
||||
categories: /categories/ || th
|
||||
archives: /archives/ || archive
|
||||
#schedule: /schedule/ || calendar
|
||||
sitemap: /sitemap.xml || sitemap
|
||||
commonweal: /404/ || heartbeat
|
||||
```
|
||||
|
||||
这里与官方配置文档写的不同的是,随着新版的更新,配置文件也更加的方便了。上述提到的 Font Awesome图标就是在menu配置后直接写的。例如:
|
||||
|
||||
```
|
||||
home: / || home
|
||||
```
|
||||
|
||||
这个`|| home`就是Font Awesome的图标名啦。
|
||||
而启用这个图标也非常的简单,就在上述配置的下方就有一个开关。
|
||||
|
||||
```
|
||||
# Enable/Disable menu icons.
|
||||
menu_icons:
|
||||
enable: true
|
||||
```
|
||||
|
||||
**除了主页与归档,其他页面都需要手动创建**
|
||||
|
||||
#### 创建独立页面
|
||||
|
||||
开启菜单的话,就需要创建一些独立页面来使用了。创建独立页面使用的就是hexo所说的“模板”了。官方文档[模板](https://hexo.io/zh-cn/docs/templates)
|
||||
|
||||
创建一个独立页面和创建一个新的文章的方式是一样的简单,但是使用到对应的模板创建成功后才能算是一个独立页面。
|
||||
|
||||
```
|
||||
hexo new page about
|
||||
```
|
||||
|
||||
使用这样的命令与创建文章的页面有什么不同呢?它也是生产一个新的`index.html`。但它会在`source`文件下创建对应的文件夹。例如:
|
||||
|
||||
```
|
||||
$ ls source/
|
||||
_posts/ about/
|
||||
```
|
||||
|
||||
创建about页面后就会有个about文件夹。其他页面同理。
|
||||
|
||||
### 菜单页面
|
||||
|
||||
上述我们启用了主题的菜单选项,但是菜单对应的都是一个独立页面。也就是类似于about所建立的独立页面。创建方法一样。不同的是根据主题的配置。
|
||||
|
||||
所有的菜单的页面配置都类似,在新建好的独立页面中配置类型“type”。例如tags:
|
||||
|
||||
```
|
||||
title: 标签
|
||||
date: 2014-12-22 12:39:04
|
||||
type: "tags"
|
||||
---
|
||||
```
|
||||
|
||||
或者是categories:
|
||||
|
||||
```
|
||||
title: 分类
|
||||
date: 2014-12-22 12:39:04
|
||||
type: "categories"
|
||||
---
|
||||
```
|
||||
|
||||
配和上述开启所需要的菜单后,我们就能在菜单栏中打开并访问对应的页面了。虽然是修改配置文件,但是也是非常的简单呢。?
|
||||
|
||||
若要禁止使用评论功能:
|
||||
|
||||
```
|
||||
comments: false
|
||||
```
|
||||
|
||||
## CNAME
|
||||
|
||||
虽然GitHub送给我们了一个二级域名,但那个二级域名是需要配置自己的GitHub用户名使用的,往往我们的用户名可能就很长。本来就是二级域名了,再加上很长的域名,可能有时候自己都懒得输。
|
||||
|
||||
所以最佳、最方便的解决办法就是添加一个自己域名的cname解析到GitHub白送我们的域名上。
|
||||
除了解析,hexo也要做相应的配置。也是非常的简单呢。
|
||||
|
||||
在网页的根目录下的`source/`文件下新建一个名为`CNAME`的空文件,在文件内写入我们cname过来的域名。
|
||||
|
||||
```
|
||||
$ cat source/CNAME
|
||||
defect.ink
|
||||
```
|
||||
|
||||
**只需要写上域名就可以了,不需要戴上http等。**
|
||||
|
||||
## 预览
|
||||
|
||||
记录的虽然不是太多,也可能不是那么详细。但是还是大致的顺着搭建成功这么一个放向来的的。
|
||||
所以就留下一个截图的纪念吧?
|
||||
|
||||

|
||||
|
||||
~~图丢了~~
|
229
source/_posts/构造函数与绑定this.md
Normal file
@ -0,0 +1,229 @@
|
||||
---
|
||||
title: 构造函数与绑定this
|
||||
date: 2020-12-31 09:04:40
|
||||
tags: JavaScript
|
||||
categories: 实践
|
||||
url: constructor-and-bind-this
|
||||
index_img: /images/构造函数与绑定this/2020-12-31-10-11-54.webp
|
||||
---
|
||||
|
||||
## 丢失this
|
||||
|
||||
this 是整个 JavaScript 语言里最令人头疼的特性。在 JavaScript 中,this 是动态的,也就是说它在运行时是变化的。也正因这一特性,this 的变化难以预料,不经意间就会发生令人意外的结果。
|
||||
|
||||
先来看一个最基本的例子。我们有一个很智能的 test 函数,它有一个 age 属性和一个 sayAge 方法:
|
||||
|
||||
```js
|
||||
let test = {
|
||||
age: 18,
|
||||
sayAge: function () {
|
||||
console.log(`hi, ${this.age}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在正常情况下,sayAge 方法里的 this 应该正确指向这个 test 对象:
|
||||
|
||||
```js
|
||||
test.sayAge()
|
||||
// 18
|
||||
```
|
||||
|
||||
但是在某些不正常的情况下,例如`setTimeout()`方法,由于它的特殊性,会导致 this 的指向不正确。
|
||||
|
||||
```js
|
||||
setTimeout(test.sayAge, 0);
|
||||
// hi, undefined
|
||||
```
|
||||
|
||||
这时候的 this 就会丢失对 test 对象的链接。这是因为`setTimeout()`调用的代码运行在与所在函数完全分离的执行环境上,它只获取到了函数体,而函数和 test 对象分开了。这就会导致 this 指向了全局对象。
|
||||
|
||||
### 如何解决
|
||||
|
||||
1. 使用一个包装函数;
|
||||
2. 使用`bind()`方法。
|
||||
|
||||
其他也有很多情况会导致 this 的丢失,包括`bind()`方法都不是本篇主要研究的。我们主要来看下使用函数包装的方法。
|
||||
|
||||
使用一个函数包装下的方法非常简单:使用一个匿名的函数作为`setTimeout()`异步函数,在匿名函数内就将对象的方法执行。这样,无论`setTimeout()`的异步函数是在什么环境下执行的都能获取到正确的值了,因为它已经执行过了。
|
||||
|
||||
```js
|
||||
setTimeout(() => { test.sayAge() }, 0);
|
||||
// hi, 18
|
||||
```
|
||||
|
||||
不过这种方法有个缺点,那就是在`setTimeout()`延迟期间,如果 test 对象的属性值有变动,那么`setTimeout()`就不能输出最新的值。所以`bind()`方法是更好的解决方案。
|
||||
|
||||
## 构造函数
|
||||
|
||||
还有一种情况就是构造函数,构造函数也是用来创建对象的,而它也能够直接添加一个带有 this 的方法。创建后的对象实例也有同样的问题。但是构造函数有更加优雅的解决方案。
|
||||
|
||||
### 箭头函数
|
||||
|
||||
往往看到箭头函数想到的就是它非常适合匿名函数,并且没有那么烦人的 this 。
|
||||
|
||||
箭头函数只是没有自己的 this,它会将 this 当作一个普通变量,向**上层作用域去搜索**。也就是说,箭头函数是继承作用域中的 this 的。这就解释了这里的箭头函数为什么能够正确找到 this。
|
||||
|
||||
举个例子,我们都知道当正常情况下闭包的返回会丢失对象的 this:
|
||||
|
||||
```js
|
||||
let test = {
|
||||
age: 18,
|
||||
sayAge: function () {
|
||||
return function () {
|
||||
console.log(this.age);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里的`test.sayAge()()`必然不能正确的找到 this 的指向。而如果我们将 this 当作一个变量,将其的值传递给一个封闭的变量 that,闭包就能正常工作啦:
|
||||
|
||||
```js
|
||||
let test = {
|
||||
age: 18,
|
||||
sayAge: function () {
|
||||
let that = this
|
||||
return function () {
|
||||
console.log(that.age);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样返回`test.sayAge()()`便正常的找到 this 的值。
|
||||
|
||||
而我们的箭头函数就是没有自己的 this,它只会把 this 当作一个变量在作用域中寻找。所以在闭包里用箭头函数也能正确找到 this:
|
||||
|
||||
```js
|
||||
let test = {
|
||||
age: 18,
|
||||
sayAge: function () {
|
||||
return () => {
|
||||
console.log(this.age); // this 当作变量在上层作用域中找到啦
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test.sayAge()() // 18
|
||||
```
|
||||
|
||||
我们的 class 也是同理,`sayAge = ()=>{console.log(this.age);}`是在作用域中找到正确的this指向的。
|
||||
|
||||
同样的,对于`setTimeout`方法,闭包的方式也能解决,因为它和包装一层同理,并且解决了延迟的问题。
|
||||
|
||||
### 构造函数的“优雅”
|
||||
|
||||
首先我们请来一位构造函数:
|
||||
|
||||
```js
|
||||
function Test(age) {
|
||||
this.age = age;
|
||||
this.sayAge = function () {
|
||||
console.log(`hi, ${this.age}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是一个很常见的构造函数,他在内部为每个实例创建了一个方法。这个方法是常规的一个函数,所以基于这个构造函数所创建的实例也会遇到同样的丢失 this 的问题。
|
||||
|
||||
根据上述的箭头函数的示例,只要在作用域链中使用箭头函数,this 就能在作用域链上层被轻松找到,并正确指向。
|
||||
|
||||
```js
|
||||
function Test(age) {
|
||||
this.age = age;
|
||||
this.sayAge = () => {
|
||||
console.log(`hi, ${this.age}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
因为构造函数本身就是闭包的一种体现(实例方法得在构造函数内创建),回顾下构造函数的 [内部原理](https://www.defectink.com/defect/javascript-object-oriented-programming.html#%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86),一个构造函数在创建实例的时候,会在函数内部隐式的声明一个 this 对象,有了 this 这个对象之后,函数的作用域赋给新对象(所以 this 指向了这个对象)。最后再隐式的 return 出这个对象给实例。
|
||||
|
||||
```js
|
||||
function Make() {
|
||||
// this = {
|
||||
// name : 'xfy'
|
||||
// };
|
||||
this.name = 'xfy';
|
||||
// return this;
|
||||
}
|
||||
```
|
||||
|
||||
构造函数内的方法都以闭包的方式 return 到实例上了,所以在**构造函数内部**给实例所创建的方法(箭头函数)根据闭包的原理,在作用域链中继承了 this,所以在实例中使用这个方法时,this 不会那么轻松的丢失。
|
||||
|
||||
但是,构造函数真的足够“优雅”吗?
|
||||
|
||||
当然不够,[构造函数的问题](https://www.defectink.com/defect/javascript-object-oriented-programming.html#%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E7%9A%84%E9%97%AE%E9%A2%98) 就在于不能在构造函数内部为实例创建方法。以这种方式创建函数,会导致不同的作用域链和标识解析符。但创建Function新实例的机制任然是相同的。所以导致由构造函数创建的实例的方法只是同名而不相等。也就是说,它会为每个实例都创建一个同名而不相同的方法:
|
||||
|
||||
```js
|
||||
let one = new Test(18);
|
||||
let two = new Test(18);
|
||||
console.log(one.sayAge === two.sayAge); // false
|
||||
```
|
||||
|
||||
于是在 ECMAScript 2015 之前,我们都是将实例的方法创建在构造函数的 prototype 对象上:
|
||||
|
||||
```js
|
||||
Test.prototype.sayAge = function () {
|
||||
console.log(`hi, ${this.age}`);
|
||||
}
|
||||
```
|
||||
|
||||
但是这样所创建的方法就无法继承构造函数内部的作用域链了。
|
||||
|
||||
### class 的优雅
|
||||
|
||||
`class`关键字为实例创建的方法都是写在`class`的大括号内的,当然这只是个写法。
|
||||
|
||||
```js
|
||||
class Test {
|
||||
constructor(age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
sayAge() {
|
||||
console.log(`hi, ${this.age}`);
|
||||
}
|
||||
}
|
||||
|
||||
let one = new Test(18);
|
||||
let two = new Test(18);
|
||||
console.log(one.sayAge === two.sayAge); // true
|
||||
```
|
||||
|
||||
它为实例所创建的方法都是完全相同的,也就是说这个方法是在我们类的 prototype 这个对象上的,事实也确实如此。
|
||||
|
||||
而当我们在类的内部创建一个箭头函数时,它便不会在 prototype 这个对象上创建方法,反而是和传统构造函数一样。
|
||||
|
||||
```js
|
||||
class Test {
|
||||
constructor(age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
sayAge = () => {
|
||||
console.log(`hi, ${this.age}`);
|
||||
}
|
||||
}
|
||||
|
||||
let one = new Test(18);
|
||||
let two = new Test(18);
|
||||
console.log(one.sayAge === two.sayAge); // false
|
||||
```
|
||||
|
||||
~~某咸鱼起初还以为 class 可以既能在 prototype 对象上创建方法,也能继承作用域链~~
|
||||
|
||||
## 总结
|
||||
|
||||
* 包装函数是一个不完美的解决 this 丢失的方法;
|
||||
* 箭头函数没有自身的 this,它会在作用域链中搜索;
|
||||
* 闭包出一个箭头函数也可以解决`setTimeout`导致的 this 丢失问题;
|
||||
* 构造函数不够优雅;
|
||||
* 在 class 中创建一个箭头函数的方法时,它就不会在 prototype 对象上创建这个方法了;
|
||||
|
||||
所以 class 也不够优雅。
|
||||
|
||||
## 推荐
|
||||
|
||||
* [Class 字段](https://zh.javascript.info/class#class-zi-duan)
|
208
source/_posts/某咸鱼的AJAX入门.md
Normal file
@ -0,0 +1,208 @@
|
||||
---
|
||||
title: 某咸鱼的 AJAX 入门🐟
|
||||
date: 2020-12-24 19:41:02
|
||||
tags: JavaScript
|
||||
categories: 笔记
|
||||
url: ajax-getting-started
|
||||
index_img: /images/某咸鱼的AJAX入门/logo.webp
|
||||
---
|
||||
|
||||
## Ajax
|
||||
|
||||
Ajax 是异步的 JavaScript 和 XML(Asynchronous JavaScript And XML)。简单点说,就是使用`XMLHttpRequest`对象与服务器通信。 它可以使用 JSON,XML,HTML 和 text 文本等格式发送和接收数据。Ajax 最吸引人的就是它的“异步”特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。
|
||||
|
||||
Ajax 最主要的两个特性:
|
||||
|
||||
* 在不重新加载页面的情况下发送请求给服务器。
|
||||
* 接受并使用从服务器发来的数据。
|
||||
|
||||

|
||||
|
||||
## 发送http请求
|
||||
|
||||
`XMLHttpRequest`方法就是发送请求必要的一个方法,通过该方法创建的实例来发送请求。
|
||||
|
||||
```js
|
||||
let httpRequest = new XMLHttpRequest();
|
||||
```
|
||||
|
||||
大部分现代浏览器都实现了`XMLHttpRequest`方法,当然也包括微软。不过早期的IE6或之前的浏览器是通过`ActiveXObject`方法来实现的。为了兼容早期的 IE 浏览器,我们可能需要这要写:
|
||||
|
||||
```js
|
||||
if (window.XMLHttpRequest) { // Mozilla, Safari, IE7+ ...
|
||||
httpRequest = new XMLHttpRequest();
|
||||
} else if (window.ActiveXObject) { // IE 6 and older
|
||||
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
|
||||
}
|
||||
```
|
||||
|
||||
不过随着时间的流逝,IE6 早已被淘汰,所以目前的兼容性无需多虑。
|
||||
|
||||
当发送了一个请求之后,就是得到相应的响应。得到响应后我们需要通知JS如何处理,这时就需要给实例的`onreadystatechange`属性赋值一个方法,当请求状态改变时调用该方法。
|
||||
|
||||
```js
|
||||
httpRequest.onreadystatechange = someFunction;
|
||||
```
|
||||
|
||||
当然也可以使用匿名函数:
|
||||
|
||||
```js
|
||||
httpRequest.onreadystatechange = () => {
|
||||
// balabalabala
|
||||
}
|
||||
```
|
||||
|
||||
当我们能够处理响应的时候,就可以发送一个实际的请求了。通过调用 HTTP 请求对象的`open()`和`send()`方法:
|
||||
|
||||
```js
|
||||
httpRequest.open('GET', 'https://www.defectink.com/balabala.html', true);
|
||||
httpRequest.send();
|
||||
```
|
||||
|
||||
* `open()`的第一个参数是 HTTP 请求方法 - 有 GET,POST,HEAD 以及服务器支持的其他方法。 保证这些方法一定要是大写字母,否则其他一些浏览器(比如 FireFox)可能无法处理这个请求。
|
||||
* 第二个参数是要发送的 URL。由于安全原因,默认不能调用第三方 URL 域名。
|
||||
* 第三个可选参数是用于设置请求是否是异步的。true 为默认值。
|
||||
|
||||
## 处理服务器响应
|
||||
|
||||
`onreadystatechange`被赋值的函数负责处理响应,这个函数首先要检查请求的状态,根据状态来决定后面执行的任务。
|
||||
|
||||
如果状态的值是`XMLHttpRequest.DONE`(对应的值是 4),意味着服务器响应收到了并且是没问题的,然后就可以继续执行。
|
||||
|
||||
```js
|
||||
if (httpRequest.readyState === 4) {
|
||||
// Everything is good, the response was received.
|
||||
} else {
|
||||
// Not ready yet.
|
||||
}
|
||||
```
|
||||
|
||||
全部的`readyState`状态的值为:
|
||||
|
||||
* 0 (未初始化) or (请求还未初始化)
|
||||
* 1 (正在加载) or (已建立服务器链接)
|
||||
* 2 (加载成功) or (请求已接受)
|
||||
* 3 (交互) or (正在处理请求)
|
||||
* 4 (完成) or (请求已完成并且响应已准备好)
|
||||
|
||||
当然接下来再继续检查 HTTP 的`response code`。可以通过响应码 200 来判断Ajax有没有成功。
|
||||
|
||||
```js
|
||||
if (httpRequest.status === 200) {
|
||||
// Perfect!
|
||||
} else {
|
||||
// There was a problem with the request.
|
||||
// For example, the response may have a 404 (Not Found)
|
||||
// or 500 (Internal Server Error) response code.
|
||||
}
|
||||
```
|
||||
|
||||
当检查完请求状态和 HTTP 响应码后, 就可以使用服务器返回的数据了。有两种方法来访问这些数据:
|
||||
|
||||
* `httpRequest.responseText` – 服务器以文本字符的形式返回
|
||||
* `httpRequest.responseXML` – 以 XMLDocument 对象方式返回,之后就可以使用 JavaScript 来处理
|
||||
|
||||
当然这一步只有在发起的请求是异步的时候有效。如果发起的请求时同步请求则不必使用函数,但是并不推荐这样做。
|
||||
|
||||
## 实例
|
||||
|
||||
也算一个总结吧:
|
||||
|
||||
1. 通过`XMLHttpRequest`生成一个实例
|
||||
2. 为实例的`onreadystatechange`方法添加一个处理响应的方法
|
||||
3. 发送请求,`open()`与`send()`
|
||||
4. 在处理响应的方法中判断状态值与HTTP响应码(4 && 200)
|
||||
5. 处理响应`responseText`与`responseXML`
|
||||
|
||||
```js
|
||||
const btn = document.querySelector('.btn');
|
||||
const title = document.querySelector('.title')
|
||||
btn.addEventListener('click', makeRequest);
|
||||
|
||||
let httpRequest;
|
||||
function makeRequest() {
|
||||
httpRequest = new XMLHttpRequest();
|
||||
if (!httpRequest) {
|
||||
throw new Error(':( Cannot create an XMLHTTP instance');
|
||||
console.error(httpRequest);
|
||||
return false;
|
||||
}
|
||||
httpRequest.onreadystatechange = handler;
|
||||
|
||||
httpRequest.open('GET', 'http://localhost/text.txt');
|
||||
httpRequest.send();
|
||||
}
|
||||
|
||||
function handler() {
|
||||
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
|
||||
title.textContent = httpRequest.responseText;
|
||||
} else {
|
||||
console.log('There was a problem with the request.');;
|
||||
console.log(httpRequest.readyState);
|
||||
console.log(httpRequest.status);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## jQuery中的Ajax
|
||||
|
||||
jQuery 极大的简化了原生 JavaScript 的一些繁琐操作,同时它也提供一些 Ajax 方法来简化操作。
|
||||
|
||||
### ajax方法
|
||||
|
||||
jQuery 提供了一个`$.ajax()`方法,方便去操作 Ajax。该方法是 jQuery 底层 Ajax 实现。简单易用的高层实现见`$.get`,`$.post`等。`$.ajax()`返回其创建的 XMLHttpRequest 对象。大多数情况下无需直接操作该函数。
|
||||
|
||||
这个方法接受一个参数,这个参数为键值对集合(对象),其中包含了 Ajax 请求的键值对集合,所有选项都是可选的。也可以通过`$.ajaxSetup()`设置任何选项的默认值。
|
||||
|
||||
```js
|
||||
$.ajax({
|
||||
url: 'test.txt',
|
||||
async: false
|
||||
});
|
||||
```
|
||||
|
||||
### 回调
|
||||
|
||||
和原生 JavaScript 一样,jQuery 也可以通过参数来设定是否同步执行(async)。当异步执行时,可以使用`success`参数来执行一个回调函数。回调函数支持传递一个参数,参数为 response。
|
||||
|
||||
```js
|
||||
$.ajax({
|
||||
url: 'test.txt',
|
||||
success: function (result) {
|
||||
$('.title').html(result);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 其他方法
|
||||
|
||||
jQuery 同样提供了一些其他简易易用的方法,例如`load()`方法,通过 Ajax 请求从服务器加载数据,并把返回的数据放置到指定的元素中。
|
||||
|
||||
按照传统的方法利用 jQuery 来写一个传递文本到元素可能需要这样:
|
||||
|
||||
```js
|
||||
$(document).ready(function () {
|
||||
$('.btn').click(function () {
|
||||
$.ajax({
|
||||
url: 'test.txt',
|
||||
success: function (result) {
|
||||
$('.title').html(result);
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
而`load()`方法更加简洁,往往只可能需要一行:
|
||||
|
||||
```js
|
||||
$(document).ready(function () {
|
||||
$('.btn').click(function () {
|
||||
$('.title').load('test.txt');
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
不过越是简洁的方法越是难以捉摸以及不方便自定义其他的参数。
|
||||
|
||||
无论怎么说,jQuery 提供了更加便利的手段来完成原本繁琐的事情,且仅仅只是多用了 300+kb 的源码。
|
138
source/_posts/水果自定义通知大法.md
Normal file
@ -0,0 +1,138 @@
|
||||
---
|
||||
title: bark-水果自定义通知大法🍎
|
||||
date: 2019-06-28 12:12:41
|
||||
tags: Tools
|
||||
categories: 踩坑
|
||||
url: bark-custom-notification-for-apple
|
||||
index_img: /images/水果自定义通知大法/logo.webp
|
||||
---
|
||||
|
||||
> ding~
|
||||
|
||||
用了一段时间的水果了,发现它的通知来的还是非常及时的。基本上只要连接了网络,通知都不会落下。简单了解过IOS的通知机制:APP→水果服务器→你的机器。也就是说这三个步骤都能够正常通信的情况下,我们的机器就能正常的收到通知。
|
||||
|
||||
在Android平台也有类似的通知机制,也是由服务端来推送通知到我们的机器,从而到达APP可以不挂后台的情况下收到推送。但是为什么国内的UI用不了我就不清楚了。
|
||||
|
||||
<img src="https://support.apple.com/library/content/dam/edam/applecare/images/zh_CN/iOS/ios12-iphone-x-notification-screen-animation.gif" alt="ding" style="zoom:50%;" />
|
||||
|
||||
## 给自己发通知🐾
|
||||
|
||||
上述我们简单了解到,既然是由APP控制的推送通知,那么我们既然想要自定义通知就非常简单了。只需要一个能够由我们控制的APP就可以了。
|
||||
|
||||
App Store中有位大佬开发的[Bark](https://apps.apple.com/cn/app/bark-给你的手机发推送/id1403753865)就是为了这事存在的,它的存在目的就是为了让我们自己给自己推送通知。、
|
||||
|
||||
Github:[Bark](https://github.com/Finb/Bark)
|
||||
|
||||

|
||||
|
||||
默认它提供了自己的服务器,如果我们需要推送一些较为隐私的消息,可以使用自建服务端。它提供了http接口,后端简单调用即可给自己的水果设备发送推送。
|
||||
|
||||
## 服务端
|
||||
|
||||
服务端是一个开源项目,[**bark-server**](https://github.com/Finb/bark-server),这是一个非常简单易用的服务端软件。
|
||||
|
||||
部署非常简单,并且也有docker的部署方式:
|
||||
|
||||
```
|
||||
docker run -dt --name bark -p 8080:8080 -v `pwd`/bark-data:/data finab/bark-server
|
||||
```
|
||||
|
||||
当然和我一样的传统用户喜欢直接部署在主机上的也很方便。相对来说,这种简单易用的服务端的部署和使用docker也差距不大,docker不一定会方便到哪去,说不定还会更加难用。
|
||||
|
||||
我们只需要简单的四步就可以部署完成。
|
||||
|
||||
- 1、Download precompiled binaries from the [releases](https://github.com/Finb/bark-server/releases) page
|
||||
- 2、Add executable permissions to the bark-server binary: `chmod +x bark-server`
|
||||
- 3、Start bark-server: `./bark-server -l 0.0.0.0 -p 8080 -d ./bark-data`
|
||||
- 4、Test the server: `curl localhost:8080/ping`
|
||||
|
||||
对于第三步来说,`-l`定义是监听的地址,`-p`为监听的端口,`./bark-data`默认使用`/data`目录,可以不定义。
|
||||
|
||||
当我们使用测试时,返回这样的结果`{"code":200,"data":{"version":"1.0.0"},"message":"pong"}`就意味着我们的服务端已经运行成功了。
|
||||
|
||||
此时,我们可以在客户端软件中添加我们的服务器地址`http://server-ip:8080`即可,正常通信后,软件界面上的服务器地址就会全部都变成我们自己刚刚搭建的服务器。
|
||||
|
||||
这个时候应该就已经能够正常的使用了。但是既然自己搭建服务端时为了推送一些较为隐私的消息。那么只使用http就显得有点白忙活了。
|
||||
|
||||
### https
|
||||
|
||||
目前还不太清楚作者有没有直接在服务端添加证书的操作,从目前的文档来看,https需要我们使用其他的思路了。
|
||||
|
||||
我目前实现的方法是,既然bark也是基于http接口的,那么我就可以将其只监听`127.0.0.1`,然后使用我的前端apache给它做反代。毕竟apache部署个证书是非常简单的操作。
|
||||
|
||||
这样可以达到apache和bark的交互只在机器内部工作,而对外开放的apache使用证书添加上https。达到传输加密的效果。
|
||||
|
||||
这样是可以实现了完全加密推送消息到我的设备上了,至于水果那段不太清楚,应该也是加密的吧。
|
||||
|
||||
我这里使用的是免费的Let'sEncrypt的证书,他家的不但免费,还有及其方便的脚本直接给apache或其他web服务端配置好证书以及配置文件。并且证书都是保存在相应的目录的,有其他需求时,可以随时使用。
|
||||
|
||||
唯一一个缺点就是一次的证书只有3个月时长,需要不停的续期,好在官方也有自动续期的脚本。不是特别的麻烦。
|
||||
|
||||
```bash
|
||||
~ # ls /etc/letsencrypt/live/api2.defectink.com
|
||||
cert.pem chain.pem fullchain.pem privkey.pem README
|
||||
```
|
||||
|
||||
### 反代
|
||||
|
||||
apache已经有了证书了,接下来直接反代到我们的bark服务端就ok了。
|
||||
|
||||
这是我的配置文件:
|
||||
|
||||
```bash
|
||||
VirtualHost *:80
|
||||
ProtocolsHonorOrder On
|
||||
Protocols h2 h2c http/1.1
|
||||
|
||||
Servername api2.defectink.com
|
||||
ServerAdmin webmaster@localhost
|
||||
|
||||
ProxyRequests Off
|
||||
ProxyMaxForwards 100
|
||||
ProxyPreserveHost On
|
||||
ProxyPass / http://127.0.0.1:8181/
|
||||
ProxyPassReverse / http://127.0.0.1:8181/
|
||||
|
||||
Proxy *
|
||||
Order Deny,Allow
|
||||
Allow from all
|
||||
/Proxy
|
||||
RewriteEngine on
|
||||
RewriteCond %{SERVER_NAME} =api2.defectink.com
|
||||
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
|
||||
/VirtualHost
|
||||
```
|
||||
|
||||
只需要注意反代的地址就ok了,也就是我们bark监听的地址。
|
||||
|
||||
如果一切都没啥问题的话,我们直接打开刚刚配置好证书的apache站点,bark应该就是能够正常运行了。
|
||||
|
||||

|
||||
|
||||
## Systemd
|
||||
|
||||
前不久才水过一篇systemd的水文,简单试了几个服务,发现还是非常方便的。尤其是像bark这样的占用一个终端,用screen又不太方便找的程序。给他使用systemd来控制更是非常的方便。
|
||||
|
||||
把配置文件直接放到`/etc/systemd/system/`目录下就可以了。只需要修改一下`ExecStart`字段的启动路劲就可以正常使用了。
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=bark note for ip
|
||||
Documentation=https://www.defectink.com
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
PIDFile=/run/bark.pid
|
||||
ExecStart=/data/bark-server_linux_amd64 -l 127.0.0.1 -p 8181
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
ExecStop=/bin/kill -s QUIT $MAINPID
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
具体的效果就和平时使用其他的systemd控制的软件一样了,并且不用再那么麻烦了。
|
||||
|
||||

|
670
source/_posts/浏览器的老伙计-BOM.md
Normal file
@ -0,0 +1,670 @@
|
||||
---
|
||||
title: 浏览器的老伙计-BOM
|
||||
date: 2020-12-22 15:03:10
|
||||
tags: JavaScript
|
||||
categories: 笔记
|
||||
url: browsers-old-friend-bom
|
||||
index_img: /images/浏览器的老伙计-BOM/logo.webp
|
||||
---
|
||||
|
||||
ECMAScript才是JavaScript的核心,在浏览器中,BOM无疑才是真正的核心(浏览器对象模型:BrowerObjectModel)。多年来,缺少事实上的规范导致BOM既有意思又有问题,因为浏览器提供商会按照各自的想法去随意拓展它。于是,浏览器之间共有的对象就成为了事实上的标准。
|
||||
|
||||

|
||||
|
||||
## window对象
|
||||
|
||||
BOM的核心对象是window,它表示浏览器的一个实例。在浏览器中,window对象有双重角色,它既是通过JavaScript访问浏览器的一个接口,又是ECMAScript规定的Global对象。
|
||||
|
||||
### 全局作用域
|
||||
|
||||
由于window在浏览器环境中扮演着Global对象,所以在全局作用域中声明的变量、函数都会变成window对象的属性和方法。(拥有块级作用域的声明则不会)
|
||||
|
||||
```js
|
||||
var age = 18;
|
||||
function sayAge() {
|
||||
console.log(this.age);
|
||||
}
|
||||
window.age;
|
||||
sayAge();
|
||||
window.sayAge();
|
||||
```
|
||||
|
||||
虽然使用`var`声明的全局变量会成为window的属性,但是经过声明的变量是无法使用delete操作符删除的,而在window对象上直接定义的属性是可以删除的。
|
||||
|
||||
```js
|
||||
let test = 123;
|
||||
delete test; // false;
|
||||
|
||||
window.xfy = 'xfy';
|
||||
delete window.xfy; // true;
|
||||
```
|
||||
|
||||
通过`var`声明的变量的属性有一个名为'configurable'的特性。这个特性的值被设置为`false`,并且无法更改。
|
||||
|
||||
> IE8以及更早的版本在遇到使用delete删除window属性的时候都会抛出错误。
|
||||
|
||||
尝试未经声明的变量会抛出错误,但是可以通过查询window对象知道由`var`声明的变量是否存在;
|
||||
|
||||
```js
|
||||
let t = test; // test id not defined
|
||||
|
||||
let t = window.test // 不会抛出错误,因为这是一次属性查询
|
||||
```
|
||||
|
||||
### 窗口关系及框架
|
||||
|
||||
如果页面包含框架,则每个框架都有自己的window对象,并且保存在frames集合中。在frames集合中,可以通过数值索引(从0开始,从左至右,从上到下)或者框架名称来访问相应的window对象。每个window对象都有一个name属性,其中包含框架名称。
|
||||
|
||||
```html
|
||||
<frameset rows="160,*">
|
||||
<frame src="frame1.html" name="topFrame">
|
||||
<frameset cols="50%,50%">
|
||||
<frame src="frame2.html" name="leftFrame">
|
||||
<frame src="frame3.html" name="rightFrame">
|
||||
</frameset>
|
||||
</frameset>
|
||||
```
|
||||
|
||||
上述代码的框架集看上去像这样,对于引用框架可以使用如下方法:
|
||||
|
||||
```js
|
||||
window.frames[0] // 引用第一个
|
||||
window.frames["rightFrame"] // 引用指定name的
|
||||
top.frames[0] // 使用top引用
|
||||
top.frames["topFrame"] // 使用top引用
|
||||
```
|
||||
|
||||
<escape>
|
||||
<table style="text-align: left">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
window.frames[0]<br/>
|
||||
window.frames["topFrame"]<br/>
|
||||
top.frames[0]<br/>
|
||||
top.frames["topFrame"]<br/>
|
||||
frames[0]<br/>
|
||||
frames["topFrame"]<br/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
window.frames[1]<br/>
|
||||
window.frames["leftFrame"]<br/>
|
||||
top.frames[1]<br/>
|
||||
top.frames["leftFrame"]<br/>
|
||||
frames[1]<br/>
|
||||
frames["leftFrame"]<br/>
|
||||
</td>
|
||||
<td>
|
||||
window.frames[2]<br/>
|
||||
window.frames["rightFrame"]<br/>
|
||||
top.frames[2]<br/>
|
||||
top.frames["rightFrame"]<br/>
|
||||
frames[2]<br/>
|
||||
frames["rightFrame"]<br/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</escape>
|
||||
|
||||
使用window的属性frames可以方便的去引用指定的框架,不过使用top将更好。top对象始终执行最外层的框架,也就是浏览器窗口。使用它类似于使用绝对路径。而window是相对的,指的都是那个框架的特定实例,而非最高层框架。
|
||||
|
||||
与top相对的是parent。parent始终指向当前框架的直接上层框架。在某些情况下,parent等于top;在没有框架的情况下parent一定等于top(都等于window)。
|
||||
|
||||
```html
|
||||
<frameset rows="100,*">
|
||||
<frame src="frame.htm" name="topFrame">
|
||||
<frameset cols="50%,50%">
|
||||
<frame src="anotherframe.htm" name="leftFrame">
|
||||
<frame src="anotherframeset.htm" name="rightFrame">
|
||||
</frameset>
|
||||
</frameset>
|
||||
```
|
||||
|
||||
在这个例子里,在rightFrame中又嵌入了另外两个frame,分别叫redFrame和blueFrame。
|
||||
|
||||
```html
|
||||
<frameset cols="50%,50%">
|
||||
<frame src="red.htm" name="redFrame">
|
||||
<frame src="blue.htm" name="blueFrame">
|
||||
</frameset>
|
||||
```
|
||||
|
||||
当浏览器加载完第一个框架集以后,会继续将第二个框架集加载到rightFrame中。如果代码位于redFrame或blueFrame中,那么parent就等于rightFrame。如果代码位于topFrame中,那么parent就等于top,因为topFrame的直接上层就是最外层的框架。
|
||||
|
||||
除非最高层窗口用`window.open()`打开,否则其window对象的name属性不会包含值。
|
||||
|
||||
与框架有关的最后一个对象时self,它始终指向window。实际上,self和window对象可以互换使用。引入self对象的目的只是为了与top和parent对象对应起来,因此它不格外包含其他值。
|
||||
|
||||
所有的这些对象都是window的属性,都可以通过`window.parent`、`window.top`等形式来访问。同时也意味着可以将不同层级的window对象连缀起来,例如`window.parent.parent.frames[0]`。
|
||||
|
||||
> 在使用框架的情况下,浏览器中会存在多个Global对象。在每个框架中定义的全局变量会自动成为框架中window对象的属性。由于每个window对象都包含类型的构造函数,因此每个框架都有一套自己的构造函数,这些构造函数一一对应,但并不相等。例如,`top.Object`并不等于`top.frames[0].Object`。这个问题会影响到对跨框架传递的对象使用instanceof操作符。
|
||||
|
||||
### 窗口位置
|
||||
|
||||
用来确定和修改window对象位置的属性和方法有很多。IE、Safari、Opera和Chrome都提供了`screenLeft`和`screenTop`来确定相对于屏幕左边和上边的位置。Firefox则在`screenX`和`screenY`属性中提供相同的窗口位置信息。
|
||||
|
||||
目前Chrome和Firefox均支持这两个属性。不过难免会出现只支持其中一个的情况,可以使用个简单的判断语句来进行判断。
|
||||
|
||||
```js
|
||||
let leftPos = (typeof window.screenLeft == 'number') ? window.screenLeft : window.screenX;
|
||||
|
||||
let topPos = (typeof window.screenTop == 'number') ? window.screenTop : window.screenY;
|
||||
```
|
||||
|
||||
各家浏览器对BOM多数没有统一的标准,最后导致的结果就是确定窗口位置不一,不同的浏览器给出的相对位置都不太一样。
|
||||
|
||||
最总结果,就是无法在跨浏览器的条件下去的窗口左边和上边的精确位置。不过,使用`moveTo()`和`moveBy()`倒是有可能将窗口精确的移动到一个新的位置。
|
||||
|
||||
`moveTo()`接收的是新位置的x和y坐标的值,而`moveBy()`接收的是在水平和垂直方向移动的像素数。
|
||||
|
||||
```js
|
||||
// 移动窗口到屏幕左上角
|
||||
window.moveTo(0, 0);
|
||||
|
||||
// 向下移动100像素
|
||||
window.moveBy(0 ,100);
|
||||
|
||||
window.moveTo(200, 200);
|
||||
window.moveBy(-50, 0);
|
||||
```
|
||||
|
||||
不过这几种方法在目前的浏览器里几乎都是被禁用的。另外这两个方法都不适用于框架,只能对最外层的window对象使用。
|
||||
|
||||
### 窗口大小
|
||||
|
||||
同样的原因,跨浏览器确定窗口大小可不是一件简单的事情。各个主流浏览器均提供了4个属性:innerWidth、innerHeight、outerWidth和outerHeight。不同的浏览器对其返回的值,以及相对的计算位置都不同。所以这里就不详记了。
|
||||
|
||||
Chrome与Firefox在1080p分辨率下输出的inner和outer的值:
|
||||
|
||||

|
||||
|
||||
另外一些浏览器在`document.documentElement.clientWidth`和`document.documentElement.clientHeight`保存了页面视口的信息。在IE6中,这些属性必须是在标准模式下才有效;如果是混杂模式,就必须通过`document.body`中的`clientWidth`和`clientHeight`取得相同的信息。
|
||||
|
||||
> 远离IE有益身体健康。
|
||||
|
||||
虽然最终无法确定浏览器本身的大小,但是可以取得页面视口的大小。
|
||||
|
||||
```js
|
||||
let pageWidth = window.innerWdith;
|
||||
let pageHeight = window.innerHeight;
|
||||
|
||||
if (typeof pageWidth != 'number') {
|
||||
if (document.compatMode == "CSS1Compat"){
|
||||
pageWidth = document.documentElement.clientWidth;
|
||||
pageHeight = document.documentElement.clientHeight;
|
||||
} else {
|
||||
pageWidth = document.body.clientWidth;
|
||||
pageHeight = document.body.clientHeight;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 对于移动设备,以及移动设备的IE浏览器情况则更加的有趣。移动开发咨询师Peter-Paul Koch记录他的研究:[https://quirksmode.org/mobile/viewports2.html](https://quirksmode.org/mobile/viewports2.html)
|
||||
|
||||
另外,使用`resizeTo()`和`resizeBy()`方法可调整浏览器窗口的大小。这两个方法都接收两个参数,其中To接收浏览器窗口的新高度和新宽度,By接收新值与旧值之差。
|
||||
|
||||
```js
|
||||
// 调整到100*100
|
||||
window.resizeTo(100, 100);
|
||||
|
||||
// 调整到200*150
|
||||
window.resizeBy(100, 50);
|
||||
|
||||
// 300*300
|
||||
window.resizeTo(300, 300);
|
||||
```
|
||||
|
||||
现代的浏览器几乎都是禁用这两个方法的,同样,这两个方法也不适用于框架,只能对最外层的window对象使用。
|
||||
|
||||
### 导航和打开窗口
|
||||
|
||||
`window.open()`方法既可以导航到一个特定的URL,也可以打开一个新的浏览器窗口。这个方法可以接收4个参数:要加载的URL、窗口目标、一个特性字符串以及一个表示新页面是否取代浏览器历史记录中当前加载页面的布尔值。
|
||||
|
||||
如果第二个参数为当前已有的窗口或框架的名称,那么就会在具有该名称的窗口或框架打开参数指定的URL。
|
||||
|
||||
```js
|
||||
window.open('https://www.defectink.com', 'topFrame');
|
||||
// 同等于:<a href="https://www.defectink.com" target="topFrame"></a>
|
||||
```
|
||||
|
||||
**弹出窗口**
|
||||
|
||||
`window.open()`会返回一个对新窗口的引用,引用的对象与其他window对象大致相同。
|
||||
|
||||
```js
|
||||
let gg = window.open('https://google.com/', '_blank');
|
||||
// global {window: global, self: global, location: {…}, closed: false, frames: global, …}
|
||||
```
|
||||
|
||||
目前的大部分浏览器都不允许跨标签页的操作,不过`window.close()`方法依然可以用,用来关闭创建的窗口。弹窗关闭后,对其的引用还在,但是除了检测窗口是否已经关闭以外,已经没有其他用处了。
|
||||
|
||||
```js
|
||||
gg.closed;
|
||||
// true
|
||||
```
|
||||
|
||||
新创建的window对象有一个opner属性,其中保存着打开它的原始窗口对象。这个属性旨在弹出窗口中的最外层window对象(top)中有定义,而且指向调用`window.open()`的窗口或框架
|
||||
|
||||
```js
|
||||
gg.opener == window
|
||||
true
|
||||
gg.opener === window
|
||||
true
|
||||
```
|
||||
|
||||
有些浏览器会在单独的进程中运行每个标签页。当一个标签页打开另一个标签页时,如果两个window对象需要互相通信,那么新标签页就不能运行在单独的进程中。在Chrome中,将新创建的标签页的opener属性设置为null,即表示在单独的进程中运行新标签页。
|
||||
|
||||
```js
|
||||
gg.opener = null
|
||||
```
|
||||
|
||||
将opener属性设置为null就是告诉浏览器新创建的标签页不需要与打开它的标签页通信,因此可以在独立的进程中运行。标签页之间的联系一旦切断,将没有办法恢复。
|
||||
|
||||
**安全限制**
|
||||
|
||||
曾经有段时间,广告商在网上使用弹出窗口达到了肆无忌惮的水平。他们经常把弹出窗口打扮成系统对话框的模样,引诱用户去点击其中的广告。
|
||||
|
||||
目前的主流浏览器对`window.open()`打开的窗口都会强制的显示状态栏或者地址栏,也禁止其模仿系统对话框的样子。此外,有的浏览器只根据用户操作来创建窗口。这样一来,在页面尚未加载完成时嗲用`window.open()`的语句根本不会执行。而且还可能将错误消息提示给用户。
|
||||
|
||||
**弹出窗口屏蔽程序**
|
||||
|
||||
现代的大多数浏览器都带有内置的弹窗屏蔽程序,而没有此类程序的浏览器也可以通过安装拓展来实现此功能。于是,在弹窗被屏蔽时,就应该考虑两种情况。如果是浏览器屏蔽的弹窗,那么`window.open()`很可能返回null;如果是拓展程序,那么很可能报错一个错误。利用这个返回值就能确定弹窗是否被屏蔽。
|
||||
|
||||
```js
|
||||
let blocked = false;
|
||||
try {
|
||||
let gg = window.open('https://google.com', '_blank');
|
||||
if (gg == null) {
|
||||
blocked = true;
|
||||
}
|
||||
} catch (ex) {
|
||||
blocked = true;
|
||||
}
|
||||
if (blocked == true) {
|
||||
console.error('The popup was blocked!');
|
||||
}
|
||||
```
|
||||
|
||||
上述利用`try...catch`语句来检测弹窗是被浏览器屏蔽还是被拓展程序屏蔽的。检测弹出窗口支是一方面,它并不会阻止浏览器显示与被屏蔽窗口的有关消息。
|
||||
|
||||
### 间歇调用与超时调用
|
||||
|
||||
JavaScript是单线程的语言,但它允许通过设置超时值和间歇时间值来调度代码在特定的时刻执行。前者在指定时间后执行代码,后者是每隔指定的时间就执行一次代码。
|
||||
|
||||
超时调用所用到的是window对象的`setTimeout()`方法,它接受两个参数:要执行的代码和以毫秒表示的时间(即在代码执行前等待的毫秒数)。其中,第一个参数可以是包含JS代码的字符串(就和在`eval()`函数中使用的字符串一样),也可以是一个函数,不过更加推荐使用函数而不是字符串,传递字符串可能会导致性能损失。
|
||||
|
||||
```js
|
||||
setTimeout(() => {
|
||||
console.log('xfy!');
|
||||
}), 1000);
|
||||
```
|
||||
|
||||
第二个参数是等待的毫秒数,但经过该时间后的指定代码不一定会执行。因为JavaScript是一个单线程的解释器,使用的是类似轮询的工作机制,一定时间内只能工作一段代码。为了控制要执行的代码,就有一个JavaScript任务队列。这些任务会按照将它们添加到队列的顺序执行。`setTimeout()`的第二个参数就是指定时间后添加到任务队列,如果队列是空的,那么添加代码就会立即执行。如果队列不是空的,那么就要等待前面的代码执行完。
|
||||
|
||||
`setTimeout()`会返回一个数值ID,表示超时调用。这个超时调用ID是计划执行代码的唯一标识符,可以通过它来去掉超时调用。可以使用`clearTimeout()`来取消超时调用计划,它的参数就是其唯一标识符ID。
|
||||
|
||||
```js
|
||||
// 设置
|
||||
let timeIdentity = setTimeout(() => {
|
||||
console.log('xfy!');
|
||||
}, 100000);
|
||||
|
||||
// 取消
|
||||
clearTimeout(timeIdentity);
|
||||
```
|
||||
|
||||
> 超时调用的代码都是在全局作用域下执行的,因此函数中的this在非严格模式下指向window,严格模式下是undefined。
|
||||
|
||||
间歇调用与超时调用类似,不过它是按照指定的时间间隔来重复的执行代码,直至间隔调用被取消或者被页面卸载。设置间歇调用的方法是`setInterval()`,它接受的参数与`setTimeout()`相同:要执行的代码(字符串或函数)和每次执行之前间隔的毫秒数。
|
||||
|
||||
```js
|
||||
let xfy = 'xf';
|
||||
setInterval(() => {
|
||||
xfy += 'y';
|
||||
console.log(xfy);
|
||||
}, 1000)
|
||||
|
||||
let xfy = 'fy';
|
||||
setInterval(() => {
|
||||
xfy = 'x' + xfy;
|
||||
console.log(xfy);
|
||||
}, 500)
|
||||
```
|
||||
|
||||
调用`setInterval()`同样也是返回一个间歇调用ID,使用`clearInterval()`方法来取消未执行的间歇调用。不过,间歇调用在不干涉的情况下将会一直执行下去。所以使用`clearInterval()`的重要性远高于`clearTimeout()`。
|
||||
|
||||
```js
|
||||
let xfy = 'fy';
|
||||
let num = 0;
|
||||
let max = 100;
|
||||
let ID;
|
||||
function xxfy() {
|
||||
xfy = 'x' + xfy;
|
||||
num ++;
|
||||
console.log(xfy);
|
||||
if (num == max) {
|
||||
clearInterval(ID);
|
||||
console.log('--- xfy! ----');
|
||||
}
|
||||
}
|
||||
|
||||
ID = setInterval(xxfy, 100);
|
||||
```
|
||||
|
||||
这个例子类似于设置一个有时间间隔的循环。这个模式也可以使用超时调用来实现。
|
||||
|
||||
```js
|
||||
let xfy = 'fy';
|
||||
let num = 0;
|
||||
let max = 5;
|
||||
function xxfy() {
|
||||
xfy = 'x' + xfy;
|
||||
num ++;
|
||||
console.log(xfy);
|
||||
if (num < max) {
|
||||
// 未达次数则再次调用
|
||||
setTimeout(xxfy, 500);
|
||||
} else {
|
||||
console.log('--- xfy! ----');
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(xxfy, 500);
|
||||
```
|
||||
|
||||
在使用超时调用时就没有必要再跟踪ID,因为每次执行完时其就会自动停止。一般情况下,使用超时调用来模拟间歇调用是一种最佳模式。原因是一个间歇调用可能会在前一个间歇调用执行结束前启动。而使用超时调用来模拟间歇调用以达到相同的效果,则可以完全避免这一点。
|
||||
|
||||
## 系统对话框
|
||||
|
||||
系统对话框通过`alert()`、`confirm()`和`prompt()`方法调用。系统对话框和正在浏览器中显示的网页没有关系,也不包含HTML。它的外观是由系统及浏览器决定的,而不是CSS。此外,通过这几个方法打开的系统对话框都是同步和模态的。也就是说,显示这几个对话框的时候代码将会暂停运行,而关掉对话框后将继续运行。
|
||||
|
||||
`alert()`方法用于生成一个带有文本的弹窗,它接受的参数就是要显示的文本字符串。它只能显示消息,点击确定按钮后将消失。
|
||||
|
||||
第二种为`comfirm()`方法,它也是接受一个显示文本的字符串,但是它还有“确认”与“取消”两个按钮。这两个按钮分别返回true和false。可以配合if语句来让用户选择性的执行,这种操作通常在用户想要删除的时候使用,例如删除电子邮件的时候。
|
||||
|
||||
```js
|
||||
if (confirm('Are you sure?')) {
|
||||
alert('Of course, sure!');
|
||||
} else {
|
||||
alert('No!');
|
||||
}
|
||||
```
|
||||
|
||||
第三个是`prompt()`方法,它会出现一个显示文本和输入框。它就收两个参数,分别是用于显示文本的字符串和输入框内的默认字符串(可以为空)。如果点击确定,则返回输入框内的内容,如果取消或者关闭窗口,则返回null。
|
||||
|
||||
```js
|
||||
let h = prompt('Hello!', 'Michael');
|
||||
if (h != null) {
|
||||
alert('Nice to meet you, ' + h);
|
||||
}
|
||||
```
|
||||
|
||||
除此之外,目前的现代浏览器都会在第二个弹窗开始出现一个复选框,以便用户阻止后续的弹窗,除非用户刷新页面。
|
||||
|
||||
还有两个可以通过JavaScript调用的对话框,他们是window对象的`print()`和`find()`方法,分别用于打开打印窗口和查找窗口。
|
||||
|
||||
## location对象
|
||||
|
||||
location是最有的BOM对象之一,它提供了与当前窗口中加载的文档有关的信息,还提供了一些导航功能。location是一个很特别的对象,它既是window对象的属性,也是document对象的属性。也就是说`window.location`和`document.location`引用的是同一个对象。
|
||||
|
||||
### 属性
|
||||
|
||||
`Location `接口不继承任何属性,但是实现了那些来自 [`URLUtils`](https://developer.mozilla.org/zh-CN/docs/Web/API/URLUtils) 的属性。
|
||||
|
||||
- [`Location.href`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/href)
|
||||
|
||||
包含整个URL的一个[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString)
|
||||
|
||||
- [`Location.protocol`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/protocol)
|
||||
|
||||
包含URL对应协议的一个[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString),最后有一个":"。
|
||||
|
||||
- [`Location.host`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/host)
|
||||
|
||||
包含了域名的一个[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString),可能在该串最后带有一个":"并跟上URL的端口号。
|
||||
|
||||
- [`Location.hostname`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/hostname)
|
||||
|
||||
包含URL域名的一个[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString)。
|
||||
|
||||
- [`Location.port`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/port)
|
||||
|
||||
包含端口号的一个[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString)。
|
||||
|
||||
- [`Location.pathname`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/pathname)
|
||||
|
||||
包含URL中路径部分的一个[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString),开头有一个“`/"。`
|
||||
|
||||
- [`Location.search`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/search)
|
||||
|
||||
包含URL参数的一个[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString),开头有一个`“?”`。
|
||||
|
||||
- [`Location.hash`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/hash)
|
||||
|
||||
包含块标识符的[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString),开头有一个`“#”。`
|
||||
|
||||
- [`Location.username`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/username)
|
||||
|
||||
包含URL中域名前的用户名的一个[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString)。
|
||||
|
||||
- [`Location.password`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/password)
|
||||
|
||||
包含URL域名前的密码的一个 [`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString)。
|
||||
|
||||
- [`Location.origin`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/origin) 只读
|
||||
|
||||
包含页面来源的域名的标准形式[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString)。
|
||||
|
||||
### 方法
|
||||
|
||||
`Location`没有继承任何方法,但实现了来自[`URLUtils`](https://developer.mozilla.org/zh-CN/docs/Web/API/URLUtils)的方法。
|
||||
|
||||
- [`Location.assign()`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/assign)
|
||||
|
||||
加载给定URL的内容资源到这个Location对象所关联的对象上。
|
||||
|
||||
- [`Location.reload()`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/reload)
|
||||
|
||||
重新加载来自当前 URL的资源。他有一个特殊的可选参数,类型为 [`Boolean`](https://developer.mozilla.org/zh-CN/docs/Web/API/Boolean),该参数为true时会导致该方法引发的刷新一定会从服务器上加载数据。如果是 `false`或没有制定这个参数,浏览器可能从缓存当中加载页面。
|
||||
|
||||
- [`Location.replace()`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/replace)
|
||||
|
||||
用给定的URL替换掉当前的资源。与 `assign()` 方法不同的是用 `replace()`替换的新页面不会被保存在会话的历史 [`History`](https://developer.mozilla.org/zh-CN/docs/Web/API/History)中,这意味着用户将不能用后退按钮转到该页面。
|
||||
|
||||
- [`Location.toString()`](https://developer.mozilla.org/zh-CN/docs/Web/API/Location/toString)
|
||||
|
||||
返回一个[`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString),包含整个URL。 它和读取[`URLUtils.href`](https://developer.mozilla.org/zh-CN/docs/Web/API/URLUtils/href)的效果相同。但是用它是不能够修改Location的值的。
|
||||
|
||||
### 查询字符串参数
|
||||
|
||||
虽然通过location对象可以查询到大部分信息,但是访问URL包含的查询字符串的属性并没有那么方便。尽管可以通过search属性返回从问号到URL末尾的所有内容,但却没有办法逐个访问每个字符串查询参数。为此,可以创建一个方法来解析查询字符串。
|
||||
|
||||
```js
|
||||
function search() {
|
||||
let ur = document.location.search;
|
||||
// 去除问号
|
||||
let qs = ur.length > 0 ? ur.substring(1) : '';
|
||||
|
||||
let args = {},
|
||||
items,
|
||||
item,
|
||||
name,
|
||||
value;
|
||||
|
||||
// 以&分割为数组
|
||||
items = qs.length ? qs.split('&') : [];
|
||||
let len = items.length;
|
||||
|
||||
for (let i = 0; i < len; i ++) {
|
||||
// 将第一项以=分割
|
||||
item = items[i].split('=');
|
||||
// =前后分别为键和值
|
||||
name = decodeURIComponent(item[0]);
|
||||
value = decodeURIComponent(item[1]);
|
||||
if (name.length) {
|
||||
args[name] = value;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
```
|
||||
|
||||
例如百度的查询字符串长这样:
|
||||
|
||||
```
|
||||
ie=utf-8&wd=javascript
|
||||
```
|
||||
|
||||
这个方法并不是很复杂,第一步就是去除查询字符串开头的问号,在它有值的情况下。接下来根据和(&)号来分割查询字符串。分割之后暂存在一个数组中,之后使用for循环,再将查询字符串以等号分割键和值,将键和值分别保存在两个变量之中。如果二者都有值,那么在将其按对象的方式写入对象中。其结果是这样的:
|
||||
|
||||
```js
|
||||
search();
|
||||
{ie: "utf-8", wd: "javascript"}
|
||||
```
|
||||
|
||||
这样,每个查询字符串参数都成了返回对象的属性。这样就极大的方便了对每个参数的访问。
|
||||
|
||||
### 位置操作
|
||||
|
||||
location对象可以通过很多方式来修改浏览器的位置。最常用的方式是使用`assign()`方法来为其传递一个URL。
|
||||
|
||||
```js
|
||||
location.assign('https://www.baidu.com');
|
||||
```
|
||||
|
||||
这样,就会立即打开新URL并在浏览器历史记录中生成一条记录。如果将window.location和location.href设置为一个url值,也会以该值调用`assign()`。
|
||||
|
||||
```js
|
||||
window.location = 'https://www.baidu.com';
|
||||
location.href = 'https://www.baidu.com';
|
||||
```
|
||||
|
||||
其他属性,除了hash以外都会刷新页面。
|
||||
|
||||
```js
|
||||
location.hash = 'test';
|
||||
location.search = '?q=javascript';
|
||||
location.hostname = 'www.yahoo.com';
|
||||
location.pathname = 'dir';
|
||||
location.port = 8080;
|
||||
```
|
||||
|
||||
上述值修改url之后都会产生浏览器历史记录,并且可以通过“后退”来访问前一个页面。可以通过`replace()`方法来禁用历史记录。
|
||||
|
||||
```js
|
||||
location.replace('https://www.baidu.com/')
|
||||
```
|
||||
|
||||
另外,可以使用`reload()`方法来刷新页面。
|
||||
|
||||
```js
|
||||
location.reload(); // 可能会度缓存
|
||||
location.reload(true); // 强制从服务器下载
|
||||
```
|
||||
|
||||
## navigator对象
|
||||
|
||||
navigator对象现已成为识别客户端浏览器的事实标准。可以使用只读的`windows.navigator`属性来检索navigator对象
|
||||
|
||||
```js
|
||||
window.navigator
|
||||
Navigator {vendorSub: "", productSub: "20030107", vendor: "Google Inc.", maxTouchPoints: 0, userActivation: UserActivation, …}
|
||||
appCodeName: "Mozilla"
|
||||
appName: "Netscape"
|
||||
appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.193 Safari/537.36"
|
||||
bluetooth: Bluetooth {}
|
||||
clipboard: Clipboard {}
|
||||
// ......
|
||||
```
|
||||
|
||||
每个浏览器都有自己的navigator对象的一套属性,不过他们也有一些共同的属性。
|
||||
|
||||
### 检测插件
|
||||
|
||||
navigator对象还有一个plugins属性,通过`navigator.plugins`来访问到浏览器的插件。不过这并不能用来检测Chrome的拓展。
|
||||
|
||||
利用plugins属性,可以这样来做一个间的的检测插件的小方法:
|
||||
|
||||
```js
|
||||
function plus(name) {
|
||||
name = name.toLowerCase();
|
||||
for (let i of navigator.plugins) {
|
||||
let res = i.name.toLowerCase().includes(name);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里使用了es6的`includes`方法,当然也可以使用es5的`indexOf`来实现
|
||||
|
||||
```js
|
||||
function plus(name) {
|
||||
name = name.toLowerCase();
|
||||
for (let i of navigator.plugins) {
|
||||
if (i.name.toLowerCase().indexOf(name) > -1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用字符查找的方法来确定其名称是否有指定字符时会有个小缺陷,例如需要查找`chrome pdf viewer`,在传入一个字母`c`的时候,就能够返回`true`,无法确定具体找到哪个插件。只有继续增加参数的长度才能确定,。
|
||||
|
||||
我这里根据上述函数稍稍修改了下,将匹配到的结果都由一个数组保存,最后返回整个数组,有点类似于搜索结果。
|
||||
|
||||
```js
|
||||
function plus(name) {
|
||||
name = name.toLowerCase();
|
||||
let out = [];
|
||||
for (let i of navigator.plugins) {
|
||||
let res = i.name.toLowerCase().includes(name);
|
||||
if (res) {
|
||||
out.push(i.name);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
```
|
||||
|
||||
这时的数组就会保存所有匹配到的字符:
|
||||
|
||||
```js
|
||||
plus('chrome')
|
||||
(2) ["Chrome PDF Plugin", "Chrome PDF Viewer"]
|
||||
```
|
||||
|
||||
## history对象
|
||||
|
||||
history保存着用户上网浏览的历史记录。history是windows对象的属性,所以每个标签乃至每个框架都有自己的history对象与特定的window对象关联。出于安全考虑,我们没有办法得知用户浏览过的url,但是借由用户访问过的页面,同样可以在不知道实际url的情况下实现前进和后退。
|
||||
|
||||
使用`go()`方法可以实现在历史记录中跳转。
|
||||
|
||||
```js
|
||||
// 后退一页
|
||||
history.go(-1);
|
||||
// 前进一页
|
||||
history.go(1);
|
||||
// 前进两页
|
||||
history.go(2);
|
||||
```
|
||||
|
||||
另外,还有两个简写的方法`back()`和`forward()`来代替`go()`方法。顾名思义,这两个方法可以模仿浏览器的“后退”和“前进”动作。
|
||||
|
||||
history甚至还有一个length属性,用来保存历史记录的数量。
|
||||
|
||||
## 总结
|
||||
|
||||
浏览器对象模型(BOM)以window对象为依托,表示浏览器窗口以及页面可见区域。同时,window对象还是ECMAScript中的Global对象,因而所有全局变量和函数都是它的属性(`let`声明的变量并不作为window的属性),且所有原生的构造函数以及其他函数也都存在于它的命名空间下。
|
||||
|
||||
* 在使用框架时,每个框架都有自己的window对象以及所有原生构造函数以及其他构造函数的副本。每个框架都保存在frames集合中,可以通过位置或通过名称来访问。
|
||||
* 有一些窗口指针,可以用来引用其他框架,包括父框架。
|
||||
* top对象始终指向最外围的框架,也就是整个浏览器窗口。
|
||||
* parent对象标识包含当前框架的框架,而self对象则指回window。
|
||||
* 使用location对象可以通过编程方式来访问浏览器的导航系统。设置相应的属性,可以逐段或整体性地修改浏览器的URL。
|
||||
* 使用replace()方法可以导航到一个新URL,同时该URL会替换浏览器历史记录中当前显示的页面。
|
||||
* navigator对象提供了与浏览器有关的信息。到底提供哪些信息,很大程度上取决于用户的浏览器;不过,也有一些公共的属性(如userAgent)存在于所有浏览器中。
|
||||
|
||||
BOM中还有两个对象:screen和history,但它们的功能有限。screen对象中保存着客户端显示器有关的信息,这些信息一般只用于站点分析。history对象为访问该刘安祺的历史记录开了一个小缝隙,开发人员可以据此判断历史记录的数量,也可以在历史记录中向后或向前导航到任意页面。
|
443
source/_posts/真的是在写JS-JavaScript的类.md
Normal file
@ -0,0 +1,443 @@
|
||||
---
|
||||
title: 真的是在写 JS - JavaScript 的类
|
||||
date: 2020-12-30 11:14:16
|
||||
tags: JavaScript
|
||||
categories: 笔记
|
||||
url: really-writing-js-javascriptclasses
|
||||
index_img: /images/真的是在写JS-JavaScript的类/2020-12-30-11-06-25.webp
|
||||
---
|
||||
在 JavaScript 中所谓的类不过是 ECMAScript 2015 为其引入的语法糖。这个糖它只有甜味,它是构造函数的另一种写法,类语法**不会**为 JavaScript 引入新的面向对象的继承模型。
|
||||
|
||||
在之前学习 [JS面向对象](https://www.defectink.com/defect/javascript-object-oriented-programming.html) 的编程时,详细的研究过了关于 JavaScript 构造函数以及继承的问题。从工厂模式一直发展至今的寄生式继承,也解决了很多语言本有的问题。虽然类只是个语法糖,但是从很多地方来说它也解决了关于构造函数继承等的复杂写法。
|
||||
|
||||
## 📍定义类
|
||||
|
||||
类实际上是一个特殊的函数,将像函数能够定义的函数表达式和函数声明一样,类语法有两个部分组成:类表达式和类声明。
|
||||
|
||||
### 声明
|
||||
|
||||
定义一个类的方法是使用一个类声明,要声明一个类,可以使用带有`class`关键字的类名。一个类看上去像这样:
|
||||
|
||||
```js
|
||||
class Person {
|
||||
constructor (name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
otherFunc() {
|
||||
console.log(name + age);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这和一些常见的语言,例如 C艹 和 Java 较为类似。相比较之下,传统的 JavaScript 构造函数对于其他的面向对象语言的程序员可能不太容易理解,来回顾下传统的构造函数。
|
||||
|
||||
```js
|
||||
function Person(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
Person.prototype.otherFunc() {
|
||||
console.log(name + age);
|
||||
}
|
||||
```
|
||||
|
||||
相较于最传统的构造函数来说,类的声明方式会更让人容易理解。虽然声明看上去只是换了种写法(确实就是换了种写法,包括继承),但是对于继承等操作来说,`class`类的方式省了不少事。
|
||||
|
||||
### 提升
|
||||
|
||||
函数声明和类声明之间的一个重要区别是函数声明会提升,而类声明不会。和新关键字`let`、`const`等一样,类需要先声明再使用。
|
||||
|
||||
```js
|
||||
let xfy = new Person(); // ReferenceError
|
||||
|
||||
class Person {
|
||||
constructor(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
否则就会抛出一个 ReferenceError(引用错误)。
|
||||
|
||||
### 类表达式
|
||||
|
||||
和函数一样,一个类表达式是定义一个类的另一种方式。类表达式可以是具名的或匿名的。
|
||||
|
||||
```js
|
||||
// 匿名类
|
||||
let Person = class {
|
||||
constructor(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(Person.name); // Person
|
||||
|
||||
// 具名类
|
||||
let Person = class Persona {
|
||||
constructor(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(Person.name); // Persona
|
||||
```
|
||||
|
||||
一个具名类表达式的名称是类内的一个局部属性,它可以通过类本身(而不是类实例)的 name 属性来获取。
|
||||
|
||||
> 类表达式也同样受到类声明中提到的类型提升的限制。
|
||||
|
||||
## 类体和方法定义
|
||||
|
||||
一个类的类体是一对花括号/大括号`{}`中的部分。这是定义类成员的位置,如方法或构造函数。
|
||||
|
||||
### 严格模式
|
||||
|
||||
类声明和类表达式的主体都在严格模式下执行。比如,构造函数,静态方法,原型方法,getter 和 setter 都在严格模式下执行。
|
||||
|
||||
### 构造函数
|
||||
|
||||
`constructor`方法是一个特殊的方法,这个方法用于创建和初始化一个由`class`创建的对象实例。一个类只能拥有一个名为`constructor`的特殊方法。如果类包含多个`constructor`的方法,则将抛出一个`SyntaxError`。
|
||||
|
||||
一个构造函数可以使用`super`关键字来调用一个父类的构造函数。
|
||||
|
||||
### 原型方法
|
||||
|
||||
原型方法与使用传统的构造函数方法实现的效果一样。不过 class 使用的是方法定义:从 ECMAScript 2015 开始,在对象初始器中引入了一种更简短定义方法的语法,这是一种把方法名直接赋给函数的简写方式。
|
||||
|
||||
```js
|
||||
class Rectangle {
|
||||
// 构造函数
|
||||
constructor(h, w) {
|
||||
this.h = h;
|
||||
this.w = w;
|
||||
}
|
||||
// get
|
||||
get area() {
|
||||
return this.calcArea();
|
||||
}
|
||||
// 自定义方法
|
||||
calcArea() {
|
||||
return this.h * this.w;
|
||||
}
|
||||
}
|
||||
|
||||
let square = new Rectangle(23, 32);
|
||||
console.log(square.area) // 736
|
||||
```
|
||||
|
||||
使用类的方式来写这个原型方法使其看上去更加类似于一些常见的面向对象的语言。当然,类不会为 JavaScript 引入新的继承模型,所以上述由类写的原型方法也可以使用传统的构造函数来写。不过就是看上去和常见的面向对象的语言不太一样而已。
|
||||
|
||||
```js
|
||||
// 构造函数
|
||||
function Rectangle(h, w) {
|
||||
this.h = h;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
// 自定义方法
|
||||
Rectangle.prototype.calcArea = function() {
|
||||
return this.h * this.w;
|
||||
}
|
||||
|
||||
// get
|
||||
Object.defineProperty(Rectangle.prototype, 'area', {
|
||||
get: function() {
|
||||
return this.calcArea();
|
||||
}
|
||||
})
|
||||
|
||||
let square = new Rectangle(23, 32);
|
||||
console.log(square.area); // 736
|
||||
```
|
||||
|
||||
### 静态方法
|
||||
|
||||
`static`关键字用于定义一个类的静态方法。调用静态方法不需要实例化该类,但不能通过一个类实例来调用静态方法。
|
||||
|
||||
通常,一个函数也是基于对象的,所以函数也有自己的属性,函数的`prototype`就是一个很好的例子。而一个类中的静态方法就相当于给这个构造函数定义了一个它自己的属性。不是在`prototype`上的属性是不会继承到实例上的。
|
||||
|
||||
```js
|
||||
class test {
|
||||
constructor(xx, yy) {
|
||||
this.x = xx;
|
||||
this.y = yy;
|
||||
}
|
||||
|
||||
add() {
|
||||
return this.x + this.y;
|
||||
}
|
||||
|
||||
static tt() {
|
||||
return '嘤嘤嘤';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
test.tt()
|
||||
"嘤嘤嘤"
|
||||
```
|
||||
|
||||
静态方法类似于将一个构造函数直接用作于一个工具函数。
|
||||
|
||||
### 原型和静态方法包装
|
||||
|
||||
在`class`体内部执行的代码总是在严格模式下执行,即使没有设置`'use strict'`。所以当调用静态或原型方法时没有指定`this`的值,那么方法内的`this`值将被置为`undefined`。
|
||||
|
||||
```js
|
||||
class test {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
show() {
|
||||
return this;
|
||||
}
|
||||
|
||||
static ss() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是一个简单的返回`this`的函数,它有两个,分别是继承给实例的属性和静态方法。若`this`的传入值为`undefined`,则在严格模式下不会发生自动装箱,`this`的返回值是`undefined`。
|
||||
|
||||
```js
|
||||
let xfy = new test();
|
||||
xfy.show();
|
||||
let t = xfy.show;
|
||||
t(); // undefined
|
||||
|
||||
test.ss()
|
||||
let tt = test.ss;
|
||||
tt(); // undefined
|
||||
```
|
||||
|
||||
而在传统的构造函数写法下,`this`的值会发生自动装箱,将指向全局对象
|
||||
|
||||
```js
|
||||
function Test(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
Test.prototype.show = function () {
|
||||
return this;
|
||||
}
|
||||
|
||||
Test.ss = function () {
|
||||
return this;
|
||||
}
|
||||
|
||||
let xfy = new Test();
|
||||
xfy.show();
|
||||
let t = xfy.show;
|
||||
t(); // Global Object
|
||||
|
||||
Test.ss()
|
||||
let tt = Test.ss;
|
||||
tt(); // Global Object
|
||||
```
|
||||
|
||||
### 实例属性
|
||||
|
||||
实例的属性必须定义在类的`constructor`方法里
|
||||
|
||||
```js
|
||||
class test() {
|
||||
constructor(h, w) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 字段声明
|
||||
|
||||
> 公共和私有字段声明是JavaScript标准委员会 [TC39](https://tc39.es/) 提出的 [实验性功能(第3阶段)](https://github.com/tc39/proposal-class-fields) 。浏览器中的支持是有限的,但是可以通过 [Babel](https://babeljs.io/) 等系统构建后使用此功能。
|
||||
|
||||
### 公有字段声明
|
||||
|
||||
class 提供了声明公有字段的方法,通过预先声明字段,类定义变得更加自我记录,并且字段始终存在。
|
||||
|
||||
```js
|
||||
class Rectangle {
|
||||
x;
|
||||
constructor(height, width) {
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
}
|
||||
static y = 1;
|
||||
clicked() {
|
||||
this.x++;
|
||||
}
|
||||
}
|
||||
|
||||
let square = new Rectangle(123, 33);
|
||||
```
|
||||
|
||||
正如上面看到的,这个字段可以用也可以不用默认值来声明。
|
||||
|
||||
共有字段重要的不同之处在于,它们会在每个独立对象中被设好,而不是设在`prototype`中。
|
||||
|
||||
```js
|
||||
Rectangle.prototype.x // undefined
|
||||
```
|
||||
|
||||
### 私有字段声明
|
||||
|
||||
从类外部引用私有字段是错误的。它们只能在类里面中读取或写入。通过定义在类外部不可见的内容,可以确保类的用户不会依赖于内部,因为内部可能在不同版本之间发生变化。
|
||||
|
||||
私有字段从大佬们之间共有约定的下划线,现在已经换为草案中的`#`了。
|
||||
|
||||
```js
|
||||
class Rectangle {
|
||||
#x;
|
||||
#width;
|
||||
#height;
|
||||
constructor(w, h) {
|
||||
this.#width = w;
|
||||
this.#height = h;
|
||||
}
|
||||
get area() {
|
||||
return this.#width * this.#height;
|
||||
}
|
||||
}
|
||||
|
||||
let s = new Rectangle(12, 33);
|
||||
```
|
||||
|
||||
> 私有字段必须先在类中声明。
|
||||
|
||||
## 使用`extends`扩展子类
|
||||
|
||||
`extends`关键字在*类声明*或*类表达式*中用于创建一个类作为另一个类的一个子类。也就是说,继承。ECMAScript 2015 的继承终于变的更加直观了。
|
||||
|
||||
```js
|
||||
class Person {
|
||||
constructor(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
sayName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
get newYear() {
|
||||
return ++this.age;
|
||||
}
|
||||
}
|
||||
|
||||
class Student extends Person {
|
||||
constructor(name, age, classes) {
|
||||
super(name, age);
|
||||
this.classes = classes;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
不需要再手动写一个用于继承的函数了,也不需要再使用`call`了,在子类中可以直接使用`super()`方法来指向超类的`this`。
|
||||
|
||||
当然,因为是语法糖,所以`extends`也可以继承传统基于函数的“类”:
|
||||
|
||||
```js
|
||||
function Person(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
Person.prototype.sayName = function () {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
Object.defineProperty(Person.prototype, 'newYear', {
|
||||
get: function () {
|
||||
return ++this.age;
|
||||
}
|
||||
})
|
||||
|
||||
class Student extends Person {
|
||||
constructor(name, age, classess) {
|
||||
super(name, age);
|
||||
this.classess = classess;
|
||||
}
|
||||
|
||||
sayAge() {
|
||||
return this.age;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
除了基于函数的“类”,其他用法还是一模一样。
|
||||
|
||||
**但是**,类无法继承常规对象,也就是非构造函数的`prototype`,或者说不可构造的常规对象。如果需要继承,可以使用`Object.setPrototypeOf()`方法。
|
||||
|
||||
```js
|
||||
class Student {
|
||||
constructor(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
let someOne= {
|
||||
sayName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
Object.setPrototypeOf(Student.prototype, someOne);
|
||||
|
||||
let p = new Student('xfy', 18);
|
||||
```
|
||||
|
||||
又见到老朋友`prototype`了。
|
||||
|
||||
### Species
|
||||
|
||||
如果我们想在一个派生出数组的构造函数中返回 Array 对象,那么 Species 允许我们覆盖默认的构造函数
|
||||
|
||||
```js
|
||||
class MyArray extends Array {
|
||||
static get [Symbol.species]() {
|
||||
return Array;
|
||||
}
|
||||
}
|
||||
|
||||
let a = new MyArray(1,2,3,4);
|
||||
```
|
||||
|
||||
## 不仅仅是语法糖
|
||||
|
||||
前面有提到过,class 不过是新的语法糖。所谓的语法糖,就是旨在不增加语法的情况下,使得代码内容更加容易阅读。根据前面的一些实例,可以发现 class 完全可以使用以前的纯函数来写出相同的内容。
|
||||
|
||||
不过他们也有一些重大差异:
|
||||
|
||||
1. `class` 创建的函数具有特殊的内部属性标记 `[[FunctionKind]]:"classConstructor"`。所以它与手动创建构造函数并不完全相同,并且 JavaScript 会在很多地方检查该属性。例如类无法当作普通函数来使用,必须配合`new`操作符来使用:
|
||||
|
||||
```js
|
||||
Error: Class constructor User cannot be invoked without 'new'
|
||||
```
|
||||
|
||||
2. 类的方法不可枚举,类定义将 `"prototype"` 中的所有方法的 `enumerable` 标志设置为 `false`。所以当使用`for/in`方法时,类的方法是不会被枚举出来的。
|
||||
|
||||
3. 类总是使用`use strict`,类中的所有代码都是严格模式。所以也不会导致 this 的自动装箱。
|
||||
4. 函数的声明会提升,类的声明不会。
|
||||
5. `class`内创建的方法既能继承作用域链,同时也是在 prototype 上,不会为每个实例创建同名不相同的方法。
|
||||
|
||||
## 参考
|
||||
|
||||
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes
|
||||
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/Class_elements
|
||||
* https://zh.javascript.info/class
|
||||
|
||||
头图来自:[Classes in JavaScript](https://dev.to/samanthaming/classes-in-javascript-30n8)
|
146
source/_posts/自动备份大法.md
Normal file
@ -0,0 +1,146 @@
|
||||
---
|
||||
title: 自动备份大法
|
||||
date: 2019-06-05 16:41:57
|
||||
tags: Linux
|
||||
categories: Linux
|
||||
url: auto-backup
|
||||
index_img: /images/自动备份大法/bakcup.webp
|
||||
---
|
||||
|
||||
## 引入
|
||||
|
||||
最近看到几个数据爆炸的可怕事件,虽然我平时偶尔有手动备份的,但还是不怎么放心。以前有用过lsyncd自动同步到其他机器。但昨天发生了一个更可怕的事情,我重启机器后发现mysql启动不了,apt也不能update了。当时就蒙了,后来发现是我的/var目录满了。mysql与apt都需要用到/var目录,所以爆炸了。但是为什么会满呢…
|
||||
|
||||
因为一个lsyncd的日志写了34GB。
|
||||
|
||||

|
||||
|
||||
## 操作
|
||||
|
||||
放弃lsyncd。
|
||||
|
||||
以前因为懒,写过一个自动压缩网页根目录的脚本,配合crontab在每天的凌晨自动执行一遍非常不错。
|
||||
|
||||
但是最重要的不是根目录,而是数据库。最近有了解到mysqldump,表示可以crontab一下。
|
||||
|
||||
### dump为sql文件
|
||||
|
||||
导出整个数据库:
|
||||
|
||||
```
|
||||
mysqldump -u 用户名 -p 数据库名 > 导出的文件名
|
||||
```
|
||||
|
||||
例:
|
||||
|
||||
```
|
||||
mysqldump -u root -p typecho > typecho_backup.sql
|
||||
```
|
||||
|
||||
导出一个表
|
||||
|
||||
```
|
||||
mysqldump -u 用户名 -p 数据库名 表名> 导出的文件名
|
||||
```
|
||||
|
||||
例:
|
||||
|
||||
```
|
||||
mysqldump -u root -p typecho users > users_backup.sql
|
||||
```
|
||||
|
||||
导出一个数据库结构
|
||||
|
||||
```
|
||||
mysqldump -u 用户名 -p -d 数据库名 > 导出的文件名
|
||||
```
|
||||
|
||||
例:
|
||||
|
||||
```
|
||||
mysqldump -u root -p -d typecho > typecho.sql
|
||||
```
|
||||
|
||||
### 导入数据库
|
||||
|
||||
```
|
||||
mysql -u 用户名 -p 数据库名 < 数据库名.sql
|
||||
```
|
||||
|
||||
例:
|
||||
|
||||
```
|
||||
mysql -u root -p typecho < typecho.sql
|
||||
```
|
||||
|
||||
实际操作了一下,确实很简单方便好用。但问题是,对于我这种勤(lan)快的人肯定要脚本自动一体化啊。
|
||||
|
||||
### 感觉很厉害的Script
|
||||
|
||||
自我感觉,自我感觉。
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
#定义数据库信息
|
||||
USER="root"
|
||||
PASS="password"
|
||||
HOST="localhost"
|
||||
NAME="typecho"
|
||||
NAME2="wordpress"
|
||||
#其他信息
|
||||
BAK_DIR="/root/backup/"
|
||||
TIME=`date +%F`
|
||||
#操作
|
||||
mysqldump -u$USER -p$PASS -h$HOST $NAME > $NAME"_"$TIME.sql
|
||||
mysqldump -u$USER -p$PASS -h$HOST $NAME2 > $NAME2"_"$TIME.sql
|
||||
#压缩并移动
|
||||
#tar -zPcvf /root/backup/tar.gz/sql/$NAME"_"$TIME.tar.gz /root/backup/$NAME"_"$TIME.sql
|
||||
#tar -zPcvf /root/backup/tar.gz/sql/$NAME2"_"$TIME.tar.gz /root/backup/$NAME2"_"$TIME.sql
|
||||
#删除多余文件
|
||||
rm -rf /root/backup/$NAME"_"$TIME.sql /root/backup/$NAME2"_"$TIME.sql
|
||||
rm -rf /root/$NAME"_"$TIME.sql /root/$NAME2"_"$TIME.sql
|
||||
#删除三天前的数据
|
||||
find /root/backup/tar.gz/sql -mtime +3 -name "*.*" -exec rm -rf {} \;
|
||||
```
|
||||
|
||||
只要将其放到crontab中,并按时间进行执行。就能实现完美的sql备份了。
|
||||
|
||||
再加上以前写过的一些备份其他文件的Shell Script,就能实现最基本的收据备份了。并且七牛的云储存有个在Linux上的下载备份脚本。正好给了我不小的帮助。
|
||||
|
||||
(虽然喜欢写交互式的脚本,但是只要将命令挑出来放crontab就好了)
|
||||
|
||||
### 写入crontab
|
||||
|
||||
先来简单的介绍下可爱的crontab文件的时间格式吧。
|
||||
|
||||

|
||||
|
||||
星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。
|
||||
|
||||
逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”
|
||||
|
||||
中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”
|
||||
|
||||
正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。
|
||||
|
||||
|
||||
|
||||
然后就是写到Crontab里去了。第一次我也是以为直接找到并编辑crontab这个文件的,后来才发现,原来人家有编辑的命令的:
|
||||
|
||||
```
|
||||
crontab -e
|
||||
```
|
||||
|
||||
然后按照格式讲我们的脚本写进去就好了。
|
||||
|
||||
```
|
||||
# m h dom mon dow comman
|
||||
0 5 * * * /bin/sh /root/backup/c.sh
|
||||
0 4 * * * /bin/sh /root/backup/d.sh
|
||||
```
|
||||
|
||||
## 结尾
|
||||
|
||||
进过超级简单的操作再配合定时任务,就能实现自动化的各种各样的操作了。对于备份这种操作,手动来做的话迟早会累死,就是不累也会感觉到烦。所以将其运用到定时任务上就是非常的人性化了。主要是方便,不需要任何的人工参与。
|
||||
|
||||
对于数据这方面的,还是经常性的备份比较重要。不光光是不本机的备份,也要经常性的实施多机备份。
|
74
source/_posts/解决inotify-watch不够.md
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
title: 解决 inotify watch 不够⌚
|
||||
date: 2019-05-29 9:05:01
|
||||
tags: Linux
|
||||
categories: 踩坑
|
||||
url: fixed-inotify-watch-not-enough
|
||||
index_img: /images/解决inotify-watch不够/logo.webp
|
||||
---
|
||||
|
||||
> Failed to add /run/systemd/ask-password to directory watch: No space left on device
|
||||
|
||||
这当然不是磁盘空间不足。
|
||||
|
||||
曾经被这问题折腾了很长时间,在磁盘空间充足的情况下,一直提示设备剩余空间不足,导致许多服务无法启动。该问题所在的根源是Inotify watch被占用光了导致的。
|
||||
|
||||
## inotify watch
|
||||
|
||||
Inotify 到底是什么?
|
||||
|
||||
Inotify 是一种文件变化通知机制,或者称之为监控文件系统,Linux 内核从 2.6.13 开始引入。在 BSD 和 Mac OS 系统中比较有名的是[kqueue](http://wiki.netbsd.se/kqueue_tutorial),它可以高效地实时跟踪 Linux 文件系统的变化。近些年来,以[fsnotify](http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=90586523eb4b349806887c62ee70685a49415124)作为后端,几乎所有的主流 Linux 发行版都支持 Inotify 机制。
|
||||
|
||||
可以简单的理解为,inotify就是监控我们当前系统上的文件变化。在日常工作中,人们往往需要知道在某些文件 (夹) 上都有那些变化,比如:
|
||||
|
||||
- 通知配置文件的改变
|
||||
- 跟踪某些关键的系统文件的变化
|
||||
- 监控某个分区磁盘的整体使用情况
|
||||
- 系统崩溃时进行自动清理
|
||||
- 自动触发备份进程
|
||||
- 向服务器上传文件结束时发出通知
|
||||
|
||||
检查当前系统内核是否支持inotify机制:
|
||||
|
||||
```
|
||||
grep INOTIFY_USER /boot/config-$(uname -r)
|
||||
```
|
||||
|
||||
如果输出为:`CONFIG_INOTIFY_USER=y`,那么当前的系统内核便是支持inotify了。
|
||||
|
||||
## 解决watch不够
|
||||
|
||||
经常打开服务无法启动,提示:
|
||||
|
||||
```
|
||||
Failed to add /run/systemd/ask-password to directory watch: No space left on device
|
||||
```
|
||||
|
||||
便是inotify watch不够导致的服务无法启动,很多程序的进程都需要使用inotify watch来监控文件系统。当某些进程使用的太多的时候,就会导致watch不够,导致一些程序直接无法启动。
|
||||
|
||||
遇到这种情况解决办法非常的简单,毕竟不是磁盘的空间不够,我们不需要删除任何的文件,只需要放大足够的watch数量就ok了。
|
||||
|
||||
临时的解决办法:
|
||||
|
||||
```
|
||||
echo 1048576 > /proc/sys/fs/inotify/max_user_watches
|
||||
```
|
||||
|
||||
直接在终端`echo`一个大量的watch数量到指定的路径,不出意外的话就能够直接解决问题。但这只是个临时的解决办法,再重启机器后将会还原。
|
||||
|
||||
该临时解决办法的好处就是方便快捷,有次我的sshd因为watch数量的不够倒是无法启动时,唯一的解决办法就是连接vnc来解决,然后网页的vnc是不支持粘贴的,所以使用这一行命令也就非常的方便了。
|
||||
|
||||
长期解决方法:
|
||||
|
||||
```
|
||||
vim /etc/sysctl.conf
|
||||
fs.inotify.max_user_watches=1048576
|
||||
```
|
||||
|
||||
长期的解决方法也很简单,我们直接在`/etc/sysctl.conf `文件的末尾添加一句话就可以了。
|
||||
|
||||
## 参考
|
||||
|
||||
* [Inotify: 高效、实时的 Linux 文件系统事件监控框架](<https://www.infoq.cn/article/inotify-linux-file-system-event-monitoring>)
|
||||
* [磁盘空间充足,但是重启服务出现报错:Failed to add /run/systemd/](<https://blog.51cto.com/zhanx/2339983>)
|
||||
* [inotify](<https://zh.wikipedia.org/wiki/Inotify>)
|
33
source/about/index.md
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: about
|
||||
date: 2020-02-29 13:07:34
|
||||
layout: about
|
||||
---
|
||||
|
||||

|
||||
|
||||
## 经历风雨
|
||||
|
||||
才会成长。
|
||||
|
||||
Wordpress 到 typecho,换来换去的主题,最后还是选择了纯静态。或许这次才会真的沉静下浮躁的心来。
|
||||
|
||||
## 到底还是
|
||||
|
||||
- 🆔:Defectink
|
||||
- ❤:😺、🍎、📸
|
||||
- 🆎:🚫
|
||||
- ⭐:♓
|
||||
- 🐣:🔞
|
||||
|
||||
## 全部艺能
|
||||
|
||||
!@#$%^&*()
|
||||
|
||||
## 还活着的服务
|
||||
|
||||
<a class="btn" href="https://stats.defectink.com/" title="status" target="_blank">监控👀</a>
|
||||
|
||||
## 排版
|
||||
|
||||
某咸鱼正在根据 [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) 来更新文章。
|
180
source/css/xfy.css
Normal file
@ -0,0 +1,180 @@
|
||||
img {
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.5s;
|
||||
-webkit-transition: all 0.5;
|
||||
-ms-transition: all 0.5;
|
||||
}
|
||||
|
||||
img:hover {
|
||||
transition: all 0.5s;
|
||||
-webkit-transition: all 0.5s;
|
||||
-ms-transition: all 0.5s;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.index-img img {
|
||||
border-radius: 0.66rem !important;
|
||||
}
|
||||
|
||||
/*ul and ol*/
|
||||
|
||||
.markdown-body a {
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
color: #00f4e8;
|
||||
transition: all 0.12s;
|
||||
}
|
||||
|
||||
.markdown-body li:hover {
|
||||
text-shadow: 3px 3px 2px rgba(47, 47, 47, 0.341);
|
||||
}
|
||||
|
||||
|
||||
/*ol使用css排序进行定义序号*/
|
||||
|
||||
.markdown-body ol {
|
||||
counter-reset: xxx 0 !important;
|
||||
}
|
||||
|
||||
.markdown-body ol li:before {
|
||||
content: counter(xxx, decimal) "." !important;
|
||||
counter-increment: xxx 1 !important;
|
||||
position: absolute;
|
||||
font-family: 'Comic Sans MS', 'Open Sans', 'Microsoft Yahei', 'Microsoft Yahei', -apple-system, sans-serif !important;
|
||||
color: #000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
opacity: 0.5;
|
||||
/*使用行高来调整前缀的上下位置*/
|
||||
line-height: 1.33;
|
||||
text-shadow: 4px 4px 1px rgba(0, 0, 0, 0.1);
|
||||
-webkit-transition: 0.5s;
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
.markdown-body ol li:hover:before {
|
||||
-webkit-transform: scale(2);
|
||||
-ms-transform: scale(2);
|
||||
transform: scale(2);
|
||||
opacity: 1;
|
||||
text-shadow: 2px 2px 1px rgba(0, 0, 0, 0.1);
|
||||
-webkit-transition: 0.1s;
|
||||
transition: 0.1s;
|
||||
}
|
||||
|
||||
.markdown-body ol li {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
padding: 0 0 0 2.1em;
|
||||
margin: 0 0 0 10px;
|
||||
text-shadow: 0px 0px 0px rgba(0, 0, 0, 0.1);
|
||||
-webkit-transition: 0.12s;
|
||||
transition: 0.12s;
|
||||
/*ul使用插入圆点来模拟无序列表*/
|
||||
}
|
||||
|
||||
.markdown-body ul li:before {
|
||||
position: absolute;
|
||||
content: '\2022';
|
||||
font-family: Arial;
|
||||
color: #000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
font-size: 1.5em;
|
||||
opacity: 0.5;
|
||||
/*使用行高来调整前缀的位置*/
|
||||
line-height: 1;
|
||||
text-shadow: 4px 4px 1px rgba(0, 0, 0, 0.1);
|
||||
-webkit-transition: 0.5s;
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
.markdown-body ul li:hover:before {
|
||||
-webkit-transform: scale(2);
|
||||
-ms-transform: scale(2);
|
||||
transform: scale(2);
|
||||
opacity: 1;
|
||||
text-shadow: 2px 2px 1px rgba(0, 0, 0, 0.1);
|
||||
-webkit-transition: 0.1s;
|
||||
transition: 0.1s;
|
||||
}
|
||||
|
||||
.markdown-body ul li {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
padding: 0 0 0 1.5em;
|
||||
margin: 0 0 0 10px;
|
||||
text-shadow: 0px 0px 0px rgba(0, 0, 0, 0.1);
|
||||
-webkit-transition: 0.12s;
|
||||
transition: 0.12s;
|
||||
}
|
||||
|
||||
.note.note-warning {
|
||||
background-color: rgba(163, 243, 241, 0.631);
|
||||
border-color: #ff81c0 !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #a2f1f1;
|
||||
text-shadow: 0px 0px 25px;
|
||||
}
|
||||
|
||||
/* 调整导航栏字体大小 */
|
||||
.navbar .nav-item .nav-link {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.modal-dialog .modal-content {
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
/* 图片圆角 */
|
||||
.markdown-body p>img,
|
||||
.markdown-body p>a>img {
|
||||
border-radius: 0.425rem;
|
||||
}
|
||||
|
||||
/* 文章内标题背景 */
|
||||
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
|
||||
background: linear-gradient(to bottom,transparent 60%,rgba(189,202,219,.3) 0) no-repeat;
|
||||
width: auto;
|
||||
display: table;
|
||||
background-size: 90%;
|
||||
}
|
||||
|
||||
.markdown-body h1, .markdown-body h2 {
|
||||
padding-bottom: .3em;
|
||||
border-bottom: 0px solid #eaecef !important;
|
||||
}
|
||||
|
||||
/* 浪漫雅园!! */
|
||||
/* @font-face {
|
||||
font-family: "LMYY";
|
||||
src: url("https://cdn.defectink.com/static/fonts/yayuan.woff")
|
||||
}
|
||||
* {
|
||||
font-family: "LMYY" !important;
|
||||
} */
|
||||
/* 代码块里的字太小 */
|
||||
/* .markdown-body pre code {
|
||||
font-size: 100% !important;
|
||||
} */
|
||||
|
||||
/* 滚动条,webkit赛高! */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
background-color: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: darkgray;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.v[data-class=v] .vwrap .vedit {
|
||||
position: relative;
|
||||
padding-top: 10px;
|
||||
background: no-repeat right bottom url(https://cdn.defectink.com/static/images/iloli.gif);
|
||||
background-size: 15%;
|
||||
}
|
BIN
source/defect/images/img/loading.gif
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
source/images/ASCII在线视频流/ascii_cat.gif
Normal file
After Width: | Height: | Size: 871 KiB |
BIN
source/images/ASCII在线视频流/logo.webp
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
source/images/ASCII在线视频流/图像-1.webp
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
source/images/ASCII在线视频流/图像-2.webp
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
source/images/AliOssForTypecho/logo.webp
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
source/images/BOM/2020-08-15-10-19-42.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
source/images/Can't install gifsicle/index.webp
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
source/images/Can't install gifsicle/批注 2020-08-04 135700.webp
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
source/images/Docker-构建属于自己的镜像/logo.webp
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
source/images/Docker全面容器化/image-20191221185631177.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
source/images/Docker全面容器化/logo.webp
Normal file
After Width: | Height: | Size: 936 B |
BIN
source/images/Gitlab尝鲜/411390967.webp
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
source/images/Gitlab尝鲜/52152339.webp
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
source/images/Gitlab尝鲜/99634888.webp
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
source/images/Header实践-得拿下这个A/header-security.webp
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
source/images/Header实践-得拿下这个A/image-20191218171928050.webp
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
source/images/Header实践-得拿下这个A/image-20191218172218399.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
source/images/Hexo-and-Github/create.webp
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
source/images/Hexo-and-Github/ggg.webp
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
source/images/Hexo-and-Github/index.webp
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
source/images/Hexo-and-Github/profile.webp
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
source/images/Hexo-and-Github/repository.webp
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
source/images/Hexo-and-Github/web.webp
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
source/images/JavaScript-可迭代对象与for-of/2020-10-27-15-01-39.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
source/images/JavaScript-可迭代对象与for-of/2020-10-29-10-17-49.webp
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
source/images/JavaScript-可迭代对象与for-of/logo.webp
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
source/images/JavaScript实践-乘法表/99-1581995512696.webp
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
source/images/JavaScript实践-乘法表/logo.webp
Normal file
After Width: | Height: | Size: 16 KiB |