├── .gitignore ├── README.md ├── assets ├── icons.woff2 └── images │ ├── github.svg │ └── home.svg ├── components ├── Detail │ ├── index.js │ └── markdown.module.css ├── Layout │ └── index.js ├── List │ └── index.js ├── ListItem │ └── index.js └── date.js ├── lib ├── posts.js └── utils.js ├── next.config.js ├── package.json ├── pages ├── _app.js ├── api │ └── hello.js ├── index.js └── posts │ └── [id].js ├── postcss.config.js ├── posts ├── CSS内联元素深入理解.md ├── CSS基础整理.md ├── ContentType与文件上传.md ├── ES6基础整理.md ├── Git基础.md ├── Http缓存.md ├── JS中的设计模式.md ├── JavaScript基础整理.md ├── Linux常用命令记录.md ├── Promise原理与实现.md ├── React Fiber实现.md ├── Redux原理与实现.md ├── V8中的垃圾回收机制.md ├── Web Workers.md ├── Webkit浏览器基础知识.md ├── Web安全.md ├── 二进制相关知识.md ├── 人类简史读书笔记.md ├── 以太坊概览.md ├── 养家心法摘录.md ├── 前端存储技术.md ├── 前端性能优化.md ├── 单页面路由原理及实现.md ├── 原则读书笔记.md ├── 双向数据绑定.md ├── 完全理解CORS跨域.md ├── 实现ES6中的类.md ├── 小白前端入门指南.md ├── 投资学读书笔记.md ├── 排序算法原理与实现.md ├── 正则表达式易错记录.md ├── 浏览器和Node的事件循环.md ├── 真正认识Generator.md ├── 网站图片优化.md ├── 网页移动端适配.md ├── 考试脑科学读书笔记.md └── 计算机中的编码和转义.md ├── public ├── favicon.ico └── images │ └── profile.jpg ├── styles ├── global.css └── icon.module.css ├── tailwind.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | .env* 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blog 2 | 此blog主要记录我的学习过程,不只包括前端,希望自己能有点感悟 3 | 4 | 请访问 https://overfronted.net 5 | -------------------------------------------------------------------------------- /assets/icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwchris/blog/05f29941299bb46c28c48858dfdcd87d9a502476/assets/icons.woff2 -------------------------------------------------------------------------------- /assets/images/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Detail/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Markdown from 'react-markdown'; 3 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' 4 | import { tomorrow } from 'react-syntax-highlighter/dist/cjs/styles/prism' 5 | import markdownStyles from './markdown.module.css' 6 | import HomeIcon from '../../assets/images/home.svg' 7 | import Date from '../date' 8 | import Link from 'next/link'; 9 | 10 | const components = { 11 | code({ node, inline, className, children, ...props }) { 12 | const match = /language-(\w+)/.exec(className || '') 13 | return !inline && match ? ( 14 | a.toUpperCase())} language={match[1]} children={String(children).replace(/\n$/, '')} {...props} /> 15 | ) : ( 16 | 17 | {String(children).replace(/\n$/, '')} 18 | 19 | ) 20 | } 21 | } 22 | 23 | const Detail = ({ data }) => { 24 | const { id, title, date, content, author, tags } = data 25 | 26 | return ( 27 | <> 28 |
29 |

{title}

30 |
31 | {author} 32 | {" · "} 33 | 34 | {" · "} 35 | {tags.split(',').join(', ')} 36 |
37 |
38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 | ) 46 | } 47 | 48 | export default Detail -------------------------------------------------------------------------------- /components/Detail/markdown.module.css: -------------------------------------------------------------------------------- 1 | 2 | /* markdown theme 少数派 */ 3 | /* Sspai Web Theme A theme to [sspai](ssp.ai) default theme. Developed by Codegass(wchweichenhao@gmial.com) & Yves(yves@sspai.com) Download Cuto on the App Store and Google Play! */ 4 | 5 | .markdown { 6 | font-size: 1rem; 7 | /* color: var(--clr-txt-100); */ 8 | /* font-family: Helvetica, Arial, "PingFang SC", "Microsoft YaHei", "WenQuanYi Micro Hei", "tohoma,sans-serif"; 9 | margin: 0; */ 10 | } 11 | 12 | .markdown h1 { 13 | display: none; 14 | } 15 | 16 | .markdown h2, 17 | .markdown h3, 18 | .markdown h4, 19 | .markdown h5, 20 | .markdown h6 { 21 | line-height: 1.5em; 22 | margin-top: 2.2em; 23 | margin-bottom: 4px; 24 | } 25 | 26 | .markdown h2 { 27 | font-size: 1.4em; 28 | margin: 40px 10px 20px 0; 29 | /* padding-left: 9px; */ 30 | /* border-left: 6px solid #ff7e79; */ 31 | /* border-left: 6px solid rgba(0, 0, 0, .84); */ 32 | font-weight: 700; 33 | line-height: 1.4; 34 | } 35 | 36 | .markdown h3 { 37 | font-weight: 700; 38 | font-size: 1.2em; 39 | line-height: 1.4; 40 | margin: 10px 0 5px; 41 | padding-top: 10px; 42 | } 43 | 44 | .markdown h4 { 45 | font-weight: 700; 46 | /* text-transform: uppercase; */ 47 | font-size: 1.1em; 48 | line-height: 1.4; 49 | margin: 10px 0 5px; 50 | padding-top: 10px 51 | } 52 | 53 | .markdown h5, 54 | .markdown h6 { 55 | font-size: .9em; 56 | } 57 | 58 | .markdown h5 { 59 | font-weight: bold; 60 | /* text-transform: uppercase; */ 61 | } 62 | 63 | .markdown h6 { 64 | font-weight: normal; 65 | color: #AAA; 66 | } 67 | 68 | .markdown img { 69 | width: 100%; 70 | border-radius: 5px; 71 | display: block; 72 | margin-bottom: 15px; 73 | height: auto; 74 | } 75 | 76 | .markdown dl, 77 | .markdown ol, 78 | .markdown ul { 79 | margin-top: 12px; 80 | margin-bottom: 20px; 81 | padding-left: 16px; 82 | line-height: 1.8; 83 | list-style: unset; 84 | } 85 | 86 | .markdown p { 87 | margin: 0 0 20px; 88 | padding: 0; 89 | line-height: 1.8; 90 | } 91 | 92 | .markdown a { 93 | /* color: #f22f27; */ 94 | text-decoration: none; 95 | } 96 | 97 | .markdown a:hover { 98 | /* color: #f55852; */ 99 | text-decoration: underline; 100 | } 101 | 102 | .markdown a:focus { 103 | outline-offset: -2px; 104 | } 105 | 106 | .markdown blockquote { 107 | font-size: 1em; 108 | font-style: normal; 109 | padding: 30px 38px; 110 | margin: 0 0 15px; 111 | position: relative; 112 | line-height: 1.8; 113 | text-indent: 0; 114 | border: none; 115 | color: #888; 116 | } 117 | 118 | .markdown blockquote:before { 119 | content: "“"; 120 | left: 12px; 121 | top: 0; 122 | color: #E0E0E0; 123 | font-size: 4em; 124 | /* font-family: Arial, serif; */ 125 | line-height: 1em; 126 | font-weight: 700; 127 | position: absolute; 128 | } 129 | 130 | .markdown blockquote:after { 131 | content: "”"; 132 | right: 12px; 133 | bottom: -26px; 134 | color: #E0E0E0; 135 | font-size: 4em; 136 | /* font-family: Arial, serif; */ 137 | line-height: 1em; 138 | font-weight: 700; 139 | position: absolute; 140 | bottom: -31px; 141 | } 142 | 143 | .markdown strong, 144 | .markdown dfn { 145 | font-weight: 700; 146 | } 147 | 148 | .markdown em, 149 | .markdown dfn { 150 | font-style: italic; 151 | font-weight: 400; 152 | } 153 | 154 | .markdown del { 155 | text-decoration: line-through; 156 | } 157 | 158 | /*code {font-size:90%;}*/ 159 | 160 | /*pre {text-align:left; overflow-x: scroll; color: #257fa0; background: #f6f6f6; padding: 10pt 15pt; border-radius: 3px; border: solid 1px #e2e2e2;}*/ 161 | 162 | .markdown pre { 163 | /* margin: 0 0 10px; 164 | font-size: 13px; 165 | line-height: 1.42857; */ 166 | word-break: break-all; 167 | word-wrap: break-word; 168 | border-radius: 4px; 169 | white-space: pre-wrap; 170 | background: var(--clr-code-bg) !important; 171 | border-radius: 4px; 172 | /* display: block; */ 173 | /* background: #f8f8f8; */ 174 | padding: 1rem .875rem; 175 | /* border: none; */ 176 | margin-bottom: 25px; 177 | /* color: #666; */ 178 | /* font-family: Courier, sans-serif; */ 179 | } 180 | 181 | .markdown pre[lang]::before { 182 | content: attr(lang); 183 | /* background-color: red; */ 184 | font-size: inherit; 185 | color: var(--clr-txt-100); 186 | display: block; 187 | line-height: 1.5; 188 | margin-bottom: 10px; 189 | } 190 | 191 | .markdown pre > pre { 192 | padding: 0 !important; 193 | margin: 0 !important; 194 | } 195 | 196 | .markdown code { 197 | color: var(--clr-code-txt); 198 | background: var(--clr-code-bg); 199 | /* padding: .125rem .375rem; */ 200 | border-radius: 4px; 201 | margin: 0 8px; 202 | /* color: #c7254e; 203 | background-color: #f9f2f4; 204 | border-radius: 4px; 205 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; */ 206 | /* line-height: 0; 207 | position: relative; 208 | vertical-align: initial; */ 209 | } 210 | 211 | .markdown p>code { 212 | /* color: var(--clr-code-txt); */ 213 | /* background: var(--clr-code-bg); */ 214 | padding: .125rem .375rem; 215 | } 216 | 217 | .markdown figure { 218 | margin: 1em 0; 219 | } 220 | 221 | .markdown figcaption { 222 | font-size: 0.75em; 223 | padding: 0.5em 2em; 224 | margin-bottom: 2em; 225 | } 226 | 227 | .markdown figure img { 228 | margin-bottom: 0px; 229 | } 230 | 231 | .markdown hr { 232 | margin-top: 20px; 233 | margin-bottom: 20px; 234 | border: 0; 235 | border-top: 1px solid #eee; 236 | } 237 | 238 | .markdown ol p, 239 | .markdown ul p { 240 | margin-bottom: 0px; 241 | } 242 | 243 | .markdown li { 244 | margin-bottom: 0.75em; 245 | margin-top: 0.75em; 246 | } 247 | 248 | .markdown ol#footnotes { 249 | font-size: 0.95em; 250 | padding-top: 1em; 251 | margin-top: 1em; 252 | margin-left: 0; 253 | border-top: 1px solid #eaeaea; 254 | counter-reset: footer-counter; 255 | list-style: none; 256 | color: #555; 257 | padding-left: 5%; 258 | margin: 20px 0; 259 | } 260 | 261 | .markdown ol#footnotes li { 262 | margin-bottom: 10px; 263 | margin-left: 16px; 264 | font-weight: 400; 265 | line-height: 2; 266 | list-style-type: none; 267 | } 268 | 269 | .markdown ol#footnotes li:before { 270 | content: counter(footer-counter) ". "; 271 | counter-increment: footer-counter; 272 | font-weight: 800; 273 | font-size: .95em; 274 | } 275 | 276 | @keyframes highfade { 277 | 0% { 278 | background-color: none; 279 | } 280 | 281 | 20% { 282 | background-color: yellow; 283 | } 284 | 285 | 100% { 286 | background-color: none; 287 | } 288 | } 289 | 290 | @-webkit-keyframes highfade { 291 | 0% { 292 | background-color: none; 293 | } 294 | 295 | 20% { 296 | background-color: yellow; 297 | } 298 | 299 | 100% { 300 | background-color: none; 301 | } 302 | } 303 | 304 | .markdown a:target, 305 | .markdown ol#footnotes li:target, 306 | .markdown sup a:target { 307 | animation-name: highfade; 308 | animation-duration: 2s; 309 | animation-iteration-count: 1; 310 | animation-timing-function: ease-in-out; 311 | -webkit-animation-name: highfade; 312 | -webkit-animation-duration: 2s; 313 | -webkit-animation-iteration-count: 1; 314 | -webkit-animation-timing-function: ease-in-out; 315 | } 316 | 317 | .markdown a:target { 318 | border: 0; 319 | outline: 0; 320 | } 321 | 322 | .dark code { 323 | background-color: white !important; 324 | } 325 | .dark pre { 326 | background-color: white !important; 327 | } -------------------------------------------------------------------------------- /components/Layout/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | 3 | const Layout = ({ children }) => { 4 | useEffect(() => { 5 | const currentTheme = window.localStorage.getItem('theme'); 6 | // On page load or when changing themes, best to add inline in `head` to avoid FOUC 7 | if (currentTheme === 'dark' || (!(currentTheme) && window.matchMedia('(prefers-color-scheme: dark)').matches)) { 8 | document.documentElement.classList.add('dark') 9 | } else { 10 | document.documentElement.classList.remove('dark') 11 | } 12 | }, []) 13 | 14 | return ( 15 |
16 | {children} 17 |
18 | ) 19 | } 20 | 21 | export default Layout -------------------------------------------------------------------------------- /components/List/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import Link from 'next/link' 3 | import Toggle from 'react-toggle' 4 | import ListItem from '../ListItem' 5 | import "react-toggle/style.css" 6 | import GithubIcon from '../../assets/images/github.svg' 7 | 8 | const useTheme = () => { 9 | const [theme, setTheme] = useState('light') 10 | 11 | useEffect(() => { 12 | const storageTheme = window.localStorage.getItem('theme'); 13 | if (storageTheme === 'dark' || (!storageTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) { 14 | setTheme('dark'); 15 | } 16 | }, []) 17 | 18 | const handleTheme = (value) => { 19 | setTheme(value) 20 | localStorage.setItem('theme', value) 21 | if (value === 'dark') { 22 | document.documentElement.classList.add('dark') 23 | } else { 24 | document.documentElement.classList.remove('dark') 25 | } 26 | } 27 | 28 | return { theme, handleTheme } 29 | } 30 | 31 | const List = ({ data }) => { 32 | const { theme, handleTheme } = useTheme(); 33 | return ( 34 | <> 35 |
36 |

Overfronted

37 |
38 | 39 | 40 | 41 | handleTheme(e.target.checked ? 'dark' : 'light')}> 42 |
43 | 48 | 49 | ) 50 | } 51 | 52 | export default List -------------------------------------------------------------------------------- /components/ListItem/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | import cs from 'classnames'; 4 | import IconStyles from '../../styles/icon.module.css' 5 | 6 | const tagMap = { 7 | 'css': 'css', 8 | 'js': 'js', 9 | 'javascript': 'js', 10 | 'react': 'react', 11 | 'blog': 'blog', 12 | 'git': 'git' 13 | } 14 | 15 | function getTagNames(tagStr) { 16 | const tags = tagStr.split(',') || ['blog']; 17 | const firstTag = tagMap[tags[0].toLowerCase()] || 'blog' 18 | return [ 19 | `icon-${firstTag}`, 20 | `icon${firstTag.replace(/^\w/, (w) => w.toUpperCase())}` 21 | ] 22 | } 23 | 24 | const ListItem = ({ data }) => { 25 | const { id, title, date, desc } = data; 26 | return ( 27 |
  • 28 |
    29 |
    IconStyles[tag]))}>
    30 |
    31 | 32 |

    33 | {title} 34 |

    35 | 36 |
    {date}
    37 |
    38 |
    39 |

    {desc}

    40 |
  • 41 | ) 42 | } 43 | 44 | export default ListItem -------------------------------------------------------------------------------- /components/date.js: -------------------------------------------------------------------------------- 1 | import { parseISO, format } from 'date-fns' 2 | 3 | export default function Date({ dateString }) { 4 | const date = parseISO(dateString) 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /lib/posts.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import matter from 'gray-matter' 4 | 5 | const postsDirectory = path.join(process.cwd(), 'posts') 6 | 7 | export function getSortedPostsData() { 8 | // Get file names under /posts 9 | const fileNames = fs.readdirSync(postsDirectory) 10 | const allPostsData = fileNames.map(fileName => { 11 | // Remove ".md" from file name to get id 12 | const id = fileName.replace(/\.md$/, '') 13 | 14 | // Read markdown file as string 15 | const fullPath = path.join(postsDirectory, fileName) 16 | const fileContents = fs.readFileSync(fullPath, 'utf8') 17 | 18 | // Use gray-matter to parse the post metadata section 19 | const matterResult = matter(fileContents) 20 | 21 | // Combine the data with the id 22 | return { 23 | id, 24 | ...matterResult.data 25 | } 26 | }) 27 | // Sort posts by date 28 | return allPostsData.sort((a, b) => { 29 | if (a.date < b.date) { 30 | return 1 31 | } else { 32 | return -1 33 | } 34 | }) 35 | } 36 | 37 | export function getAllPostIds() { 38 | const fileNames = fs.readdirSync(postsDirectory) 39 | return fileNames.map(fileName => { 40 | return { 41 | params: { 42 | id: fileName.replace(/\.md$/, '') 43 | } 44 | } 45 | }) 46 | } 47 | 48 | export async function getPostData(id) { 49 | const fullPath = path.join(postsDirectory, `${id}.md`) 50 | const fileContents = fs.readFileSync(fullPath, 'utf8') 51 | 52 | // Use gray-matter to parse the post metadata section 53 | const matterResult = matter(fileContents) 54 | 55 | // Use remark to convert markdown into HTML string 56 | // const processedContent = await remark() 57 | // .use(html) 58 | // .process(matterResult.content) 59 | // const contentHtml = processedContent.toString() 60 | 61 | // Combine the data with the id and contentHtml 62 | return { 63 | id, 64 | content: matterResult.content, 65 | ...matterResult.data 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | export const escapeHTML = str => 3 | str 4 | .replace(/&/g, '&') 5 | .replace(/>/g, '>') 6 | .replace(/ { 11 | let count = 0; 12 | let output = data; 13 | do { 14 | output = output.replace(regexp, replacer); 15 | count = 0; 16 | while (regexp.exec(output) !== null) ++count; 17 | } while (count > 0); 18 | return output; 19 | }; 20 | 21 | /** Optimizes all nodes in an HTML string. 22 | * @param {string} html - The HTML string to be optimized. 23 | */ 24 | export const optimizeAllNodes = html => { 25 | let output = html; 26 | // Optimize punctuation nodes 27 | output = optimizeNodes( 28 | output, 29 | /([^\0<]*?)<\/span>([\n\r\s]*)([^\0]*?)<\/span>/gm, 30 | (match, p1, p2, p3) => 31 | `${p1}${p2}${p3}` 32 | ); 33 | // Optimize operator nodes 34 | output = optimizeNodes( 35 | output, 36 | /([^\0<]*?)<\/span>([\n\r\s]*)([^\0]*?)<\/span>/gm, 37 | (match, p1, p2, p3) => `${p1}${p2}${p3}` 38 | ); 39 | // Optimize keyword nodes 40 | output = optimizeNodes( 41 | output, 42 | /([^\0<]*?)<\/span>([\n\r\s]*)([^\0]*?)<\/span>/gm, 43 | (match, p1, p2, p3) => `${p1}${p2}${p3}` 44 | ); 45 | return output; 46 | }; -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack(config) { 3 | config.module.rules.push({ 4 | test: /\.svg$/, 5 | use: ['@svgr/webpack'], 6 | }); 7 | 8 | return config; 9 | }, 10 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "classnames": "^2.3.1", 12 | "date-fns": "^2.11.1", 13 | "gray-matter": "^4.0.2", 14 | "next": "^11.1.1", 15 | "prism-react-renderer": "^1.2.1", 16 | "react": "17.0.1", 17 | "react-dom": "17.0.1", 18 | "react-markdown": "^6.0.2", 19 | "react-syntax-highlighter": "^15.4.3", 20 | "react-toggle": "^4.1.2", 21 | "remark": "^13.0.0" 22 | }, 23 | "devDependencies": { 24 | "@svgr/webpack": "^6.2.1", 25 | "autoprefixer": "^10.4.4", 26 | "postcss": "^8.4.12", 27 | "tailwindcss": "^3.0.23" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/global.css' 2 | 3 | export default function App({ Component, pageProps }) { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | export default (req, res) => { 2 | res.status(200).json({ text: 'Hello' }) 3 | } 4 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Layout from '../components/Layout' 3 | import { getSortedPostsData } from '../lib/posts' 4 | import List from '../components/List' 5 | 6 | export default function Home({ allPostsData }) { 7 | return ( 8 |
    9 | 10 | 11 | overfronted 12 | 13 | 14 | 15 |
    16 | ) 17 | } 18 | 19 | export async function getStaticProps() { 20 | const allPostsData = getSortedPostsData() 21 | return { 22 | props: { 23 | allPostsData 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pages/posts/[id].js: -------------------------------------------------------------------------------- 1 | import Layout from '../../components/Layout' 2 | import { getAllPostIds, getPostData } from '../../lib/posts' 3 | import Head from 'next/head' 4 | import Detail from '../../components/Detail' 5 | 6 | export default function Post({ postData }) { 7 | return ( 8 |
    9 | 10 | 11 | {postData.title} 12 | 13 | 14 | 15 |
    16 | ) 17 | } 18 | 19 | export async function getStaticPaths() { 20 | const paths = getAllPostIds() 21 | return { 22 | paths, 23 | fallback: false 24 | } 25 | } 26 | 27 | export async function getStaticProps({ params }) { 28 | const postData = await getPostData(params.id) 29 | return { 30 | props: { 31 | postData 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /posts/CSS内联元素深入理解.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "CSS内联元素深入理解" 3 | date: "2019-05-04" 4 | tags: css 5 | author: xwchris 6 | desc: "在前端开发中,似乎控制不同大小文字之间,或者文字与图标之间对齐总是不那么得心应手,总觉得少了点什么,这其中其实跟CSS中几种跟内联元素相关的属性有很大的关系,从我的感觉看,平时遇到的30%的css问题都是由于搞不清这几种属性的关系有关" 7 | --- 8 | 9 | 首先我们要从介绍字体开始,掌握相关概念。 10 | 11 | 技术是以目的为导向的,那么先来抛出一个问题,前端开发中我们使用`font-size`来设置文字的大小,但检查元素后发现内联元素的高好像大多数时间并不是我们设置的`font-size`大小,这是为什么哪? 12 | 13 | 这里面涉及到浏览器的影响,字体的设计等方面的知识。尤其是字体设计是一门高深的学问,而我们只是为了开发网页中控制文字大小和对齐,所以这里我们只取我们需要的部分,下面先介绍字体概念。 14 | 15 | ## 字体相关概念 16 | 我们先来了解文字的各个概念。图看起来更直观,所以我简单画了个图。 17 | 18 | ![645884385-5ad4b84063b4f_articlex](https://user-images.githubusercontent.com/13817144/38925840-52a2c0c6-4333-11e8-9cd8-297749724fa6.png) 19 | 20 | ### 基线 21 | 对照着图,可以看出基线是位于字母`x`下方的那条线,所有字符都基于这条线进行定位。这应该大家比较常见,毕竟`vertical-align`的默认值就是`baseline`,指的就是这条线。 22 | 23 | ### em-box 24 | `em-box`可能比较少见,但大家一定知道单位`em`。`1em`跟该元素的`font-size`大小相同。还是以图为例如果设置`font-size: 20px`,那么`em-box`的高度就为`20px`。 25 | 26 | ### content-area 27 | 这是一个很重要的概念,`content-area`翻译成中文是内容区域,这里图中没有标出,因为通常默认情况下,`content-area`与`line-height`是一样高的。给一个内联元素设置背景,这背景所占的区域就可以看成内容区域。 28 | 29 | 大多数情况下`content-area`于`em-box`的高度不同,`content-area`的高度受到`font-family`和`font-size`的影响,有时候即使`font-size`一致可能只是因为字体不同导致`content-area`也不一样,这是在字体设计的时候就决定了的。而`em-box`只受`font-size`影响。这也就解释了为什么我们有时候检查元素看到的高度与实际字体设置大小不一样的问题。 30 | 31 | ### 半行距 32 | 先看行距怎么算,`行距 = line-height - font-size`。半行距就是将行距一分为二,一份加在`em-box`的上面,一份加在下面,就构成了完整的文字高度。 33 | 34 | ### x-height 35 | `x-height`顾名思义就是`x`字符高度的意思,`1ex`就是`x-height`的值,通常没什么作用,但是对于我们理解概念有很大帮助,css中`vertical-align: middle`的解释就是Aligns the middle of the element with the baseline plus half the x-height of the parent,意思就是将该元素的中心点与父元素基线加上`x-height`高度的一半对齐。 36 | 37 | 这句话里有几个关键的部分一定要弄明白`该元素的中心点`、`父元素的基线`和`基线加上x-height高度的一半`,我们常常使用`veritcal-align: middle`出错,往往就是这几个点没有搞清楚,这里先试着理解,以后的文章会详细说明。 38 | 39 | 40 | 以下部分是介绍字体单位和一些理解,如果已经了解可以直接跳过,别浪费时间,时间宝贵~ 41 | 42 | ## 度量单位 43 | 字体的大小在css中用`font-size`来表示,它的度量单位有很多,主要分为三大类:关键词类型、数值类型、百分比类型。 44 | 45 | ### 关键词类型 46 | 关键词类型分为绝对尺寸和相对尺寸。由于使用的较少,不必记忆,所以这里仅仅罗列出来。 47 | 48 | 绝对尺寸:`xx-small`、`x-small`、`small`、`medium`、`large`、`x-large`、`xx-large`。 49 | 50 | 相对类型:`smaller`、`larger`。 51 | 52 | ### 数值类型 53 | 数值类型在平时使用较多,而数值类型的单位常用的有`px`、`em`和`rem`,可能偶尔会用到`ex`其他基本用不到,不再列举。`px`表示像素,`em`是相对于父元素的字体进行计算,而`rem`则是相对于根元素``的字体大小进行计算。 54 | 55 | ## 其他应用 56 | 57 | ### 神奇的font-size: 0 58 | `font-size: 0`这种写法很常用,如果出现不能调整为0的情况,可以在浏览器设置里面进行调整。使用`font-size: 0`让字体大小变为0,通常也会使得`line-hight`变为0,这经常可以解决`line-block`元素出现的空隙问题,这个问题具体的原因我们后面再说。 59 | 60 | 看了很多文章最后总结和整理了这些,下一篇介绍`line-height`说说`line-height`在内联元素中的作用。在这篇文章中如有错误或我与大家理解不一致的地方,欢迎指出,感谢~[原文链接](https://github.com/xwchris/blog/issues/13)。 61 | 62 | 刚才我们讨论了`font-size`,下面来说另一个与文字关系密切的属性`line-height`。先来说下内联盒模型概念,然后是度量单位,最后来聊一个常见的内联元素的现象。 63 | 64 | ## 内联盒模型相关概念 65 | 说到盒模型,只要了解css的基本都知道这个概念,但说到内联盒模型可能很多人不是特别清楚。现在我们就来说说内联盒模型几个重要的概念。先看图概览一下,再逐项往下看。 66 | 67 | ![image](https://user-images.githubusercontent.com/13817144/38943440-024647de-4364-11e8-8fcd-a2037a045608.png) 68 | 69 | Tip: 图中用不同颜色的框标注了很多框,这些代表我们下面要说的不同的盒子或不同的概念。 70 | 71 | ### inline-box 72 | `inline-box`又名内联盒子,通常由一些标签包裹形成,最常用的如``标签包裹文字会形成内联盒子,那些没有标签包裹的文字默认自己形成一个盒子称为`anonymous inline box`匿名内联盒子。 73 | 74 | ### line-box 75 | `line-box`名为行框,从名字就可以看出,它是由单行内联元素形成的一个区域,注意是每一行都会形成,如果文字由五行,就会形成5个行框。行框的高度基本上是由行框中行高最大的内联盒子决定的。我使用基本上这个词,是因为还有其他情况,比如受到`vertical-align`属性的影响。 76 | 77 | ### containing-box 78 | `container-box`就是包含块的意思,在内联元素中,包含块是由行框组成的。说白了就是包裹在所有行框外面的那层盒子。 79 | 80 | ### struct 81 | 这个词可能很多同学见的比较少,张鑫旭的文章里称之为”幽灵空白节点“,我们这里就用其英文名称`struct` 82 | 83 | 在CSS规范中有这么一句话 84 | 85 | > Each line box starts with a zero-width inline box with the element's font and line-height properties. We call that imaginary box a ”struct" 86 | 87 | 意思就是每一个行框开始的位置都有一个宽度为0,并且行高和字体大小都与该元素相同的内联盒子。这个假想的内联盒子就被称为struct。简单可以理解为行框前面的一个宽为0的空字符。 88 | 89 | 可能有的人会问,这个东西有什么用那?(⊙v⊙)嗯,用处大了去了,由于其几乎无处不在的特性并且由于宽度为0,我们平时在内联元素中遇到的很多奇怪的问题都是由`struct`引起的。我们暂时先放下这个,文章的最后我们再看这个东西引发的问题。 90 | 91 | ## 度量单位 92 | 这一部分说说行高的单位,我们不再列出所有的单位,因为这些完全可以从官方文档上看到,我们这里只说说重要的或者说容易错的部分。 93 | 94 | ### 默认值 95 | `line-height`的默认值是什么,查下手册我们就能很容易的看到,它的值是`normal`,那么`normal`是多大?从我的理解来说,它的值受到`font-family`和浏览器的影响,IE/Firfox与chorme表现存在部分不一样的地方。但可以知道的是`normal`的初始值与字体的`content-area`的区域高度相同。不了解`content-area`的同学可以去看我的[上一篇](https://github.com/xwchris/blog/issues/13)文章的`line-height`部分。这里简单说一下,`inline`水平的元素设置背景后,背景部分就是内容区域的部分,与初始`line-height`同高。 96 | 97 | ### 百分比、em和数值的区别 98 | 百分比和em与数值的区别在于继承性质上的不同,百分比和em被继承的是计算后的值,而数值类型的继承的是一种“计算规则”。 99 | 100 | ## 常见问题 101 | 这一部分我们来说一说常见的内联元素的一些问题。 102 | 103 | ### display: inline-block元素间的空隙 104 | 这个问题,大家都遇到过,大家可以看看我做的[实例](https://codepen.io/xwchris/pen/deoLwO)的第一部分。首先我们会看到元素和元素之间的一定的间隙。这个间隙会引发的问题有,如果我们设置两个`display: inline-block`的元素宽度为`width: 50%`都向右或向左浮动,但是确无法排在一行的情况。这就是由于多出了这一点间隙,导致容器宽度不足,最后容器一行无法放不下两个宽度都为容器一半宽度的元素。 105 | 106 | 有人可能觉得这些元素间的间隙比较奇怪,但这是一种正常现象,因为内联元素本来的排版就应该有间隙,你能够想象文字和文字之间没有间隙的情况吗?内联元素之间本来就应该独立分开。怎么解决这个问题,网上也有很多答案,这里只简单提一下,因为我们主要说的是原理嘛。通过设置`font-size`为0,或者使用`letter-spacing`属性,都可以达到去除内联元素之间的间隙的目的。 107 | 108 | 在我[实例](https://codepen.io/xwchris/pen/deoLwO)第一部分中应该很多人也注意到,在父容器的最底端也有一个空隙,这个到底是什么那?还记得我上面概念中提到的`struct`吗,这种现象就是由这个东西引起的,由于其不可见,所有我们用一个内联元素`x`来代替。这里可以看[实例](https://codepen.io/xwchris/pen/deoLwO)的第二部分。这个现象的原因是由于内联元素是默认`vertical-align: baseline`对齐的,而我们的内联元素的基线是默认为元素的底边缘,`sturct`又有自己的行高,当它的基线与我们的内联元素对齐时,它的行高会撑起一部分距离,就出现了实例中的现象。 109 | 110 | 知道了原因,我们解决起来也很简单。只要`struct`元素的行高为0就可以了,所以我们可以设置父元素的`line-height: 0`或者通过设置`font-size: 0`来间接设置`line-height`为0。可能已经有人发现,`font-size: 0`可以同时解决元素间的间隙和底部的间隙,所以我更喜欢用这个方法。 111 | 112 | 下面我们做一下其他的操作,我们在[实例](https://codepen.io/xwchris/pen/deoLwO)第三部分中,前面三个`inline-block`元素中加上文字,发现元素都往下移动了,这又是什么原理那。这其中就涉及到了`vertical-align`属性的一些性质,下一部分就来讲讲`vertical-align`,并且来说说这种现象的原因。 113 | 114 | 从头到尾,我们都试图在梳理清楚内联元素中一些较为重要的概念,其中一个很主要的目的,就是为了解决内联元素的对齐问题。对于很多前端开发来说,对齐问题一直是一个非常常见,但有时候又让我们感到非常困惑的问题。在这篇文章里我会用最简洁的解释来解释内联元素到底是怎么回事。这篇换个形式,我们用几个问答来说明关键问题。 115 | 116 | ## 内联元素的对齐主要由哪些属性影响 117 | 我们以下所说的对齐主要是针对内联元素的。我们都知道CSS最后展现在界面中的结果,经常是由多个属性共同作用的结果。对齐主要是由`font-size`,`line-height`和`vertical-align`这三个属性共同作用的结果,这也是为什么我分了三篇来讲。 118 | 119 | ## 内联水平元素的baseline是什么 120 | `vertical-align`属性有非常多的值,它的默认值是`baseline`即相对于基线对齐。基线的概念我们在[font-size](www.baidu.com)那部分也说过,那里解释的是文字的基线。那么对于一个`display: inline-block`,并且拥有固定宽和高的元素来说,它的基线是什么那?我可能问了一个很蠢的问题,很多人应该都知道,它的基线就是其`margin-box`即该元素的底部边界(包括margin)。如图所示, 121 | 122 | ![image](https://user-images.githubusercontent.com/13817144/55063668-71324a00-50b3-11e9-8f87-906d552a3eff.png) 123 | 124 | 但是如果在该元素中其中加入文字,它的基线还是底边界吗?实践出真知,我们还是用[实例](https://codepen.io/xwchris/pen/vjrgrE)来看下。 125 | 126 | ![image](https://user-images.githubusercontent.com/13817144/39965395-77358518-56ca-11e8-8724-c04ba573e304.png) 127 | 128 | 这种情况这个在css2.1规范里有提及 129 | 130 | > The baseline of an 'inline-block' is the baseline of its last line box in the normal flow, unless it has either no in-flow line boxes or if its 'overflow' property has a computed value other than 'visible', in which case the baseline is the bottom margin edge 131 | 132 | 意思就是`inline-block`如果有`overflow`非`visible`的属性或者没有其中任何行盒那么它的基线就是底部的margin边界否则的话它的基线就是其中最后一个行盒的基线。(tip: 行盒的概念在第一篇文章中也有介绍)。 133 | 134 | ## 实例 135 | 下面我们要实现一个定宽高的div在容器中垂直水平居中的效果,当然有很多方法,我们这里只利用内联元素的特性来实现,来说明内联元素的对齐原理,[点我看实例](https://codepen.io/xwchris/pen/gzKmMR)。 136 | 137 | 先来看实例中的第一个,我们将box元素设置为`inline-block`水平,然后使用`text-align: center`让其水平居中,(base元素中x用来辅助观察父容器的基线位置,实际应用中不应该存在),然后将box的`text-align`属性设置为`vertical-align: middle`。emm...,然而并没有垂直居中,这是为什么哪? 138 | 139 | ![image](https://user-images.githubusercontent.com/13817144/39965940-69337668-56d5-11e8-8efe-449df111159b.png) 140 | 141 | 这里我们首先要知道`vertical-align: middle`是相对于谁来对齐,前面的文章也提到过,`vertical-align: middle`是会将元素的中心点与父容器基线向上1/2`x-height`的位置对齐。在我们第一个例子中,随着我们设置`vertical-align`的值,父容器的基线位置在不停的变化。由于我们要实现垂直居中的效果,所有我们需要父容器的的基线能够在容器的中央。 142 | 143 | 现在我们需要使用一个辅助元素将父容器的基线撑开,这里使用父容器的`:after`伪元素来做一个辅助元素,具体看实例的第二个设置其高与父容器一致,并设置其`veritcal-align: middle`。这样父容器的基线就会在容器垂直正中央偏下1/2`x-height`的位置,box的中心线与基线加上1/2的位置对齐,正好在中央。现在垂直居中已经实现了。 144 | 145 | ![image](https://user-images.githubusercontent.com/13817144/39965944-7563cf50-56d5-11e8-9545-f9591ab0b5a3.png) 146 | 147 | 但是我们这里还有一个问题,仔细观察会发现该box元素水平并没有完全水平居中。给辅助元素设置了`1px`宽度和背景颜色后我们很容易看出问题原因,这是由于内联元素间的间隙造成的。要解决这个间隙,我们使用最简单的方法,设置父容器的`font-size: 0`,最后我们的水平垂直居中效果如实例中第三个所示。 148 | 149 | ![image](https://user-images.githubusercontent.com/13817144/39965947-8a74194a-56d5-11e8-9b84-508161afc065.png) 150 | 151 | ## 总结 152 | 到这篇文章为止,关于内联元素几个比较重要的属性以及内联元素对齐的知识也差不多讲完了,可能里面还有一些很细节的部分没有说到,这就要大家以后多多发挥google的功效了。 153 | 154 | 初始学习css,感觉css是一个非常简单的东西,一天就能用css写出一些网页效果。但当慢慢深入的时候,会发现这是一个非常非常复杂的东西,这才是真正实践了什么叫”入门容易,精通难“。这是我自己学习css到现在一个最大的感受。总之,css学习之路还有很长,前端之路学习之路还有很长。当真正能够独立解决并解释所有别人提出的所以前端方面的问题,或许是前端无敌吧。又写了些废话,突然有感而发,这里与诸君共勉之~,共同努力 155 | 156 | -------------------------------------------------------------------------------- /posts/CSS基础整理.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "CSS基础整理" 3 | date: "2017-08-04" 4 | tags: css 5 | author: xwchris 6 | desc: "读CSS权威指南记录下的知识点,待以后方便查阅,回忆,学习" 7 | --- 8 | 9 | ## CSS和文档 10 | - 文档一定要有一个内部结构,而且这与视觉结构完全是两码事。页面应当博爱韩有某种结构含义的信息。 11 | - 为了成功加载一个外部样式表,`link`必须放在`head`元素中,但不能放在其他元素内部。 12 | - `link`属性中的`rel`代表关系,关系为`stylesheet`。type表示类型总是`text/css`。这个值描述了使用`link`标记加载的数据类型。`href`是样式表的URL。`media`属性使用`all`说明这个样式表要应用于所有的表现媒体 13 | - `@import`用于指示Web浏览器加载一个外部样式表,并在表现HTML文档时使用样式。与`link`的区别是他必须放在`style`中,也就是要放在其他css规则之前,否则根本不起作用。 14 | ``` 15 | @import url(sheet.css); 16 | ``` 17 | ## 选择器 18 | - 简单属性选择器和具体属性选择器。简单属性选择器根据是否有该属性来选择某个元素。语法为`ele[attribute]`。具体属性选择器可以只选择有特定属性的元素。语法为`ele[class="value"]` 19 | ``` 20 | //简单属性选择器 21 | h1[class] {color: silver;} 22 | //具体属性选择器 23 | h1[class="warning"] {color: silver} 24 | ``` 25 | - 根据部分属性选择。它可以匹配属性中的属性来选取元素。 26 | ``` 27 | p[class~="warning"] {font-weight: bold;} 28 | ``` 29 | 这个例子可以匹配`

    `,波浪号根据属性值中出现的一个用空格分割的词来完成原则。如果忽略该波浪号,就需要完成完全值匹配。 30 | ### 子串匹配属性选择器 31 | 32 | 类型 | 描述 33 | ---|--- 34 | [foo^="bar"] | 选择foo属性值以"bar"开头的所有元素 35 | [foo$="bar" | 选择foo属性值以"bar"结尾的所有元素 36 | [foo*="bar"] | 选择属性值中包含子串"bar"的所有元素 37 | ## 结构和层叠 38 | - 当css声明冲突的时候,特殊性高的优先显示。特殊性有相应的规则和计算方法。内联样式比其他的声明都高。同时重要声明`!important`超过了超过了所有其他声明。该标志一般放在样式声明的最后。 39 | - 继承。基于继承机制,样式不仅应用到指定的元素,还会应用到它的后代元素。样式会向下传递,绝不会向上传递,但有一个例外,在HTML中,对于向上传播规则有一个例外:应用到body元素的背景样式可以传递到html元素(html是文档的根元素),相应地可以定义其画布。有些属性是无法继承的比如边框和外边距,内边距。而且继承无特殊性,就是比0还要低。这也就解释了当设置``标签之外的父容器的字体颜色时,``标签的颜色不会变,这是因为用户代理的超链接占上风。 40 | - CSS2.1层叠规则: 41 | 1. 找出所有相关的规则,这些规则都包含与一个既定元素匹配的选择器。 42 | 2. 读者的重要声明 > 创作人员的重要声明 > 创作人员的正常声明 > 读者的正常声明 > 用户的代理声明 43 | 3. 按特殊性给应用到的给定元素排序。有较高特殊性的元素权重要大于有较低特殊性的元素 44 | 4. 按出现顺序对应用到既定元素的所有声明排序。一个声明在样式表或文档中越后出现,它的权重就越大。如果样式表中有导入的样式表,一般认为出现在导入样式表中的声明在前,主样式表中所有声明在后。 45 | - 由于顺序排序所以推荐的链接样式顺序为link-visited-hover-active(LVHA)的顺序声明链接样式。 46 | ## 值和单位 47 | - Web安全颜色是指,在256色计算机系统上总能避免抖动的颜色,Web安全颜色的简写十六进制值是0,3,6,9,C和F;因此,`#693`、`#0C6`和`#F0F`都是Web安全颜色的例子。 48 | - 长度单位分为绝对长度单位和相对长度单位,绝对长度单位有英寸(in)、厘米(cm)、毫米(mm)、点(pt)、排卡(pc)。相对单位有em, ex,px。常用的为相对单位,如果一个元素的`font-size`为14像素,那么对于该元素1em就等于14像素。px就是像素。常用的基本就是这两个。 49 | - inherit。inherit关键字使一个属性的值与其父元素的值相同。在大多数的情况下,大多数属性会自然继承,不过在某些情况下还是很有用的。 50 | - css2单位。角度值用于定义给定的声音从哪个位置发出。共有三种角度:度(deg)、梯度(grad)、和弧度(rad)。时间值秒(s)、毫秒(ms)。频率值赫兹(Hz)、或兆赫(MHz)。css2和css2.1规范都要求URI要以url(...)形式声明。 51 | ## 字体 52 | - 建议在所有`font-family`规则中都提供一个通用字体系列。这样一来,就提供了一条后路,在用户代理无法提供与规则匹配的特定字体时,就可以选择一个候选字体。如果字体名称有空格则需求加入单引号,如`'New Century'`。 53 | - `font-weight`取的值有 54 | 55 | 值 | 说明 56 | ---|--- 57 | normal | 标准粗细 58 | bold | 粗体 59 | bolder | 更粗的字体 60 | lighter | 更细的字体 61 | 100,200,300,400,500,600,700,800,900 | 一般400为normal,700为bold,规则复杂 62 | - 字体大小继承继承的是计算后的大小而不是百分比。 63 | - `font-style`的默认值是normal。`italic`是斜体,`oblique`是倾斜。 64 | - `font-variant`用来提供字体变形,除了`normal`值可以使用`small-caps`来使用小型大写字母文本。这种效果如果单词是大写则会更大,如果单词问小写则变为大写。如果不存在则全部大写。 65 | - `font`是所有字体元素属性的整合。font的前三个值为`font-style`、`font-weight`、`font-variant`的顺序可以随意,值为`normal`可以忽略,但是`font-size`和`font-family`必须为最后两个值,且他们必须存在,如果少了其中一期则整个规则都是无效的,很可能被用户代理完全忽略。同时`line-height`可以设置在`font`中作为对`font-size`的一个补充,并用一个斜线(/)与之分隔。 66 | ## 文本属性 67 | - `text-indent`文本缩进,设置百分数设置的是父元素宽度的百分数。 68 | - `text-align`的`justify`用来两端对齐。即每行文本的两端和父容器的左右边缘对齐。`middle`可以使文本居中它影响的只是内容并不会影响元素。同时`middle`可以使内联元素如``和``水平居中。 69 | - `line-height`的`normal`值通常为字体大小的1.2倍,百分数是相对于字体大小来说的。当用em来设置`line-height`的时候可能会出现问题,因为子元素会从父元素继承,除了显示给每个子元素指定行高外还有一个更加简单的办法,那就是制定一个数,由它设置缩放因子。各个元素会根据自己的`font-size`计算`line-height` 70 | - `vertical-align`属性不能继承。它作用于行内元素和替换元素。它不能影响块级元素中的内容对齐。由于该属性能够影响表单元格中元素的垂直对齐,所以可以利用让包含块`display:table-cell`结合`vertical-align:middle`来让图片垂直居中显示。[demo](http://www.zhangxinxu.com/study/201005/verticle-align-test-demo.html) 71 | 72 | 值 | 描述 73 | ---|--- 74 | top | 将元素行内框的顶端与包含该元素的行框的顶端对齐 75 | bottom | 将元素行内框的底端与包含该元素的行框的底端对齐 76 | text-top | 将元素行内框的顶端与父容器内容区的顶端对齐 77 | text-bottom | 将元素行内框的底端与父容器内容区的底端对齐 78 | middle | 将元素行内框的垂直中点与元素内容区的底端对齐 79 | super | 将元素的内容区和行内框上移。上移的距离未指定,可能因用户代理的不同而不同。 80 | sub | 与super相同,只不过元素会下移而不是上移 81 | | 将元素上移或下移一定距离,这个距离由相对于元素`line-height`值指定一个百分比确定。 82 | 83 | - `word-spacing`用来增加字之间的间隔。`letter-spacing`用来增加字母之间的间隔。他们默认为`normal`值就是0。`word-spacing`的值可能会受`text-align`属性值的影响。如果一个元素是两端对齐的,字母和字之间的空间可能会调整,以便文本在整行中正好放下。如果为`letter-spacing`指定一个长度值,字符间隔不会受`text-align`的影响,但是如果`letter-spacing`的值是`normal`那么字符间的间隔就可能改变,以便使文本两端对齐。CSS没有指定应当如何计算间隔,所以用户代理只是将其填满。一个元素的子元素会继承该元素的计算值。如果字母间隔与文本大小成比例,得到字母间隔的唯一办法就是显示地设置。 84 | - `text-transform`用来进行文字转换`none`保持原状,`upppercase`全部大写`lowercase`全部小写`capitalize`只对每个单词的首字母大写。 85 | - `text-decoration`默认为`none`,`underline`会对元素加下划线`overline`会加上划线,`line-through`会在文本中间画一条贯穿线。`blink`会让文本闪烁,该属性有的浏览器不支持。该属性不会被子元素继承,虽然看起来很怪,但这是创作人员希望的。 86 | - `text-shadow`定义了文本的阴影效果,颜色可以写在后面或前面,其他三个值为向右偏移距离,向下偏移距离,模糊半径。这三个顺序不能乱。 87 | - `white-space`属性 88 | 89 | 值 | 空白符 | 换行符 | 自动换行 90 | ---|---|---|--- 91 | pre-line | 合并 | 保留 | 允许 92 | normal | 合并 | 忽略 | 允许 93 | nowrap | 合并 | 忽略 | 不允许 94 | pre | 保留 | 保留 | 不允许 95 | pre-wrap | 保留 | 保留 | 允许 96 | - `direction`和`unicode-bidi`不常用。 97 | ## 基本视觉格式化 98 | - 内边距不能为负,而外边距可以。块级元素的宽度由左右外边距、左右内边距、边框宽度和内容宽度7个属性决定。 99 | - 如果没有设置边框颜色,那么边框将使用前景色即`color`属性的颜色。 100 | - 块级元素默认撑满容器,可以通过设置左右外边距来控制宽度。 101 | - 将两个外边距设置为auto是将元素居中的一种正确方法,这不同于使用`text-align`(`text-align`只应用于块级元素的内联内容,所以将元素的`text-align`设置为`center`并不能将这个元素居中) 102 | - 边框的宽度不能是百分数。 103 | - 替换元素的所有规则都适用于非替换元素,只有一个例外:如果`width`为`auto`元素的宽度则是内容的固有宽度。如果元素的宽或高被更改,那么另一属性将成比例改变。 104 | - 如果将一个元素的上、下外边距设置为`auto`,实际上他们都会充值为0,使元素框没有外边距。因此将元素垂直居中的唯一办法就是把上、下边距都设置为25%。不过这是在包含块的高度确定的情况下,如果没有显示声明包含块的高度,百分数高度会重置为`auto`。 105 | - 如果块级正常流元素的高度设置为`auto`,并且只有块级子元素,其默认高度将是从最高块级子元素的外边框边界到最低块元素边框之间的距离。因此,子元素的外边距会超出包含这些元素的元素。但是如果包含元素设置了上边框或者下边框,上内边距或下内边距,其高度则是从最后子元素的上外边距边界到最低子元素的下外边距边界之间的距离。 106 | - 垂直外边距合并。这种合并行为只会应用于外边距,如果元素有内边距和边框,它们绝不会合并。两个垂直外边距都为负则取其中绝对值较大的。如果是一个正一个负则会从正值中减去负值。 107 | - 行内元素基本术语。 108 | 1. em框:em框在字体中定义,也称为字符框。实际的字形可能比其em框更高或更矮。在CSS中,`font-size`的值确定了各个em框的高度。 109 | 2. 内容区:在非替换元素中,内容区可能有两种,CSS2.1规范允许用户代理选择其中任意一种。内容区可以使元素中个字符的em框穿在一起构成的框,也可以是由元素中字符字形的框。在替换元素中,内容区就是元素的固有高度再加上可能有的外边距、边框或内边距。 110 | 3. 行间距:行间距是`font-size`值和`line-height`值只差。这个差实际上要分为两半,分别应用到内容区的顶部和底部。毫不奇怪,为内容区增加的这两部分分别称为半间距。行间距值应用于非替换元素。 111 | 4. 行内框:这个框通过向内容区增加行间距来描述。对于非替换元素,元素行内框的高度刚好等于`lin-height`的值。对于替换元素,元素行内框的高度则恰好等于内容区的高度,因为行间距不应用到替换元素。 112 | 5. 行框:这是包含该行中出现的行内框的最高点和最低点的最小框。换句话说,行内框的上边界要位于最高行内框的上边界,而行框的底边要放在最低行内框的下边界。 113 | - CSS提供了一组行为和有用的概念。 114 | 1. 内容区类似于一个块级元素的内容框。 115 | 2. 行内元素的背景应用于内容区及所有内边距。 116 | 3. 行内元素的边框要包括内容区及所有内边距和边框。 117 | 4. 非替换元素的内边距、边框和外边距对行内元素或其生成的框没有垂直效果;也就是说,他们不会影响元素行内框的高度(也不会影响包含该元素的行框高度)。 118 | 5. 替换元素的外边距和边框确实会影响该元素行内框的高度,相应的,也可能影响包含该元素的行框高度。 119 | - 行内元素的边框边界由`font-size`而不是`line-height`控制,换句话说,如果一个`span`元素的`font-size`为12px,`line-height`为36px,其内容区就是12px高,边框将包围该内容区。或者可以为行内元素指定内边距,这会把边框从文本本身拉开,这个内边框并没有改变内容区的具体形状,类似的向一个行内元素增加边框也不会影响行框的生成和布局。 120 | - 实际上,外边距不会应用到行内非替换元素的顶端和底端,他们不影响行框的高度。不过,行内元素的两端则是另一回事。 121 | - 行内替换元素。一般认为行内替换元素有固有高度。因此有固有高度的替换元素可能会导致行框比正常要高。这不会改变行中任何元素的`line-height`值,包括替换元素本身。换句话说,会用替换元素整体(包括内容、外边距、边框、和内边距属性)来定义元素的行内框。`line-height`对图像的行内框没有任何影响。行内替换元素能有`line-height`是为了在垂直对齐时能正确的对齐。 122 | - 对于替换元素来说,内边距和边框确实会影响行框的高度,因为他们要作为行内替换元素的行内框的一部分(不同于行内非替换元素)。负外边距会师替换元素的行内框小于正常大小。负外边距会使替换元素的行内框小于正常大小。负外边距是使行内替换元素挤入其他行的唯一办法。 123 | - 在行内块元素内部,会像块级元素一样设置内容的格式。就像所有块级或行内替换元素一样,行内块元素也有属性`width`和`height`,如果比周围内容高,这些属性会使行高增加。 124 | - `display`的`run-in`。在css中不论元素是块元素还是行内元素都无关紧要,重要的是元素生成的框,而不是元素本身。设置`display:block`会生成一个块级框,设置`display:inline`则会生成一个行内框。如果一个元素生成`run-in`框,而且该框后面是一个块级框,那么该`run-in`元素将成为块级框开始处的一个行内框。 125 | ## 内边距、边框和外边距 126 | - 外边距和内边距的百分数都是相对于父元素的`width`计算的。 127 | - 值复制。CSS定义了一些规则,允许为外边距指定少于4个值。规则如下: 128 | 1. 如果缺少左外边距的值,则使用右外边距的值。 129 | 2. 如果缺少下外边距的值,则使用上外边距的值。 130 | 3. 如果缺少右外边距的值,则使用上外边距的值。 131 | - `border-style`用于设置边框样式值有none,hidden,dotted,dashed,solid,double,groove,ridge,inset,outset。一般情况下hidden等价于none,不过应用于表时除外,对于表,hidden用于解决边框冲突。同时边框样式都可以单独设置。 132 | - `border-width`用于设置边框宽度,它的值有thin,medium(默认),thick。 133 | ## 颜色和背景 134 | - `background-color`背景色默认为透明,所有背景属性都无法继承。 135 | - `background-image`的值为url(图片地址)。 136 | - `background-repeat`默认为平铺方式。它的值有repeat,repeat-x,repeat-y。 137 | - `background-position`将相对于元素的内边距边界放置原图像。但有的浏览器会仙姑低于外边框边界而不是内边框边界来放置原图片。它的值有left,center,right,top,bottom或者百分数和数值。百分会让图片对应的与父容器的点对齐,如设置为50% 50%会让图像中心和元素中心对齐。 138 | - 为了放置文档滚动的时候图片背景滚动可以使用`background-attachment`使用`fixed`值可以让背景保持不动,这样做的后果由两个一是原图像不会随文档滚动。其次,原图像的放置由可视区的大小确定而不是包含该图像的元素的大小(或在可视区中的位置)决定,而值如果是`scroll`则情况完全不同。值为`fixed`会使图像都从可视区左上角开始平铺而不是每个元素左上角,他们一样大只是他们都只在自己的区域可见,这就可以做出很有意思的[复螺旋变形](http://meyerweb.com/eric/css/edge/complexspiral/glassy.html) 139 | - `background`可以简写这些属性,没有顺序限制,但是如果`background-position`有两个值,它们必须一起出现,而且如果这两个值是长度或百分数值,则必须按水平值在前垂直值在后的顺序。如果省略了某个属性会自动填入其他默认值。 140 | ## 浮动和定位 141 | - 元素浮动的时候会以某种方式将浮动元素从文档的正常流中剔除,不过它还是会影响布局,它会使得其他内容环绕该元素。 142 | - 浮动元素周围的外边距不会合并。浮动元素会自动生成一个块级框,不论这个元素本身是什么。 143 | - 如果一个浮动元素在两个合并外间距之间,放置这个浮动元素时就好像在两个元素之间有一个块级父元素。另外浮动元素的顶端不能比之前所有浮动元素或块级元素的顶端更高。如果源文档中一个浮动元素之前出现另一个元素,浮动元素的顶端不能比包含该元素所生成框的任何行框的顶端更高。这三条规则都是为了放置浮动元素一直浮动到父元素顶端。在其他规则下,浮动元素应尽可能高的放置,向左右浮动尽可能远。 144 | - 当浮动元素与正常流元素重叠时: 145 | 1. 行内框与一个浮动元素重叠时,其边框、背景和内容都在该浮动元素“之上”显示。 146 | 2. 块框与一个浮动元素重叠时,其边框和背景在该浮动元素“之下”显示,而内容在浮动元素“之上”显示。 147 | - `clear`清除浮动,放在要清除浮动的元素上面,不允许左边或右面出现浮动元素,它就会另起新的一行。 148 | - `position`定位。`position`值的含义如下: 149 | 1. static:元素框正常生成。块级元素生成一个矩形框,作为文档流的一部分,行内元素则会创建一个或多个行框,置于其父元素中。 150 | 2. `relative`:元素框偏移某个距离。元素扔保持未定位前的形状,它原本所占据的空间扔保留。 151 | 3. `absolute`元素框从文档流完全删除,并相对于其包含块定位,包含块可能是文档中的另一个元素或者是包含块。元素原先在正常文档流中所占的空间就会关闭,就好像该元素原来就不存在一样。元素定位后生成一个块级框,而不论原来它在正常流中生成何种类型的框。 152 | 4. `fixed`:元素框的表现类似于将`position`设置为`absolute`,不过其包含块是视窗本身。 153 | 如果是`abosolute`则其包含块设置为最近的`position`值不是`static`的祖先元素(可以是任何类型),如果祖先为块级元素则设置包含块为该祖先元素的内边距。 154 | - `top`和`bottom`偏移百分数是以包含块高度为基准的,`left`和`right`偏移百分数是以包含块宽度为基础的。偏移定位的是元素的外边距边界。 155 | - 有时候通过使用`top`,`right`,`bottom`和`left`来描述元素四个边的放置位置,那么元素的高度和宽度将由这些偏移隐含确定。 156 | - `overflow`的`visible`,`hidden`,`scroll`和`auto`其中`auto`是由用户代理来确定的。可以以配合`clip`来指定裁剪区域它的值有`rect(top,right,botton,left)`和`auto`。它不是相对于边偏移的是相对于父元素左上角偏移的。 157 | - `visible`用来控制整个元素的可见性。`hidden`会使元素不可见但是仍然会影响文档布局,这与`display:none`是由区别的。`collapse`值在css表中显示使用。 158 | - 绝对元素可以通过设置上下外边距设置为auto来实现垂直居中的行为。 159 | ``` 160 |
    161 |
    162 |
    163 | ``` 164 | 水平方向当然也是可以的,与竖直方向同理。 165 | - 在水平布局中,如果值设置为`auto`,`right`或`left`都可以根据其静态位置放置。但在垂直布局中,只有`top`可以取静态位置,出于某种原因,`bottom`做不到。 166 | - 利用`z-index`可以改变元素相互覆盖的顺序。元素的所有后代相对于该祖先元素都有其自己的叠放顺序。对于默认值`auto`规范有如下说明:当前得方上下文中生成框的栈层次与其父框的层次相同,这个框不会建立新的局部叠放上下文。所有可以将`auto`处理为`z-index:0`。在css2.1中规定元素绝对不会叠放在其叠放上下文的背景之下,也就说还在不能叠放在它的直接父亲下,但可以叠放在内容下。在mozila浏览器中会产生不一样的结果。所以`z-index`为负会导致不可预料的后果,小心使用。 167 | - 在CSS中,内部元素表元素生成矩形框,这些框有内容、内边距和边框,但是没有外边距。即使指定了也会被忽略。 168 | ## 表布局 169 | - `display`中的table相关值。 170 | 171 | 表元素 | 相应值 172 | ---|--- 173 | table | display:table 174 | tr | display:table-row 175 | thead | display:table-header-grounp 176 | tbody | display:table-row-group 177 | tfoot | display:table-footer-group 178 | col | display:table-column 179 | colgroup | display-column-group 180 | td, td | display:table-cell 181 | caption | display:table-caption 182 | - 在CSS中列和列组只能接受四种样式: 183 | 1. border:只有当`border-collapse`属性值为`collapse`时才能为列和列组设置边框,在这种情况下,列和列组边框会参与设置各单元格边界边框样式的合并算法。 184 | 2. background:只有当单元格及其行有透明背景时,列或列组的背景才可见。 185 | 3. width:`width`属性定义了列或列组的最小宽度。列(或列组)中单元格的内容可能要求列更宽。 186 | 4. visibility:如果一个列或列组的`visibility`为`collapse`,则该列中所有单元格都不显示。从合并列跨到其他列的单元格会被裁剪,这类似于从其他列跨到隐藏列中的单元格。另外,表中总宽度会减去已合并列的宽度。如果对列 列组将`visibility`声明为任何非`collapse`值,则会被 187 | - CSS定义了机制,可以将“遗漏”的组件作为匿名对象插入。 188 | - 为了完成显示,CSS定义了6个不同的层,可以分别放表的不同方面从下到上分别为表>列组>列>行组>行>单元格。 189 | - `border-collapse`指定了两种模型一种为`collapse`合并边框模型,另一种为`separate`分隔单元格边框模型。 190 | - `border-spacing`可以为单元格边框分隔一段距离。这是在分隔单元格模型下,否则会忽略该属性。 191 | - 对于空单元格处理可以使用`empty-cells`设置为`show`会画出空单元格的边框和背景。如果为`hide`则不会画出该单元格的任何部分,就好像是`visibility: hidden`。如果这整行单元格都为空则处理完看起来像`display: none`。这是在分隔单元格模型下,否则会忽略该属性。 192 | - 合并单元格模型使得单元格不能有任何内边距不过可以有内边距。极其复杂,暂忽略。 193 | - 表的宽度由两种不同的方法:固定宽度布局和自动宽度布局。但不论使用哪种方法,高度都会自动计算。创作人员可以使用`table-layout`来选择采用哪种方法计算表的,二者之间最大的区别就是速度。使用固定宽度表布局时,相对于自动宽度模型,用户代理可以更快地计算出列的布局。 194 | - 固定布局`fixed`。该布局是根据表以及表中列和单元格的`width`值决定的。确定宽度的步骤如下: 195 | 1. `width`属性值不是`auto`的所有列元素会根据`width`值设置该列的宽度。 196 | 2. 如果一个列的宽度为`auto`,不过,表首行中位于该列的单元格`width`不是`auto`则会根据该单元格宽度设置此列的宽度。如果这个单元格跨多列,则宽度在这些列上平均分配。 197 | 3. 在以上两步之后,如果列的宽度仍为`auto`会自动确定其大小,使其宽度尽可能相等。 198 | 此时,表设置为表的`width`或列宽度之和(取其中较大者)。如果表宽度大于其列宽总和,将二者之差除以列数,再把得到的这个宽度增加到每一列上。如果一个单元格的内容无法放下,该单元格的`overflow`值将决定单元格内容是裁剪、可见还是生成一个滚动条。 199 | - 自动布局`auto`。该模型计算过程: 200 | 1. 用`width`和最小可能宽度进行比较,用较大的作为列的最小宽度,最大宽度为百分比或数值。 201 | 2. 最后跟表的宽度比较取较大的一个。 202 | - 表的高度确定。最简单的直接由`height`属性显示设置高度。`height`看做表框的最下高度。 203 | - 单元格中对齐非常容易。水平对齐使用`text-align:center`。垂直对齐使用`vertical-align:middle`。 204 | ## 列表与生成内容 205 | - `list-style-type`用来修改列表项的标志类型。由于值太多这里就不列出。默认值为`disc`。该属性只能应用于`display`值为`list-item`的元素。该属性会继承如果不希望继承,可以显示声明。如果要使用图像作为列表项标志可以使用`list-style-image`。`list-style-position`用于设置标志项位置。默认为`outside`,也可以设置为`inside`。他们可以`list-style`简单写出,三个的顺序随意写出也都可以忽略。 206 | - 生成内容。为了向文档中插入生成内容,可以使用`:before`和`:after` 伪元素。这些伪元素会根据`content`属性把生成内容放在一个内容前面或后面。同时。CSS2和CSS2.1明确地明确进制浮动或定位:`:before`和`:after`内容,还禁止使用样式列表属性以及表属性。另外还有以下限制: 207 | 1. 如果`:before`或`:after`选择器的主体是块级元素,则`display`属性只接受值`none`,`inline`,`block`和`marker`其他值都处理为`block`。 208 | 2. 如果`:before`或`:after`选择器的主体是行内元素,属性`display`只能接受值`none`和`inline`。所有其他值都处理为`inline`。另外生成元素会继承与之关联的元素的属性值。当然这种继承只适用于可继承属性。 209 | - `content`属性除了串和uri还有许多其他的属性。串会原样显示,包括标签也会原样显示在里面。 210 | - 有些情况下,要选取一个元素的属性值,使之作为文档显示的一部分。 211 | ``` 212 | a[href]:after { 213 | content: attr(href); 214 | } 215 | ``` 216 | 这也会导致生成内容与具体内容冲出的问题。为解决这个问题,可以向声明增加一些串值。 217 | ``` 218 | a[href]:after { 219 | content: "[" attr(href) "]"; 220 | } 221 | ``` 222 | - 生成内容有一种特殊形式,即引号。CSS2.x提供了`open-quote`等成对的`content`值以及属性`quotes`,使得生成引号的管理成为可能。可以用`content`的`open-quote`,`close-quote`,`no-open-quote`,`no-close-quote`来控制是否用引号。 223 | - 计数器。创建计数器的基础包括两个方面,一个是设置计数器起点,二是能将其递增一定的量。设置起点由`counter-reset`处理 224 | ``` 225 | h1{ counter-reset: chapter 4; } 226 | ``` 227 | 创建一个初始值为4的名为chapter的计数器。不写值则默认为0,`counter-increment`用来递增计数器,可以使负值,默认为1. 228 | ``` 229 | ol { 230 | counter-reset: ordered; 231 | }/* defaults to 0 */ 232 | 233 | ol li { 234 | counter-increment: ordered; 235 | }/* defaults to 1 */ 236 | ``` 237 | 要显示具体的数值还要配合`content`属性使用。 238 | ``` 239 | ol li:before{ 240 | content: counter(ordered); 241 | } 242 | ``` 243 | 还可以为每个计数器定义一个样式列表,作为`counter()`格式的一部分。为此可以在计数器的标识符后面增加一个`list-style-type`关键字,用逗号分隔。 244 | ``` 245 | counter(ordered, uppper-alpha) 246 | ``` 247 | 如果需要嵌套,就需要作用域的概念。简单的说,每层嵌套都会为给定计数器创建一个新的作用域。正是因为有作用域,以下规则才能以常规HTML方式实现嵌套表计数。 248 | ``` 249 | ol { counter-reset: ordered; } 250 | ol li:before { counter-increment: ordered; 251 | content: counter(ordered) ". "} 252 | ``` 253 | 这些规则会使有序列表(设置嵌套在其他)列表中的有序列表从1开始计数,并且逐项增1.一所以能做到,是每层嵌套都生成了一个新的order实例。 254 | 如果希望每层嵌套都创建一个新计数器追加到老计数器后面例如:1.1. 1.2. 1.3.就需要`counters`而不是`counter()`。可以使用`counters(ordered, ".")`来讲计数器串起来。 255 | -------------------------------------------------------------------------------- /posts/ContentType与文件上传.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "ContentType与文件上传" 3 | date: "2020-11-21" 4 | tags: js 5 | author: xwchris 6 | desc: "最近在处理一个node文件上传问题的时候,遇到了很大的阻碍,最终查了很多资料并求助大佬才解决。这促使我要把这一块完全弄明白,下次遇到不能再到处查资料来解决问题" 7 | --- 8 | 9 | ## Content-Type 基本概念 10 | Content-Type是HTTP的实体头部,用于指示资源的MIME类型。它可以出现在请求头或者响应头中。 11 | 12 | 1. 请求头中,客户端告诉服务端要发送给的数据类型,服务端就可以知道如何正确处理数据。 13 | 2. 响应头中,服务端告诉客户端返回的响应数据的类型,客户端(浏览器)会根据MIME类型进行相应的处理,或者开发者自己处理。 14 | 15 | Content-Type的书写格式类似于下面这种 16 | ``` 17 | Content-Type: text/html; charset=utf-8 18 | Content-Type: multipart/form-data; boundary=something 19 | ``` 20 | 值的部分主要包含三部分 21 | 1. mimetype(资源或数据的MIME类型) 22 | 2. charset(字符集) 23 | 3. boundary(内容边界,在多类型内容中有用) 24 | 25 | 各部分之间用`;`分割,其中[MIME](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)的种类很多,下面主要说要我们常用的几种 26 | 27 | ## Content-Length 28 | 常与Content-Type一起出现的有Content-Length 29 | 30 | Content-Length表示八位字节的长度,如果Content-Length是出现并有效的话,一定要保证该值的正确性,否则会出现意料之外的错误。 31 | 32 | 1. Content-Length大于实际长度则可能会出现无响应的情况(客户端或服务端等待读取下一个字节,却读取不到) 33 | 2. Content-Length小于实际长度则可能会截断数据信息 34 | 35 | ## Content-Type 应用在响应中 36 | 在响应中常见的Content-Type有: 37 | 1. `application/json` ajax请求数据返回json 38 | 2. `application/javascript` javascript文件 39 | 3. `text/css` css文件(注意与js不同的是这里是text,application通常表示可执行或可解析的二进制数据) 40 | 4. `text/html` html文件 41 | 5. `text/plain` 纯文本 42 | 43 | ## Content-Type 应用在请求中 44 | 请求发送数据使用`POST`请求,如使用`form`表单发送给的请求,通常使用的Content-Type有 45 | 46 | ### application/x-www-form-urlencoded 47 | `application/x-www-form-urlencoded` 数据会被编码。编码规则为使用`&`分割键值对,键值对之间使用`=`分割键值,对于非字母和数字的字符则会进行[percent-encoding](https://developer.mozilla.org/zh-CN/docs/Glossary/percent-encoding),特殊字符会以`%`后加ASCII码十六进制表示,空白符会以`+`或`%20`表示 48 | 49 | ```http 50 | POST / HTTP/1.1 51 | Host: test.com 52 | Content-Type: application/x-www-form-urlencoded 53 | Content-Length: 19 54 | 55 | name=xiaoming&age=8 56 | ``` 57 | 58 | ### application/json 59 | `application/json` 现在也有很多用在请求头中,用于高速服务端发送的是一个JSON字符串 60 | 61 | ```http 62 | POST / HTTP/1.1 63 | Host: test.com 64 | Content-Type: application/json 65 | Content-Length: 22 66 | 67 | {"name": "xiaoming", "age": 8} 68 | ``` 69 | 70 | ### multipart/form-data 71 | `multipart/form-data` 通常表单需要传文件的时候使用,因为文件需要使用二进制方式表示 72 | 73 | ```http 74 | POST /test.html HTTP/1.1 75 | Host: test.com 76 | Content-Type: multipart/form-data;boundary="boundary" 77 | 78 | --boundary 79 | Content-Disposition: form-data; name="name1" 80 | 81 | value1 82 | --boundary 83 | Content-Disposition: form-data; name="name2" 84 | 85 | value2 86 | ``` 87 | 88 | ## 文件上传 89 | ### 浏览器中上传 90 | 在浏览器中文件上传需要构造`FormData`对象,数据需要使用`append`加入`FormData`对象中,文件通常就是`File`或`blob`类型。`FormData`构造成功后,通过请求api将`FormData`传入即可,`Content-Type`和`Content-Length`浏览器客户端会自动设置 91 | 92 | ```js 93 | const file = new File(["foo"], "foo.txt", { 94 | type: "text/plain", 95 | }) 96 | const form = new FormData() 97 | form.append('name', 'xiaoming') 98 | form.append('file', file, 'text.png') 99 | form.append('age', 8) 100 | 101 | const xhr = new XMLHttpRequest() 102 | xhr.open('POST', 'http://test.com/test/upload') 103 | xhr.send(form) 104 | ``` 105 | 106 | 请求内容是这样的 107 | ```http 108 | POST /test/upload HTTP/1.1 109 | Host: test.com 110 | Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryYr963ldXs1XPBv7A 111 | 112 | ------WebKitFormBoundaryYr963ldXs1XPBv7A 113 | Content-Disposition: form-data; name="name" 114 | 115 | xiaoming 116 | ------WebKitFormBoundaryYr963ldXs1XPBv7A 117 | Content-Disposition: form-data; name="file"; filename="text.png" 118 | Content-Type: text/plain 119 | 120 | foo 121 | ------WebKitFormBoundaryYr963ldXs1XPBv7A 122 | Content-Disposition: form-data; name="age" 123 | 124 | 8 125 | ------WebKitFormBoundaryYr963ldXs1XPBv7A-- 126 | ``` 127 | 128 | ### Node中上传 129 | 那如果要在node中像浏览器一样上传,要怎么做。node中没有FormData对象,这是我们可以借助第三方包[form-data](https://www.npmjs.com/package/form-data) 130 | ```js 131 | const FormData = require('form-data') 132 | const fs = require('fs') 133 | 134 | const mime = 'image/png' 135 | const form = new FormData() 136 | form.append('name', 'xiaoming') 137 | form.append('file', fs.createReadStream('/foo/bar.png'), { 138 | contentType: mime, 139 | filename: 'test.png' 140 | }) 141 | form.append('age', 8) 142 | 143 | // 获取内容length 144 | const length = await new Promise((resolve, reject) => { 145 | data.getLength((err, length) => { 146 | if (err) { 147 | reject(err) 148 | } else { 149 | resolve(length) 150 | } 151 | }) 152 | }) 153 | 154 | // 这里使用axios发送请求,换成任何库都是一样的,主要需要注意的是正确设置`Content-Type`和`Content-Length` 155 | const headers = data.getHeaders() 156 | const res = await axios 157 | .post(pssConfig.uploadLink, data, { 158 | responseType: 'json', 159 | headers: { 160 | 'Content-Type': headers['content-type'], 161 | 'Content-Length': length 162 | } 163 | }) 164 | ``` 165 | 166 | ## 总结 167 | 感谢大家耐心看完,很多写的不好的地方希望能指正。通常我们会忽略看起来很简单的东西,但真正用到的时候才显示出这些基础的重要性。但最重要的,不论是查资料还是问别人,在解决完问题后都要及时思考和总结才能变成自己的东西 168 | -------------------------------------------------------------------------------- /posts/Git基础.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Git基础" 3 | date: "2019-08-04" 4 | tags: git 5 | author: xwchris 6 | desc: "GIT本质上是一个内容寻址文件系统。从核心来看就是简单的存储键值对" 7 | --- 8 | 9 | ## GIT基本原理 10 | 在GIT中有`远程仓库`、`本地仓库`、`暂存区`和`工作区`的概念。我们平时修改文件的地方就属于`工作区`,在文件修改完成后,我们使用`git add .`的命令将工作区内容放入缓存区。 11 | 12 | 在缓存区中,GIT会根据文件内容生成SHA-1值(校验和),作为文件的唯一ID,我们可以使用 13 | 14 | ```bash 15 | git hash-object [filename] 16 | ``` 17 | 18 | 来查看指定文件计算后的SHA-1值。GIT会使用校验和的前两个字符作为目录名称,用剩下的字符作为文件名,将文件以二进制的形式存储进当前GIT根目录的`.git/objects`文件夹,这里我们可以使用以下命令来查看所有保存的文件列表 19 | 20 | ```bash 21 | find .git/objects -type f 22 | ``` 23 | 24 | 注意这些文件里存储的只是文件内容,不包括文件的信息。查看二进制文件的内容可以使用 25 | 26 | ```bash 27 | // 查看内容 28 | git cat-file -p [sha-1] 29 | 30 | // 查看类型 31 | git cat-file -t [sha-1] 32 | ``` 33 | 34 | 在使用`git commit`命令后,GIT会生成本次提交的快照,将其永久保存到本地仓库。那么GIT是如何表示快照和生成快照的?我们可以在提交后再次使用 35 | 36 | ```bash 37 | find .git/objects -type f 38 | ``` 39 | 40 | 来查看目录,会发现多了一些文件,其实除去我们的文件,剩下的都记录了我们的目录信息和提交信息。总的来说`.git/objects`里的文件分为三类 41 | 42 | 1. blob:这就是我们的文件类型 43 | 2. tree:tree有多条记录,每一条记录含有一个指向 blob 或子 tree 对象的 SHA-1 指针,并附有该对象的权限模式 (mode)、类型和文件名信息 44 | 3. commit: 用来记录我们的提交信息,包含了根目录SHA-1值,以及提交者信息和提交信息 45 | 46 | 通过上面的解释我们会发现一个commit指向根目录,根目录指向各个子目录和文件。因此我们完全可以用commit来表示本次提交,保存提交的快照。当然GIT就是这么做的。 47 | 48 | 分支就是执行某次提交的一个指针,所以我们创建分支,就只是创建了一个指针,所以GIT进行分支创建非常迅速。 49 | 50 | ## 分支合并 51 | 在GIT中,分支的合并有两种方式,分别是`git merge`和`git rebase`。无论哪种都可能产生冲突,有冲突就需要先解决冲突才能完成合并。 52 | 53 | ![分支合并](https://dn-coding-net-production-pp.qbox.me/b39e3a39-0091-4265-ba6d-73ef0fd457b2.png) 54 | 55 | 56 | 冲突是以两个分支的分歧点(merge base)进行定义的,如上图所示两个分支的分歧点为1。如果对比1来说两个分支相同的文件都与1不同,这时候就产生了冲突,其他情况有一个相同或者都相同,可以自动进行合并。 57 | 58 | 使用`git merge`的方式进行分支合并,在解决冲突后,会形成一次新的提交到目标分支上。这种方式不会修改历史提交记录。 59 | 60 | 还可以使用`git rebase`进行分支合并,与`git merge`方式不同,该方式会改变提交历史。该方式合并时GIT会把merge base以来的所有提交,一个个以补丁的形式打到目标分支上,在这里有冲突需要解决冲突。最终所有的提交会形成一条线。这种方式可以让分支更加干净整洁。 61 | 62 | ## 版本回退 63 | 如果我们某次提交完后,发现这次提交有问题,想要回到某次提交,有这么几种方式 64 | 65 | 可以使用 66 | 67 | ```bash 68 | git revert [sha-1] 69 | ``` 70 | 71 | 命令来反转某次提交,相当于取消这一次提交所提交的代码。这种方式是用一个新的提交来进行代码还原的,并不会改变分支提交的历史。 72 | 73 | 除了`git revert`,还可以使用`git reset`配合`--soft`,`--mixed`和`--hard`参数来进行回退,这三个参数的作用域依次增大,`--soft`会将HEAD指针指向某次提交,`--mixed`与`--soft`相比就是多了个缓存区,`--hard`会完全将代码还原到当次提交,慎重使用。不过真的不小心用错了,可以使用 74 | 75 | ```bash 76 | git reset --hard ORIG_HEAD 77 | ``` 78 | 79 | 进行还原,或者使用 80 | 81 | ```bash 82 | git reflog 83 | ``` 84 | 85 | 命令找到当时HEAD的SHA-1值,将HEAD重置到该节点就可以了 86 | 87 | ## BUG查找 88 | 如果某次我们发现有问题,又找不到bug在那次引入的,这时就可以使用`git bisect`命令帮助我们进行查找。 89 | 90 | 使用的命令如下 91 | 92 | ```bash 93 | git bisect start 94 | 95 | git bisect bad HEAD 96 | git bisect good v4.1 97 | ``` 98 | 我们只需要使用`git bisect good`和`git bisect bad`进行标记,`git bisect`会使用二分法帮助我们定位直到找到bug引入的源头。如果操作很简单,可以是使用来执行脚本进行操作 99 | 100 | ```bash 101 | git bisect run test/run.sh 102 | ``` 103 | 104 | > 本文参考[廖雪峰的git教程](http://www.liaoxuefeng.com/),整理学习,尊重原创 105 | 106 | ## 配置git 107 | `git config`用来配置`git` 108 | ```git 109 | git config --global user.name 'xxxxx' 110 | git config --global user.email 'xxxxxx@gmail.com' 111 | ``` 112 | 生成密钥使用 113 | ```shell 114 | // 使用rsa方式生成密钥 115 | ssh-keygen -t rsa 116 | ``` 117 | 在linux中密钥默认存在用户的`.ssh`文件里。可以使用`ssh-copy-id`命令来讲公钥拷贝到远程的`.ssh/authorized_keys`文件中 118 | ```shell 119 | // -i 用来指定认证文件(公钥) 120 | ssh-copy-id -i ~/.ssh/id_ras.pub root@192.0.0.1 121 | ``` 122 | 123 | ## 创建版本库 124 | `git init`用来把一个目录变成git仓库,该文件夹可以不是空的 125 | ```git 126 | git init 127 | ``` 128 | `git add`添加文件,可以使用通配符`.`添加所有文件。`git add`是添加到缓存区,缓存区(stage)是`git`与`svn`不同的地方,可以简单理解为将修改完的通通放到缓存区,最后可以一次性提交 129 | 130 | ```git 131 | git add . 132 | ``` 133 | `git commit` 一次性的把所有缓存区的内容提交到分支,`-m` 后面跟提交信息 134 | 135 | ```git 136 | git commit -m "fix: fixed some bugs" 137 | ``` 138 | 139 | ## 版本文件管理 140 | 141 | `git status`可以查看仓库当前状态 142 | 143 | ```git 144 | git status 145 | ``` 146 | `git diff`查看工作区和版本库最新版的不同(目前还不太会用) 147 | ```git 148 | git diff 149 | ``` 150 | 151 | ### 版本回退 152 | `git log`可以显示从最近到最远的提交,如果想要精简输出可以在后面加上`--pretty=oneline`参数 153 | ```git 154 | git log --pretty=oneline 155 | ``` 156 | 结果显示的`3628164...882e1e0`等一大串数字字母组合是`commit id`(版本号),是SHA1计算出来的,可以避免冲突 157 | 158 | `git reset`可以用来回退版本,在`git`中`HEAD`表示最新版,`HEAD^`表示上一版本,`HEAD^^`表示上上版本,当然往上100个版本,数不过来可以写成`HEAD~100`,同时`git reset --hard commitId`可以回到指定的版本 159 | ```git 160 | git reset --hard HEAD^ 161 | git reset --hard 3628164 162 | ``` 163 | `git reflog` 查看commit id 164 | ```git 165 | git reflog 166 | ``` 167 | 168 | ### 撤销修改 169 | `git checkout -- file `会丢弃工作区的修改,让文件回到最后一次`git commit`或`git add`的状态 170 | ```git 171 | git checkout -- readme.txt 172 | ``` 173 | 这个命令里面的`--`很重要,没有`--`就变成了切换到另一个分支的命令 174 | `git reset HEAD file`将缓存区回退到工作区 175 | ```git 176 | git reset HEAD readme.txt 177 | ``` 178 | 179 | ### 删除文件 180 | `git rm`可以用来删除文件,相当于先在工作区删除,再添加到缓存区,最后再使用`git commit`就可以删除分支的文件了 181 | ```git 182 | git rm 183 | ``` 184 | 185 | ## 远程仓库 186 | 187 | ### 关联远程库 188 | `git remote add origin url`关联远程的仓库 189 | ```git 190 | git remote add origin git@github.com:用户名/仓库名称.git 191 | ``` 192 | `git push`把本地库的内容推送到远程,第一次推送时加上`-u`,`git`不但会把本地的`master`分支内容推送到远程的master分支,还会把本地的`master`分支和远程的`master`分支关联起来,在以后推送和拉取的时候就可以简化命令,直接使用`git push origin master` 193 | ```git 194 | git push -u origin master 195 | ``` 196 | 197 | ### 从远程库克隆 198 | `git clone`从远程克隆库到本地 199 | ```git 200 | git clone git@github.com:用户名/仓库名.git 201 | ``` 202 | 203 | ## 分支管理 204 | ### 创建和合并分支 205 | `git branch`查看当前的所有分支和当前分支, 206 | 后面跟名字可以创建新的分支,`git checkout `用来切换分支 207 | ```git 208 | git branch dev 209 | git checkout dev 210 | ``` 211 | 这两个命令可以简写为`git checkout -b ` 212 | ```git 213 | git checkout -b dev 214 | ``` 215 | 表示创建并切换到新的分支 216 | 217 | `git merge `将某分支与当前分支合并 218 | ```git 219 | git merge dev 220 | ``` 221 | `git merge -d `合并并删除某分支 222 | ```git 223 | git merge -d dev 224 | ``` 225 | 226 | ### 解决冲突 227 | `git merge`合并文件发生冲突的时候,我们可以用`git status`查看文件,然后手动解决冲突后再添加-提交-合并 228 | `git log --graph`可以看到分支合并图 229 | ```git 230 | git log --graph` 231 | ``` 232 | 233 | ### 分支管理策略 234 | 在`git`中,如果可以的话,`git`会默认使用`Fast forward`模式,但在这种模式下,删除分支后,会丢掉分支信息,强制禁止'Fast forward'就可以在`merge`时,生成一个新的`commit`,从分支历史上就可以看出分支信息,禁止`Fast forward`使用`--no-ff` 235 | ```git 236 | git merge --no-ff -m 'merge with --no-ff' dev 237 | ``` 238 | 这样使用`git log`就可以看到曾经做过合并,否则使用`Fast forward`是看不到曾经做过合并的 239 | 在实际开发中,应该按照以下几个基本原则进行分支管理: 240 | 1. `master`是非常稳定的,仅仅用来发布新版本 241 | 2. 平时大家都在`dev`上干活,也就是说,`dev`是不稳定的,到版本发布的时候,再把`dev`合并到`master`上 242 | 3. 每个人都有自己的分支,时不时的往`dev`上合并就可以了 243 | 244 | ### bug分支 245 | `git stash`可以把当前工作现场储存起来 246 | ```git 247 | git stash 248 | ``` 249 | `git stash list`列出所有存储的现场 250 | ```git 251 | git stash list 252 | ``` 253 | `git stash apply`可以恢复现场,而且可以恢复指定的现场 254 | ```git 255 | git stash apply 256 | git stash apply stash@{0} 257 | ``` 258 | 现场恢复后,`stash`并不删除,需要用`git stash drop`来删除 259 | ```git 260 | git stash apply 261 | git stash drop 262 | ``` 263 | 也可以用`git stash pop`代替那两个命令,直接恢复并删除 264 | ```git 265 | git stash pop 266 | ``` 267 | 268 | ### Feature分支 269 | 开发新的功能最好建立一个新的分支,然后合并,删除分支。如果现在新功能取消,只能删除掉该分支,但由于该分支还没有合并,会导致删除失败,只能强行删除,这时就要用`git branch -D `强行删除 270 | ```git 271 | git branch -D new-fearture 272 | ``` 273 | 274 | ### 多人协作 275 | 当从远程仓库克隆到本地之后,远程的`master`分支就和本地的`master`分支对应起来了,并且远程仓库的默认名称是origin。要查看远程仓库可以用`git remote` 276 | ```git 277 | git remote 278 | ``` 279 | 查看更详细的信息,后面加`-v`参数 280 | ```git 281 | git remote -v 282 | ``` 283 | 会显示可以抓取和推送的地址,如果没有推送权限就看不到push地址。 284 | 并不一定所有的分支都需要推送 285 | - `master`是主分支,因此要时刻与远程保持同步 286 | - `dev`是开发分支,团队所有成员都在上面工作,因此也需要与远程同步 287 | - `bug`分支只用于在本地修复bug没必要推送 288 | - `feature`是否推送,取决于是否与他人合作开发 289 | 290 | 当从远程克隆到本地时只能看到`master`分支,由于要在`dev`分支上开发,所以可以创建与远程`dev`分支对应的分支,使用`git checkout -b branch-name origin/branch-name`,本地分支与远程分支名称最好一致 291 | ```git 292 | git checkout -b dev origin/dev 293 | ``` 294 | 从本地推送到远程时最后先`git pull`,避免冲突,如果`pull`的时候出现no tracking information说明本地分支与远程分支没有建立关系,这时可以使用`git branch --set-upstream branch-name origin/branch-name`命令 295 | ```git 296 | git branch --set-upstream dev origin/dev 297 | ``` 298 | 299 | ## 标签管理 300 | 标签就是指向某个`commit`的指针,跟分支很像,不过分支可以移动而标签`tag`不能,为了快速找到版本,而`commit id`又不好记,这时就可以用tag取一个有意义的名字 301 | 302 | ### 创建分支 303 | `git tag `用来创建分支 304 | ```git 305 | git tag v1.0 306 | ``` 307 | 默认是打在最新的`commit`上面的,如果要给以前的`commit`打标签,就可以用`git log`找到当时的`commit id`然后用`git tag `来就可以了 308 | ```git 309 | git tag v0.9 622494 310 | ``` 311 | `git tag`可以查看所有标签,它们是按字母排序的 312 | ```git 313 | git tag 314 | ``` 315 | `git show `可以查看标签的详细信息 316 | ```git 317 | git show v0.1 318 | ``` 319 | 还可以创建带有说明的标签,用`-a`指定标签名,`-m`指定说明文字 320 | ```git 321 | git tag -a v1.0 -m "version 1.0 release" 322 | ``` 323 | 还可以通过`-s`用私钥签名一个标签 324 | ```git 325 | $ git tag -s v0.2 -m "signed version 0.2 released" fec145a 326 | ``` 327 | 328 | ### 操作标签 329 | 标签只会存在本地,因此可以在本地安全的删除,使用`git tag -d ` 330 | ```git 331 | git tag -d v1.0 332 | ``` 333 | 要推送标签到远程使用`git push origin ` 334 | ```git 335 | git push origin v1.0 336 | ``` 337 | 或者一次性推送所有未推送的本地标签 338 | `git push origin --tags` 339 | ```git 340 | git push origin --tags 341 | ``` 342 | 推送到远程再删除就要两步,先用`git tag -d `删除本地的,在用`git push origin :refs/tags/` 343 | ```git 344 | git push origin :ref/tags/v1.0 345 | ``` 346 | 347 | ## 自定义git 348 | ### 忽略特殊文件 349 | 如果不想提交`git`中的某些文件可以,在根目录下添加一个`.gitignore`文件,把要忽略的文件填进去,git就会自动忽略这些文件。github准备了各种[配置文件](https://github.com/github/gitignore) 350 | 351 | ### 设置别名 352 | 有时候为了方便可以设置一些别名使用`alias` 353 | ```git 354 | git config --global alias.st status 355 | ``` 356 | 这样`git status`就可以直接用`git st`调用,在配置`git`的时候`--global`是对当前用户起作用的,如果不加只能对当前仓库起作用。配置文件都放在`.git/config`中 357 | 另一个别名例子 358 | ```git 359 | git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" 360 | ``` 361 | -------------------------------------------------------------------------------- /posts/Http缓存.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Http缓存" 3 | date: "2019-08-01" 4 | tags: 网络,http 5 | author: xwchris 6 | desc: "http缓存帮助减少网络间传输,加快传输速度和页面加载速度" 7 | --- 8 | 9 | ## 缓存相关头部 10 | 11 | 1. Expires 响应头,代表资源过期时间 12 | 2. Cache-Control 请求头/响应头,缓存控制字段,精确控制缓存策略 13 | 3. If-Modified-Since 请求头,资源最近修改时间,由浏览器告诉服务器 14 | 4. Last-Modified 响应头,资源最近修改时间,由服务器告诉浏览器 15 | 5. Etag 响应头,资源标识,由服务器告诉浏览器 16 | 6. If-None-Match 请求头,缓存资源标识,由浏览器告诉服务器 17 | 18 | 19 | ## 字段详解 20 | 21 | 假设现在不看请求头的情况下要浏览器要缓存一个文件,读相同的文件的时候直接读取本地的缓存,这样减少了请求,但是会造成服务器资源过期也不更新的情况,所以这时候引入响应头`Expires`。 22 | 23 | ### Expires 24 | 响应头`Expires`是由服务器告知浏览器的资源过期时间,在资源过期之前,浏览器可以一直使用本地的缓存,过期之后,浏览器可以再次请求服务器。它的值是GMT格式的标准时间,如`Fri, 01 Jan 1990 00:00:00 GMT`。 25 | 26 | 虽然服务器端可以设置资源过期时间,但是服务器端并无法精确的知道资源什么过期,因此存在浏览器中`Expires`过期时间到了,但是服务器资源并没有更新的情况,这个时候请求一个相同的文件影响带宽。为了解决这个问题,在服务器响应头中增加了`Last-modified`字段。 27 | 28 | ### Last-Modified & If-Modified-Since 29 | 响应头`Last-Modified`和请求头`If-Modified-Since`配合使用,为了解决上述问题,服务器端使用`Last-Modified`返回资源最新一次的修改时间。当浏览器资源过期的再次请求服务器的时候,带上`If-Modified-Since`请求头,该请求头与上次请求`Last-Modified`的值保持一致。如果服务器端检测到`If-Modified-Since`的值与目前资源的修改时间一致,则直接返回`304`告诉浏览器资源没有更改,可以继续使用缓存。 30 | 31 | 到了这里还是存在问题,如果资源的修改时间确实变化了,但是文件内容没有变,再次请求相同的文件,同样是一种浪费带宽的表现,所以引出下一个概念`Etag` 32 | 33 | ### Etag && If-None-Match 34 | `Etag`是根据文件内容计算出的唯一标识符,当`Etag`变化时,说明文件确实被修改了。与`Etag`配合使用的是`If-None-Match`,`Etag`是服务器响应头,`If-None-Match`是浏览器请求头。每次请求如果有`If-None-Match`那么会忽略`If-Modified-since`。 35 | 36 | ### Catch-Control 37 | 一种新的缓存过期控制方案,它可以甚至相对时间等很多属性,如果存在`Catch-Control`则忽略掉`Expires`。`Catch-Control`的值包括: 38 | 1. max-age:可以设定相对时间,时间为s 39 | 2. public: 资源允许被中间代理服务器缓存 40 | 3. private: 资源不允许被中间代理服务器缓存 41 | 4. no-cache: 浏览器不做缓存检查,服务器检查文件,如果没变返回304 42 | 5. no-store: 浏览器和中间代理服务器都不缓存,服务器不检查文件,直接返回资源 43 | 6. must-revalidate:可以缓存,但是使用之前必须先向源服务器确认。 44 | 7. proxy-revalidate:要求缓存服务器针对缓存资源向源服务器进行确认。 45 | 8. s-maxage:缓存服务器对资源缓存的最大时间。 46 | 47 | ## 实际开发中的缓存方案 48 | http缓存固然很好,但是由于http缓存的存在,浏览器无法主动感知资源的变化。会造成我们资源更新了,但是浏览器没有刷新的情况。实际情况中可以不给html文件做缓存,每次都请求最新的文件,至于资源改变后,我们可以用一个独特的MD5值来命名文件,引入html中,例如`bundle.xxxx.name`这种命名,这样每次都会引用最新的资源,同时这样可以做到服务器热更新。 -------------------------------------------------------------------------------- /posts/JS中的设计模式.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "JS中的设计模式" 3 | date: "2017-11-12" 4 | tags: js 5 | author: xwchris 6 | desc: "JS设计模式阅读记录要点,待以后方便查阅,回忆,学习" 7 | --- 8 | 9 | ## Javascript的多态 10 | 多态是程序语言中很重要的一部分。多态最重要的思想是将“做什么”和“谁来做”分开,降低两者之间的耦合度,增强程序的扩展性和可维护性。使用多态的思想能让我们减少和消除`if...else`分支判断。一个渲染地图程序的例子。 11 | ```js 12 | var googleMap = { 13 | show: function() { 14 | console.log('show google map'); 15 | } 16 | } 17 | 18 | var baiduMap = { 19 | show: function() { 20 | console.log('show baidu map'); 21 | } 22 | } 23 | 24 | // 使用if...else分支 25 | function renderMap(type) { 26 | if (type === 'google') { 27 | googleMap.show(); 28 | } else if (type === 'baidu') { 29 | baiduMap.show(); 30 | } 31 | } 32 | 33 | // 使用多态特性 34 | function renderMap(map) { 35 | if (map.show instanceof Function) { 36 | map.show(); 37 | } 38 | } 39 | ``` 40 | 可以看出使用多态特性,代码简洁了很多,可扩展性页增强了很多。以后遇到`if...else`语句,先判断能否利用多态特性来让代码变得更DRY。 41 | 42 | ## Javascript中的原型继承 43 | 要实现原型继承的方式,需要遵循以下四点: 44 | 1. 所有的数据都要是对象 45 | 2. 对象的生成不是通过实例化类,而是找到一个对象并作为一个原型克隆它 46 | 3. 对象会记住它的原型 47 | 4. 如果对象无法响应某个请求,它会把这个请求委托给它的自己的原型 48 | 49 | 在js中,引入两套类型机制,基本类型和对象类型,基本类型包括`undefined`,`number`,,`string`, `boolean`, `function`,`object`。除了`undefined`之外,一切都应该是对象,`number`,`boolean`,`string`也可通过包装类的方式变成对象类型数据来处理。 50 | 51 | 在js中,生成一个对象通过克隆的方式,没有类的概念,通过`new Person()`构造一个对象,这里的`Person`并不是类,而是函数构造器。下面来模拟一下new运算的过程。 52 | ```js 53 | var objectFactory = function() { 54 | var obj = {}, Constructor = [].shift.call(arguments); 55 | obj.__proto__ = Constructor.prototype; 56 | var ret = Constructor.apply(obj, arguments); 57 | return typeof ret === 'obj' ? ret : obj; 58 | } 59 | 60 | // 这两段代码效果相同 61 | var a = objectFactory(A, 'chris'); 62 | var b = new A('chris'); 63 | ``` 64 | 65 | 对象的原型更确切的应该说是对象构造器的原型,js给对象提供了一个`__proto__`的属性在某些浏览器中会被公开出来。或者使用`Object.getPrototypeOf`来获取对象的原型。 66 | ```js 67 | var a = new Object(); 68 | a.__proto__ === Object.prototype; // true 69 | Object.getPrototypeOf(a) === Object.prototype; // true 70 | ``` 71 | 72 | ## 闭包 73 | 闭包主要跟变量的作用域和声明周期密切相关。从理论上来说所有函数都是闭包,但是这没有什么意义,根据使用为主原则,除了分割作用域外,更常用的一个作用是延长变量的声明周期,一个js中经典的问题。 74 | ```js 75 | // 假设现在有五个div元素 76 | var nodes = document.getElementsByTagName('div'); 77 | 78 | for (var i = 0; i < nodes.length; i++) { 79 | nodes[i].onclick = function() { 80 | console.log(i); 81 | } 82 | } 83 | ``` 84 | 这段程序中不论点击哪一个都会输出5,这是因为`click`事件为异步事件,当我们点击的时候,更具作用域向上查找`i`,这个时候早已循环完毕,所有的`i`都是5。现在用闭包进行修改。 85 | ```js 86 | var nodes = document.getElementsByTagName('div'); 87 | 88 | for (var i = 0; i < nodes.length; i++) { 89 | (function(i) { 90 | nodes[i].onclick = function() { 91 | console.log(i); 92 | } 93 | })(i); 94 | ``` 95 | 这次在点击会依次输出`0,1,2,3,4`,同上理,点击触发的时候寻找`i`,首先查找闭包内部的`i`,闭包帮助我们把每一次循环的值记录了下来。 96 | 97 | 同理编出以下判断类型的代码 98 | ```js 99 | var Type = {}; 100 | 101 | for (var i = 0, type; type = ['String', 'Array', 'Number'][i++];) { 102 | (function (type) { 103 | Type['is' + type] = function(obj) { 104 | return Object.prototype.toString.call(obj) === '[object ' + type + ']'; 105 | } 106 | })(type); 107 | } 108 | 109 | Type.isNumber(2); // true 110 | Type.isArray([]); // true 111 | ``` 112 | 113 | 闭包主要用来封装私有变量和延长变量声明周期。 114 | 115 | ## 高阶函数 116 | 所谓高阶函数就是一种参数为函数,同时返回值为函数的函数,他的用法有很多,这里列一些常见的高阶函数的应用。 117 | 118 | ### currying 119 | currying又称部分求值,一个currying的函数首先接收一些参数,接收到参数后并不立刻求值,而是返回另一个函数,刚才传入的参数在函数中形成闭包被保存起来,带到函数真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。下面是一个计算每日开销的例子。 120 | ```js 121 | function currying(fn) { 122 | var args = []; 123 | 124 | return function() { 125 | if (arguments.length === 0) { 126 | return fn.apply(this. args); 127 | } else { 128 | [].push.apply(args, arguments); 129 | return arguments.callee; 130 | } 131 | } 132 | } 133 | 134 | var cost = (function() { 135 | var money = 0; 136 | 137 | return function () { 138 | for (var i = 0; i < arguments.length; i++) { 139 | money += arguments[i]; 140 | } 141 | return money; 142 | }; 143 | })(); 144 | 145 | // 并不会真正计算 146 | cost(100); 147 | cost(200); 148 | 149 | // 真正计算 150 | cost(); 151 | ``` 152 | 153 | ### uncurrying 154 | uncurrying可以把函数的this泛化的过程提取出来 155 | ```js 156 | Function.prototype.uncurrying = function () { 157 | var self = this; 158 | return function() { 159 | var obj = Array.prototype.shift.call(arguments); 160 | return self.apply(obj, arguments); 161 | } 162 | } 163 | 164 | var push = Array.prototype.push.uncurrying(); 165 | 166 | (function() { 167 | push(arguments, 4); 168 | console.log(arguments); 169 | })(1,2,3); 170 | ``` 171 | 172 | ### 函数节流 173 | 函数节流用来控制很频繁的操作,比如`mousemove`事件等。 174 | ```js 175 | var throttle = function (fn, interval) { 176 | var _self = fn, 177 | timer, 178 | firstTime = true; // 第一次调用 179 | 180 | return function() { 181 | var _me = this; 182 | var args = arguments; 183 | 184 | if (firstTime) { 185 | _self.apply(_me, args); 186 | return firstTime = false; 187 | } 188 | 189 | if (timer) { 190 | return false; 191 | } 192 | 193 | timer = setTimeout(function() { 194 | clearTimeout(timer); 195 | timer = null; 196 | _self.apply(_me, args); 197 | }, interval || 300); 198 | } 199 | } 200 | 201 | window.onresize = throttle(function() { 202 | console.log(1); 203 | }, 500); 204 | ``` 205 | 206 | ### 分时函数 207 | 如果要同时创建100000个div,浏览器会假死,我们可以利用分时函数来分组创建元素。 208 | ```js 209 | var ary = []; 210 | 211 | for (var i = 0; i < 100000; i++) { 212 | ary.push(i); 213 | } 214 | 215 | var timeChunk = function(ary, fn, count, interval) { 216 | var timer; 217 | 218 | var start = function() { 219 | for (var i = 0; i < Math.min(count, ary.length); i++) { 220 | var obj = ary.shift(); 221 | fn(obj); 222 | } 223 | } 224 | 225 | return function() { 226 | timer = setInterval(function() { 227 | if (ary.len === 0) { 228 | return clearInterval(timer); 229 | } 230 | 231 | start(); 232 | }, interval || 200) 233 | } 234 | } 235 | 236 | var renderFriendsList = timeChunk(ary, function(n) { 237 | var dom = document.createElement('div'); 238 | dom.innerHTML = n; 239 | document.body.appendChild(dom); 240 | }, 20, 100); 241 | 242 | renderFriendsList(); 243 | ``` 244 | 245 | ## 策略模式 246 | 策略模式是定义一系列算法,并把它们封装起来,并且使他们可以互相替换。下面是一个简单的动画的例子 247 | ```js 248 | // 定义渐变函数 249 | var tween = { 250 | // t动画已消耗时间,b动画初始位置,c动画目标位置,d动画持续总时间 251 | linear: function (t, b, c, d) { 252 | return c * t / d + b; 253 | }, 254 | easeIn: function (t, b, c, d) { 255 | return c * (t /= d) * t + b; 256 | }, 257 | strongEaseIn: function (t, b, c, d) { 258 | return c * (t /= d) * t * t * t * t + b; 259 | }, 260 | strongEaseOut: function (t, b, c, d) { 261 | return c * ((t = t / d - 1) * t * t * t * t + 1) + b; 262 | }, 263 | sineaseIn: function (t, b, c, d) { 264 | return c * (t /= d) * t * t + b; 265 | }, 266 | sineaseOut: function (t, b, c, d) { 267 | return c * ((t = t / d - 1) * t * t + 1) + b; 268 | } 269 | } 270 | 271 | // 动画构造器 272 | var Animate = function(dom) { 273 | this.dom = dom; 274 | this.startTime = 0; 275 | this.startPos = 0; // 开始位置 276 | this.endPos = 0; // 结束位置 277 | this.propertyName = null; // 要改变的属性名称 278 | this.duration = null; // 动画持续时间 279 | this.easing = null; // 缓动算法 280 | } 281 | 282 | // 动画开始函数start 283 | Animate.prototype.start = function (propertyName, duration, endPos, easing) { 284 | this.startTime = +new Date; 285 | this.propertyName = propertyName; 286 | this.duration = duration; 287 | this.startPos = this.dom.getBoundingClientRect()[propertyName] || 0; 288 | this.endPos = endPos; 289 | this.easing = tween[easing]; 290 | 291 | var self = this; 292 | var timer = setInterval(function() { 293 | if (self.step() === false) { 294 | clearInterval(timer); 295 | return; 296 | } 297 | }, 19); 298 | } 299 | 300 | // 动画计算位置函数step 301 | Animate.prototype.step = function () { 302 | // 获得当前时间 303 | var current = +new Date; 304 | 305 | // 修正位置 306 | if (current - this.startTime > this.duration) { 307 | this.update(this.endPos); 308 | return false; 309 | } 310 | 311 | this.update(this.easing(current - this.startTime, this.startPos, this.endPos, this.duration)); 312 | } 313 | 314 | // 动画更新函数update 315 | Animate.prototype.update = function (pos) { 316 | console.log(this.dom, pos); 317 | return this.dom.style[this.propertyName] = pos + 'px'; 318 | } 319 | 320 | // 测试动画效果 321 | var div = document.getElementById('div'); 322 | var animate = new Animate(div); 323 | 324 | animate.start('marginLeft', 1000, 200, 'sineaseIn'); 325 | ``` 326 | 327 | ## 命令模式 328 | 命令模式以一种松耦合的方式将接收者和命令之间关联起来。 329 | 以下是一个命令模式的实例,模拟街头霸王游戏,WASD用来移动,并且具有播放录像功能。实现原理就是将移动封装成命令,然后将命令进行缓存。点击播放录像的时候重新执行一遍命令即可。 330 | ```js 331 | var Ryu = { 332 | "up": function() { 333 | console.log('up'); 334 | }, 335 | "down": function() { 336 | console.log('down'); 337 | }, 338 | "left": function() { 339 | console.log('left'); 340 | }, 341 | "right": function() { 342 | console.log('right'); 343 | } 344 | } 345 | 346 | var commands = { 347 | "119": "up", // w 348 | "115": "down", // s 349 | "97": "left", // a 350 | "100": "right" // d 351 | }; 352 | 353 | var commandStack = []; // 保存命令的堆栈 354 | 355 | var makeCommand = function (receiver, action) { 356 | return function () { 357 | receiver[action](); 358 | } 359 | } 360 | 361 | document.onkeypress = function(ev) { 362 | var keyCode = ev.keyCode, command = makeCommand(Ryu, commands[keyCode]); 363 | 364 | if (command) { 365 | command(); 366 | commandStack.push(command); 367 | } 368 | } 369 | 370 | // 点击播放录像 371 | document.getElementById('replay').onclick = function() { 372 | var command; 373 | while(command = commandStack.shift()) { 374 | command(); 375 | } 376 | } 377 | ``` 378 | 379 | ## 宏命令 380 | 宏命令是一组命令的集合,通过执行宏命令我们可以一次执行一组命令。 381 | ```js 382 | var closeCommand = { 383 | execute: function() { 384 | console.log('关门'); 385 | } 386 | } 387 | 388 | var openPcCommadn = { 389 | execute: function() { 390 | console.log('开PC'); 391 | } 392 | } 393 | 394 | var MacroCommand = function () { 395 | return { 396 | commandList: [], 397 | add: function(command) { 398 | this.commandList.push(command); 399 | }, 400 | execute: function() { 401 | for (var i = 0, command; command = this.commandsList[i++];) { 402 | command.execute(); 403 | } 404 | } 405 | } 406 | } 407 | 408 | var macroCommand = MacroCommand(); 409 | macroCommand.add(closeCommand); 410 | macroCommand.add(openPcCommand); 411 | 412 | macroCommand.execute(); 413 | 414 | ``` 415 | 这个例子的命令如`closeCommand`并没有包含任何receiver的信息,它本身就包揽了执行请求的行为,这更我们之前看到的命令对象都包含了一个receiver是矛盾的。包含receiver的命令是“傻瓜式”的,它只负责把客户的请求转交给接收者来执行。没用接收者的称谓智能命令,这种方式和策略模式非常接近,没有办法从结构上分辨,只是他们的意图不同,策略模式所有策略对象的目标都是一致的。而智能命令解决目标更具有发散性。 416 | 417 | ## 享元模式 418 | 享元模式要求将对象的属性划分为内部状态和外部状态,内部状态可以共享,外部状态无法共享。它的目标是尽量减少共享对象的数量。下面是划分内部状态和外部状态的原则。它是为解决性能问题而生的。 419 | 420 | 1. 内部状态存储于对象内部 421 | 2. 内部状态可以被一些对象共享 422 | 3. 内部状态独立于具体的场景,通常不会改变 423 | 4. 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享 424 | 425 | 下面是一个对象池的例子。对象池是一种共享技术,需要对象的时候不是直接创建而是从对象池中获取,如果对象池中有空闲的对象则直接获取,没有则创建一个新对象。使用完后再将对象放回对象池。它跟享元模式有一些相似之处,但是没有分离内部状态和外部状态的过程。 426 | ```js 427 | var objectPoolFactory = function (createObjFn) { 428 | var objectPool = []; 429 | 430 | return { 431 | create: function() { 432 | var obj = objectPool.length === 0 ? createObjectFn.apply(this, arguments) : objectPool.shift(); 433 | return obj; 434 | }, 435 | recover: function(obj) { 436 | objectPool.push(obj); 437 | } 438 | } 439 | } 440 | 441 | var imageFactory = objectPoolFactory(function() { 442 | var image = document.createElement('image'); 443 | 444 | image.onload = function() { 445 | console.log('image loaded'); 446 | imageFactory.recover(image); 447 | } 448 | 449 | return image; 450 | } 451 | 452 | var img1 = imageFactory.create(); 453 | img1.src = 'http://baidu.com'; 454 | 455 | var img2 = imageFacotry.create(); 456 | img2.src = 'http://google.com'; 457 | ``` 458 | 459 | ### 用AOP实现职责链 460 | ```js 461 | 462 | Function.prototype.after = function(fn) { 463 | var self = this; 464 | 465 | return function() { 466 | var ret = self.apply(this, arguments); 467 | 468 | if (ret === 'nextSuccessor') { 469 | return fn.apply(this, arguments); 470 | } 471 | 472 | return ret; 473 | } 474 | } 475 | 476 | var order = order500.after(order200).after(orderNormal); 477 | ``` 478 | 使用AOP实现职责链既简单又巧妙,但是这种把函数叠在一起的方式,同时也叠加了函数的作用域,如果链条较长也会对性能有较大影响。 479 | 480 | ## 职责链模式 481 | 职责链模式是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递,知道有一个对象可以处理它为止。下面是一个职责链模式的例子。 482 | 483 | 假设一个手机售卖网站针对用户交纳的不同定金有不同的优惠,已经支付过500元定金的用户会受到100元的优惠券,已经支付200元定金的用户可以受到50元的优惠券,没有支付定金的用户只能进入普通购买模式,且有库存限制。我们要写一个`order`函数来给出用户订单信息。`orderType`表示订单类型,`pay`表示用户是否已支付定金没有支付要降级到普通用户,`stock`用来表示库存。如果用普通方式写那么我们的函数会充斥大量的`if...else`语句,这里我们使用职责链模式来写这个函数。 484 | ```js 485 | var order500 = function(orderType, pay) { 486 | if (orderType === 1 && pay) { 487 | console.log('500元定金订购,获得100元优惠券'); 488 | } else { 489 | return 'nextSuccessor'; 490 | } 491 | } 492 | 493 | var order200 = function(orderType, pay) { 494 | if (orderType === 2 && pay) { 495 | console.log('200元定金订购,或得50元优惠券'); 496 | } else { 497 | return 'nextSuccessor'; 498 | } 499 | } 500 | 501 | var orderNormal = function(orderType, pay, stock) { 502 | if (stock > 0) { 503 | console.log('普通购买,无优惠券'); 504 | } else { 505 | console.log('手机库存不足'); 506 | } 507 | } 508 | 509 | var Chain = function(fn) { 510 | this.fn = fn; 511 | this.nextSuccessor = null; 512 | } 513 | 514 | Chain.prototype.setNextSuccessor = function(successor) { 515 | return this.nextSuccessor = successor; 516 | } 517 | 518 | Chain.prototype.passRequest = function() { 519 | var ret = this.fn.apply(this, arguments); 520 | 521 | if (ret === 'nextSuccessor') { 522 | return this.nextSuccessor && this.nextSuccessor.passRequest.apply(this.nextSuccessor, arguments); 523 | } 524 | 525 | return ret; 526 | } 527 | 528 | var chainOrder500 = new Chain(order500); 529 | var chainOrder200 = new Chain(order200); 530 | var chainOrderNormal = new Chain(orderNormal); 531 | 532 | chainOrder500.setNextSuccessor(chainOrder200).setNextSuccessor(chainOrderNormal); 533 | 534 | // 测试 535 | chainOrder500.passRequest(1, true, 500); 536 | chainOrder500.passRequest(1, false, 500); 537 | chainOrder500.passRequest(2, true, 500); 538 | chainOrder500.passRequest(2, false, 500); 539 | chainOrder500.passRequest(3, true, 500); 540 | chainOrder500.passRequest(3, false, 0); 541 | ``` 542 | 543 | ## 中介者模式 544 | 中介者模式使得各个对象之间得以解耦,以中介者和对象之间的一对多关系取代了对象之间的网状多对多欢喜。各个对象之间只需关注自身功能的实现,对象之间的交互关系交给了中介者对象来实现和维护。中介者模式虽然可以方便的对模块和对象进行解耦但是对象之间并非一定需要解耦。我们写程序是为了快速完成项目交付生产,而不是堆砌模式和过度设计。关键在于如何去衡量对象之间的耦合关系。下面是一个中介者模式的简单例子,虽然这里不用中介者模式可以很简单的实现,但是为了说明中介者模式,用中介者模式来实现。 545 | 546 | 这是一个小游戏,可以加入不同的玩家,为简化问题,当一个玩家死亡时,其他玩家获胜。如果用普通的写法,当一个玩家死亡时,我们需要通知所有的玩家他们获胜,这时玩家之间都耦合在一起。使用中介者模式,我们可以解开玩家之间的耦合关系,每个玩家只需要关注自己的状态,当状态改变通知中介者,它会帮助我们通知到各个玩家改变他们的状态。中介者模式用在这里可能有点大材小用,但是足以说明问题。 547 | ```js 548 | var Player = function(name) { 549 | this.name = name; 550 | this.state = 'alive'; 551 | } 552 | 553 | Player.prototype.die = function() { 554 | this.state = 'die'; 555 | mediator.receiveMessage('playerDie', this); 556 | } 557 | 558 | Player.prototype.win = function() { 559 | this.state = 'win'; 560 | console.log(this.name + ':' + ' win'); 561 | } 562 | 563 | var PlayerFactory = function(name) { 564 | var player = new Player(name); 565 | 566 | mediator.receiveMessage('addPlayer', player); 567 | return player; 568 | } 569 | 570 | var mediator = { 571 | players: [], 572 | receiveMessage: function(instructor, player) { 573 | var players = this.players; 574 | if (instructor === 'addPlayer') { 575 | players.push(player); 576 | } else if (instructor === 'playerDie'){ 577 | for(var i = 0; i < players.length; i++) { 578 | if (players[i] !== player) { 579 | players[i].win(); 580 | } 581 | } 582 | } 583 | } 584 | } 585 | 586 | var player1 = new PlayerFactory('player1'); 587 | var player2 = new PlayerFactory('player2'); 588 | var player3 = new PlayerFactory('player3'); 589 | 590 | player1.die(); 591 | 592 | ``` 593 | 594 | ## 装饰者模式 595 | 装饰者模式可以给对象动态地增加职责,而不改变对象自身。跟继承相比,这是一种更加灵活的方式。 596 | 597 | ### AOP 598 | AOP中文翻译为“面向切面编程”,什么是切面,其实我理解的也不是很好。不过有一篇[文章](https://hackernoon.com/aspect-oriented-programming-in-javascript-es5-typescript-d751dda576d0)中写到:如果你为了满足当前环境而每次都粘贴一些重复变量或参数,那么这就是一个基础的AOP候选。目前我暂时把它理解为,将操作想像成一个个平面,如果可以将各层分开,那么就可以在一个操作之前或之后进行动态装饰,这是我理解的AOP。 599 | 600 | 下面是一个使用AOP装饰函数进行数据上报的例子。加入有一个登录弹框,我们要记录有多少人点击了登录,当没有使用AOP装饰时,我们像下面这样写: 601 | ```js 602 | // 弹出浮层 603 | var showLogin = function() { 604 | // 弹出浮层 605 | } 606 | 607 | var log = function() { 608 | // 记录日志 609 | } 610 | 611 | document.getElementById('button').onclick = showLogin; 612 | ``` 613 | 614 | 这样在`showLogin`函数中既要打开浮层又要负责数据上报,两个层面的功能却被耦合在一个函数中,使用AOP分离后,可以像这样: 615 | ```js 616 | Function.prototype.after = function(afterfn) { 617 | var _self = this; // 保存原函数 618 | 619 | return function() { 620 | var ret = _self.apply(this, arguments); // 保证this不被劫持 621 | afterfn.apply(this, arguments); 622 | return ret; 623 | } 624 | } 625 | 626 | // 弹出浮层 627 | var showLogin = function() { 628 | // 弹出浮层 629 | } 630 | 631 | var log = function() { 632 | // 记录日志 633 | } 634 | 635 | showLogin = showLogin.after(log); 636 | 637 | document.getElementById('button').onclick = showLogin; 638 | ``` 639 | 可以看出这样我们将两个层面分离开来,如果有其他需要记录日志的函数,我们可以很方面的同时进行装饰。 640 | 641 | ## 状态模式 642 | 状态模式的关键是区分事务f内部的状态,事务内部状态的改变忘完会带来事务的行为改变。通常谈到封装我们都喜欢先封装事务的行为,但是在状态模式中,要把各个状态都封装成单独的类。js是一种无类语言,没有规定状态一定要从类型中创建而来。状态模式是状态机的实现之一,下面是一个开关的例子: 643 | ```js 644 | var Light = function() { 645 | this.currState = FSM.off; 646 | this.button = null; 647 | } 648 | 649 | Light.prototype.init = function() { 650 | var button = doument.createElement('button'), self = this; 651 | 652 | button.innerHTML = '已关灯'; 653 | this.button = document.body.appendChild(button); 654 | 655 | 656 | this.button.onClick = function( ){ 657 | self.currState.buttonWasPressed.call(self); // 把请求委托给FSM状态机 658 | } 659 | }; 660 | 661 | var FSM = { 662 | off: { 663 | buttonWasPressed: function() { 664 | console.log('关灯'); 665 | this.button.innerHTML = '下一次按我是开灯'; 666 | this.currState = FSM.on; 667 | } 668 | }, 669 | on: { 670 | buttonWasPressed: function() { 671 | console.log('开灯'); 672 | this.button.innerHTML = '下一次按我是关灯'; 673 | this.currState = FSM.off; 674 | } 675 | } 676 | }; 677 | ``` -------------------------------------------------------------------------------- /posts/Linux常用命令记录.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Linux常用命令记录" 3 | date: "2020-11-12" 4 | tags: linux 5 | author: xwchris 6 | desc: "记录平时常用到的命令,方式使用的时候可以更快查询" 7 | --- 8 | 9 | 命令查询 http://linux.51yip.com/ 10 | 11 | ### 查看指定端口程序 12 | ```bash 13 | lsof -i:端口号 14 | netstat -tunlp | grep 端口号 15 | ``` 16 | 17 | ### 文件传输 18 | ```bash 19 | // 本地复制到远程 20 | scp -r ./files root@xxxxx:~/data 21 | ``` -------------------------------------------------------------------------------- /posts/Promise原理与实现.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Promise原理与实现" 3 | date: "2019-07-10" 4 | tags: js,前端 5 | author: xwchris 6 | desc: "Promise到底是什么,具体是如何工作的,如何处理错误的,这篇文章从零实现一个Promise,来帮助理解这些问题" 7 | --- 8 | 9 | ## 简单介绍 10 | Promise是什么,相信不用说了,写过js的人或多或少都,接触过。刚开始用Promise的时候,总感觉这种写法非常的怪异,但是当慢慢熟悉的时候,发现一切都是那么和谐。 11 | 12 | 我自己理解的Promise用来解决异步回调嵌套的问题,它代表了异步操作的一种结果。它是一种状态机,从实现上来说一种回调包装器。 13 | 14 | 当真正看了下[Promise/A+](https://promisesaplus.com/)规范和看了别人的实现代码后,发现Promise的原理和实现也不是很难,所以就有了这篇文章,旨在自我学习,可能也能顺手帮到一些人。这是最后实现的[Promise完整代码](https://github.com/xwchris/simple-promise)和[github原文地址](https://github.com/xwchris/blog/issues/60)。我会频繁更新学习深入现代js框架的文章, 欢迎star。 15 | 16 | ## Promise规范 17 | Promise在ES6出现之前,就已经有了各个版本的"Promise",但是当这些用在一起的时候,如果没有一个统一的实现标准,那将是灾难性的。所以就有了规范,Promise/A+就是Promise目前最新的规范。规范旨在帮助实现者指定要达到的目标,并不是规定如何实现。当写完自己的Promise后,要确定是否符合Promise/A+规范,可以使用[promises-tests](https://github.com/promises-aplus/promises-tests)来进行测试。 18 | 19 | ## 原理和实现 20 | 先来想想Promise的实现应该是个什么样的过程。Promise把我们异步的回调函数进行了封装隐藏,让我们可以在then函数以链式的写法在里面写回调和错误处理,所以按照正常思路就是我们应该将then里的回调函数进行存储,然后等异步执行完后,我们由Promise自行调用。 21 | 22 | 首先是Promise的构造函数 23 | ```js 24 | function Promise (executor) { 25 | // 为了方面后面函数调用,防止this被破坏 26 | var self = this; 27 | this.status = 'pending'; 28 | this.value = null; 29 | this.reason = null; 30 | // resolve的回调队列 31 | this.resolveCbs = []; 32 | // reject的回调队列 33 | this.rejectCbs = []; 34 | 35 | // resolve的时候改变状态,保存好传入的值,并调用相应的回调队列 36 | function resolve(value) { 37 | if (self.status === 'pending') { 38 | // 由于promise需要异步执行,这里使用setTimeout来延迟执行 39 | setTimeout(function() { 40 | self.status = 'fulfilled'; 41 | self.value = value; 42 | self.resolveCbs.forEach(function (cb) { 43 | cb(value); 44 | }); 45 | }); 46 | } 47 | } 48 | 49 | /// 与resolve相似,不过这里保存的是原因,改变状态为rejected 50 | function reject(reason) { 51 | if (self.status = 'pending') { 52 | setTimeout(function() { 53 | self.status = 'rejected'; 54 | self.reason = reason; 55 | self.rejectCbs.forEach(function (cb) { 56 | cb(reason); 57 | }); 58 | }); 59 | } 60 | } 61 | 62 | executor(resolve, reject); 63 | } 64 | ``` 65 | 66 | Promise有三种状态,分别是`pending`,`fulfilled`和`rejected`。它的状态可以由`pending`转为`fulfilled`,由`pending`转为`rejected`,状态一旦确定将无法更改。 67 | 68 | 有了构造器,下面我们来实现Promise中的关键函数,then函数。为了实现链式调用,我们需要返回一个Promise对象,但是我们不能返回自己,因为Promise的状态不可更改,我们需要返回一个新的Promise对象。基本解释都包含在了注释中 69 | 70 | ```js 71 | Promise.prototype.then = function (onResolved, onRejected) { 72 | var self = this; 73 | var promise = null; 74 | 75 | // onResolved是可选的,当其不存在或不是函数时,将其接受到的值一次往后透传 76 | onResolved = typeof onResolved === 'function' ? onResolved : function (value) { return value; }; 77 | // onRejected是可选的,当其不存在或不是函数时,将其错误继续向后抛 78 | onRejected = typeof onRejected === 'function' ? onRejected : function (error) { throw error; }; 79 | 80 | // 新的promise状态需要根据x的具体情况来确定 81 | function resolvePromise(promise, x, resolve, reject) { 82 | // 这一部分属于Promise/A+规范的Resolution Procedure部分 83 | 84 | // 2.3.1: 如果promise对象和x引用的是同一个对象,那么应该用一个TypeError的错误来reject掉promise 85 | // 如果两个对象是同一个对象,那么会无限循环调用,会出现错误 86 | if (promise === x) { 87 | return reject(new TypeError('Chaining cycle detected for promise!')); 88 | } 89 | 90 | // 2.3.2: 如果x是一个promise,应该用以下这些来决定它的状态 91 | if (x instanceof Promise) { 92 | // 2.3.2.1: 如果x是pending状态,那么promise必须是pending状态,直到x是fulfillded或rejected状态 93 | // 2.3.2.2: 如果x是fulfilled状态,那么promise需要用相同的值来resolve 94 | // 2.3.2.3: 如果x是rejected状态,那么promise需要用相同的原因来reject 95 | if (x.status === 'pending') { 96 | x.then(function(value) { 97 | // 由于x可能还是一个promise,所以这里递归调用 98 | resolvePromise(promise, value, resolve, reject); 99 | }, reject); 100 | } else { 101 | x.then(resolve, reject); 102 | } 103 | return; 104 | } 105 | 106 | // 2.3.3: 如果x是一个对象或者函数,这里是出里thenable的情况,thenable是指具有then函数的对象或函数 107 | // 2.3.4: 如果x既不是对象也不是函数,那么直接使用x来resolve promise 108 | if ((x !== null && typeof x === 'object') || typeof x === 'function') { 109 | var isCalled = false; 110 | 111 | try { 112 | // 2.3.3.1: 将x.then赋值为then 113 | var then = x.then; 114 | // 2.3.3.2: 如果检索到x.then的结果抛出了错误,那么直接reject掉 115 | // 2.3.3.3: 如果then是一个函数,那么用x作为this,第一个参数是resolvePromise,第二个参数是rejectPromise 116 | if (typeof then === 'function') { 117 | // 2.3.3.3.1: 如果resolvePromise被使用一个参数值y调用,执行[[Resolve]](promise, y) 118 | // 2.3.3.3.2: 如果rejectPromise被使用一个原因r调用,使用r来reject promise 119 | then.call(x, function (y) { 120 | // 2.3.3.3.3: 如果resolvePromise和rejectPromise同时被调用,或者这两个函数被使用相同的参数多次调用,那么只执行最开始的,其他的全部忽略 121 | if (isCalled) return; 122 | isCalled = true; 123 | return resolvePromise(promise, y, resolve, reject); 124 | }, function (r) { 125 | if (isCalled) return; 126 | isCalled = true; 127 | return reject(r); 128 | }); 129 | } else { 130 | // 2.3.3.4: 如果then不是函数,用x来resolve promise 131 | resolve(x); 132 | } 133 | } catch(err) { 134 | // 2.3.3.3.4: 如果调用then的时候抛出错误 135 | // 2.3.3.3.4.1: 如果resolvePromise和rejectPromise已经被调用了,那么直接忽略掉 136 | // 2.3.3.3.4.2: 否则使用err来reject promise 137 | if (isCalled) return; 138 | isCalled = true; 139 | reject(err); 140 | } 141 | } else { 142 | resolve(x); 143 | } 144 | } 145 | 146 | function handlePromise(modifier, resolve, reject) { 147 | return function (value) { 148 | setTimeout(function() { 149 | try { 150 | var x = modifier(value); 151 | resolvePromise(promise, x, resolve, reject); 152 | } catch(err) { 153 | reject(err) 154 | } 155 | }); 156 | } 157 | } 158 | 159 | if (self.status === 'fulfilled') { 160 | promise = new Promise(function (resolve, reject) { 161 | handlePromise(onResolved, resolve, reject)(self.value); 162 | }); 163 | } else if (self.status === 'rejected') { 164 | promise = new Promise(function (resolve, reject) { 165 | handlePromise(onRejected, resolve, reject)(self.reason); 166 | }); 167 | } else { 168 | promise = new Promise(function (resolve, reject) { 169 | self.resolveCbs.push(handlePromise(onResolved, resolve, reject)); 170 | self.rejectCbs.push(handlePromise(onRejected, resolve, reject)); 171 | }); 172 | } 173 | 174 | return promise; 175 | } 176 | ``` 177 | 到了这里我们的Promise基本已经实现完成。下面让我们来测试我们的Promise是否符合Promise/A+规范。 178 | ## 测试 179 | 这里我们使用[promises-tests](https://github.com/promises-aplus/promises-tests)进行测试,用它进行测试要先实现一个适配器。下面我们按照它的要求来实现一个 180 | ```js 181 | Promise.deferred = function () { 182 | var global = {}; 183 | 184 | var promise = new Promise(function (onResolve, onReject) { 185 | global.onResolve = onResolve; 186 | global.onReject = onReject; 187 | }); 188 | 189 | var resolve = function (value) { 190 | global.onResolve(value); 191 | }; 192 | 193 | var reject = function (reason) { 194 | global.onReject(reason); 195 | } 196 | 197 | return { 198 | promise, 199 | resolve, 200 | reject 201 | } 202 | } 203 | ``` 204 | 205 | 然后导出我们的Promise 206 | ```js 207 | module.exports = Promise; 208 | ``` 209 | 210 | 调用我们的Promise进行测试 211 | ```js 212 | var promisesAplusTests = require('promises-aplus-tests'); 213 | var adapter = require('./promise'); 214 | 215 | promisesAplusTests(adapter); 216 | ``` 217 | 218 | 最后不考虑性能问题,我们的Promise全部通过Promise/A+测试。 -------------------------------------------------------------------------------- /posts/React Fiber实现.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "React Fiber实现" 3 | date: "2018-09-18" 4 | tags: react,前端 5 | author: xwchris 6 | desc: "本文主要对React Fiber的原理进行简单介绍,解决什么是Fiber的疑问。阅读本节需要你对React有一定的了解" 7 | --- 8 | 9 | React Fiber和React Hook简单实现 - [Redul](https://github.com/xwchris/redul) 10 | 11 | ## 知识准备 12 | ### 什么是React 13 | React是用于构建用户界面的 JavaScript 库。同时React是一个pull类型的库,开发者只需要关注业务,不需要过多关注优化与调度等,这是与push类型的库不同的地方。 14 | 15 | ### 浏览器渲染 16 | 浏览器渲染主体流程如下,需要了解的是js与css都会阻塞浏览器的渲染 17 | 18 | ![浏览器渲染图片](https://api.xwchris.me/static/image/a55c1a2d183e6cc1b65f9c3d96d0ff5ab65d7738c63631a8b52cedcc79e58eed.png) 19 | 20 | ## 为什么需要Fiber 21 | 1. 每一个状态的改变不是都需要马上反应在UI界面上 22 | 2. 不同的状态改变应该有不同的优先级,如动画,输入等应该有更高的优先级 23 | 3. 对于大量状态的改变复杂操作应该进行更好的调度,避免UI页面卡顿 24 | 25 | ### Fiber vs Stack 26 | [stack demo page](https://claudiopro.github.io/react-fiber-vs-stack-demo/stack.html) 27 | 28 | [fiber demo page](https://claudiopro.github.io/react-fiber-vs-stack-demo/fiber.html) 29 | 30 | ## Fiber 原理 31 | ### Fiber 结构 32 | 我们知道代码执行是在栈中执行,栈中的代码会一直执行直到栈为空。 33 | 所以为了实现上述的目标我们需要一个能够打断,保存恢复状态和自由调度的栈。这就是Fiber 34 | 在之前的React16之前的版本中我们都知道virtual dom(存储了待渲染节点的信息),这个就很适合做为自定义栈的栈帧,而且我们需要在其中添加更多的信息,这就个就被称为fiber节点(虚拟栈帧) 35 | 36 | ![fiber结构](https://api.xwchris.me/static/image/7082fb1664ee00263833a66d725239ebfe7233df5e97bbd9e41823678e62b261.png) 37 | 38 | 39 | fiber中除了要渲染的节点信息,还包括了节点间的关系的信息,以及其他一些额外的信息 40 | 41 | ```js 42 | interface FiberNode

    { 43 | tag: FiberNodeTag 44 | // element attrs 45 | // HOST_ROOT_NODE has node type 46 | type?: ElementType 47 | props?: PropsWithChildren

    48 | children: ElementChildren 49 | 50 | // fiber relations 51 | alternate?: FiberNode | null 52 | parent?: FiberNode | null 53 | child?: FiberNode | null 54 | sibling?: FiberNode | null 55 | 56 | // effect 57 | effectTag?: EffectTag | null 58 | effects: FiberNode[] 59 | 60 | // other 61 | statNode: HTMLElementOrText | RootHTMLElementWithFiberNode | null 62 | hooks?: Hook | null 63 | isPartialStateChanged?: boolean 64 | updateQueue?: HookEffect[] 65 | isMount?: boolean 66 | } 67 | ``` 68 | 69 | fiber树的整体结构是一个双向循环链表,这种结构能够更加快速的找到相对应的节点。 70 | 71 | 在Reconcile过程中为了能够知道之前节点的信息,需要将新的fiber节点与老fiber节点进行关联。 72 | 73 | Fiber中会同时存在两种fiber tree,每次Reconcile的过程就是新fiber tree构建的过程,当commit之后新的fiber tree就变成了current fiber tree,如此循环往复。 74 | 75 | [fiber简单实现](https://github.com/xwchris/redul/blob/master/src/reconcile.ts) 76 | ### Fiber Effect 77 | 在Reconcile的过程中,需要给节点设置状态,与旧节点相比需要达到的状态。每个fiber节点构建完成后(设置自己的effectTag状态),如果有effect则将自己以及其子孙元素放入父节点的effects中,这样层层构建,最终新的fiber tree的effects中存储的就是所有要处理的fiber node。然后进入到commit阶段,将所有的fiber node进行到dom的转换,进行UI页面的刷新。 78 | 79 | ```js 80 | // fiber effect 81 | export enum EffectTag { 82 | NOTHING, 83 | UPDATE, 84 | REPLACE, 85 | ADD, 86 | REMOVE 87 | } 88 | ``` 89 | ### Fiber 调度 90 | Fiber既然是一个虚拟栈,那么就需要进行调度。为了实现更佳的UI体验,就需要在合适的时间执行我们的代码 91 | 这里介绍一个浏览器API 92 | ![idle period](https://api.xwchris.me/static/image/1cb846cfb73e4aa8769b91c33c3eeadf50710b774d8fe64e492bd969a2a3c132.png) 93 | 94 | 所以我们可以利用该函数在浏览器空闲的时候来执行我们的代码,这样可以达到不阻塞页面渲染的目的 95 | ```js 96 | window.requestIdleCallback(callback[, options]) 97 | ``` 98 | 该函数会在浏览器空闲的时候调用,并传递一个[IdleDeadline](https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline)对象给`callback`,我们要用到`IdleDeadline.timeRemaining`函数,该函数会返回一个值,告诉我们浏览器的idle时间还有多久,如果已经结束则值是`0` 99 | 100 | ```js 101 | export function render(element: ElementInput, containerDom: HTMLElement) { 102 | // clear all before render 103 | dispatcher.clearDomContent(containerDom) 104 | const rootFiberNode = createRootFiberNode(element, containerDom) 105 | taskQueue.push(rootFiberNode) 106 | 107 | requestIdleCallback(performWork) 108 | return containerDom 109 | } 110 | 111 | export function scheduleUpdate(fiberNode: FiberNode) { 112 | taskQueue.push(fiberNode) 113 | 114 | // when no work in progress, start immediately 115 | if (!nextUnitWork) { 116 | requestIdleCallback(performWork) 117 | } 118 | } 119 | 120 | function performWork(deadline: RequestIdleCallbackDeadline) { 121 | nextUnitWork = resolveNextUnitWork() 122 | if (!nextUnitWork) { 123 | commitAllWork() 124 | return 125 | } 126 | 127 | if (deadline.timeRemaining() > ENOUGH_TIME) { 128 | nextUnitWork = performUnitWork(nextUnitWork) 129 | } 130 | 131 | requestIdleCallback(performWork) 132 | } 133 | ``` 134 | 135 | 真正的React中使用的并不是RequestIdleCallback API,因为它有两个问题 136 | 137 | 1. 兼容性不好 138 | 2. 一秒钟仅能调用20次,对于UI任务来说基本没什么用 139 | 140 | 所以React中实际上是自己实现了一个requestIdleCallback,实现中要用的一个API 141 | ```js 142 | window.requestAnimationFrame(callback) 143 | ``` 144 | 用该函数作为定时器,其会在下一次页面重绘前进行调用,精度较高。但它也有一个缺点,就是在后台的时候不会执行,这个时候可以使用`setTimeout`做降级处理 145 | ```js 146 | rAFID = requestAnimationFrame(function(timestamp) { 147 | // cancel the setTimeout 148 | localClearTimeout(rAFTimeoutID); 149 | callback(timestamp); 150 | }); 151 | rAFTimeoutID = setTimeout(function() { 152 | // 定时 100 毫秒是算是一个最佳实践 153 | localCancelAnimationFrame(rAFID); 154 | callback(getCurrentTime()); 155 | }, 100); 156 | ``` 157 | 158 | 有了定时器之后,我们根据当前时间`performance.now()`和每一帧的时间(假如是60fps则每一帧平均时间为16.6ms)算出下一帧的时间,执行的时候跟当前时间比对就可以知道是否还有空余时间 159 | 160 | ### Fiber 优先级 161 | 为了更好的用户体验,需要让优先级更高的任务优先执行,如动画,输入等。Fiber中分为五种优先级,每种优先级对应一个过期时间。 162 | 163 | ```js 164 | 165 | // 5种优先级 166 | 167 | // TODO: Use symbols? 168 | var ImmediatePriority = 1; 169 | var UserBlockingPriority = 2; 170 | var NormalPriority = 3; 171 | var LowPriority = 4; 172 | var IdlePriority = 5; 173 | 174 | // Max 31 bit integer. The max integer size in V8 for 32-bit systems. 175 | // Math.pow(2, 30) - 1 176 | // 0b111111111111111111111111111111 177 | var maxSigned31BitInt = 1073741823; 178 | 179 | // 5种优先级对应的5种过期时间 180 | 181 | // Times out immediately 182 | var IMMEDIATE_PRIORITY_TIMEOUT = -1; 183 | // Eventually times out 184 | var USER_BLOCKING_PRIORITY = 250; 185 | var NORMAL_PRIORITY_TIMEOUT = 5000; 186 | var LOW_PRIORITY_TIMEOUT = 10000; 187 | // Never times out 188 | var IDLE_PRIORITY = maxSigned31BitInt; 189 | ``` 190 | 191 | 每次循环,如果有过期的任务,那么无论如何要把过期的任务执行完毕,然后如果有剩余时间则按照到过期时间小的优先执行,以此类推。 192 | 193 | ## 参考资料&好文推荐 194 | 1. [react-fiber-architecture](https://github.com/acdlite/react-fiber-architecture) 195 | 2. [The how and why on React’s usage of linked list in Fiber to walk the component’s tree](https://medium.com/react-in-depth/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-67f1014d0eb7) 196 | 3. [Didact Fiber: Incremental reconciliation](https://engineering.hexacta.com/didact-fiber-incremental-reconciliation-b2fe028dcaec) 197 | 4. [learn-react-essence/调度原理](https://github.com/KieSun/learn-react-essence/blob/master/%E8%B0%83%E5%BA%A6%E5%8E%9F%E7%90%86.md) 198 | -------------------------------------------------------------------------------- /posts/Redux原理与实现.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Redux原理与实现" 3 | date: "2018-09-18" 4 | tags: react,前端,js 5 | author: xwchris 6 | desc: "这篇文章要介绍的是redux,这里不会讲解redux的用法,我们的主要目标是理解redux的思想,并看看redux是如何实现的" 7 | --- 8 | 9 | redux作为一个如此受欢迎的状态管理框架,当看到他的源码缩减起来只有区区几十行时,真的是惊呆了我。我们在实现一个redux的过程中理解redux的思想。为了简便,这里我们忽略错误处理(值为空等情况) 10 | 11 | ## redux的目标 12 | 实现redux的第一步,我们要先了解我们的目标。在redux中,使用一个单一的`store`来管理状态,所有的数据操作操作都不能直接进行操作,需要使用`dispatch`来触发特定的动作,并结合先前定义的`reducer`来生成新的状态。 13 | 14 | ## createStore 15 | 创建store需要使用createStore函数,它的参数分别是`reducer`、`initState`和`enhancer`,如果第二个参数是函数时,则将第二个参数作为enhancer。下面我们来创建这个函数 16 | ```js 17 | function createStore(reducer, initState, enhancer) { 18 | // 如果第二个参数是函数则将该值赋给enhancer 19 | if (typeof initState === "function") { 20 | enhancer = initState; 21 | } 22 | 23 | // 这里处理中间件的情况,我们稍后处理 24 | if (enhancer && typeof enhancer === 'function') { 25 | return enhancer(createStore)(reducer, initState); 26 | } 27 | 28 | function dispatch() {} 29 | 30 | function getState() {} 31 | 32 | function subscribe() {} 33 | 34 | // 返回 35 | return { 36 | dispatch, 37 | getState, 38 | subscribe 39 | }; 40 | } 41 | ``` 42 | 43 | 我们已经将基本的结构写完了,接下来分别实现我们要返回的三个函数`dispatch`、`getState`和`subscribe`。 44 | 45 | ### getState 46 | `getState`函数返回当前的state的值,这个函数利用闭包特性访问到当前state,并返回该值。 47 | ```js 48 | /* ... other code */ 49 | const currentState = reducer(initState, {}); 50 | 51 | function getState() { 52 | return currentState; 53 | } 54 | /* ... other code */ 55 | ``` 56 | 57 | ### subscribe 58 | `subscribe`函数用来进行事件的订阅,每次发生新的动作时,都要通知订阅者。为了保证新加入的订阅,不影响当前的动作,我们将新的订阅加入一个临时监听器列表中`nextListeners`。下次调用的时候再更新监听器列表。 59 | ```js 60 | let currentListeners = []; 61 | let nextListeners = []; 62 | 63 | /* ... other code */ 64 | function subscribe(func) { 65 | ensureCanMuteNextListeners(); 66 | 67 | nextListeners.push(func); 68 | 69 | let isSubscribe = true; 70 | 71 | // 返回取消订阅函数 72 | return function unsubscribe() { 73 | if (!isSubscribe) { 74 | return; 75 | } 76 | 77 | ensureCanMuteNextListeners(); 78 | 79 | const index = nextListeners.indexOf(func); 80 | nextListeners.splice(index, 1); 81 | } 82 | } 83 | 84 | function ensureCanMuteNextListeners() { 85 | if (currentListeners === nextListeners) { 86 | nextListeners = currentListeners.slice(); 87 | } 88 | } 89 | /* ...other code */ 90 | ``` 91 | 92 | 这里的`ensureCanMuteNextListeners`保证改变`nextListeners`能够不影响`currentListeners`。 93 | 94 | ### dispatch 95 | 剩下的`dispatch`函数就很简单了,该函数接收一个`action`参数,返回一个新的状态,并通知所有订阅者。 96 | ```js 97 | let currentState = initState; 98 | let currentListeners = []; 99 | let nextListeners = []; 100 | 101 | function dispatch(action) { 102 | if (action && action.type) { 103 | currentState = reducer(currentState, action); 104 | 105 | const listeners = currentListeners = nextListeners; 106 | listeners.forEach(listener => listener()); 107 | } 108 | } 109 | ``` 110 | 111 | ## 中间件 112 | redux强大的地方在于,它对中间件的支持,我们刚才的`createStore`函数参数中有`enhancer`函数,这个就是中间用来处理中间件的情况的。 113 | 114 | 在redux中使用`applyMiddleware`处理中间件,它接收一个中间件数组,下面我们来实现它吧! 115 | 116 | ## applyMiddleware 117 | 先说说applyMiddleware怎么来处理中间件。 118 | 119 | 所谓中间件,就是一种中间处理的函数,它接受一个输入,在处理输入后,再进行输出。由于中间件可能是一系列的列表所以我们要,将这些中间件做成一条链的形式。即每个中间件接受下一个中间件的输出作为输入来进行处理。 120 | 121 | 中间件函数的形式一般都是 122 | ```js 123 | const middleware = ({ dispatch, getState }) => next => action => { 124 | /* code */ const val = next(action); 125 | /* code */ return val; 126 | }; 127 | ``` 128 | 129 | 下面是我们`applyMiddleware`函数。因为我们要处理的是`action`,所以加入中间件的本质操作就是改写我们的`dispatch`函数,做一个增强版的`dispatch`。 130 | 131 | ```js 132 | function applyMiddleware(...middlewares) { 133 | // 我们需要先来创建store实例,利用createStore函数和reducer & initState 134 | return createStore => (reducer, initState) => { 135 | const store = createStore(reducer, initState); 136 | 137 | // 默认的dispatch函数 138 | let dispatch = () => {}; 139 | 140 | // 中间件函数的形式我们上面提到过 141 | const middlewareAPI = { 142 | getState: store.getState, 143 | dispatch: (...args) => dispatch(...args) 144 | }; 145 | 146 | const chains = middlewares.map(middleware => middleware(middlewareAPI)); 147 | 148 | function compose(...funcs) { 149 | if (funcs.length <= 1) { 150 | return funcs[0] || (() => {}); 151 | } 152 | 153 | // compose函数,如[a,b,c,d]最终组合成的函数形式是(..args) => a(b(c(d(..args)))) 154 | return funcs.reduce((a, b) => (...args) => a(b(...args))); 155 | } 156 | 157 | // 重写dispatch 158 | dispatch = compose(chains)(store.dispatch); 159 | 160 | // 返回store 161 | return { 162 | ...store, 163 | dispatch, 164 | } 165 | } 166 | } 167 | ``` 168 | 169 | ## 最后 170 | 写到这里redux的核心功能我们已经完成了,这些都是实际上redux的实现方式,只不过我们少了一些边界条件的处理和一些特殊情况的判定。在学习redux核心的时候真正明白了,优秀的思想才是一个开源库成功最大的助推因素。同时对于我自己也学到了一些例如闭包的实用性和compose函数等等。总之,还要多多加油,革命尚未成功,同志仍需努力。 171 | 172 | 最后附上github上的原文链接:[原文链接](https://github.com/xwchris/blog/issues/67),之后我会在github上稳定更新,希望能和大家多多交流~。 -------------------------------------------------------------------------------- /posts/V8中的垃圾回收机制.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "V8中的垃圾回收机制" 3 | date: "2020-11-05" 4 | tags: node 5 | author: xwchris 6 | desc: "垃圾回收即将无用的内存进行回收的过程。在v8中采用了基于分代式的垃圾回收机制。在长期的实践中,人们发现没有一种方法可以一劳永逸的解决所有情况。因为js中的对象生存周期不同。为了更加高效的进行垃圾回收,采用了分代式的垃圾回收机制" 7 | --- 8 | 9 | v8中将分配的内存分为新生代和老生代。在新生代中采用scavenge算法进行垃圾回收。在老生代中采用headp-sweep和heap-compact进行垃圾回收。 10 | 11 | ## scavenge 12 | scanvenge算法是一种复制算法,主要处理那些生命周期短的对象。它是一种牺牲空间来换取时间的算法。它将新生代分为两个semispace空间,分别称为`From`空间和`To`空间。每次内存分配都会在`From`空间中进行分配。当进行垃圾回收时,遍历`From`空间,将存活对象复制到`To`空间中,最后将其他未存活的对象释放掉,然后`From`和`To`的角色互换。这样就完成了一次,新生代中的垃圾回收。 13 | 14 | 该方法虽然很快,但是缺点也很明显。它通过复制的方法白白浪费了一半的空间,这些用来处理生命周期短的对象还可以,如果处理其他生命周期比较长的对象,将会浪费巨大的空间。 15 | 16 | ## Mark-Sweep和Mark-Compact 17 | 对于生命周期长的对象采用`Mark-Sweep`进行清理。当使用scavenge算法进行清理的时候,想要看对象是否符合以下两个条件,如果符合,则应该将该对象放入老生代中。 18 | 19 | - 该对象是否进行过scavenge的过程 20 | - `To`空间的使用量是否超过25%(如果使用量太高,当与`From`进行角色对换后,会造成内存不够分配的问题) 21 | 22 | 这个将对象放入老生代的过程称为“对象晋升”。 23 | 24 | ### Mark-Sweep 25 | `Mark-Sweep`意为标记清除,分为标记和清除两个阶段。它清理内存的过程为,标记所有存活对象,然后清理掉没有被标记的对象。 26 | 27 | 这样进行标记清理节省了空间,但是会带来一个内存不连续,出现碎片的问题。为了解决这个问题,`Mark-Compact`被提出来。 28 | 29 | ### Mark-Compact 30 | `Mark-Compact`是标记整理的意思,它是在`Mark-Sweep`的基础上演变来的。它在整理过程中,将所有存活对象向一侧移动,移动完成后清理掉所有边界外的内存。 31 | 32 | 由于`Mark-Compact`的过程较慢,实际使用中`Mark-Sweep`和`Mark-Compact`是配合使用的。一般使用`Mark-Sweep`当需要分配大内存时,使用`Mark-Compact`进行整理。 33 | 34 | ## 垃圾回收方法对比 35 | 36 | 回收算法 | mark-sweep | mark-compact | scavenge 37 | --- | --- | --- | --- 38 | 速度 | 中等 | 最慢 | 最快 39 | 空间开销 | 小(有碎片) | 小(无碎片) | 大 40 | 是否移动对象 | 否 | 是 | 是 41 | 42 | ## node中的内存和堆外内存 43 | v8分配的堆内存有带下限制,64位电脑约为1.4G,32位电脑约为0.7G。可以使用如下代码查看垃圾分配的过程。使用的方法为`process.memoryUsage`。它会返回`{ heapTotal, heapUsed, rss }`他们分别对应总堆内存大小,已使用堆内存大小,常驻内存大小。 44 | ```js 45 | const showMem = () => { 46 | const memory = process.memoryUsage(); 47 | 48 | const format = (size) => `${size / 1024 / 1024} MB`; 49 | 50 | console.log(`heapTotal: ${format(memory.heapTotal)}, heapUsed: ${format(memory.heapUsed)}, rss: ${format(memory.rss)}`); 51 | console.log('------------------------------------------------------------------------------'); 52 | } 53 | 54 | const useMem = () => { 55 | const size = 20 * 1024 * 1024; 56 | const arr = new Array(size); 57 | for (let i = 0; i < size; i++) { 58 | arr[i] = 0; 59 | } 60 | return arr; 61 | } 62 | 63 | const total = []; 64 | 65 | for (let i = 0; i < 15; i++) { 66 | showMem(); 67 | total.push(useMem()); 68 | } 69 | 70 | showMem(); 71 | ``` 72 | 执行以上代码会发现,每次调用`useMem`都会导致三个值增长。在接近1500MB的时候,无法继续分配内存,然后进程内存溢出。我们可以看出堆的总用量总是小与进程的常驻内存用量,这意味着内存使用并非都是通过v8进行分配的,那些不是通过v8进行分配的内存称为堆外内存。 73 | 74 | 将上述`useMem`方法稍微变一下: 75 | ```js 76 | const useMem = () => { 77 | const size = 200 * 1024 * 1024; 78 | const buffer = new Buffer(size); 79 | for (let i = 0; i < size; i++) { 80 | buffer[i] = 0; 81 | } 82 | return buffer; 83 | } 84 | ``` 85 | 改造后,发现唯一明显变化的值只有`rss`,并且该值已经远远超过v8内存的限制,这是由于`Buffer`对象不同于其他对象,它不经过v8的内存分配机制,所以也不会有堆内存的大小限制。这意味可以利用堆外内存突破内存限制问题。 -------------------------------------------------------------------------------- /posts/Web Workers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Web Workers" 3 | date: "2019-06-07" 4 | tags: js,前端 5 | author: xwchris 6 | desc: " Web Workers允许Web应用程序在一个与主线程分离的后台线程中运行脚本。这样的好处是,可以让一些费时的任务在worker中处理而不阻塞或放慢主线程" 7 | --- 8 | 9 | > 该文参考[MDN Web Worker](https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API) 10 | 11 | ## Web Workers 12 | 可以使用Worker函数来创建一个worker对象,它接收一个js文件路径,该js文件中包括了要在worker中执行的代码。 13 | 14 | 在worker中,全局上下文不是window,而是一个[DedicatedWorkerGlobalScope](https://developer.mozilla.org/zh-CN/docs/Web/API/DedicatedWorkerGlobalScope)对象 15 | 16 | ```js 17 | // 创建worker 18 | const worker = new Worker('test.js'); 19 | ``` 20 | 21 | worker和主线程以及其他worker之间可以使用`postMessage`方法进行通信。使用`onmessage`来监听接收的消息。 22 | 23 | ##### main.js 24 | ```js 25 | if (window.Worker) { 26 | const worker = new Worker('worker.js'); 27 | worker.postMessage({ name: 'xxx' }); 28 | } 29 | ``` 30 | 31 | ##### worker.js 32 | ```js 33 | this.onmessage = function (e) { 34 | console.log(e.data); 35 | } 36 | 37 | // output: {name: 'xxx'} 38 | ``` 39 | 40 | 除了该标准worker,还有其他特殊的worker,以下部分都是介绍这些特殊的worker。 41 | 42 | ## Shared Workers 43 | 不同于上面介绍的专用worker,shared workers可以在多个浏览上下文中共享,如多个页面、多个worker之间共享。它的全局对象是`SharedWorkerGlobalScope`的实例。 44 | 45 | 与专用worker不同,shared worker需要通过其返回的`MessagePort`的对象进行通信和控制。获取该对象需要使用`port`属性。 46 | 47 | 如果使用addEventListener进行了事件绑定,需要使用`port.start()`方法进行激活。 48 | 49 | shared worker代码内部,需要使用`onconnect`事件来监听连接事件。 50 | 51 | ##### index.js 52 | ```js 53 | var worker = new SharedWorker("worker.js"); 54 | worker.port.addEventListener("message", function(e) { 55 | console.log("Got message: " + e.data); 56 | }, false); 57 | worker.port.start(); 58 | worker.port.postMessage("start"); 59 | ``` 60 | 61 | ##### worker.js 62 | ```js 63 | var connections = 0; 64 | 65 | self.addEventListener("connect", function(e) { 66 | var port = e.ports[0]; 67 | connections ++; 68 | port.addEventListener("message", function(e) { 69 | if (e.data === "start") { 70 | var ws = new WebSocket("ws://localhost:6080"); 71 | port.postMessage("started connection: " + connections); 72 | } 73 | }, false); 74 | port.start(); 75 | }, false); 76 | ``` 77 | 78 | ## Service Workers 79 | service worker有着自己独特的功能,虽然它也是一个worker。引用MDN上对Service Worker的介绍,它能够拦截网络请求,本质上充当Web应用程序和浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。目的是为了能够在浏览器上创建有效的离线体验。此外,他们还允许访问推送通知和后台同步API。 80 | 81 | 出于安全考量,Service Worker只能由HTTPS承载,毕竟修改网络请求能力暴露给中间人会非常危险。Service Worker能细致的控制每一件事情。它被设计为完全异步,同步API(如果XHR和localStorage)不能再service worker中使用。 82 | 83 | Service Worker生效的步骤是:注册 -> 安装 -> 激活。激活后的service worker就可以托管web app了。 84 | 85 | ## 其他Workers 86 | 其他特殊的workers还包括[Chrome Workers](https://developer.mozilla.org/zh-CN/docs/Web/API/ChromeWorker)和[Audio Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API#Audio_Workers),由于它们的常用性和兼容性,这里不再介绍。 87 | -------------------------------------------------------------------------------- /posts/Webkit浏览器基础知识.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Webkit浏览器基础知识" 3 | date: "2019-07-10" 4 | tags: 前端 5 | author: xwchris 6 | desc: "webkit浏览器的一些基础知识,用以记录、查阅和复习" 7 | --- 8 | 9 | ## 浏览器组成 10 | ![浏览器组成](https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/layers.png) 11 | 12 | 浏览器主要由这其部分组成,从上到下,从左到右分别为: 13 | - 用户界面:用来展示和交互 14 | - 浏览器引擎:用来在用户界面和渲染引擎之间传递指令 15 | - 渲染引擎:用来解析HTML和CSS等,然后将解析的内容在界面上显示 16 | - 网络:用来下载各种网络资源 17 | - js解释器:用来解释和执行Javascript 18 | - 后端UI:用来绘制图形 19 | - 数据存储:持久层,用来保存各种数据 20 | 21 | ## 浏览器渲染流程 22 | 在渲染引擎从网络请求号对应的html和css资源后,开始解析和渲染,步骤如下: 23 | ```js 24 | 构造出DOM树 => 构造出渲染树 => 计算布局 => 进行绘制 25 | ``` 26 | 27 | 需要了解的是,这是一个渐进的过程,为了更好的用户体验,渲染引擎会尽快将内容显示在屏幕上。在不断接收和处理网络请求的同时,渲染引擎会渲染部分内容,不必等到整个html解析完毕。 28 | 29 | ## 构造渲染树 30 | 在解析html构造DOM树的过程中,渲染引擎会解析出DOM树节点对应的样式信息。然后在构造渲染树的时候,将相应的样式信息附加到DOM节点中。 31 | 32 | 渲染树与DOM有对应关系,但并不是一一对应的,例如``和`display: none`的dom节点不会出现在渲染树中,还有如select有节点可能有多个渲染对象。 33 | 34 | 有`float`和`position`的元素会调整自己在渲染树中的位置,每个渲染对象在计算完布局后都会有宽度,高度和位置等信息,这些对应的就是css中的盒模型。 35 | 36 | 计算布局的过程称为“重排”,布局完成后,最后需要调用后台UI进行绘制,这样就能够在用户界面展现页面了。 37 | 38 | ## 重排和重绘 39 | 当布局发生变化如插入了新的dom节点,会发生重排,重排分为全局重排和增量重排。全局重排即将所有元素重新进行布局,增量重排只重排那些需要重排的元素。同样的重绘发生在节点外观发生改变时,它也分为全局重绘和增量重绘。它与重排类似,如果只是局部变化,只是重绘变化的部分。 40 | 41 | ## 总结 42 | 浏览器渲染是一个很复杂的过程,大步骤就上面提到的那么四步。本文只是简单记录了下渲染过程,但其中网络请求,html解析,构建dom树,解析css,构建渲染树,布局,绘制都是有很多细节没有提到。可以看下参考文章原文,它说的较为详细。 43 | 44 | ## 参考文章 45 | - [浏览器的工作原理:新式网络浏览器幕后揭秘](https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#The_browsers_we_will_talk_about) -------------------------------------------------------------------------------- /posts/Web安全.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Web安全" 3 | date: "2020-06-07" 4 | tags: 前端 5 | author: xwchris 6 | desc: "很多网站经常会有各种方面的漏洞,这些漏洞会被黑客利用来窃取用户信息或做一些其他不合法的事。目前对于网站最常见的两种攻击方式xss和csrf" 7 | --- 8 | 9 | ## XSS 10 | ### 什么是xss 11 | xss全称为cross site script,翻译过来是跨站脚本攻击。该攻击主要利用的是浏览器对服务器内容的信任造成的,如果有用户恶意在网站上插入了脚本,而该内容又被保存到服务器展示给其他用户,那么这就完成了一个跨站脚本攻击。通常恶意脚本会执行一些其他操作,如冒充用户发送请求等,那么这就是csrf攻击。因此xss一般不是独立存在的,它是一种攻击常用到的手段。 12 | 13 | ### 如何防护 14 | 为了防止xss攻击,关键是不能执行恶意脚本。因此有以下几种手段: 15 | - 在客户端对用户输入的内容进行过滤,如过滤` 53 | 54 | ``` 55 | 请求服务器后服务器需要拿到`callback`字段的值,然后将要返回的值变成JSON,放入`getName`中。最终返回的值x像这个形式`;getName({"name": "chris"})`,这样getName函数就可以就可以拿到相应的值。 56 | 57 | JSONP相比CORS,只能进行GET请求,但是兼容性好一些。不过现代浏览器基本上都支持了CORS请求,可以放心食用。 -------------------------------------------------------------------------------- /posts/实现ES6中的类.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "实现ES6中的类" 3 | date: "2020-11-17" 4 | tags: js 5 | author: xwchris 6 | desc: "为了真正理解ES6中类的概念,来学习类是如何实现的" 7 | --- 8 | 9 | 我们都知道在JS中,函数是“一等公民”,“类”的概念是在ES6中提出的,它好像跟我们自己写的函数构造器一样,但又有好像有些不一样的地方,那么它到底是如何实现的那?为了达到这个目的,我们利用babel来看下它编译后的代码。 10 | 11 | ## 不带继承的类 12 | 首先我们写一个简单的类,该类没有任何继承,只有一个简单的构造函数和`getName`函数 13 | ```js 14 | class App { 15 | constructor(name) { 16 | this.name = name; 17 | } 18 | 19 | getName() { 20 | return this.name; 21 | } 22 | } 23 | ``` 24 | 25 | 然后babel一下,我们得到以下结果: 26 | ```js 27 | "use strict"; 28 | 29 | var _createClass = function () { 30 | function defineProperties(target, props) { 31 | for (var i = 0; i < props.length; i++) { 32 | var descriptor = props[i]; 33 | descriptor.enumerable = descriptor.enumerable || false; 34 | descriptor.configurable = true; 35 | if ("value" in descriptor) descriptor.writable = true; 36 | Object.defineProperty(target, descriptor.key, descriptor); 37 | } 38 | } 39 | return function (Constructor, protoProps, staticProps) { 40 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 41 | if (staticProps) defineProperties(Constructor, staticProps); 42 | return Constructor; 43 | }; 44 | }(); 45 | 46 | function _classCallCheck(instance, Constructor) { 47 | if (!(instance instanceof Constructor)) { 48 | throw new TypeError("Cannot call a class as a function"); 49 | } 50 | } 51 | 52 | var App = function () { 53 | function App(name) { 54 | _classCallCheck(this, App); 55 | 56 | this.name = name; 57 | } 58 | 59 | _createClass(App, [{ 60 | key: "getName", 61 | value: function getName() { 62 | return name; 63 | } 64 | }]); 65 | 66 | return App; 67 | }(); 68 | ``` 69 | 东西还挺多,一眼并看不出来什么东西来,我们接下来一点点分析。我们先看最后一个函数: 70 | ```js 71 | // 立即执行函数 72 | var App = function () { 73 | 74 | // 构造函数变形成这样 75 | function App(name) { 76 | 77 | // 从这个函数的名字上看,好像是类调用检查,我们暂时先不看这个函数 78 | _classCallCheck(this, App); 79 | 80 | this.name = name; 81 | } 82 | 83 | // 调用了一个_createClass函数,应该是在给App附加一些值 84 | _createClass(App, [{ 85 | key: "getName", 86 | value: function getName() { 87 | return name; 88 | } 89 | }]); 90 | 91 | // 返回一个名为App的函数 92 | return App; 93 | }(); 94 | ``` 95 | 96 | 下面来看`_createClass`函数,该函数用来定义各个属性值: 97 | ```js 98 | // 从返回值看,该函数是一个高阶函数 99 | var _createClass = function () { 100 | 101 | // 为目标值添加多个属性 102 | function defineProperties(target, props) { 103 | for (var i = 0; i < props.length; i++) { 104 | 105 | // 开始设定描述符对象 106 | var descriptor = props[i]; 107 | 108 | // 默认不可枚举 109 | descriptor.enumerable = descriptor.enumerable || false; 110 | 111 | // 默认可配置 112 | descriptor.configurable = true; 113 | 114 | // 存在value值则默认可写 115 | if ("value" in descriptor) descriptor.writable = true; 116 | 117 | // 使用Object.defineProperty来设置属性 118 | Object.defineProperty(target, descriptor.key, descriptor); 119 | } 120 | } 121 | 122 | // 函数接收三个参数,分别是:构造函数,原型属性,静态属性 123 | return function (Constructor, protoProps, staticProps) { 124 | 125 | // 为构造函数prototype添加属性(即为用构造函数生成的实例原型添加属性,可以被实例通过原型链访问到) 126 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 127 | 128 | // 为构造函数添加属性 129 | if (staticProps) defineProperties(Constructor, staticProps); 130 | return Constructor; 131 | }; 132 | }(); 133 | ``` 134 | 好像很简单,跟我们平时使用函数实现差别不是很多,就相差了一个描述符的设定过程。最后看一下类调用检查函数`_classCallCheck`: 135 | ```js 136 | // 类调用检查,不能像普通函数一样调用,需要使用new关键字 137 | function _classCallCheck(instance, Constructor) { 138 | if (!(instance instanceof Constructor)) { 139 | throw new TypeError("Cannot call a class as a function"); 140 | } 141 | } 142 | ``` 143 | 增加了错误处理,当我们调用方式不正确时,抛出错误。 144 | ## 模拟实践 145 | 我们简单实现以下没有继承的方式,来加深我们的印象,为了简化不添加错误处理和描述符的设定过程。 146 | ```js 147 | var App = function(name) { 148 | this.name = name; 149 | } 150 | 151 | App.prototype.getName = function() { 152 | return this.name; 153 | } 154 | 155 | var app = new App('miniapp'); 156 | 157 | console.log(app.getName()); // 输出miniapp 158 | ``` 159 | 这个很简单,就是我们平常模拟“类”所使用的方法,js所有对象都是通过原型链的方式“克隆”来的。注意我们这里的`App`不能叫做类,在js中没有类的概念。它是一个函数构造器,它可以被当做普通函数调用,也可以被当做函数构造器调用,调用函数构造器使用`new`关键字,函数构造器会克隆它的`prototype`对象,然后进行一些其他操作,如赋值操作,最后返回一个对象。 160 | 161 | 下面想一个问题,实现继承我们一般都是利用原型链的方式,像下面这样: 162 | ```js 163 | var dog = { 164 | name: 'goudan' 165 | }; 166 | 167 | var animal = { 168 | getName: function() { 169 | return this.name; 170 | } 171 | } 172 | 173 | // 对象的原型通过`__proto__`暴露出来(tip: 实际中不要这么写) 174 | dog.__proto__ = animal; 175 | console.log(dog.getName()); // 输出goudan 176 | ``` 177 | 我们如何在两个类之间继承那?在ES6中实现很简单 178 | ```js 179 | class Animal { 180 | constructor(name) { 181 | this.name = name; 182 | } 183 | 184 | getName() { 185 | return this.name; 186 | } 187 | } 188 | 189 | class Dog extends Animal{ 190 | constructor(name) { 191 | super(name); 192 | this.name = name; 193 | } 194 | } 195 | ``` 196 | 如果我们自己实现一个要怎么实现,我们先写一个: 197 | ```js 198 | var Animal = function(name) { 199 | this.name = name; 200 | } 201 | 202 | Animal.prototype.getName = function() { 203 | return this.name; 204 | } 205 | 206 | var Dog = function(name) { 207 | Animal.call(this, name); 208 | this.name = name; 209 | } 210 | 211 | Dog.prototype = Animal.prototype; 212 | 213 | var dog = new Dog('goudan'); 214 | console.log(dog.getName()); // 输出goudan 215 | ``` 216 | 但这种方式总感觉不太好,那么ES6中的的继承是如何实现的?下面我们看看继承的实现方式 217 | 218 | ## 继承的实现 219 | 还是用这个继承的例子: 220 | ```js 221 | class Animal { 222 | constructor(name) { 223 | this.name = name; 224 | } 225 | 226 | getName() { 227 | return this.name; 228 | } 229 | } 230 | 231 | class Dog extends Animal{ 232 | constructor(name) { 233 | super(name); 234 | this.name = name; 235 | } 236 | } 237 | ``` 238 | 我们babel一下,得到如下代码: 239 | ```js 240 | "use strict"; 241 | 242 | var _createClass = function () { 243 | function defineProperties(target, props) { 244 | for (var i = 0; i < props.length; i++) { 245 | var descriptor = props[i]; 246 | descriptor.enumerable = descriptor.enumerable || false; 247 | descriptor.configurable = true; 248 | if ("value" in descriptor) descriptor.writable = true; 249 | Object.defineProperty(target, descriptor.key, descriptor); 250 | } 251 | } 252 | return function (Constructor, protoProps, staticProps) { 253 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 254 | if (staticProps) defineProperties(Constructor, staticProps); 255 | return Constructor; 256 | }; 257 | }(); 258 | 259 | function _possibleConstructorReturn(self, call) { 260 | if (!self) { 261 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 262 | } 263 | return call && (typeof call === "object" || typeof call === "function") ? call : self; 264 | } 265 | 266 | function _inherits(subClass, superClass) { 267 | if (typeof superClass !== "function" && superClass !== null) { 268 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 269 | } 270 | 271 | subClass.prototype = Object.create(superClass && superClass.prototype, { 272 | constructor: { 273 | value: subClass, 274 | enumerable: false, 275 | writable: true, 276 | configurable: true 277 | } 278 | }); 279 | 280 | if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 281 | } 282 | 283 | function _classCallCheck(instance, Constructor) { 284 | if (!(instance instanceof Constructor)) { 285 | throw new TypeError("Cannot call a class as a function"); 286 | } 287 | } 288 | 289 | var Animal = function () { 290 | function Animal(name) { 291 | _classCallCheck(this, Animal); 292 | 293 | this.name = name; 294 | } 295 | 296 | _createClass(Animal, [{ 297 | key: "getName", 298 | value: function getName() { 299 | return this.name; 300 | } 301 | }]); 302 | 303 | return Animal; 304 | }(); 305 | 306 | var Dog = function (_Animal) { 307 | 308 | _inherits(Dog, _Animal); 309 | 310 | function Dog(name) { 311 | _classCallCheck(this, Dog); 312 | 313 | var _this = _possibleConstructorReturn(this, (Dog.__proto__ || Object.getPrototypeOf(Dog)).call(this, name)); 314 | 315 | _this.name = name; 316 | return _this; 317 | } 318 | 319 | return Dog; 320 | }(Animal); 321 | ``` 322 | 323 | `Animal`的代码与上节非继承的方式一致,直接跳过,来看下最后一部分`Dog`的代码: 324 | ```js 325 | // 这还是一个高阶函数,与没有继承的对象相比,这里多出了两个函数_inherits和_possibleConstructorReturn 326 | var Dog = function (_Animal) { 327 | 328 | // 继承函数,继承Animal的属性 329 | _inherits(Dog, _Animal); 330 | 331 | function Dog(name) { 332 | _classCallCheck(this, Dog); 333 | 334 | // 获取this 335 | var _this = _possibleConstructorReturn(this, (Dog.__proto__ || Object.getPrototypeOf(Dog)).call(this, name)); 336 | 337 | _this.name = name; 338 | return _this; 339 | } 340 | 341 | return Dog; 342 | }(Animal); 343 | ``` 344 | 在来看`_inherits`如何实现的: 345 | ```js 346 | // 继承函数 347 | function _inherits(subClass, superClass) { 348 | 349 | // 异常情况处理 350 | if (typeof superClass !== "function" && superClass !== null) { 351 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 352 | } 353 | 354 | // 将父函数构造器的prototype“拷贝”(使用原型链的方式并不是真正的赋值)一份给子函数构造器的prototype 355 | subClass.prototype = Object.create(superClass && superClass.prototype, { 356 | constructor: { 357 | value: subClass, 358 | enumerable: false, 359 | writable: true, 360 | configurable: true 361 | } 362 | }); 363 | 364 | // 设定子函数构造器的原型为父函数构造器 365 | if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 366 | } 367 | ``` 368 | 这里面涉及到了`subClass.__proto__`和`subClass.prototype`,那么`__proto__`和`prototype`的区别是什么? 369 | 370 | 实际上`__proto__`是真正查找时所用的对象,而`prototype`是当你用`new`关键在来构建对象时被用来建造`__proto__`的,`Object.getPrototypeof(dog) === dog.__proto__ === Dog.prototype`。 371 | 372 | 函数`__possibleConstructorReturn`处理了构造函数有返回值的情况。这种情况下,需要改变`this`使用该返回值作为`this`。 373 | ```js 374 | // 构造函数有返回值的情况 375 | function _possibleConstructorReturn(self, call) { 376 | if (!self) { 377 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 378 | } 379 | return call && (typeof call === "object" || typeof call === "function") ? call : self; 380 | } 381 | ``` 382 | 383 | ## 实际模拟 384 | 看了上面的实现,我们模拟这个步骤,为了简化我们省去错误处理和特殊情况。 385 | ```js 386 | var Animal = function(name) { 387 | this.name = name; 388 | } 389 | 390 | Animal.prototype.getName = function() { 391 | return this.name; 392 | } 393 | 394 | var Dog = function(name) { 395 | 396 | Animal.call(this.name); 397 | _this.name = name; 398 | } 399 | 400 | Dog.prototype = Animal.prototype; 401 | ``` 402 | 实现完成后发现,跟我们[上一篇](https://xwchris.me/article/62760050-8a99-11e8-959e-f10086e564b5)文章结尾,猜想实现的一样,这就很尴尬,本来觉得这种写法不太顺眼,看官方的支持,现在看起来就顺眼多了-_-。与完整实现相比我们缺少了一些原型赋值的步骤`Dog.__proto__ = Animal`,但总体来说原理是一样的。 -------------------------------------------------------------------------------- /posts/小白前端入门指南.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "小白前端入门指南" 3 | date: "2021-02-05" 4 | tags: 前端 5 | author: xwchris 6 | desc: "本文主要针对完全前端小白的同学,做一个简单的前端学习路径规划,可能有很多不全或者不对的地方,脑袋有点糊,感觉少了东西,又想不到,欢迎大佬多多指出。会附上简单的资源链接。只会列一些核心点的东西,其他不是特别核心的东西,在这里都没有列出,可以在学习过程中慢慢接触和摸索,本质都是为了提升效率" 7 | --- 8 | 9 | ## 基础类 10 | 学习前端首先要掌握网络相关的东西,这是所有网站运行的基础,这些要理解。 11 | - [HTTP](https://www.runoob.com/http/http-tutorial.html) 12 | - [浏览器](https://zhuanlan.zhihu.com/p/47407398) 13 | 14 | ## 前端基础类 15 | 前端基础类的就是前端入门的最最重要的东西。下面列出的三个分别对应一个网页的框架,样式和逻辑,三者在开发中都缺一不可。 16 | 17 | HTML&HTML5都要学习。需要了解常用的标签以及标准。 18 | 19 | CSS要学习CSS和CSS3。CSS可能很多人不太重视,一些工作多年的人对CSS也一知半解。CSS的东西比较杂,属性间可以相互影响,而且同一种效果有很多实现方式,可谓真正的入门容易精通难,开始学会觉得CSS东西很好,然后学的越深会发现内容越多。建议是如果你是想从事前端这门工作的,开始就把CSS基础打牢,这将会一直是你宝贵的财富。 20 | 21 | 最后的Javascript就是最重要的啦,这将会在你以后的工作中占据你大部分的时间,js相关的基础概念如闭包等,基础API都要有一定的掌握,新的API也要有一些了解,最少ES6相关的语法都需要掌握。 22 | 23 | 这些学完你就可以开发网页了,你已经正式打开前端的大门。 24 | 25 | - [HTML(HTML5)](https://www.runoob.com/html/html-tutorial.html) 26 | - [CSS(CSS3)](https://www.runoob.com/css/css-tutorial.html) 27 | - [Javascript基础](https://www.runoob.com/js/js-tutorial.html) 28 | - [ES6](https://es6.ruanyifeng.com/#README) 29 | 30 | 前面有的内容比较浅,想要更深的掌握还是需要阅读更多,推荐几本读过的书,经典不过时 31 | - CSS世界 32 | - Javascript权威指南 33 | 34 | ## 框架类 35 | 目前比较成熟的前端开发都会使用框架进行开发,前端承载了很多的业务逻辑,现代网站体量也比较大,原始的开发方式已经支撑不了大规模的网站开发,所以都会选用前端框架。框架部分比较流行的有React或者Vue,可以二选一,掌握一个就可以,可以根据实际业务和个人喜好选择。 36 | 37 | - [React](https://react.docschina.org/) 38 | - [Vue(二选一)](https://cn.vuejs.org/) 39 | 40 | 当你用起了框架,你就打开了新世界的大门,React全家桶和Vue全家桶迎面砸来,刚入门的同学可能会眼花缭乱。不过莫慌,所有的东西都是为了解决问题,当我们知道这些库或者工具的目标的时候,就可以梳理清楚了,让我们慢慢来。 41 | 42 | 由于我用React较多,下面就从React的视角来说,如果用的Vue都可以找到对标的库或者工具,因为不论哪种框架要解决的问题都是类似的。下面这些问题都是用框架的时候会遇到的问题,我们从问题的角度来引出相关的库和工具。 43 | 44 | ### 如何解决单页面路由问题 45 | 现代网站常见的都是SPA(即单页面应用),服务端返回的只有HTML,页面路由都由前端来控制,每个路由挂载不同的组件,根据路由来切换组件达到多页面的效果。下面就是React的解决方案 46 | - [React-Router](https://github.com/ReactTraining/react-router) 47 | 48 | ### 如何解决应用状态同步的问题 49 | 对一个网站应用来说,我们有很多数据或状态需要处理,单组件的数据处理我们可以用框架提供的方式来处理。但如果是全局数据,比如登录数据等,我们要如何共享,如何修改,如何通知,如何追踪,就有很多问题都需要解决。目前数据处理方案有很多,如Redux,Mobx等等。入门的话推荐先学习Redux,因为思路比较巧,源码极少,很方便我们新手学习和掌握。 50 | - [Redux](https://github.com/camsong/redux-in-chinese) 51 | 52 | ## 工具类 53 | 现在我们使用了框架,有了很多代码和很多页面,我们要如何将这些代码组织在一起,如何处理各种类型的文件如图片,如何分包优化按需加载。这个时候就需要用到打包工具了。这里只说一种Webpack,还有一些其他的打包工具Rollup,Parcel等都各有优劣有各自的场景,由于Webpack功能最全面稳定,所以这里给出Webpack。 54 | - [Webpack](https://webpack.docschina.org/) 55 | 56 | ## Typescript 57 | 由于现在Typescript越来越流行,而且确实在前端的稳定性已经代码可维护性方面有着突出表现,所以单独把Typescript也拎了出来,Typescript是Javascript的超集,为JS带来了类型。感兴趣可以学习下 58 | - [Typescript](https://www.tslang.cn/) 59 | 60 | ## 题目类 61 | 62 | 简单列了些问题,如果能回答好下面的问题说明你是一个合格成熟的前端啦,把脑子里第一时间想到的问题先列在这里,以后想到慢慢补充,有时间也补充下答案,大家可以先自己搜,网上很多 63 | 64 | - HTTP、TCP是什么,TCP的通信过程是怎样的 65 | - HTTP2、HTTP3分别带来的什么新特性 66 | - HTTPS是什么,解决什么问题,如果保证安全,怎么做的 67 | - HTTP方法GET,POST使用场景,为什么 68 | - 浏览器有哪几部分,浏览器解析HTML渲染页面的过程 69 | - 什么是进程,什么是线程,有什么区别 70 | - JS中闭包是什么、原型链是什么 71 | - JS中的this指向是什么,在箭头函数中有何区别,函数与apply和bind结合this是如何表现 72 | - 盒模型是什么,有哪几种 73 | - CSS各种布局方式的掌握 74 | - 用尽可能多的方式用CSS实现子元素在父元素中实现水平居中垂直居中 75 | - 浮动相关问题,如何清除 76 | - BFC是什么,什么应用 77 | - 什么是事件捕获和事件冒泡,应用是什么 78 | - 浏览器的EventLoop(事件循环)过程 79 | - 重绘和回流是什么,有什么后果,如何避免 80 | - 前端模块化IIFE,AMD,CJS,ES6 Module分别是什么,有什么区别,应用场景是什么 81 | - Promise是什么,解决什么问题。Promise如何进行错误处理,Promise.resolve、Promise.all怎么使用 82 | - 如果要进行网站性能优化要优化哪些点,措施是什么 83 | - 网站安全方面要注意哪些问题,如何解决这些问题 84 | - 如何做跨域,有哪些方案,这些方案的技术细节和原理是什么 85 | - 防抖和节流是什么,如何应用,如何实现 86 | - 浏览器存储分别有哪些,应用场景有什么 87 | - 浏览器缓存怎么做 88 | - 移动端网页适配方案是什么 89 | - 单页面路由实现原理 90 | - V8引擎垃圾回收过程 91 | - React生命周期有哪些,写高性能的React要注意什么 92 | - React原理,虚拟DOM,Diff,Fiber都是什么,过程是什么 93 | - Redux思路以及实现原理 94 | - 打包工具如何组织文件,过程是什么 95 | - 简单的模板引擎如何实现 96 | - 常用正则表达式编写能力,不依赖搜索 97 | - 常见的设计模式 98 | 99 | ## 源码实现系列 100 | 最近在写一个[源码实现系列](https://github.com/xwchris/core-rewrite),帮助自己和大家能更深入的了解我们平时用的一些东西,如果你也感兴趣可以关注下,会一直更新,有问题或想法欢迎在讨论区讨论 101 | -------------------------------------------------------------------------------- /posts/投资学读书笔记.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "投资学读书笔记" 3 | date: "2022-05-24" 4 | tags: 读书笔记 5 | author: xwchris 6 | desc: "记录读投资学过程的读书摘要,并加一下自己的评论与理解" 7 | --- 8 | 9 | ## 资产类别与金融工具 10 | 金融市场主要分为两类:货币市场和资本市场。 11 | 货币市场证券的特点是期限短、流动性好、收益低、风险小。资本市场与之相对,它的特点是期限长、流动性差、收益高、风险大。 12 | 货币市场证券常包括: 13 | - 短期国库券 14 | - 大额存单,通常指100000美元以上的存单 15 | - 商业票据,通常是大型公司发行的无担保债务票据 16 | - 银行承兑汇票,由银行向持有汇票的人付款,银行承担风险 17 | - 欧洲美元 18 | - 回购和逆回购,政府的短期借款手段,通常是今天卖,明天买,逆回购是相反的过程 19 | - 联邦基金,银行准备金在不同银行间可以相互拆借 20 | - 经纪人拆借 21 | - 伦敦银行同业拆借市场,LIBOR 22 | 23 | 资本市场证券包括: 24 | - 债券 25 | - 中长期国债 26 | - 通胀保值证券 27 | - 联邦机构债券 28 | - 国际债券 29 | - 市政债券,收益免税,相比应税债券更适合高税率人群 30 | - 公司债券,向公众借款的方式 31 | - 抵押贷款和抵押担保证券,这是一种转递证券 32 | - 权益证券 33 | - 普通股,剩余索取权和有限责任 34 | - 优先股,70%股息收益可免税,公司有发放股息的自主权,不具备决策权 35 | - 存托凭证 36 | - 衍生工具(依赖其他资产价值,通常为看涨或看跌) 37 | - 期权,这是一种权利 38 | - 期货合约,这是一种义务 39 | 40 | 指数根据权重不同主要分为三类: 41 | - 价格加权平均,道琼斯工业指数(30绩优股) 42 | - 市值加权平均,标准普尔500指数 43 | - 等权重平均,需要不断调整 44 | 45 | > 想法:金融市场就是用风险来博取收益,市场资源分布不均衡,市场的较量归根到底是人心的较量,人不可避免都是有通用的弱点的,因此是通过努力和分析来找到市场上低估的资产来获取收益的方式是可行的。证券无非就是债券、股票和衍生品三大类。 46 | 47 | ## 证券是如何交易 48 | 公司通过发行证券的方式来筹集公司所需资金,投资银行作为承销商,负责向公众兜售这些证券,并在其中赚取差价。这种叫做一级市场,证券售出后公众可以在二级市场来自由交易这些证券。 49 | 50 | 二级市场主要包括证券交易所和场外交易市场,大宗交易还需要通过谈判的方式来交易。有交易许可证的经纪公司才可以在交易所交易,他们代替个人投资者进行交易并收取佣金。 51 | 52 | 纳斯达克属于交易商市场,交易商通过报价来赚取差价。纽交所属于专家做市商市场,专家做市商管理最新交易薄,通过保持价格的连续性来保证市场的有序,他们有时也从自己的股票库存中进行买卖。 53 | 54 | 电子交易越来越受欢迎,现在大部分交易都是通过电子交易完成的。有很多人通过算法做交易以及高频交易。 55 | 56 | 股票买入可以通过向经纪人借款来配资购买股票,虽然潜在收益更高,但也存在更大的风险。 57 | 58 | 卖空即从经纪人出借入股票,当股票预期下跌时,再买入股票偿还来获利。如果股票上涨,则需时刻注意需要增加保证金否则有被平仓的风险。 59 | 60 | 证券市场的监管除了政府机构监管还包括交易所的自我监管。同时内幕消息交易是不被允许的。 61 | 62 | > 想法:公司通过公开发行证券的方式融资,在这其中,投行、投资人、经纪人、交易商都参与其中通过自己的方式来赚取利润。 63 | 64 | 65 | ## 共同基金与其他投资公司 66 | 单位投资信托是固定投资组合,分散度高,运营费用低。共同基金,分散度高,运营费用高。封闭式基金与共同基金不同的是,它只在投资者间交易基金股份,不涉及基金的赎回,因此它不用保持有4%-5%的现金等价物。还有一种交易所交易基金,简称ETF,它可以在交易所任意时间交易,它的投资组合一般都是指数或者黄金白银等。 67 | 68 | 基金等费用也包括很多种,如前端费用(销售费用),撤回费用,运营费用,12b-1等,这些都会影响基金等收益率。各种基金等费用组成以及高低会存在一些差异,需要依据情况选择。同时基金等所得税不对基金征收,而对投资者征收,基金收益算做投资者收益。 69 | 70 | > 想法:各类基金以及个人投资,都需要依据不同的情况来选择,各有各的优势与缺点,同时要考虑费用对收益率的影响。 -------------------------------------------------------------------------------- /posts/排序算法原理与实现.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "排序算法原理与实现" 3 | date: "2019-11-04" 4 | tags: 算法 5 | author: xwchris 6 | desc: "复习经典排序算法,更细致地进行了解,有了比以前没有的领悟,最重要的是思想,比如分治策略" 7 | --- 8 | 9 | # 插入排序 10 | ## 直接插入排序 11 | ### 原理分析 12 | 插入排序主要过程是对给定的一个数组从头部开始进行遍历,每遇到一个数字就要对其进行判断,与前面已经排好序的数组进行比较,找出它的位置,然后把该位置及其之后之后的所有元素后移,然后插入该元素。 13 | ### 时间复杂度 14 | 由于要遍历n-1次,每一次遍历又要遍历n- i个元素,故平均复杂度为O(n^2) 15 | 当序列为有序时候,不需要再进行比较和移动,所以最好情况复杂度为O(n) 16 | ### 伪代码实实现 17 | ``` 18 | INSERT-SORT(A) 19 | for j = 2 to A.length 20 | key = A[j] 21 | //insert A[j] into the sorted sequence A[1..j -1] 22 | i = j - 1 23 | while i > 0 and A[i] > key 24 | A[i + 1] = A[i] 25 | i = i - 1 26 | A[i + 1] = key 27 | ``` 28 | ### Java实现 29 | ``` 30 | //插入排序 31 | public void insertSort(int[] array){ 32 | //数组长度 33 | int n = array.length; 34 | for(int i = 1; i < n; i++){ 35 | //保存要插入的数据 36 | int temp = array[i]; 37 | int j = i - 1; 38 | //比待插入数据大的依次后移 39 | while(j >= 0 && array[j] > temp){ 40 | array[j + 1] = array[j]; 41 | j--; 42 | } 43 | //将当前位置插入带插入数据 44 | array[j + 1] = temp; 45 | } 46 | } 47 | ``` 48 | 49 | ## 希尔排序 50 | ### 原理分析 51 | 希尔排序,利用的就是分治思想,开始将数组分为n/2组,每组有2个元素,在每组中使用插入排序,然后再分成n / 2i组,以此类推,直达分成一组,调整完就是一个有序的数列 52 | ### 时间复杂度 53 | 希尔排序的时间复杂度最好为n^1.3 最差为n^1.5 54 | ### 伪代码实现 55 | 56 | ``` 57 | SHELL-SORT(A) 58 | d = A.length / 2 59 | while d >= 1 60 | for i = 0 to d 61 | for j = i + d to A.length by d 62 | key = A[j] 63 | k = j - d 64 | while k > i + d and key < A[k] 65 | A[j + d] = A[j] 66 | k -= d 67 | A[k + d] = key 68 | d = d / 2 69 | ``` 70 | ### Java实现 71 | ``` 72 | //希尔排序 73 | public void shellSort(int[] array){ 74 | //数组长度 75 | int n = array.length; 76 | //分组的个数 77 | int d = n / 2; 78 | while(d >= 1){ 79 | //对每一组进行插入排序 80 | for(int i = 0; i < d; i++){ 81 | //插入排序 82 | for(int j = i + d; j < n; j += d){ 83 | int temp = array[j]; 84 | //比待插入数据大的依次后移 85 | int k = j - d; 86 | while(k >= i && array[k] > temp){ 87 | array[k + d] = array[k]; 88 | k -= d; 89 | } 90 | //将当前位置插入待插入数据 91 | array[k + d] = temp; 92 | } 93 | } 94 | //改变每组的大小 95 | d = d / 2; 96 | } 97 | } 98 | ``` 99 | # 选择排序 100 | ## 简单选择排序 101 | ### 原理分析 102 | 选择排序就是遍历还没有排序的元素,选择其中最小的然后将第一个与之交换,重复此过程,知道数组有序 103 | ### 时间复杂度 104 | 一共要进行n-1轮选择 每一轮都要比较 n - 1,n -2, n - 3次,故时间复杂度为O(n^2) 105 | ### 伪代码实现 106 | 107 | ``` 108 | SELECT-SORT(A) 109 | n = A.length 110 | for i = 0 to n - 1 111 | for j = i to n - 1 112 | min = j 113 | if A[j] < A[min] 114 | min = j 115 | exchage A[i] with A[min] 116 | ``` 117 | ### Java实现 118 | ``` 119 | //简单选择排序 120 | public void selectSort(int[] array){ 121 | //数组长度 122 | int n = array.length; 123 | for(int i = 1; i < n; i++){ 124 | int min = i - 1; 125 | for(int j = i; j < n; j++){ 126 | if(array[j] < array[min]){ 127 | min = j; 128 | } 129 | } 130 | int temp = array[i - 1]; 131 | array[i - 1] = array[min]; 132 | array[min] = temp; 133 | } 134 | } 135 | ``` 136 | ## 堆排序 137 | ### 原理分析 138 | 最大堆就是指父节点键值大于子节点键值的一棵二叉树,堆排序就是利用最大堆的性质。首先构造最大堆,此时跟节点的值为最大值,将根节点与最后一个叶子节点互换位置,再调整二叉树为最大堆,将刚才的交换出去的最大值排除出去,以此类推,循环执行,最后得到的就是一个递增序列 139 | ### 时间复杂度 140 | 构建最大堆需要花费O(n)的时间,调整一次时间复杂度为O(lgn)由于有n个节点,故时间复杂度为O(n^lgn)。 141 | ### 伪代码实现 142 | 143 | ``` 144 | MAX-HEAPIFY(A,i) 145 | l = LEFT(i) 146 | r = RIGHT(i) 147 | if l <= A.size and A[l] > A[i] 148 | max = l 149 | else 150 | max = i 151 | if r <= A.size and A[r] > A[max] 152 | max = r 153 | if max != i 154 | exchage A[i] with A[max] 155 | MAX-HEAPITY(A,MAX) 156 | 157 | BUILD-MAX-HEAP(A) 158 | for i = A.size/2 downto i 159 | MAX-HEAPIFY(A,i) 160 | 161 | HEAP-SORT(A) 162 | BUILD-MAX-HEAP(A) 163 | for i = A.size - 1 downto 0 164 | exchage A[0] with A[i] 165 | A.heap-size = A.heap-size - 1 166 | MAX-HEAPIFY(A,0) 167 | ``` 168 | ### Java实现 169 | ``` 170 | //堆排序 171 | public void heapSort(int[] array){ 172 | //数组长度 173 | int n = array.length; 174 | //构造堆 175 | buildHeap(array); 176 | for(int i = n - 1; i > 0; i--){ 177 | int temp = array[0]; 178 | array[0] = array[i]; 179 | array[i] = temp; 180 | shiftDown(array, 0, i); 181 | } 182 | } 183 | 184 | //堆排序-构造堆 185 | public void buildHeap(int[] array){ 186 | //数组长度 187 | int n = array.length; 188 | //最后一个非叶子节点的节点 189 | int p = n / 2 - 1; 190 | for(int i = p; i >= 0; i--){ 191 | shiftDown(array, i, n); 192 | } 193 | } 194 | 195 | //堆排序-调整堆 196 | public void shiftDown(int[] array, int i, int n){ 197 | int left = 2 * i + 1, right = 2 * i + 2; 198 | int max = i; 199 | if(left < n && array[left] > array[max]){ 200 | max = left; 201 | } 202 | if(right < n && array[right] > array[max]){ 203 | max = right; 204 | } 205 | //交换 206 | if(max != i){ 207 | int temp = array[max]; 208 | array[max] = array[i]; 209 | array[i] = temp; 210 | //递归调整 211 | shiftDown(array, max, n); 212 | } 213 | } 214 | ``` 215 | # 交换排序 216 | ## 冒泡排序 217 | ### 原理分析 218 | 冒泡排序是一种交换排序,冒泡一共进行n-1次每次,都没还没有排序的数组,交换出最大的一个,基本步骤是第一个元素与下一个元素进行比较,如果该元素比下一元素大,就交换他们,以此类推,直到最后一个无序的元素,再次循环。 219 | ### 时间复杂度 220 | 冒泡要进行n-1轮,每轮比较 n -2 次 , n - 3次 ……故时间复杂度为O(n^2) 221 | 最好情况下,数组是有序的,如果用改进的冒泡排序则当没有发生交换时,就说明数组有序,则停止算法,这时的时间复杂度是O(n) 222 | ### 伪代码实现 223 | ``` 224 | BUBBLE-SORT(A) 225 | length = A.length 226 | for i = 1 to length 227 | for j = 0 downto length - i - 1 228 | if A[j] > A[j+1] 229 | exchage A[j] with A[j+1] 230 | ``` 231 | ### Java实现 232 | ``` 233 | //冒泡排序 234 | public void bubbleSort(int[] array){ 235 | //数组长度 236 | int n = array.length; 237 | //循环n - 1次 238 | for(int i = 1; i < n; i++){ 239 | //比较n - i次 240 | for(int j = 1; j < n - i + 1; j++){ 241 | if(array[j - 1] > array[j]){ 242 | //交换位置 243 | int temp = array[j - 1]; 244 | array[j - 1] = array[j]; 245 | array[j] = temp; 246 | } 247 | } 248 | } 249 | } 250 | ``` 251 | ## 快速排序 252 | ### 原理分析 253 | 快速排序也是一种交换排序,它应用了分治策略,选取一个轴元素,将比轴元素大的放到右面,比轴元素小的放在左面,以此类推,使用递归,得到最终的有序数列 254 | ### 时间复杂度 255 | 每次一递归的元素交换时间复杂度为O(n) ,每次递归一分为二,根据主方法,时间复杂度为O(n^lgn) 256 | 在最坏的情况下,数组有序,每一次划分成T(n-1),故T(n) =T(n-1) + 1使用主方法得到时间复杂度为O(n^2) 257 | ### 伪代码实现 258 | ``` 259 | QUICK-SORT(A,start,end) 260 | if start < end 261 | t = PATITION(A,start,end) 262 | QUICK-SORT(A,start,t - 1) 263 | QUICK-SORT(A,t+1,end) 264 | 265 | PATITION(A,start,end) 266 | pivot = A[end] 267 | i = start - 1 268 | for j = start to end - 1 269 | if A[j] < pivot 270 | i = i + 1 271 | exchange A[i] with a[j] 272 | exchange A[i + 1] with A[r] 273 | ``` 274 | ### Java实现 275 | ``` 276 | //快速排序 277 | public void quickSort(int[] array, int left, int right){ 278 | if(left < right){ 279 | int p = partition(array, left, right); 280 | quickSort(array, left, p - 1); 281 | quickSort(array, p + 1, right); 282 | } 283 | } 284 | 285 | //快速排序辅助函数 286 | public int partition(int[] array, int left, int right){ 287 | //数组长度 288 | int n = array.length; 289 | //中轴点 290 | int pivot = array[left]; 291 | while(left < right){ 292 | while(left < right && array[right] >= pivot){ 293 | right--; 294 | } 295 | array[left] = array[right]; 296 | while(left < right && array[left] < pivot){ 297 | left++; 298 | } 299 | array[right] = array[left]; 300 | } 301 | array[left] = pivot; 302 | return left; 303 | } 304 | ``` 305 | # 归并排序 306 | ## 归并排序 307 | ### 原理分析 308 | 归并排序典型运用了分治策略,将数组进行一分为二,依次分解,当分解到一时,进行合并,直到数组全部合并 309 | ### 时间复杂度 310 | 每次递归分成两份, 每次合并操作时间复杂度为O(n),根据主定理,可以得出时间复杂度为O(n^lgn) 311 | ### 伪代码实现 312 | ``` 313 | MERGE-SORT(A,start,end) 314 | if start < end 315 | q = (start + end) / 2 316 | MEGE-SORT(A,start,q) 317 | MEGE-SORT(A,q+1,end) 318 | MEGE(A,start,q,end) 319 | ``` 320 | ``` 321 | MERGE(A,start,q,end) 322 | n1 = q - p +1 323 | n2 = r - q 324 | let L and R be new arrays 325 | for i = 1 to n1 326 | L[i] = A[P + i - 1] 327 | for j = 1 to n2 328 | R[j] = A[q + j] 329 | 330 | L[n1 + 1] = ∞ 331 | L[n2 + 1] = ∞ 332 | 333 | i = 1 334 | j = 1 335 | for k = start to end 336 | if L[i] <= R[j] 337 | A[k]=L[i] 338 | i = i + 1 339 | else 340 | A[k] = R[j] 341 | j = j + 1 342 | ``` 343 | ### Java实现 344 | ``` 345 | //归并排序 346 | public void mergeSort(int[] array, int start, int end){ 347 | if(start < end){ 348 | int mid = (start + end) / 2; 349 | mergeSort(array, start, mid); 350 | mergeSort(array, mid + 1, end); 351 | merge(array, start, mid ,end); 352 | } 353 | } 354 | 355 | //归并排序-合并数组 356 | public void merge(int[] array, int start, int mid, int end){ 357 | //数组1长度 358 | int n1 = mid - start + 1; 359 | //数组2长度 360 | int n2 = end - mid; 361 | 362 | //数组1 363 | int[] array1 = new int[n1 + 1]; 364 | //数组2 365 | int[] array2 = new int[n2 + 1]; 366 | 367 | //求出数组1 368 | for(int i = 0; i < n1; i++){ 369 | array1[i] = array[start + i]; 370 | } 371 | array1[n1] = Integer.MAX_VALUE; 372 | //求出数组2 373 | for(int j = 0; j < n2; j++){ 374 | array2[j] = array[mid + j + 1]; 375 | } 376 | array2[n2] = Integer.MAX_VALUE; 377 | 378 | int i = 0, j = 0; 379 | for(int k = start; k <= end; k++){ 380 | if(array1[i] <= array2[j]){ 381 | array[k] = array1[i]; 382 | i++; 383 | }else{ 384 | array[k] = array2[j]; 385 | j++; 386 | } 387 | } 388 | } 389 | ``` 390 | # 线性时间排序 391 | ## 计数排序 392 | ### 原理分析 393 | 对每一输入的元x,确定小于x的元素个数,利用这一信息可以直接把x放在它在输出数组中的位置上 394 | ### 时间复杂度 395 | 时间复杂度为O(n)级别 396 | ### 伪代码实现 397 | ``` 398 | COUNTING-SORT(A,B,k) 399 | let c[0..k]be a new array 400 | for i = 0 to k 401 | C[i] = 0 402 | for j = 1 to A.length 403 | C[A[j]] = C[A[j]] + 1 404 | for i = 1 to k 405 | C[i] = C[i] + C[i - 1] 406 | for j = A.length downto 1 407 | B[C[A[j]]] = A[j] 408 | C[A[j]] = C[A[j]] - 1 409 | ``` 410 | ### Java实现 411 | ``` 412 | //计数排序 413 | public void countSort(int[] array, int k){ 414 | //数组长度 415 | int n = array.length; 416 | //用来存放排序数组 417 | int[] array1 = new int[n]; 418 | //临时辅助数组 419 | int[] array2 = new int[k]; 420 | for(int i = 0; i < k; i++){ 421 | array2[i] = 0; 422 | } 423 | 424 | for(int i = 0; i < n; i++){ 425 | array2[array[i]] += 1; 426 | } 427 | 428 | for(int i = 1; i < k; i++){ 429 | array2[i] += array2[i - 1]; 430 | } 431 | 432 | for(int i = n - 1; i >= 0; i--){ 433 | int j = array[i]; 434 | int index = array2[j] - 1; 435 | array1[index] = array[i]; 436 | array2[array[i]] -= 1; 437 | } 438 | 439 | for(int i = 0; i < n; i++){ 440 | array[i] = array1[i]; 441 | } 442 | } 443 | ``` 444 | ## 基数排序 445 | ### 原理分析 446 | 从最低位到最高位,没有则视为0,每位,都进行一次排序,直到把所有的位都排完 447 | ### 时间复杂度 448 | 给定n个d位数,其中每一个数位有k个可能的取值。如果它使使用了稳定排序方法好事O(n+k),那么他就可以在O(d(n+k))时间内将这些数排好序 449 | ### 伪代码实现 450 | 451 | ``` 452 | RADIX-SORT(A,d) 453 | for i = 1 to d 454 | use a stable sort to sort array A on digit i 455 | ``` 456 | ## 桶排序 457 | ### 原理分析 458 | 桶排序将[0,1)区间 划分为n个相同大小的子区间,或称为桶。然后,将n个输入数分别放入各个桶中,然后对各个桶进行插入排序,按照次序把桶列出来即可 459 | ### 时间复杂度 460 | 时间复杂度为O(n) 461 | ### 伪代码实现 462 | 463 | ``` 464 | BUCKET-SORT(A) 465 | n=a.length 466 | let B[0..n-1] be a new array 467 | for i = 0 to n -1 468 | make B[i] an empty list 469 | for i = 1 to n 470 | insert A[i] into list B[nA[i]] 471 | for i = 0 to n - 1 472 | sort list B[i] with insertion sort 473 | ``` 474 | 475 | 476 | -------------------------------------------------------------------------------- /posts/正则表达式易错记录.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "正则表达式易错记录" 3 | date: "2020-11-21" 4 | tags: 前端 5 | author: xwchris 6 | desc: "记录自己容易忘记的正则语法和相关方法" 7 | --- 8 | 9 | ## 正则表达式语法 10 | 11 | 易错语法表(for me) 12 | 13 | | 语法 | 意义 | 14 | | --- | --- | 15 | | ? | 量词,表示是否有指定格式,相当于`{0,1}`,当用在量词之后如:`. * {} ? `时,表示非贪婪匹配,正则默认为贪婪匹配 | 16 | | x(?=y) | 正向肯定查找,只有当x后有y的时候才会匹配x,匹配结果不包括y | 17 | | x(!=y) | 正向否定查找,与正向匹配相反,只有当x后没有y的时候才会匹配x,匹配结果不包括y | 18 | | [] | 使用[^]形式表示否定,同时`. */`在这里面没有特殊意义,可以不进行转义 | 19 | | (?:x) | 非捕捉括号,括号默认是会进行捕捉存储的,如果只是为了分组可以使用这种非捕捉括号的形式 | 20 | | \b | 单词边界 | 21 | 22 | 字符串替换语法表 23 | 24 | | 模式 | 插入 | 25 | | --- | --- | 26 | | $$ | 插入"$"| 27 | | $& | 插入匹配的字符串 | 28 | | $n | 插入匹配的第n个子串,从1开始 | 29 | | $` | 已匹配字符串前面的部分 | 30 | | $' | 已匹配字符串后面的部分 | 31 | 32 | ## 正则表达式函数 33 | 34 | `RegExp`拥有`test`和`exec`方法,`test`返回布尔值。 35 | 36 | 使用`exec`如果匹配成功则会返回一个数组,数组的第一项的完全匹配的字符串,后面是括号匹配到的子串。该数组拥有`index`属性用来表示完全匹配的字符串开始的位置,属性`input`表示原始字符串。 37 | 38 | 如果没有成功匹配,则返回`null` 39 | 40 | 如果正则表达式使用了全局模式,那么可以正则表达式对象会使用`lastIndex`记录最后匹配的位置,再次执行会继续向后匹配,直到没有匹配返回为`null`,重置`lastIndex`为0,除了`lastIndex`对象,正则对象还有`ignoreCase`表示是否忽略大小写,`global`表示是否是全局匹配,`multiline`表示是否使用多行匹配,`source`表示正则表达式的字符。 41 | 42 | 除了`RegExp`对象拥有的方法外,还有很多字符串对象的方法可以进行正则匹配。 43 | `search`会返回第一个匹配位置的索引,如果没有匹配到会返回`-1`,因此,如果只是需要判断时候存在,可以使用`search`或`test`方法 44 | 45 | `replace`和`split`也可以使用正则来分别进行替换和分割, 46 | 47 | `match`方法比较特殊,如果正则是全局模式则返回一个完全匹配所组成的数组,如果不是全局模式则返回值与`exec`的返回值类似 -------------------------------------------------------------------------------- /posts/浏览器和Node的事件循环.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "浏览器和Node的事件循环" 3 | date: "2020-09-03" 4 | tags: node,js,前端 5 | author: xwchris 6 | desc: "众所周知,js是单线程的,原因是因为它诞生之初就是作为浏览器的脚本。操作dom如果同时在多个线程中进行,会出现许多问题,为了减少复杂度,采用了单线程的方式。js的另一个特点是非阻塞,这是如何实现的那,这就要说到EventLoop(事件循环)了" 7 | --- 8 | 9 | ## 浏览器中的EventLoop 10 | 浏览器在执行代码的过程中,依次执行代码,将同步代码放入到执行栈中进行执行。当遇到异步代码的时候,暂时挂起。待异步代码执行完后,会将异步代码的处理事件(如回调函数)放入到任务队列中。当执行栈中为空时,主线程会检查任务队列,并按照次序执行(这里如果有setTimeout等没有到达指定时间则要延后执行)。 11 | 12 | 任务队列根据异步任务的不同分为宏任务(macro task)和微任务(micro task)。不同的任务会被放到不同的任务队列中去,每次执行栈为空后,主线程会先检查微任务队列,待微任务队列为空后,在检查宏任务队列。 13 | 14 | 当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。 15 | 16 | 常见的微任务有: 17 | 18 | - process.nextTick() 19 | - Promise 20 | 21 | 常见的宏任务有: 22 | 23 | - setInterval 24 | - setTimeout 25 | - setImmediate 26 | - I/O tasks 27 | 28 | ## Node中的EventLoop 29 | node中有自己的一套事件循环机制,js代码通过v8引擎进行分析后,调用node api,这些api最后由libv进行驱动。node中的事件循环存在于libv引擎中。 30 | ``` 31 | ┌───────────────────────┐ 32 | ┌─>│ timers │ 33 | │ └──────────┬────────────┘ 34 | │ ┌──────────┴────────────┐ 35 | │ │ I/O callbacks │ 36 | │ └──────────┬────────────┘ 37 | │ ┌──────────┴────────────┐ 38 | │ │ idle, prepare │ 39 | │ └──────────┬────────────┘ ┌───────────────┐ 40 | │ ┌──────────┴────────────┐ │ incoming: │ 41 | │ │ poll │<──connections─── │ 42 | │ └──────────┬────────────┘ │ data, etc. │ 43 | │ ┌──────────┴────────────┐ └───────────────┘ 44 | │ │ check │ 45 | │ └──────────┬────────────┘ 46 | │ ┌──────────┴────────────┐ 47 | └──┤ close callbacks │ 48 | └───────────────────────┘ 49 | └───────────────────────┘ 50 | ``` 51 | 52 | ### poll阶段 53 | 外部数据输入后进入poll阶段,这个阶段会按照先后顺序处理poll queue中的事件。如果队列为空,则检查是否有`setImmediate`的回调,如果有就放入check queue,之后进入check阶段执行回调。同时也会检查是否有到期的timer,如果有就放入timer queue,之后进入timer阶段执行queue中的回调,这两者的顺序是不固定的,受到代码运行环境的影响。如果这两者都为空,那么loop会停留在poll阶段,直到一个I/O事件返回,循环立即进入I/O阶段,并执行I/O回调。poll阶段在执行poll queue中的回调时实际上不会无限的执行下去。有两种情况poll阶段会终止执行poll queue中的下一个回调: 54 | 55 | 1.所有回调执行完毕。 56 | 2.执行数超过了node的限制。 57 | 58 | ### check阶段 59 | 专门用来执行`setImmediate`的回调,如果poll阶段空闲,且check queue有事件,则进入该阶段执行。 60 | 61 | ### close阶段 62 | 当一个socket或者handle被突然关闭,close事件会被发送到这个阶段执行回调。否则会以process.nextTick()方法发出去。 63 | 64 | ### timer阶段 65 | 按照先进先出的顺序执行timer queue中的回调。一个timer callback是指由setTimeout或setInterval设置的回调函数。 66 | 67 | ### I/O callback阶段 68 | 该阶段执行大部分I/O操作的回调,包括操作系统的一些回调。 69 | 70 | ### process.nextTick、setTimeout和setInterval的区别 71 | 72 | #### process.nextTick 73 | 使用`process.nextTick`执行的回调函数会进入到nextTick queue,虽然没有单独将这个作为一个阶段,但其会在每个阶段执行完,进入到下一个阶段时执行。与poll阶段不同的是,知道nextTick queue完全清空,才会继续向下执行。 74 | 75 | #### setTimeout 76 | 使用setTimeout来设置定时执行时间,并不一定能在精准的时间间隔内执行,这收到其他代码和环境的执行的影响。 77 | 78 | #### setImmediate 79 | setImmediate看起来和setTimeout很像,但是它是在poll阶段进行执行的。就像如下代码所示,哪个会先执行,这个很难进行准确的判断 80 | ```js 81 | setTimeout(() => { 82 | console.log('timeout'); 83 | }, 0); 84 | 85 | setImmediate(() => { 86 | console.log('immediate'); 87 | }); 88 | ``` 89 | 90 | 唯一可以确定的是,在IO回调中,setImmediate总是在setTimeout之前进行执行的 91 | ``` 92 | const fs = require('fs'); 93 | 94 | fs.readFile(__filename, () => { 95 | setTimeout(() => { 96 | console.log('timeout'); 97 | }, 0); 98 | setImmediate(() => { 99 | console.log('immediate'); 100 | }); 101 | }); 102 | 103 | // 输出: 104 | /// immediate 105 | // timeout 106 | ``` -------------------------------------------------------------------------------- /posts/真正认识Generator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "真正认识Generator" 3 | date: "2020-04-01" 4 | tags: js, 前端 5 | author: xwchris 6 | desc: "这篇文章旨在帮你真正了解Generator,文章较长,不过如果能花时间耐心看完,相信你已经能够完全理解generator" 7 | --- 8 | 9 | ## 为什么要用generator 10 | 在前端开发过程中我们经常需要先请求后端的数据,再用拿来的数据进行使用网页页面渲染等操作,然而请求数据是一个异步操作,而我们的页面渲染又是同步操作,这里ES6中的`generator`就能发挥它的作用,使用它可以像写同步代码一样写异步代码。下面是一个例子,先忽略下面的写法,后面会详细说明。如果你已经理解`generator`基础可以直接跳过这部分和语法部分,直接看深入理解的部分。 11 | ```javascript 12 | function *foo() { 13 | // 请求数据 14 | var data = yield makeAjax('http://www.example.com'); 15 | render(data); 16 | } 17 | ``` 18 | 在等待数据的过程中会继续执行其他部分的代码,直到数据返回才会继续执行`foo`中后面的代码,这是怎么实现的那?我们都知道js是单线程的,就是说我们不可能同时执行两段代码,要实现这种效果,我们先来猜想下,我们来假设有一个“王杖”(指代cpu的执行权),谁拿到这个“王杖”,谁就可以做自己想做的事,现在代码执行到`foo`我们现在拿着“王杖”然后向服务器请求数据,现在数据还没有返回,我们不能干等着。作为王我们有着高尚的马克思主义思想,我们先把自己的权利交出去,让下一个需要用的人先用着,当然前提是要他们约定好一会儿有需要,再把“王杖”还给我们。等数据返回之后,我们再把我们的“王杖”要回来,就可以继续做我们想做的事情了。 19 | 如果你理解了这个过程,那么恭喜你,你已经基本理解了`generator`的运行机制,我这么比喻虽然有些过程不是很贴切,但基本是这么个思路。更多的东西还是向下看吧。 20 | 21 | ## generator语法 22 | 23 | ### generator函数 24 | 在用`generator`之前,我们首先要了解它的语法。在上面也看到过,它跟函数声明很像,但后面有多了个`*`号,就是`function *foo() { }`,当然也可以这么写`function* foo() { }`。这里两种写法没有任何区别,全看个人习惯,这篇文章里我会用第一种语法。现在我们按这种语法声明一个`generator`函数,供后面使用。 25 | ```javascript 26 | function *foo() { 27 | 28 | } 29 | ``` 30 | 31 | ### yield 32 | 到目前为止,我们还什么也干不了,因为我们还缺少了一个重要的老伙计`yield`。`yield`翻译成汉语是产生的意思。`yield`会让我们跟在后面的表达式执行,然后交出自己的控制权,停在这里,直到我们调用`next()`才会继续向下执行。这里新出现了`next`我们先跳过,先说说`generator`怎么执行。先看一个例子。 33 | 34 | ```javascript 35 | function *foo() { 36 | var a = yield 1 + 1; 37 | var b = yield 2 + a; 38 | console.log(b); 39 | } 40 | 41 | var it = foo(); 42 | it.next(); 43 | it.next(2); 44 | it.next(4); 45 | ``` 46 | 下面我们来逐步分析,首先我们定义了一个`generator`函数`foo`,然后我们执行它`foo()`,这里跟普通函数不同的是,它执行完之后返回的是一个迭代器,等着我们自己却调用一个又一个的`yield`。怎么调用那,这就用到我们前面提到的`next`了,它能够让迭代器一个一个的执行。好,现在我们调用第一个`it.next()`,函数会从头开始执行,然后执行到了第一个`yield`,它首先计算了`1 + 1`,嗯,然后停了下来。然后我们调用第二个`it.next(2)`,注意我这里传入了一个`2`作为`next`函数的参数,这个`2`传给了`a`作为它的值,你可能还有很多其他的疑问,我们详细的后面再说。接着来,我们的`it.next(2)`执行到了第二个`yield`,并计算了`2 + a`由于`a`是`2`所以就变成了`2 + 2`。第三步我们再调用`it.next(4)`,过程跟上一步相同,我们把`b`赋值为`4`继续向下执行,执行到了最后打印出我们的`b`为`4`。这就是`generator`执行的全部的过程了。现在弄明白了`yield`跟`next`的作用,回到刚才的问题,你可能要问,为什么要在`next`中传入`2`和`4`,这里是为了方便理解,我手动计算了`1 + 1`和`2 + 2`的值,那么程序自己计算的值在哪里?是`next`函数的返回值吗,带着这个疑问,我们来看下面一部分。 47 | 48 | ### next 49 | 50 | #### next的参数 51 | `next`可以传入一个参数,来作为上一次`yield`的表达式的返回值,就像我们上面说的`it.next(2)`会让`a`等于`2`。当然第一次执行`next`也可以传入一个参数,但由于它没有上一次`yield`所以没有任何东西能够接受它,会被忽略掉,所以没有什么意义。 52 | 53 | #### next的返回值 54 | 在这部分我们说说`next`返回值,废话不多说,我们先打印出来,看看它到底是什么,你可以自己执行一下,也可以直接看我执行的结果。 55 | 56 | ```javascript 57 | function *foo() { 58 | var a = yield 1 + 1; 59 | var b = yield 2 + a; 60 | console.log(b); 61 | } 62 | 63 | var it = foo(); 64 | console.log(it.next()); 65 | console.log(it.next(2)); 66 | console.log(it.next(4)); 67 | ``` 68 | 69 | 执行结果: 70 | ``` 71 | { value: 2, done: false } 72 | { value: 4, done: false } 73 | 4 74 | { value: undefined, done: true } 75 | ``` 76 | 77 | 看到这里你会发现,`yield`后面的表达式执行的结果确实返回了,不过是在返回值的`value`字段中,那还有`done`字段使用来做什么用的那。其实这里的`done`是用来指示我们的迭代器,就是例子中的`it`是否执行完了,仔细观察你会发现最后一个`it.next(4)`返回值是`done: true`的,前面的都是`false`,那么最后一个打印值的`undefined`又是什么那,因为我们后面没有`yield`了,所以这里没有被计算出值,那么怎么让最后一个有值那,很简单加个`return`。我们改写下上面的例子。 78 | 79 | ```javascript 80 | function *foo() { 81 | var a = yield 1 + 1; 82 | var b = yield 2 + a; 83 | return b + 1; 84 | } 85 | 86 | var it = foo(); 87 | console.log(it.next()); 88 | console.log(it.next(2)); 89 | console.log(it.next(4)); 90 | ``` 91 | 92 | 执行结果: 93 | ``` 94 | { value: 2, done: false } 95 | { value: 4, done: false } 96 | { value: 5, done: true } 97 | ``` 98 | 99 | 最后的`next`的`value`的值就是最终`return`返回的值。到这里我们就不再需要手动计算我们的值了,我们在改写下我们的例子。 100 | 101 | ```javascript 102 | function *foo() { 103 | var a = yield 1 + 1; 104 | var b = yield 2 + a; 105 | return b + 1; 106 | } 107 | 108 | var it = foo(); 109 | var value1 = it.next().value; 110 | var value2 = it.next(value1).value; 111 | console.log(it.next(value2)); 112 | ``` 113 | 大功告成!这些基本上就完成了`generator`的基础部分。但是还有更多深入的东西需要我们进一步挖掘,看下去,相信你会有收获的。 114 | 115 | ## 深入理解 116 | 前两部分我们学习了为什么要用`generator`以及`generator`的语法,这些都是基础,下面我们来看点不一样的东西,老规矩先带着问题才能更有目的性的看,这里先提出几个问题: 117 | 118 | - 怎样在异步代码中使用,上面的例子都是同步的啊 119 | - 如果出现错误要怎么进行错误的处理 120 | - 一个个调用next太麻烦了,能不能循环执行或者自动执行那 121 | 122 | ### 迭代器 123 | 进行下面所有的部分之前我们先说一说迭代器,看到现在,我们都知道`generator`函数执行完返回的是一个迭代器。在ES6中同样提供了一种新的迭代方式`for...of`,`for...of`可以帮助我们直接迭代出每个的值,在数组中它像这样。 124 | 125 | ```javascript 126 | for (var i of ['a', 'b', 'c']) { 127 | console.log(i); 128 | } 129 | 130 | // 输出结果 131 | // a 132 | // b 133 | // c 134 | ``` 135 | 136 | 下面我们用我们的`generator`迭代器试试 137 | ```javascript 138 | function *foo() { 139 | yield 1; 140 | yield 2; 141 | yield 3; 142 | return 4; 143 | } 144 | 145 | // 获取迭代器 146 | var it = foo(); 147 | 148 | for(var i of it) { 149 | console.log(i); 150 | } 151 | 152 | // 输出结果 153 | // 1 154 | // 2 155 | // 3 156 | ``` 157 | 现在我们发现`for...of`会直接取出我们每一次计算返回的值,直到`done: true`。这里注意,我们的`4`没有打印出来,说明`for...of`迭代,是不包括`done`为`true`的时候的值的。 158 | 159 | 下面我们提一个新的问题,如果在`generator`中执行`generator`会怎么样?这里我们先认识一个新的语法`yield *`,这个语法可以让我们在`yield`跟一个`generator`执行器,当`yield`遇到一个新的`generator`需要执行,它会先将这个新的`generator`执行完,再继续执行我们当前的`generator`。这样说可能不太好理解,我们看代码。 160 | ```javascript 161 | function *foo() { 162 | yield 2; 163 | yield 3; 164 | yield 4; 165 | } 166 | 167 | function * bar() { 168 | yield 1; 169 | yield *foo(); 170 | yield 5; 171 | } 172 | 173 | for ( var v of bar()) { 174 | console.log(v); 175 | } 176 | ``` 177 | 这里有两个`generator`我们在`bar`中执行了`foo`,我们使用了`yield *`来执行`foo`,这里的执行顺序会是`yield 1`,然后遇到`foo`进入`foo`中,继续执行`foo`中的`yield 2`直到`foo`执行完毕。然后继续回到`bar`中执行`yield 5`所以最后的执行结果是: 178 | ``` 179 | 1 180 | 2 181 | 3 182 | 4 183 | 5 184 | ``` 185 | 186 | ### 异步请求 187 | 我们上面的例子一直都是同步的,但实际上我们的应用是在异步中,我们现在来看看异步中怎么应用。 188 | 189 | ```javascript 190 | function request(url) { 191 | makeAjaxCall(url, function(response) { 192 | it.next(response); 193 | }) 194 | } 195 | 196 | function *foo() { 197 | var data = yield request('http://api.example.com'); 198 | console.log(JSON.parse(data)); 199 | } 200 | 201 | var it = foo(); 202 | it.next(); 203 | ``` 204 | 这里又回到一开头说的那个例子,异步请求在执行到`yield`的时候交出控制权,然后等数据回调成功后在回调中交回控制权。所以像同步一样写异步代码并不是说真的变同步了,只是异步回调的过程被封装了,从外面看不到而已。 205 | 206 | ### 错误处理 207 | 我们都知道在js中我们使用`try...catch`来处理错误,在generator中类似,如果在`generator`内发生错误,如果内部能处理,就在内部处理,不能处理就继续向外冒泡,直到能够处理错误或最后一层。 208 | 209 | 内部处理错误: 210 | ```javascript 211 | // 内部处理 212 | function *foo() { 213 | try { 214 | yield Number(4).toUpperCase(); 215 | } catch(e) { 216 | console.log('error in'); 217 | } 218 | } 219 | 220 | var it = foo(); 221 | it.next(); 222 | 223 | // 运行结果:error in 224 | ``` 225 | 226 | 外部处理错误: 227 | ```javascript 228 | // 外部处理 229 | function *foo() { 230 | yield Number(4).toUpperCase(); 231 | } 232 | 233 | var it = foo(); 234 | try { 235 | it.next(); 236 | } catch(e) { 237 | console.log('error out'); 238 | } 239 | 240 | // 运行结果:error out 241 | ``` 242 | 243 | 在`generator`的错误处理中还有一个特殊的地方,它的迭代器有一个`throw`方法,能够将错误丢回`generator`中,在它暂停的地方报错,再往后就跟上面一样了,如果内部能处理则内部处理,不能内部处理则继续冒泡。 244 | 245 | 内部处理结果: 246 | ```javascript 247 | function *foo() { 248 | try { 249 | yield 1; 250 | } catch(e) { 251 | console.log('error', e); 252 | } 253 | yield 2; 254 | yield 3; 255 | } 256 | 257 | var it = foo(); 258 | it.next(); 259 | it.throw('oh no!'); 260 | 261 | // 运行结果:error oh no! 262 | ``` 263 | 264 | 外部处理结果: 265 | ```javascript 266 | function *foo() { 267 | yield 1; 268 | yield 2; 269 | yield 3; 270 | } 271 | 272 | var it = foo(); 273 | it.next(); 274 | try { 275 | it.throw('oh no!'); 276 | } catch (e) { 277 | console.log('error', e); 278 | } 279 | 280 | // 运行结果:error oh no! 281 | ``` 282 | 283 | 根据测试,发现迭代器的`throw`也算作一次迭代,测试代码如下: 284 | ```javascript 285 | function *foo() { 286 | try { 287 | yield 1; 288 | yield 2; 289 | } catch (e) { 290 | console.log('error', e); 291 | } 292 | yield 3; 293 | } 294 | 295 | var it = foo(); 296 | console.log(it.next()); 297 | it.throw('oh no!'); 298 | console.log(it.next()); 299 | 300 | // 运行结果 301 | // { value: 1, done: false } 302 | // error oh no! 303 | // { value: undefined, done: true } 304 | ``` 305 | 当用`throw`丢回错误的时候,除了`try`中的语句,迭代器迭代掉了`yield 3`下次再迭代就是,就是最后结束的值了。错误处理到这里就没有了,就这么点东西^_^。 306 | 307 | ### 自动运行 308 | `generator`能不能自动运行?当然能,并且有很多这样的库,这里我们先自己实现一个简单的。 309 | 310 | ```javascript 311 | function run(g) { 312 | var it = g(); 313 | 314 | // 利用递归进行迭代 315 | (function iterator(val) { 316 | var ret = it.next(val); 317 | 318 | // 如果没有结束 319 | if(!ret.done) { 320 | // 判断promise 321 | if(typeof ret.value === 'object' && 'then' in ret.value) { 322 | ret.value.then(iterator); 323 | } else { 324 | iterator(ret.value); 325 | } 326 | } 327 | })(); 328 | } 329 | ``` 330 | 这样我们就能自动处理运行我们的`generator`了,当然我们这个很简单,没有任何错误处理,如何让多个`generator`同时运行,这其中涉及到如何进行控制权的转换问题。我写了一个简单的执行器`Fo`,其中包含了Kyle Simpson大神的一个[ping-pong](http://jsbin.com/qutabu/1/edit?js,output)的例子,感兴趣的可以看下这里是[传送门](https://github.com/xwchris/fo),当然能顺手star一下就更好了,see you next article ~O(∩_∩)O~。 331 | 332 | ## 参考链接 333 | - [The Basics Of ES6 Generators](https://davidwalsh.name/es6-generators) 334 | - [Diving Deeper With ES6 Generators](https://davidwalsh.name/es6-generators-dive) 335 | - [Going Async With ES6 Generators](https://davidwalsh.name/async-generators) 336 | - [Getting Concurrent With ES6 Generators](https://davidwalsh.name/concurrent-generators) 337 | 338 | 339 | 340 | 341 | -------------------------------------------------------------------------------- /posts/网站图片优化.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "网站图片优化" 3 | date: "2019-07-06" 4 | tags: 前端 5 | author: xwchris 6 | desc: "图片在网站中会占用很大一部分流量,如何优化图片一直以来是一个有趣且必要的话题" 7 | --- 8 | 9 | ## 图像选择 10 | 图片分为矢量图形和光栅图形,矢量图形和像素与分辨率无关,所以在合适的条件下首选矢量图片。 11 | 12 | 如果图片较为简单,且包含几何图形,使用矢量图形是一种很好的方案。如果图片较为复杂,使用图片图形可能会用到很多几何图形,文件大小暴增,但表现质量可能还不如光栅图形。这个时候选择光栅推行比矢量图形更好。 13 | 14 | 光栅图形有很多种类,普遍支持的有GIF、PNG、JPEG。一些较新的格式(例如Webp和JPEG PR),他们的总体压缩率更高,提供的功能也更多。 15 | 16 | 如果需要动画GIF是唯一的选择,GIF的调色板最多为256色。对于调色板较小的图片,PNG-8的压缩效果更佳。除了选择调色板大小,PNG不采用任何有损压缩算法,因此它能生成最高质量的图片,但是代价是比其他格式尺寸大很多。JPEG组合使用有损和无损优化来减少图片的大小。 17 | 18 | ## 图像压缩 19 | ### 矢量图形压缩 20 | 对于矢量图形svg来说,里面有时候会包含很多元数据,包括图层信息,舒适和命名空间等,在浏览器中渲染,我们通常不需要这些参数。因此可以使用[svgo](https://github.com/svg/svgo)工具对svg图片进行压缩。 21 | 22 | ### 光栅图形压缩 23 | 24 | 光栅图形是由一个个像素组成的,每个像素由RGBA四种通道组成,而每个通道都由8位来表示,所以单个像素(css)的大小为4字节,一个100X100的图片的大小为`100 * 100 * 4 / 1024 = 39KB`。如果图片像素更多,那么我们的文件大小会迅速暴增。 25 | 26 | 压缩的手段有很多: 27 | 28 | - 删除图像多余的元数据如地理信息和相机信息 29 | - 减少每个通道位数(位深)即减少调色板大小。 30 | - 增量编码即只记录相邻像素的差异部分 31 | 32 | 任何类型的图像都可以通过有损和无损压缩来减少他们的大小,实际上这些类型之间的差异就在于压缩使用的有损和无损压缩的算法不同。使用有损压缩,去除某些像素数据。使用无损压缩,压缩像素数据。 33 | 34 | ## 图像优化总结 35 | 总的来说,图像的优化有以下几个步骤: 36 | 37 | 1. 选择合适的图像格式 38 | 2. 选择合适的图片尺寸(让图片更接近自然尺寸) 39 | 3. 对图像进行合理压缩 40 | 4. 自动化处理以上这些 41 | 42 | ## 图片优化工具 43 | ### sharp 44 | node端使用,用来减少图片分辨率和压缩图片。[github地址](https://github.com/lovell/sharp) 45 | 46 | ### svgo 47 | node端使用,用来优化svg图形。[github地址](https://github.com/svg/svgo) 48 | 49 | ## 图片在手机上的表现 50 | 通常,我们在手机上用图片的时候需要用到2倍图,甚至3倍图,否则图片会变的很模糊这是为什么那。 51 | 52 | 为了解答以上问题先来说说什么是DPR。DPR即Device Pixel Radio它通过`DPR = 设备实际像素 / 设备无关像素`来得出,通常DPR的值就是我们要使用的图片的倍数。设备实际像素指的是设备实际的物理像素。以iphone6为例,它的分辨率即实际的物理像素为750X1334。我们平时开始所用的iphone6的尺寸375X667指的是它的css像素大小,css像素又被称为设备无关像素。所以我们公式就改为`DPR = 分辨率 / CSS像素` 53 | 54 | 为了让图片清晰,我们需要让图片的每一像素都能对应一个实际的物理像素。如果我们使用375X667的图片,那么图片被拉伸到750X1334会导致每个图片像素对应了四个物理像素,对于物理像素无法确认颜色就会取周围的值来填充,造成图片看起来很模糊。因此为了让每个像素都能对应一个实际的物理像素,我们需要多倍图。 55 | 56 | 这就是为什么在移动端上通常使用2倍,3倍图的原因。 57 | ## 参考文章 58 | [Google develop 59 | 图像优化](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization) -------------------------------------------------------------------------------- /posts/网页移动端适配.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "网页移动端适配" 3 | date: "2019-05-01" 4 | tags: javascript, 前端 5 | author: xwchris 6 | desc: "我在网上看过很多相关的资料,都在说淘宝适配方案和网易适配方案。说了dpr,meta等好多概念,说实话我感觉写的都好复杂,跟我自己想的有出入。新学东西我总想找到这个东西设计的出发点,但我没在这些文章中找出来。在看了些现在主流网站的代码后,觉得自己有了一点心得,所以献丑拿来分享下,希望对你有点帮助" 7 | --- 8 | 9 | ### 准备 10 | 11 | 既然是适配我们开始肯定要有一个参考屏幕,这里我们先提前确定下面所有的例子都是以iphone6的屏幕(宽度为`375px`)为参照。通常设计稿是2倍的设计稿,所以我们拿到的设计稿设计稿最终宽度为`750px`。 12 | 13 | ### 屏幕适配 14 | 15 | 屏幕适配最终的目标或者说就是实现 **等比缩放**。 16 | 17 | 现在各大网站虽然方案有差异,但步骤和目的其实是一样的,主要分为以下几步: 18 | 19 | 1. 找到一个基准,基准能随屏幕宽度变化 20 | 2. 确定基准的值 21 | 3. 根据基准的值来写我们的样式 22 | 23 | 为什么要有个基准?因为我们不希望每种屏幕写一种布局样式,所以我们需要有一个基准来随屏幕宽度变化,我们只要根据基准来确定我们的css值,就可以适配所有的屏幕了。 24 | 25 | 这就是适配的全部了,下面我们来看看这几步可以用什么方案来解决。 26 | 27 | 基准是什么?为了简单基准我们看成单位,所以我们需要找一个能变化的单位,思考下,css中哪些单位可以变化?`rem`是以根字体的大小来确定自己的值的,符合条件。所以我们可以让根字体随屏幕变化而变化,我们直接用`rem`进行布局可以了。 28 | 29 | 下一步就是确定基准值,我们这里就是确定根字体的值。为了方便我们计算我们可以设置一个很容易计算的值,比如我们可以让设计稿中`1rem=100px`,那么写起来就是 30 | 31 | ```javascript 32 | // 屏幕宽度 / 7.5 = 1rem 33 | 34 | // 或 35 | 36 | // 100vw / 7.5 = 1rem 37 | ``` 38 | 39 | 这两种在这个例子中是一样的,然后我们写样式的时候用`rem`做为单位就可以了,比如设计稿上有一个宽度为`80px`的`div`元素,我们只需要这样写: 40 | 41 | ```css 42 | div { 43 | width: .8rem; 44 | } 45 | ``` 46 | 47 | 如果你还是嫌每次手动计算麻烦,可以用现在样式预处理器(如less、sass)中的mixin的来帮你或者使用js来动态计算。 48 | 49 | 到这里我们的适配就说完了。你可能会问dpr、meta头设置视图宽度那些东西怎么没看到,我明明在很多文章看到这些概念。别急,其实这些都是为了解决一个问题,下面我们就来说说这个问题 50 | 51 | ### hairline 52 | 53 | `hairline`是啥?hairline其实就是很细的线,很多设计师特别喜欢用这种线,让我们前端头大🙄。这种线直接用`0.5px`行吗?这在以前一些旧的屏幕上是不行的,会被自动修正为`1px`,我们都知道。 54 | 55 | 但是现代很多手机都是高倍屏,即一个css像素会有多个物理像素,这样显示的图像更细腻并且更清晰,有的已经支持css使用小数,这种情况下我们可以直接使用像`0.5px`来写出这种宽度的线了。这里有个概念,物理像素数和css像素被称为`设备像素比`,也就是我们经常说的dpr了 56 | 57 | ``` 58 | 物理像素数 / css像素数 = dpr 59 | ``` 60 | `dpr`的值可以通过`window.devicePixelRadio`来获取。 61 | 62 | 问题好像已经解决了。但是我们前端还有很重要的一部分的工作是兼容,如果遇到不支持这种小数css写法的怎么办?我们想个很通用的解决方案,那就是缩放,比如我们把`1px`宽度的线缩小一半就能得到`0.5px`宽度的线了。 63 | 64 | 为了让我们所有`1px`宽度缩为一个物理像素宽,我们就需要让页面宽度为`屏幕css宽度 * dpr`。然后我们在这个宽度下写`1px`宽度的线,最后再缩小`dpr`倍我们就可以得得到1物理像素宽度了。 65 | 66 | 为了实现让页面变为`屏幕css宽度 * dpr`的宽的目的,我们需要按比例改变我们上面的适配方案。 67 | 68 | 假如现在`dpr=3`,我们就需要让页面宽度为 375 * 3 = 1125,而我们的设计稿是750。我们就需要让我们的基准值成比例变化 69 | 70 | ```javascript 71 | // 屏幕宽度 / 7.5 => 屏幕宽度 / 7.5 * 2 / dpr 72 | ``` 73 | 74 | 现在我们得到尺寸为`屏幕css宽度 * dpr`的页面了,为了让页面完全显示在屏幕中我们需要在html中设置meta头(不了解这些的自己查下,有很多资料) 75 | 76 | ```html 77 | 78 | ``` 79 | 80 | 然后缩小dpr倍变成 81 | 82 | ```html 83 | 84 | ``` 85 | 86 | 到这里,我们所有东西都讲完了,希望你已经理解了为什么会有那么多写法不同的适配方案了,他们都是殊途同归。 87 | 88 | ### 思考题 89 | 90 | 最后附上现在淘宝和网易的部分代码,你可以自己直接去他们网站找到这些代码。你应该能根据这些代码分析他们的方案了,这些留给你自己思考和分析了 91 | 92 | [手机淘宝网](https://h5.m.taobao.com/?sprefer=sypc00)部分适配代码 93 | 94 | ```javascript 95 | ! function (e, t) { 96 | var n = t.documentElement, 97 | d = e.devicePixelRatio || 1; 98 | 99 | function i() { 100 | var e = n.clientWidth / 3.75; 101 | n.style.fontSize = e + "px" 102 | } 103 | if (function e() { 104 | t.body ? t.body.style.fontSize = "16px" : t.addEventListener("DOMContentLoaded", e) 105 | }(), i(), e.addEventListener("resize", i), e.addEventListener("pageshow", function (e) { 106 | e.persisted && i() 107 | }), 2 <= d) { 108 | var o = t.createElement("body"), 109 | a = t.createElement("div"); 110 | a.style.border = ".5px solid transparent", o.appendChild(a), n.appendChild(o), 1 === a.offsetHeight && n.classList.add("hairlines"), n.removeChild(o) 111 | } 112 | }(window, document) 113 | ``` 114 | 115 | [手机网易新闻网](https://3g.163.com/touch/#/)部分适配代码 116 | 117 | ```css 118 | html { 119 | font-size: 13.33333vw 120 | } 121 | 122 | @media screen and (max-width: 320px) { 123 | html { 124 | font-size:42.667px; 125 | font-size: 13.33333vw 126 | } 127 | } 128 | 129 | @media screen and (min-width: 321px) and (max-width:360px) { 130 | html { 131 | font-size:48px; 132 | font-size: 13.33333vw 133 | } 134 | } 135 | 136 | @media screen and (min-width: 361px) and (max-width:375px) { 137 | html { 138 | font-size:50px; 139 | font-size: 13.33333vw 140 | } 141 | } 142 | 143 | @media screen and (min-width: 376px) and (max-width:393px) { 144 | html { 145 | font-size:52.4px; 146 | font-size: 13.33333vw 147 | } 148 | } 149 | 150 | @media screen and (min-width: 394px) and (max-width:412px) { 151 | html { 152 | font-size:54.93px; 153 | font-size: 13.33333vw 154 | } 155 | } 156 | 157 | @media screen and (min-width: 413px) and (max-width:414px) { 158 | html { 159 | font-size:55.2px; 160 | font-size: 13.33333vw 161 | } 162 | } 163 | 164 | @media screen and (min-width: 415px) and (max-width:480px) { 165 | html { 166 | font-size:64px; 167 | font-size: 13.33333vw 168 | } 169 | } 170 | 171 | @media screen and (min-width: 481px) and (max-width:540px) { 172 | html { 173 | font-size:72px; 174 | font-size: 13.33333vw 175 | } 176 | } 177 | 178 | @media screen and (min-width: 541px) and (max-width:640px) { 179 | html { 180 | font-size:85.33px; 181 | font-size: 13.33333vw 182 | } 183 | } 184 | 185 | @media screen and (min-width: 641px) and (max-width:720px) { 186 | html { 187 | font-size:96px; 188 | font-size: 13.33333vw 189 | } 190 | } 191 | 192 | @media screen and (min-width: 721px) and (max-width:768px) { 193 | html { 194 | font-size:102.4px; 195 | font-size: 13.33333vw 196 | } 197 | } 198 | 199 | @media screen and (min-width: 769px) { 200 | html { 201 | font-size:102.4px; 202 | font-size: 13.33333vw 203 | } 204 | } 205 | ``` 206 | 207 | 本文原文更新在我的github上,这里是[原文链接](https://github.com/xwchris/blog/issues/65)。如果文章有任何错误或不准确之处,欢迎指出,非常感谢! -------------------------------------------------------------------------------- /posts/考试脑科学读书笔记.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "考试脑科学读书笔记" 3 | date: "2021-07-10" 4 | tags: 读书笔记 5 | author: xwchris 6 | desc: "考试脑科学是一本关于记忆的书,从脑科学的角度提出了很多帮助记忆的点,这里是其内容的简单要点记录,方便之后回忆" 7 | --- 8 | 9 | 记忆本身是有科学依据的,人的记忆本能在于记住更有利于生存的东西。记忆从记忆期限上来分,分为短期记忆和长期记忆,分别对应电脑的内存和硬盘。长期记忆长期不使用也可能会忘记。但这些记忆并不是消失了,而是不好获取,如果能够再次学习,那么学习和记忆起来会更加快速。 10 | 11 | 海马体会帮助筛选记忆,筛选后的会从短期记忆晋升为长期记忆。一般情况下,只有对生命有益的记忆容易通过筛选。但我们平时学习的知识不属于这一类,但为了能记住这些知识,我们需要“欺骗”海马体,让其认为这些记忆很重要从而形成长期记忆。方法就是不断重复,科学复习,根据艾宾浩斯遗忘曲线进行复习。 12 | 13 | 同时输出也是很重要的一点,研究表明,只有输入而缺少输出更容易忘记,输出能帮助我们加深记忆。 14 | 15 | 热情在记忆中也扮演很重要的角色,如果我们有着好奇心,或者在记忆的时候有着强烈的情绪,则我们更容易记住。 16 | 17 | 有一种θ波能帮助我们更快速的记忆,这种波通常在有情绪或者处于不那么舒适的环境中产生,所以在吃饱前进行记忆更加有效 18 | 19 | 脑袋运行不存在疲劳,疲劳的只是我们的身体,及时在睡觉脑袋也在运转。在睡觉的时候,我们的脑袋会帮我们整理记忆碎片,剔除不重要的记忆,留下精髓。如果熬夜记忆而不能保证充足的睡眠,记忆会衰减很快,所以要保持充足的睡眠。由于睡眠能帮助我们整理记忆,所以睡前1、2小时是最佳的记忆时间。 20 | 21 | 我们学习知识要循序渐进,从整体到细节一步步学习,这样更容易掌握和记忆。 22 | 23 | 记忆分为方法记忆、知识记忆、经验记忆。经验记忆更容易记住,知识记忆相对更不容易记忆,如果我们能把知识记忆庄边成经验记忆则更容易记住。方法记忆就是常说的肌肉记忆,及时我们很久不用,也能“下意识”来做,这种记录步骤的方式称为方法记忆,方法记忆如围棋大师能够完整复盘下过的对局,甚至很久前的对局,这不是因为他们是天才,而是因为他们能够根据局势和经验来记住下一步。所以我们在学习中最好要理解原理,自己能够推到出来,而不是死记硬背。 24 | 25 | 记忆间是有联结效果的,A记忆能帮助B更好的记住,同时记住B能够帮助A的印象更深刻。所以这类似于一个指数效果的过程,所以当你努力而没有看到特别大的效果是不要气馁,保持乐观,持续学习,最终你会仿佛突然达到一个突变点,豁然开朗。 -------------------------------------------------------------------------------- /posts/计算机中的编码和转义.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "计算机中的编码和转义" 3 | date: "2020-01-10" 4 | tags: 计算机基础 5 | author: xwchris 6 | desc: "编码是计算机的基础,有时候我们会将编码和转义混为一谈。本文旨在让自己完全理解何为编码和转义" 7 | --- 8 | 9 | ## 编码 10 | 编码的目的是为了方便计算机存储、传输和识别内容。 11 | 12 | 编码从出现到现在一直处在发展之中,目前编码方式已经算是比较成熟了 13 | 14 | ### ASCII 15 | 16 | 最早出现的一种编码方式,目前仍在使用,它使用一个字节,最高位置0,其他位用来编码的方式来进行编码。 17 | 18 | 因此它最多有128个字符。 19 | 20 | ### GBK等其他编码方式 21 | 22 | 由于像汉语、日语等包含大量字符,因此以前ASCII编码的方式完全不能满足需求。 23 | 24 | 像欧洲就将ASCII的最高位也放进编码位,这样就能最多对256个字符进行编码。 25 | 26 | GBK等使用两个字节进行编码最多有256*256=65536个字符 27 | 28 | ### Unicode 29 | 30 | 为了应对各种编码方式混乱的问题,Unicode又称万国码诞生了,它能够对世界上所有字符语言进行编码,它规定了字符集和编码方式 31 | 32 | - UTF-8 33 | 34 | 这是一种变长的编码方式,为了解决使用固定字节数会浪费空间的问题。 35 | 36 | 当只需要一个字节的时候,最高位置0。与ASCII编码方式相同,故UTF-8兼容ASCII编码方式。 37 | 38 | 当需要多个字节的时候,为了区分需要再每个字节上加一些标志位。当需要N(N>1)个字节时候,第一个字节首先置N个1,然后接一个0,后面所有的字节,开头都置10。剩下的其他位为编码的位置 39 | 40 | - UTF-16 41 | 42 | UTF-16中以16位为一个word。 43 | 44 | 对于BMP中的字符使用1个word进行编码,这种方式与UCS-2的编码方式相同,故UTF-16是UCS-2的超集。 45 | 46 | 对于BMP之外的字符,使用2个word进行编码,前16位开头置为`110110`,范围是U+D800-U+DBFF,后16位开头置为`110111`范围是U+DC00-U+DFFF。 47 | 48 | - UTF-32 49 | 50 | UTF-32使用固定4个字节进行编码,它的问题是浪费了很多空间 51 | 52 | ## 转义 53 | 54 | 55 | ### js中的转义 56 | 57 | - encodeURIComponent 58 | 59 | 对这些字符不会转义: 60 | `A-Z a-z 0-9 - _ . ! ~ * ' ( )` 61 | 62 | - encodeURI 63 | 64 | 对这些字符不会转义: 65 | `A-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #` 66 | 67 | ### 其他转义 68 | 69 | 转义有很多,各不相同 70 | 71 | ## BMP 72 | 73 | unicode中有17个panel,每个panel包含了一些字符集,他们是连续的2^16个码点。常用的panel是panel0 又称为BMP(Basic Multilingual Panel) 74 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwchris/blog/05f29941299bb46c28c48858dfdcd87d9a502476/public/favicon.ico -------------------------------------------------------------------------------- /public/images/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwchris/blog/05f29941299bb46c28c48858dfdcd87d9a502476/public/images/profile.jpg -------------------------------------------------------------------------------- /styles/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html { 6 | font-family: 'Hiragino Sans GB', Helvetica, Arial, sans-serif; 7 | } 8 | 9 | .react-toggle-track { 10 | background-color: rgb(30, 41, 59) !important; 11 | } 12 | .react-toggle--checked .react-toggle-track { 13 | background-color: white !important; 14 | } 15 | .react-toggle--checked .react-toggle-thumb { 16 | border-color: rgb(30, 41, 59) !important; 17 | background-color: rgb(30, 41, 59) !important; 18 | } 19 | 20 | :root { 21 | --clr-bg: #09091b; 22 | --clr-link: #94b9f4; 23 | /* --clr-code-bg: #20273c; */ 24 | --clr-code-bg: #20273c; 25 | --clr-code-txt: #d7def9; 26 | --clr-txt-050: hsla(0, 0%, 100%, 0.64); 27 | --clr-txt-100: hsla(0, 0%, 100%, 0.76); 28 | --clr-txt-150: hsla(0, 0%, 100%, 0.87); 29 | --clr-txt-200: hsla(0, 0%, 100%, 0.95); 30 | --br-lg: .5rem; 31 | } -------------------------------------------------------------------------------- /styles/icon.module.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "icons"; 3 | src: url("../assets/icons.woff2?24b22a509b7ef7b75bd38af25460adc9") format("woff2"); 4 | } 5 | 6 | .icon:before { 7 | font-family: icons !important; 8 | font-style: normal; 9 | font-weight: normal !important; 10 | font-variant: normal; 11 | text-transform: none; 12 | line-height: 1; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | 18 | .icon.icon-blog:before { 19 | content: "\f101"; 20 | } 21 | .icon.icon-check:before { 22 | content: "\f102"; 23 | } 24 | .icon.icon-chevron-down:before { 25 | content: "\f103"; 26 | } 27 | .icon.icon-chevron-left:before { 28 | content: "\f104"; 29 | } 30 | .icon.icon-chevron-right:before { 31 | content: "\f105"; 32 | } 33 | .icon.icon-chevron-up:before { 34 | content: "\f106"; 35 | } 36 | .icon.icon-clipboard:before { 37 | content: "\f107"; 38 | } 39 | .icon.icon-codepen:before { 40 | content: "\f108"; 41 | } 42 | .icon.icon-csharp:before { 43 | content: "\f109"; 44 | } 45 | .icon.icon-css:before { 46 | content: "\f10a"; 47 | } 48 | .icon.icon-deduck:before { 49 | content: "\f10b"; 50 | } 51 | .icon.icon-git:before { 52 | content: "\f10c"; 53 | } 54 | .icon.icon-github:before { 55 | content: "\f10d"; 56 | } 57 | .icon.icon-golang:before { 58 | content: "\f10e"; 59 | } 60 | .icon.icon-home:before { 61 | content: "\f10f"; 62 | } 63 | .icon.icon-js:before { 64 | content: "\f110"; 65 | } 66 | .icon.icon-list:before { 67 | content: "\f111"; 68 | } 69 | .icon.icon-node:before { 70 | content: "\f112"; 71 | } 72 | .icon.icon-php:before { 73 | content: "\f113"; 74 | } 75 | .icon.icon-python:before { 76 | content: "\f114"; 77 | } 78 | .icon.icon-react:before { 79 | content: "\f115"; 80 | } 81 | .icon.icon-search:before { 82 | content: "\f116"; 83 | } 84 | .icon.icon-share:before { 85 | content: "\f117"; 86 | } 87 | .icon.icon-star:before { 88 | content: "\f118"; 89 | } 90 | .icon.icon-twitter:before { 91 | content: "\f119"; 92 | } 93 | 94 | .iconJs { 95 | background: #f6d854; 96 | color: #392f31; 97 | } 98 | .iconCss { 99 | background: #3f4de4; 100 | color: #ffffff; 101 | } 102 | .iconReact { 103 | background: #000; 104 | color: #61dafb; 105 | } 106 | .iconPython { 107 | background: #3c77a9; 108 | color: #ffffff; 109 | } 110 | .iconPhp { 111 | background: #8b9bd6; 112 | color: #2a2843; 113 | } 114 | .iconCsharp { 115 | background: #672179; 116 | color: #ffffff; 117 | } 118 | .iconGolang { 119 | background: #5ac9e2; 120 | color: #000000; 121 | } 122 | .iconDart { 123 | background: #1b2634; 124 | color: #ffffff; 125 | } 126 | .iconBlog { 127 | background: #048f0e; 128 | color: #ffffff; 129 | } 130 | .iconNode { 131 | background: #333333; 132 | color: #539e43; 133 | } 134 | .iconGit { 135 | background: #f05133; 136 | color: #ffffff; 137 | } 138 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./pages/**/*.{js,ts,jsx,tsx}", 4 | "./components/**/*.{js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | darkMode: 'class', 11 | } 12 | --------------------------------------------------------------------------------