├── .vscode ├── launch.json └── settings.json ├── DataBinding ├── NodeExample │ ├── Binding.js │ ├── Observer.js │ └── test.js └── WebExample │ ├── index.html │ └── js │ ├── Observer │ ├── Observer.js │ └── Watch.js │ └── index.js ├── LearnWebpack ├── .gitignore ├── build.js ├── note │ ├── 入门Webpack笔记(一).md │ └── 入门Webpack笔记(二).md ├── package.json ├── src │ ├── App.js │ ├── global.js │ ├── index.css │ ├── index.html │ └── index.js └── webpack.config.js ├── NodeReptile ├── .gitignore ├── DataProcess.js ├── GlobalVariable.js ├── StoreData.js ├── files │ └── data.json ├── package.json └── reptile.js ├── Notes ├── Webpack配置Vue开发环境.md ├── pics │ ├── 20181014_1.png │ ├── npmrunbuild_20181201.png │ ├── npmrundev_20181201.png │ ├── page.png │ ├── run.png │ ├── 创建项目.png │ ├── 命名项目.png │ ├── 新建包新建类.png │ ├── 深度截图_deepin-terminal_20180728130718.png │ └── 深度截图_选择区域_20180728134235.png ├── 六种排序算法的JavaScript实现以及总结.md ├── 在Deepin中搭建GitLab.md ├── 基于 Webpack4 搭建 Vue 开发环境.md └── 对于JavaScript排序算法的一些其他测试.md ├── PubSub ├── Emitter.js ├── EventEmitter.js └── Listener.js ├── README.md ├── ReactCarousel ├── .babelrc ├── .gitignore ├── index.html ├── npm-debug.log ├── package.json ├── src │ ├── assets │ │ └── close.svg │ ├── components │ │ ├── App.js │ │ ├── Carousel.js │ │ ├── Dialog.js │ │ └── Message.js │ ├── main.css │ └── main.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── SortExamples ├── BubbleSort.js ├── Global.js ├── HeapSort.js ├── InsertionSort.js ├── MergeSort.js ├── PythonImplementation │ ├── BubbleSort.py │ ├── Global.py │ ├── HeapSort.py │ ├── InsertionSort.py │ ├── MergeSort.py │ ├── QuickSort.py │ ├── SelectionSort.py │ └── __pycache__ │ │ └── Global.cpython-35.pyc ├── QuickSort.js ├── RawSort.js ├── SelectionSort.js ├── _QuickSort.js └── _SelectionSort.js ├── UnderscoreSourceCode ├── README.md ├── notes │ ├── JavaScript中判断两变量相等的方法.md │ ├── Underscore中各种类型的判断.md │ ├── images │ │ ├── 20180301.png │ │ ├── 20180301143254.png │ │ ├── jsequal.png │ │ └── throttle.png │ ├── 利用Underscore求数组的交集、并集和差集.md │ ├── 理解Underscore中的_.template函数.md │ ├── 理解Underscore中的bind函数.md │ ├── 理解Underscore中的flatten函数.md │ ├── 理解Underscore中的restArgs函数.md │ ├── 理解Underscore中的uniq函数.md │ ├── 理解Underscore中的去抖函数.md │ ├── 理解Underscore中的节流函数.md │ └── 理解Underscore的设计架构.md ├── test.js └── underscore.1.8.3.js ├── UsefulSnippet ├── CSSSnippet │ ├── CSS 奇技淫巧.md │ ├── demo.html │ └── gif │ │ ├── bounce-loading.gif │ │ ├── checkbox.gif │ │ ├── custom-scrollbar.gif │ │ ├── custom-text-selection.gif │ │ ├── donut-loading.gif │ │ ├── gradient-text.gif │ │ ├── hover-underline-animation-1.gif │ │ ├── hover-underline-animation-2.gif │ │ ├── hover-underline-animation.gif │ │ ├── not-selector.gif │ │ ├── overflow-scroll-gradient.gif │ │ ├── system-font.png │ │ └── truncate-text.png ├── CSharpTools.txt └── JSSnippet │ ├── debounce.js │ └── throttle.js ├── Webpack-React ├── .babelrc ├── .gitignore ├── build │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── index.html ├── package.json └── src │ ├── App.js │ ├── components │ ├── Footer.jsx │ ├── Footer.less │ ├── Header.js │ ├── Header.less │ ├── ListPanel.css │ └── ListPanel.js │ ├── index.css │ ├── index.js │ └── store │ ├── actions.js │ ├── reducers.js │ └── store.js ├── Webpack-Vue ├── .babelrc ├── .gitignore ├── build │ ├── build.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── imgs │ ├── 云之家图片20181012093335.png │ ├── 云之家图片20181012093521.png │ ├── 使用插件后的第二次打包.png │ └── 使用插件后第一次打包.png ├── index.html ├── package.json ├── postcss.config.js └── src │ ├── App.vue │ ├── components │ └── tab.vue │ ├── index.css │ └── index.js └── 图解HTTP ├── 1.md ├── 2.md ├── 3.md ├── 4.md ├── 5.md ├── 6.md ├── 7.md ├── 8.md ├── pics ├── bodystructure.png ├── httpbackground.png ├── httpbody.png ├── httpcache.png ├── httpcert.png ├── httpconn.png ├── httpgateway.png ├── httpresstructure.png ├── https.png ├── httpsession.png ├── httpskey.png ├── httpstream.png ├── httpstructure.png ├── httptransfer.png ├── httpvia.png ├── multihttpproxy.png ├── relationship.png ├── requestcontent.png ├── responsecontent.png ├── tcphandshaking.png ├── urlformat.png ├── webproxy.png └── xss.png └── 《图解 HTTP》读书笔记.md /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "python", 9 | "pythonPath": "/usr/bin/python3", 10 | "request": "launch", 11 | "name": "Run Python", 12 | "program": "${file}" 13 | }, 14 | { 15 | "type": "node", 16 | "request": "launch", 17 | "name": "Run Current File", 18 | "program": "${file}" 19 | }, 20 | { 21 | "type": "node", 22 | "request": "launch", 23 | "name": "Ubuntu Run Test", 24 | "program": "${workspaceFolder}/UnderscoreSourceCode/test.js" 25 | }, 26 | { 27 | "type": "node", 28 | "request": "launch", 29 | "name": "Ubuntu Run Underscore", 30 | "program": "${workspaceFolder}/UnderscoreSourceCode/underscore.1.8.3.js" 31 | }, 32 | { 33 | "type": "node", 34 | "request": "launch", 35 | "name": "Run Test", 36 | "program": "${workspaceFolder}/UnderscoreSourceCode\\test.js" 37 | }, 38 | { 39 | "type": "node", 40 | "request": "launch", 41 | "name": "Run Underscore", 42 | "program": "${workspaceFolder}/UnderscoreSourceCode\\underscore.1.8.3.js" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.fontWeight": "600", 3 | "python.linting.pylintEnabled": false, 4 | "python.pythonPath": "/usr/bin/python3", 5 | "editor.fontSize": 22 6 | } -------------------------------------------------------------------------------- /DataBinding/NodeExample/Binding.js: -------------------------------------------------------------------------------- 1 | const O = require('./Observer'); 2 | const Observer = new O(); 3 | 4 | function watch(data) { 5 | if (typeof data !== 'object') 6 | return; 7 | Object.keys(data).forEach(k => { 8 | let value; 9 | if (typeof data[k] === 'object') { 10 | watch(data[k]); 11 | } else { 12 | Object.defineProperty(data, k, { 13 | set(newVal) { 14 | value = newVal; 15 | watch(data[k]); 16 | Observer.notify(newVal); 17 | }, 18 | get() { 19 | return value; 20 | } 21 | }); 22 | } 23 | }); 24 | } 25 | 26 | var a = { 27 | name: '', 28 | family: { 29 | dad: '', 30 | mom: '' 31 | } 32 | }; 33 | watch(a); 34 | const update = (x) => { 35 | console.log(x); 36 | }; 37 | Observer.add({update}); 38 | a.family; 39 | a.family.dad = '1'; 40 | a.family.mom = '2'; 41 | console.log(a.family.dad); -------------------------------------------------------------------------------- /DataBinding/NodeExample/Observer.js: -------------------------------------------------------------------------------- 1 | class Observer { 2 | constructor() { 3 | this.observers = []; 4 | } 5 | add(observer) { 6 | if (!this.observers.includes(observer)) { 7 | this.observers.push(observer); 8 | } 9 | } 10 | remove(observer) { 11 | this.observers.forEach((o, i) => { 12 | if (o === observer) { 13 | this.observers.splice(i, 1); 14 | } 15 | }); 16 | } 17 | notify(...args) { 18 | this.observers.forEach(o => { 19 | o.update(args); 20 | }); 21 | } 22 | } 23 | module.exports = Observer; -------------------------------------------------------------------------------- /DataBinding/NodeExample/test.js: -------------------------------------------------------------------------------- 1 | const Observer = require('./Observer'); 2 | 3 | const update = (x) => { 4 | console.log(x); 5 | } 6 | const o = new Observer(); 7 | o.add({update}); 8 | o.add({update}); 9 | o.notify('...'); -------------------------------------------------------------------------------- /DataBinding/WebExample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /DataBinding/WebExample/js/Observer/Observer.js: -------------------------------------------------------------------------------- 1 | class Observer { 2 | constructor() { 3 | this.observers = []; 4 | } 5 | add(observer) { 6 | if (!this.observers.includes(observer)) { 7 | this.observers.push(observer); 8 | } 9 | } 10 | notify(...args) { 11 | this.observers.forEach(o => { 12 | o.update(...args) 13 | }); 14 | } 15 | remove(observer) { 16 | this.observers.forEach((o, i) => { 17 | if (o === observer) { 18 | this.observers.splice(i, 1); 19 | } 20 | }); 21 | } 22 | } -------------------------------------------------------------------------------- /DataBinding/WebExample/js/Observer/Watch.js: -------------------------------------------------------------------------------- 1 | 2 | const OBS = new Observer(); 3 | OBS.add({ 4 | update(newVal) { 5 | document.getElementById('input').value = newVal; 6 | document.getElementById('h1').innerText = newVal; 7 | } 8 | }); 9 | 10 | function watch(data) { 11 | if (typeof data !== 'object') 12 | return; 13 | Object.keys(data).forEach(k => { 14 | let value = ''; 15 | if (typeof data[k] === 'object') 16 | watch(data[k]); 17 | else { 18 | Object.defineProperty(data, k, { 19 | set(newVal) { 20 | value = newVal; 21 | watch(data[k]); 22 | OBS.notify(newVal); 23 | }, 24 | get() { 25 | return value; 26 | } 27 | }) 28 | } 29 | }); 30 | return data; 31 | } 32 | -------------------------------------------------------------------------------- /DataBinding/WebExample/js/index.js: -------------------------------------------------------------------------------- 1 | 2 | let data = watch({ 3 | value: '' 4 | }); 5 | document.getElementById('input').addEventListener('input', function(e) { 6 | data.value = e.currentTarget.value; 7 | }) -------------------------------------------------------------------------------- /LearnWebpack/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | 3 | /dist/ -------------------------------------------------------------------------------- /LearnWebpack/build.js: -------------------------------------------------------------------------------- 1 | // 该文件演示使用Node API调用webpack进行打包。 2 | 3 | const webpack = require('webpack'); 4 | const config = require('./webpack.config'); 5 | const fs = require('fs'); 6 | const compiler = webpack(config); 7 | 8 | var deleteFolderRecursive = function(path) { 9 | if (fs.existsSync(path)) { 10 | fs.readdirSync(path).forEach(function(file, index){ 11 | var curPath = path + "/" + file; 12 | if (fs.lstatSync(curPath).isDirectory()) { // recurse 13 | deleteFolderRecursive(curPath); 14 | } else { // delete file 15 | fs.unlinkSync(curPath); 16 | } 17 | }); 18 | fs.rmdirSync(path); 19 | } 20 | }; 21 | 22 | deleteFolderRecursive(__dirname + '/dist'); 23 | compiler.run((err, stats) => { 24 | if(err) { 25 | console.error(err); 26 | } 27 | else { 28 | console.log(stats.hash); 29 | } 30 | }); -------------------------------------------------------------------------------- /LearnWebpack/note/入门Webpack笔记(二).md: -------------------------------------------------------------------------------- 1 | # 一款破产版脚手架的开发 2 | 3 | 前些天一直在学习入门Webpack,后来尝试了自己搭建一下一个简单的React开发环境,后来就在想可不可以自己写一个简单的脚手架,以免每次搭建一个简单的开发环境都需要自己一个个的配置,这样很麻烦,使用`create-react-app`的话,配置一大堆可能不会用到的功能,比较冗余,所以自己写一个超级简化的脚手架,只处理ES6代码、JSX语法和css模块,这样就满足了基本的使用。 4 | 5 | 后来在开发的过程中又遇到了新的麻烦,比如使用Node的`child_process.spawn`方法调用npm命令时,会出现错误,因为在Windows环境下,实际上要调用`npm.cmd`,而非`npm`,在这里出现了问题,还有一些其他问题,后来正好看到了[@Jsonz](https://juejin.im/user/58dbb0b844d904006954ca8e)大神写的两篇文章:[探索 create-react-app 源码](https://juejin.im/post/5af452fd518825671c0e96e5)和[create-react-app 源码解析之react-scripts](https://juejin.im/post/5af98aaf518825426d2d4142),于是也照着学习了一下`create-react-app`脚手架的源码,基本解决了一些问题,最终写出来了一个简(can)单(fei)的React脚手架,当然还有许许多多的不足,但是这个学习的过程值得我记录下来。 6 | 7 | 这篇文章记录了以下知识: 8 | 9 | * 如何使用Node开发一个简单的脚手架。 10 | * 如何发布你的npm模块并定制命令。 11 | 12 | ## 一、开发React脚手架 13 | 14 | `create-react-app`是一个很成功的、功能完善的脚手架,考虑到了许多方面,比如使用`npm`或者`yarn`,比如`npm`和`Node`版本、日志的记录和打印等等诸多方面,开发环境搭建的也十分完善,除了基本的React开发之外,还考虑了图片、postcss、sass、graphQL等等模块的处理。由于能力有限,本文开发的脚手架只涵盖了基本模块的处理,不包含图片、sass……等等。 15 | 16 | 脚手架的作用主要是建立一个React开发的标准目录、并且配置好webpack打包工具,使得开发过程中可以直接在标准的目录上修改,然后通过配置好的命令启动本地服务器或者打包app。所以脚手架中应该包括一个模板文件夹,里面放入应该拷贝到用户工程文件夹的所有文件或目录。在使用脚手架时,先把模板文件夹中的内容拷贝到用户工程文件夹下,然后修改`package.json`配置文件,最后安装所有模块。这就是我开发的脚手架所完成的基本工作。 17 | 18 | 脚手架工程目录结构如下: 19 | 20 | ROOT 21 | │ .gitignore 22 | │ .npmignore 23 | │ LICENSE 24 | │ package-lock.json 25 | │ package.json 26 | │ README.md 27 | │ 28 | ├─dist 29 | ├─package 30 | │ create-react.js 31 | │ 32 | └─templates 33 | │ .babelrc 34 | │ .gitignore 35 | │ README.md 36 | │ webpack.base.conf.js 37 | │ webpack.dev.conf.js 38 | │ webpack.prod.conf.js 39 | │ 40 | ├─dist 41 | └─src 42 | │ index.css 43 | │ index.html 44 | │ index.js 45 | │ 46 | └─components 47 | App.js 48 | 49 | 根据我的[前一篇文章](https://juejin.im/post/5afc29fa6fb9a07ab379a2ae),搭建React开发环境,最小化的标准目录结构应该如下: 50 | 51 | ROOT 52 | │ .babelrc 53 | │ .gitignore 54 | │ README.md 55 | │ webpack.base.conf.js 56 | │ webpack.dev.conf.js 57 | │ webpack.prod.conf.js 58 | │ 59 | ├─dist 60 | └─src 61 | │ index.css 62 | │ index.html 63 | │ index.js 64 | │ 65 | └─components 66 | App.js 67 | 68 | 所以在脚手架根目录下的`templates`文件夹中应该包含以上文件,文件内的内容可以自由定制。 69 | 70 | 同样根据上一篇文章,需要安装的模块主要有: 71 | 72 | 'webpack', 73 | 'webpack-cli', 74 | 'html-webpack-plugin', 75 | 'clean-webpack-plugin', 76 | 'webpack-dev-server', 77 | 'css-loader', 78 | 'webpack-merge', 79 | 'style-loader', 80 | 'babel-preset-env', 81 | 'babel-loader', 82 | 'babel-polyfill', 83 | 'babel-preset-react' 84 | 85 | 和 86 | 87 | 'react', 88 | 'react-dom' 89 | 90 | 第一部分只需要安装在开发环境(`npm i -D ...`),第二部分生产环境也要安装(`npm i --save ...`)。 91 | 92 | 那么接下来可以通过Node实现脚手架的开发了。 93 | 94 | 首先介绍一些有用的并且会用到的模块: 95 | 96 | * `cross-spawn`:解决跨平台使用npm命令的问题的模块。 97 | * `chalk`:实现控制台彩色文字输出的模块。 98 | * `fs-extra`:实现了一些fs模块不包含的文件操(比如递归复制、删除等等)的模块。 99 | * `commander`: 实现命令行传入参数预处理的模块。 100 | * `validate-npm-package-name`:对于用户输入的工程名的可用性进行验证的模块。 101 | 102 | 首先,在代码中引入这些基本的模块: 103 | 104 | const spawn = require('cross-spawn'); 105 | const chalk = require('chalk'); 106 | const os = require('os'); 107 | const fs = require('fs-extra'); 108 | const path = require('path'); 109 | const commander = require('commander'); 110 | const validateProjectName = require('validate-npm-package-name'); 111 | const packageJson = require('../package.json'); 112 | 113 | 然后定义我们的模板复制函数: 114 | 115 | function copyTemplates() { 116 | try { 117 | if(!fs.existsSync(path.resolve(__dirname, '../templates'))) { 118 | console.log(chalk.red('Cannot find the template files !')); 119 | process.exit(1); 120 | } 121 | fs.copySync(path.resolve(__dirname, '../templates'), process.cwd()); 122 | console.log(chalk.green('Template files copied successfully!')); 123 | return true; 124 | } 125 | catch(e) { 126 | console.log(chalk.red(`Error occured: ${e}`)) 127 | } 128 | } 129 | 130 | fs模块首先检测模板文件是否存在(防止被用户删除),如果存在则通过fs的同步拷贝方法(copySync)拷贝到脚手架的当前工作目录(即`process.cwd()`),如果不存在则弹出错误信息,随后使用[退出码](http://nodejs.cn/api/process.html#process_exit_codes)1退出进程。 131 | 132 | 随后定义`package.json`的处理函数; 133 | 134 | function generatePackageJson() { 135 | let packageJson = { 136 | name: projectName, 137 | version: '1.0.0', 138 | description: '', 139 | scripts: { 140 | start: 'webpack-dev-server --open --config webpack.dev.conf.js', 141 | build: 'webpack --config webpack.prod.conf.js' 142 | }, 143 | author: '', 144 | license: '' 145 | }; 146 | try { 147 | fs.writeFileSync(path.resolve(process.cwd(), 'package.json'), JSON.stringify(packageJson)); 148 | console.log(chalk.green('Package.json generated successfully!')); 149 | } 150 | catch(e) { 151 | console.log(chalk.red(e)) 152 | } 153 | } 154 | 155 | 可以看出先是定义了一个JavaScript Object,然后修改属性之后通过fs模块将其JSON字符串写入到了`package.json`文件中,实现了`package.json`的生成。 156 | 157 | 最后安装所有的依赖,分为devDependencies和dependencies: 158 | 159 | function installAll() { 160 | console.log(chalk.green('Start installing ...')); 161 | let devDependencies = ['webpack', 'webpack-cli', 'html-webpack-plugin', 'clean-webpack-plugin', 'webpack-dev-server', 'css-loader', 'webpack-merge', 'style-loader', 'babel-preset-env', 'babel-loader', 'babel-polyfill', 'babel-preset-react']; 162 | let dependencies = ['react', 'react-dom']; 163 | const child = spawn('cnpm', ['install', '-D'].concat(devDependencies), { 164 | stdio: 'inherit' 165 | }); 166 | 167 | child.on('close', function(code) { 168 | if(code !== 0) { 169 | console.log(chalk.red('Error occured while installing dependencies!')); 170 | process.exit(1); 171 | } 172 | else { 173 | const child = spawn('cnpm', ['install', '--save'].concat(dependencies), { 174 | stdio: 'inherit' 175 | }) 176 | child.on('close', function(code) { 177 | if(code !== 0) { 178 | console.log(chalk.red('Error occured while installing dependencies!')); 179 | process.exit(1); 180 | } 181 | else { 182 | console.log(chalk.green('Installation completed successfully!')); 183 | console.log(); 184 | console.log(chalk.green('Start the local server with : ')) 185 | console.log(); 186 | console.log(chalk.cyan(' npm run start')) 187 | console.log(); 188 | console.log(chalk.green('or build your app via :')); 189 | console.log(); 190 | console.log(chalk.cyan(' npm run build')); 191 | } 192 | }) 193 | } 194 | }); 195 | } 196 | 197 | 函数中,通过`cross-spawn`执行了`cnpm`的安装命令,值得注意的是其配置项: 198 | 199 | { 200 | stdio: 'inherit' 201 | } 202 | 203 | 代表将子进程的输出管道连接到父进程上,及父进程可以自动接受子进程的输出结果,详情见[options.stdio](http://nodejs.cn/api/child_process.html#child_process_options_stdio)。 204 | 205 | 206 | 通过`commander`模块实现命令行参数的预处理; 207 | 208 | const program = commander 209 | .version(packageJson.version) 210 | .usage(' [options]') 211 | .arguments('') 212 | .action(name => { 213 | projectName = name; 214 | }) 215 | .allowUnknownOption() 216 | .parse(process.argv); 217 | 218 | 其中,`version`方法定义了`create-react-application -V`的输出结果,`usage`定义了命令行里的用法,`arguments`定义了程序所接受的默认参数,然后在`action`函数回调中处理了这个默认参数,`allowUnknownOption`表示接受多余参数,`parse`表示把多余未解析的参数解析到`process.argv`中去。 219 | 220 | 最后是调用三个方法实现React开发环境的搭建: 221 | 222 | if(projectName == undefined) { 223 | console.log(chalk.red('Please pass the project name while using create-react!')); 224 | console.log(chalk.green('for example:')) 225 | console.log(); 226 | console.log(' create-react-application ' + chalk.yellow('')); 227 | } 228 | else { 229 | const validateResult = validateProjectName(projectName); 230 | if(validateResult.validForNewPackages) { 231 | copyTemplates(); 232 | generatePackageJson(); 233 | installAll(); 234 | //console.log(chalk.green(`Congratulations! React app has been created successfully in ${process.cwd()}`)); 235 | } 236 | else { 237 | console.log(chalk.red('The project name given is invalid!')); 238 | process.exit(1); 239 | } 240 | } 241 | 242 | 如果接受的工程名为空,那么弹出警告。如果不为空,就验证工程名的可用性,如果不可用,就弹出警告并且退出进程,否则调用之前定义的三个主要函数,完成环境的搭建。 243 | 244 | 截止到此,使用该程序的方式仍然是`node xxx.js --parameters`的方式,我们需要自定义一个命令,并且最好将程序上传到npm,便于使用。 245 | 246 | ## 二、定义你的命令并且发布npm包 247 | 248 | 实现自定义命令并发布npm模块只需要以下几步: 249 | 250 | * 修改入口文件,头部添加以下两句: 251 | 252 | #!/usr/bin/env node 253 | 'use strict' 254 | 255 | 第二行也一定不能少! 256 | 257 | * 修改`package.json`,添加`bin`属性: 258 | 259 | // package.json 260 | { 261 | "bin": { 262 | "create-react-application": "package/create-react.js" 263 | } 264 | } 265 | 266 | * 执行以下命令: 267 | 268 | npm link 269 | 270 | * [注册npm账户](https://www.npmjs.com/signup)(如已经注册则可以忽略)。 271 | 272 | * 执行以下命令: 273 | 274 | npm adduser 275 | 276 | 并输入账户密码。 277 | 278 | * 执行以下命令: 279 | 280 | npm publish 281 | 282 | 接下来就可以收到发布成功的邮件啦! 283 | 284 | 285 | 如果要更新你的npm模块,执行以下步骤: 286 | 287 | * 使用一下命令更新你的版本号: 288 | 289 | npm version x.x.x 290 | 291 | * 再使用以下命令发布; 292 | 293 | npm publish 294 | 295 | 执行完以上步骤之后,就可以在npm下载你的模块啦! 296 | 297 | ## 三、FQA 298 | 299 | ### (1)关于`#!/usr/bin/env node` 300 | 301 | 这是Unix系操作系统中的一种写法,名字叫做`Shebang`或者`Hashbang`等等。在[Wikipedia的解释](https://en.wikipedia.org/wiki/Shebang_(Unix))中,把这一行代码写在脚本中,使得操作系统把脚本当做可执行文件执行时,会找到对应的程序执行(比如此文中的node),而这段代码本身会被解释器所忽略。 302 | 303 | ### (2)关于`npm link` 304 | 305 | 在npm官方文档的解释中,`npm link`的执行,是一个两步的过程。当你在你的包中使用`npm link`时,会将全局文件夹:`{prefix}/lib/node_modules/`链接到执行`npm link`的文件夹,同样也会将执行`npm link`命令的包中的所有可执行文件链接到全局文件夹`{prefix}/bin/{name}`中。 306 | 307 | 此外,`npm link project-name`会将全局安装的`project-name`模块链接到执行`npm link`命令的当前文件夹的`node_modules`中。 308 | 309 | 根据npm官方文档,prefix的值可为: 310 | 311 | * /usr/local (大部分系统中) 312 | * %AppData%\npm (Windows中) 313 | 314 | 具体参考:[prefix configuration](https://docs.npmjs.com/files/folders#prefix-configuration)和[npm link](https://docs.npmjs.com/cli/link) 315 | 316 | ## 四、简单尝试 317 | 318 | 本文所开发的脚手架已经上传到了npm,可以通过以下步骤查看实际效果: 319 | 320 | * 安装`create-react-application` 321 | 322 | npm i -D create-react-application 323 | 324 | 或者 325 | 326 | npm i -g create-react-application 327 | 328 | * 使用`create-react-application` 329 | 330 | create-react-application 331 | 332 | 源码已经上传到了[GitHub](https://github.com/zhongdeming428/create-react-application),欢迎大家一起哈啤(#手动滑稽)。 333 | 334 | 此外文中还有许多不足,比如关于`npm link`的解释我也还不是很清楚,欢迎大家补充指教! 335 | 336 | ## 五、参考文章 337 | 338 | * [npm link 命令的作用浅析](https://blog.csdn.net/juhaotian/article/details/78672390) 339 | * [prefix configuration](https://docs.npmjs.com/files/folders#prefix-configuration) 340 | * [npm link](https://docs.npmjs.com/cli/link) 341 | * [基于Webpack搭建React开发环境](https://juejin.im/post/5afc29fa6fb9a07ab379a2ae) 342 | * [探索 create-react-app 源码](https://juejin.im/post/5af452fd518825671c0e96e5) 343 | * [npm 发布 packages](https://zhuanlan.zhihu.com/p/31901377) -------------------------------------------------------------------------------- /LearnWebpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learnwebpack", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "webpack": "^4.8.3", 6 | "webpack-cli": "^2.1.3" 7 | }, 8 | "devDependencies": { 9 | "css-loader": "^0.28.11", 10 | "html-webpack-plugin": "^3.2.0", 11 | "webpack-dev-server": "^3.1.4", 12 | "workbox-webpack-plugin": "^3.2.0" 13 | }, 14 | "scripts": { 15 | "build": "node build.js", 16 | "server": "webpack-dev-server --open" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LearnWebpack/src/App.js: -------------------------------------------------------------------------------- 1 | import { sayName } from './global'; 2 | 3 | console.log(sayName()); -------------------------------------------------------------------------------- /LearnWebpack/src/global.js: -------------------------------------------------------------------------------- 1 | const global = { 2 | name: 'zdm', 3 | age: 22, 4 | gender: 'male', 5 | class: 'zy1401', 6 | job: 'developer' 7 | }; 8 | export function sayName() { 9 | console.log(global.name); 10 | }; 11 | export function sayJob() { 12 | console.log(global.job); 13 | }; -------------------------------------------------------------------------------- /LearnWebpack/src/index.css: -------------------------------------------------------------------------------- 1 | .Content { 2 | color: white; 3 | } -------------------------------------------------------------------------------- /LearnWebpack/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Learn Webpack 5 | 6 | 7 |

Hello World!

8 |
9 | 10 | -------------------------------------------------------------------------------- /LearnWebpack/src/index.js: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | import './App.js'; 3 | 4 | if ('serviceWorker' in navigator) { 5 | window.addEventListener('load', () => { 6 | navigator.serviceWorker.register('./service-worker.js').then(registration => { 7 | console.log('SW registered: ', registration); 8 | }).catch(registrationError => { 9 | console.log('SW registration failed: ', registrationError); 10 | }); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /LearnWebpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const webpack = require('webpack'); 4 | const WorkboxPlugin = require('workbox-webpack-plugin'); 5 | 6 | const config = { 7 | mode: 'development', 8 | entry: './src/index.js', 9 | output: { 10 | filename: 'bundle.[hash].js', 11 | path: path.join(__dirname, '/dist') 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.css$/, 17 | use: 'css-loader' 18 | } 19 | ] 20 | }, 21 | plugins: [ 22 | new HtmlWebpackPlugin({ 23 | template: './src/index.html' 24 | }), 25 | new WorkboxPlugin.GenerateSW({ 26 | // 这些选项帮助 ServiceWorkers 快速启用 27 | // 不允许遗留任何“旧的” ServiceWorkers 28 | clientsClaim: true, 29 | skipWaiting: true 30 | }) 31 | ], 32 | devtool: 'line-source-map', 33 | devServer: { 34 | contentBase: path.join(__dirname, "dist"), 35 | compress: true, 36 | port: 9000, 37 | open: true 38 | } 39 | }; 40 | 41 | module.exports = config; -------------------------------------------------------------------------------- /NodeReptile/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /NodeReptile/DataProcess.js: -------------------------------------------------------------------------------- 1 | /* 2 | 这是一个数据处理文件 3 | 我把数据库中的数据整理成了189个数据点 4 | 最后存储为json文件,供前端调用 5 | */ 6 | 7 | const MongoClient = require('mongodb').MongoClient; 8 | const fs = require('fs'); 9 | const variables = require('./GlobalVariable'); 10 | 11 | //对于一个输入的工作对象 12 | //返回其最高薪水和最低薪水 13 | function processSalary(job){ 14 | var str = job.salary; 15 | var salary = null; 16 | if(str.indexOf('-') < 0){ 17 | salary = { 18 | lowerSalary: parseInt(str), 19 | higherSalary: parseInt(str) 20 | }; 21 | } 22 | else{ 23 | let arr = str.split('-'); 24 | salary = { 25 | lowerSalary: parseFloat(arr[0]), 26 | higherSalary: parseFloat(arr[1]) 27 | }; 28 | } 29 | return salary; 30 | } 31 | 32 | //给定工作岗位和工作地点 33 | //返回对应的薪水平均值 34 | function fetchAvg(jobName, jobLocation) { 35 | return new Promise((resolve, reject) => { 36 | MongoClient.connect('mongodb://localhost:27017', (e, db) => { 37 | if (e) 38 | reject(e); 39 | var jobs = db.db('jobs'); 40 | var collection = jobs.collection('jobs'); 41 | collection.find({ type: jobName, location: jobLocation }).toArray((e, arr) => { 42 | if (e) 43 | console.error(e); 44 | let res = []; 45 | let lowerSalary = 0; 46 | let higherSalary = 0; 47 | //用于测试空白数据 48 | // if(arr.length === 0){ 49 | // console.log(jobName, jobLocation); 50 | // } 51 | arr.forEach((job) => { 52 | let salary = processSalary(job); 53 | res.push(salary); 54 | }); 55 | db.close(); 56 | res.forEach(obj => { 57 | lowerSalary += obj.lowerSalary; 58 | higherSalary += obj.higherSalary; 59 | }); 60 | resolve({ 61 | lowerSalary: (lowerSalary * 1.0 / res.length).toFixed(2), 62 | higherSalary: (higherSalary * 1.0 / res.length).toFixed(2), 63 | name: jobName, 64 | city: jobLocation, 65 | count:res.length 66 | }); 67 | }); 68 | }); 69 | }); 70 | } 71 | 72 | //处理数据结果的函数 73 | //对于一个数组的结果 74 | //返回一个处理好的数据对象 75 | function processData(arr){ 76 | var resObj = {}; 77 | for(code in variables.cityCode){ 78 | for(index in arr){ 79 | if(variables.cityCode[code] === arr[index].city){ 80 | resObj[`${code}_${arr[index].name}`] = arr[index]; 81 | } 82 | } 83 | } 84 | return resObj; 85 | } 86 | 87 | //主函数 88 | function main() { 89 | var resArr = []; 90 | for (job in variables.jobs) { 91 | for (city in variables.cities) { 92 | (function (jobName, jobLocation, resArr) { 93 | fetchAvg(jobName, jobLocation).then(obj => { 94 | resArr.push(obj); 95 | if(resArr.length === 189){ 96 | // console.log(processData(resArr)); 97 | fs.writeFileSync('./files/data.json', JSON.stringify(processData(resArr))); 98 | } 99 | }, e => { 100 | console.error(e); 101 | }); 102 | })(job, variables.cities[city], resArr) 103 | } 104 | } 105 | } 106 | 107 | main(); -------------------------------------------------------------------------------- /NodeReptile/GlobalVariable.js: -------------------------------------------------------------------------------- 1 | var cities = ['深圳','武汉','上海','广州','北京','杭州','西安','长沙','成都','南京','天津', 2 | '大连','长春','沈阳','济南','青岛','苏州','无锡','重庆','宁波','厦门', 3 | '福州','哈尔滨','石家庄','合肥','惠州','郑州']; 4 | var jobs = { 5 | Web:'864', 6 | Android:'2039', 7 | Java:'2040', 8 | PHP:'2041', 9 | C:'2042', 10 | IOS:'2038', 11 | DB:'047' 12 | }; 13 | var cityCode = { 14 | sz:'深圳', 15 | wh:'武汉', 16 | sh:'上海', 17 | gz:'广州', 18 | bj:'北京', 19 | hzhou:'杭州', 20 | xa:'西安', 21 | cs:'长沙', 22 | cd:'成都', 23 | nj:'南京', 24 | tj:'天津', 25 | dl:'大连', 26 | cc:'长春', 27 | sy:'沈阳', 28 | jn:'济南', 29 | qd:'青岛', 30 | szhou:'苏州', 31 | wx:'无锡', 32 | cq:'重庆', 33 | nb:'宁波', 34 | xm:'厦门', 35 | fz:'福州', 36 | heb:'哈尔滨', 37 | sjz:'石家庄', 38 | hf:'合肥', 39 | hz:'惠州', 40 | zz:'郑州' 41 | }; 42 | exports.cities = cities; 43 | exports.jobs = jobs; 44 | exports.cityCode = cityCode; -------------------------------------------------------------------------------- /NodeReptile/StoreData.js: -------------------------------------------------------------------------------- 1 | /* 2 | 该程序用于将数据存储到LeanCloud平台 3 | */ 4 | var APP_ID = 'Me872SwKDBgiMyDLnneSS9T3-gzGzoHsz'; 5 | var APP_KEY = '8hyFXTocldzAvkPSr0i5ba0C'; 6 | var LC = require('leancloud-storage'); 7 | var fs = require('fs'); 8 | 9 | LC.init({ 10 | appId: APP_ID, 11 | appKey: APP_KEY 12 | }); 13 | 14 | var Data = LC.Object.extend('Data'); 15 | 16 | var str = fs.readFileSync(`${__dirname}\\files\\data.json`); 17 | 18 | var obj = JSON.parse(str); 19 | 20 | var i = 0; 21 | for(let key in obj){ 22 | i++; 23 | (function(interval){ 24 | setTimeout(() => { 25 | let data = new Data(); 26 | data.set('lowerSalary', obj[key].lowerSalary); 27 | data.set('higherSalary', obj[key].higherSalary); 28 | data.set('name', obj[key].name); 29 | data.set('city', obj[key].city); 30 | data.set('count', obj[key].count); 31 | data.save().then(() => { 32 | console.log('Success...'+interval); 33 | }, 34 | (e) => { 35 | console.error(e); 36 | }); 37 | }, interval * 1000); 38 | })(i); 39 | } -------------------------------------------------------------------------------- /NodeReptile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "cheerio": "^1.0.0-rc.2", 4 | "leancloud-storage": "^3.6.0", 5 | "mongodb": "^3.0.2" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /NodeReptile/reptile.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | const fs = require('fs'); 3 | const http = require('http'); 4 | const assert = require('assert'); 5 | const MongoClient = require('mongodb').MongoClient; 6 | const variables = require('./GlobalVariable'); 7 | 8 | function getData(pageNo, job, jobLocation, jobsCount){ 9 | var url = encodeURI(`http://sou.zhaopin.com/jobs/searchresult.ashx?bj=160000&sj=${variables.jobs[job]}&in=160400&jl=${jobLocation}&p=${pageNo}&isadv=0`); 10 | var jobs = []; 11 | console.log(`${job}-${jobLocation}...`); 12 | return new Promise((resolve, reject)=>{ 13 | try{ 14 | http.get(url, (res)=>{ 15 | res.setEncoding('utf-8'); 16 | var str = ''; 17 | res.on('data', (data)=>{ 18 | str += data; 19 | }); 20 | res.on('end',()=>{ 21 | var $ = cheerio.load(str); 22 | $('table.newlist').each((k, v)=>{ 23 | var job = {}; 24 | job.company = $('td.gsmc', v).text(); 25 | job.salary = $('td.zwyx', v).text(); 26 | job.name = String($('td.zwmc>div', v).text()).replace(/\s/g, ''); 27 | job.location = jobLocation; 28 | if(job.company !== '' && job.salary !== '面议'){ 29 | jobs.push(job); 30 | } 31 | }); 32 | var realJobs = null; 33 | if(jobsCount < 60){ 34 | realJobs = jobs.slice(0, jobsCount); 35 | } 36 | else { 37 | realJobs = jobs; 38 | } 39 | resolve(realJobs); 40 | }); 41 | }); 42 | } 43 | catch(e){ 44 | reject(e); 45 | } 46 | }); 47 | } 48 | 49 | function fetchCount(url){ 50 | return new Promise(function(resolve, reject){ 51 | try{ 52 | http.get(url, (res)=>{ 53 | res.setEncoding('utf-8'); 54 | var str = ''; 55 | res.on('data', (data)=>{ 56 | str += data; 57 | }); 58 | res.on('end', function(){ 59 | var $ = cheerio.load(str); 60 | var txt = $('span.search_yx_tj>em').text(); 61 | var jobsCount = parseInt(txt); 62 | var pagesCount = 0; 63 | if(jobsCount%60 === 0){ 64 | pagesCount = jobsCount/60; 65 | } 66 | else{ 67 | pagesCount = jobsCount/60 + 1; 68 | } 69 | resolve(pagesCount, jobsCount); 70 | }); 71 | }); 72 | } 73 | catch(e){ 74 | reject(e); 75 | } 76 | }); 77 | } 78 | 79 | function callGetData(pagesCount, job, jobLocation, jobsCount){ 80 | try { 81 | for (let i = 1; i <= pagesCount; i++) { 82 | //返回Promise对象 83 | getData(i, job, jobLocation, jobsCount).then((realJobs) => { 84 | // console.log(realJobs); 85 | // writeFile(job, jobLocation, realJobs); 86 | writeDB(job, realJobs); 87 | }, (e) => { 88 | console.log(e); 89 | }); 90 | } 91 | } 92 | catch (e) { 93 | console.log(e); 94 | } 95 | } 96 | 97 | //写入.txt文件 98 | //用于测试 99 | //生产环境写入数据库 100 | // function writeFile(jobCode, jobLocation, arr){ 101 | // fs.writeFile(`${__dirname}\\files\\${jobCode}_${jobLocation}.txt`, JSON.stringify(arr), (e)=>{ 102 | // assert.ifError(e); 103 | // }); 104 | // } 105 | 106 | //将数据写入数据库 107 | function writeDB(type, realJobs){ 108 | MongoClient.connect('mongodb://localhost:27017', (e, db)=>{ 109 | assert.ifError(e); 110 | var jobs = db.db('jobs'); 111 | var collection = jobs.collection('jobs'); 112 | realJobs.forEach((job)=>{ 113 | collection.insert(Object.assign({}, job, { type })); 114 | }); 115 | db.close(); 116 | }); 117 | } 118 | 119 | //主函数 120 | function main(job){ 121 | if(City !== undefined){ 122 | let url = encodeURI(`http://sou.zhaopin.com/jobs/searchresult.ashx?bj=160000&sj=${variables.jobs[job]}&in=160400&jl=${City}&p=1&isadv=0`); 123 | fetchCount(url).then((pagesCount, jobsCount)=>{ 124 | callGetData(pagesCount, job, City, jobsCount); 125 | },(e)=>{ 126 | console.log(e); 127 | }); 128 | } 129 | else { 130 | for(city in variables.cities){ 131 | (function(city){ 132 | let url = encodeURI(`http://sou.zhaopin.com/jobs/searchresult.ashx?bj=160000&sj=${variables.jobs[job]}&in=160400&jl=${variables.cities[city]}&p=1&isadv=0`); 133 | fetchCount(url).then((pagesCount, jobsCount)=>{ 134 | callGetData(pagesCount, job, variables.cities[city], jobsCount); 135 | },(e)=>{ 136 | console.log(e); 137 | }); 138 | })(city) 139 | } 140 | } 141 | } 142 | 143 | const Job = process.argv[2]; 144 | const City = process.argv[3]; 145 | if(variables.cities.indexOf(City) < 0 && City !== undefined){ 146 | console.error('无法查询该岗位数据... ...'); 147 | } 148 | if(variables.jobs.hasOwnProperty(Job)){ 149 | main(process.argv[2]); 150 | } 151 | else { 152 | console.error('无法查询该岗位数据... ...'); 153 | } -------------------------------------------------------------------------------- /Notes/Webpack配置Vue开发环境.md: -------------------------------------------------------------------------------- 1 | # 基于Webpack搭建Vue开发环境 2 | 3 | ## 一、初始化项目文件夹 4 | 5 | 新建一个文件夹(路径与命名随意),然后打开Terminal,切换路径到该文件夹,通过以下命令初始化: 6 | 7 | npm init -y 8 | 9 | `-y`是简化的初始化过程,所有`package.json`的所有属性都使用默认值。 10 | 11 | 在项目文件夹下新建文件夹:`/dist`、`/src`。 12 | 13 | 在`/src`文件夹下新建文件:`main.js`和`index.html`,`main.js`是打包的入口文件。 14 | 15 | ## 二、安装Webpack与Webpack-cli 16 | 17 | 通过以下命令安装: 18 | 19 | npm install -D webpack webpack-cli 20 | 21 | ## 三、配置最基础的Webpack 22 | 23 | 通过以下命令安装基础模块: 24 | 25 | npm install -D style-loader css-loader webpack-merge vue-loader babel-loader babel-preset-env babel-polyfill html-webpack-plugin clean-webpack-plugin webpack-dev-server vue-template-compiler vue-style-loader 26 | 27 | 在项目文件夹的根目录下新建三个文件:`webpack.dev.conf.js`、`webpack.base.conf.js`和`webpack.prod.conf.js`。 28 | 29 | 内容分别如下: 30 | 31 | **webpack.base.conf.js** 32 | 33 | const path = require('path'); 34 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 35 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 36 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 37 | 38 | module.exports = { 39 | entry: './src/main.js', 40 | output: { 41 | filename: 'bundle.[hash].js', 42 | path: path.join(__dirname, './dist') 43 | }, 44 | module: { 45 | rules: [ 46 | { 47 | test: /\.css$/, 48 | use: ['vue-style-loader', 'css-loader'] 49 | }, 50 | { 51 | test: /\.js$/, 52 | use: 'babel-loader', 53 | exclude: /node_modules/ 54 | } 55 | ] 56 | }, 57 | plugins: [ 58 | new VueLoaderPlugin(), 59 | new HtmlWebpackPlugin({ 60 | template: './src/index.html' 61 | }), 62 | new CleanWebpackPlugin(['dist']) 63 | ], 64 | resolve: { 65 | alias: { 66 | 'vue': 'vue/dist/vue.js' 67 | } 68 | } 69 | }; 70 | 71 | **webpack.dev.conf.js** 72 | 73 | const merge = require('webpack-merge'); 74 | const baseConfig = require('./webpack.base.conf.js'); 75 | 76 | module.exports = merge(baseConfig, { 77 | mode: 'development', 78 | devtool: 'inline-source-map', 79 | devServer: { 80 | port: 3000, 81 | contentBase: './dist' 82 | } 83 | }); 84 | 85 | **webpack.prod.conf.js** 86 | 87 | const merge = require('webpack-merge'); 88 | const baseConfig = require('./webpack.base.conf.js'); 89 | 90 | module.exports = merge(baseConfig, { 91 | mode: 'production' 92 | }); 93 | 94 | ## 四、配置`npm scripts` 95 | 96 | 修改`package.json`,在scripts属性中新加入以下命令: 97 | 98 | // package.json 99 | { 100 | "scripts": { 101 | "start": "webpack-dev-server --open --config webpack.dev.conf.js", 102 | "build": "webpack --config webpack.prod.conf.js" 103 | } 104 | } 105 | 106 | 现在可以测试webpack的基础配置: 107 | 108 | 修改页面文件,内容分别如下: 109 | 110 | **index.html** 111 | 112 | 113 | 114 | 115 | Vue & Webpack 116 | 117 | 118 |
119 |

Hello Vue & Webpack!

120 |
121 | 122 | 123 | 124 | **main.js** 125 | 126 | console.log('Yes!'); 127 | 128 | 输入命令`npm run start`和`npm run build`,分别查看效果。 129 | 130 | ## 五、配置Vue开发环境 131 | 132 | 通过以下命令安装Vue模块: 133 | 134 | npm install --save vue 135 | 136 | 在`webpack.base.conf.js`的`module.rules`属性下,在**js和css的规则之前**新加一条规则,修改之后,`webpack.base.conf.js`内容如下: 137 | 138 | const path = require('path'); 139 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 140 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 141 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 142 | 143 | module.exports = { 144 | entry: './src/main.js', 145 | output: { 146 | filename: 'bundle.[hash].js', 147 | path: path.join(__dirname, './dist') 148 | }, 149 | module: { 150 | rules: [ 151 | { 152 | test: /\.vue$/, 153 | use: 'vue-loader' 154 | }, 155 | { 156 | test: /\.css$/, 157 | use: ['vue-style-loader', 'css-loader'] 158 | }, 159 | { 160 | test: /\.js$/, 161 | use: 'babel-loader', 162 | exclude: /node_modules/ 163 | } 164 | ] 165 | }, 166 | plugins: [ 167 | new VueLoaderPlugin(), 168 | new HtmlWebpackPlugin({ 169 | template: './src/index.html' 170 | }), 171 | new CleanWebpackPlugin(['dist']) 172 | ], 173 | resolve: { 174 | alias: { 175 | 'vue': 'vue/dist/vue.js' 176 | } 177 | } 178 | }; 179 | 180 | 新建文件`App.vue,内容如下: 181 | 182 | 187 | 188 | 197 | 198 | 203 | 204 | 修改`main.js`中的内容,修改之后如下: 205 | 206 | import Vue from 'vue'; 207 | import App from './App.vue'; 208 | 209 | new Vue({ 210 | el: '#app', 211 | components: { App } 212 | }); 213 | 214 | 使用`npm run start`启动服务器,查看结果。 215 | 216 | # 由于未解决scoped CSS功能,该文章暂时搁置。 -------------------------------------------------------------------------------- /Notes/pics/20181014_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Notes/pics/20181014_1.png -------------------------------------------------------------------------------- /Notes/pics/npmrunbuild_20181201.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Notes/pics/npmrunbuild_20181201.png -------------------------------------------------------------------------------- /Notes/pics/npmrundev_20181201.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Notes/pics/npmrundev_20181201.png -------------------------------------------------------------------------------- /Notes/pics/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Notes/pics/page.png -------------------------------------------------------------------------------- /Notes/pics/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Notes/pics/run.png -------------------------------------------------------------------------------- /Notes/pics/创建项目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Notes/pics/创建项目.png -------------------------------------------------------------------------------- /Notes/pics/命名项目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Notes/pics/命名项目.png -------------------------------------------------------------------------------- /Notes/pics/新建包新建类.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Notes/pics/新建包新建类.png -------------------------------------------------------------------------------- /Notes/pics/深度截图_deepin-terminal_20180728130718.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Notes/pics/深度截图_deepin-terminal_20180728130718.png -------------------------------------------------------------------------------- /Notes/pics/深度截图_选择区域_20180728134235.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Notes/pics/深度截图_选择区域_20180728134235.png -------------------------------------------------------------------------------- /Notes/在Deepin中搭建GitLab.md: -------------------------------------------------------------------------------- 1 | # 在 Deepin 中搭建 GitLab 2 | 3 | 入职半个月了,一直在接受业务知识以及企业文化知识的培训,下周终于要开始上岗入手项目了。由于公司使用自己搭建的 GitLab 服务作为项目版本控制器,所以我决定学习一下 GitLab,由于这货跟 GitHub 都是基于 Git,所以代码管理方面没有啥区别,主要学习的是 GitLab 服务的搭建。 4 | 5 | ## 一、安装步骤 6 | 7 | 输入一下命令更新源,然后安装依赖 `openssh-server` 和 `ca-certificates`。 8 | 9 | sudo apt-get update 10 | sudo apt-get install -y openssh-server ca-certificates 11 | 12 | 如果需要邮箱提醒服务,还需要安装 `postfix`,当然你也可以安装其他邮件服务。 13 | 14 | 安装方法: 15 | 16 | sudo apt-get install -y postfix 17 | 18 | 如果没有配置过 postfix,那么安装过程中会跳出来配置选项。依次选择“Internet Site” => “确定” => 填入服务器域名 => “确定”。 19 | 20 | 安装 GitLab 包。 21 | 22 | [官网教程](https://about.gitlab.com/installation/#ubuntu)上面写的是使用 curl 下载一个 Shell 脚本,然后通过这个脚本安装 GitLab,但是实际上访问的时候,会提示 404 不存在的错误,所以此路不通。实际上访问 [GitLab 官网](https://about.gitlab.com/downloads/)的下载页面的时候,也是 404 不存在,不知道为什么官方人员还没有发现这个问题。 23 | 24 | 这里我使用的是手动安装,先去 GitLab 的 [GitLab 仓库](https://packages.gitlab.com/gitlab/gitlab-ce/)下载 deb 包(因为 Deepin 属于 Debian 系),然后通过 `dpkg` 命令进行安装。 25 | 26 | 这里最好选择社区版(gitlab-ce)。 27 | 28 | 下载之后可以有两种方法进行安装: 29 | 30 | * 1.命令行安装 31 | 32 | sudo dpkg -i gitlab-ce_xx.x.x-ce.x_amd64.deb 33 | 34 | 效果如图: 35 | ![截图](./pics/深度截图_deepin-terminal_20180728130718.png) 36 | 37 | * 2.右键 deb 包,然后在”打开方式“中选择“深度软件包管理器”就可以了,然后就可以开始安装。 38 | 39 | 安装之后开始配置 GitLab,使用 gedit 或者 vim 打开 `/etc/gitlab/gitlab.rb`。然后修改 `external_url` 的值为你的本机ip,比如“127.0.0.1”。 40 | 41 | 然后重新配置 GitLab: 42 | 43 | sudo gitlab-ctl reconfigure 44 | 45 | 配置完成后通过下面命令查看 GitLab 的服务状况: 46 | 47 | sudo gitlab-ctl status 48 | 49 | 如果结果如下,则代表开启成功: 50 | 51 | ok: run: alertmanager: (pid 9288) 1s 52 | ok: run: gitaly: (pid 9297) 0s 53 | ok: run: gitlab-monitor: (pid 9311) 0s 54 | ok: run: gitlab-workhorse: (pid 9314) 1s 55 | ok: run: logrotate: (pid 9331) 0s 56 | ok: run: nginx: (pid 9337) 0s 57 | ok: run: node-exporter: (pid 9347) 0s 58 | ok: run: postgres-exporter: (pid 9349) 1s 59 | ok: run: postgresql: (pid 9362) 0s 60 | ok: run: prometheus: (pid 9364) 0s 61 | ok: run: redis: (pid 9403) 0s 62 | ok: run: redis-exporter: (pid 9444) 0s 63 | ok: run: sidekiq: (pid 9460) 0s 64 | ok: run: unicorn: (pid 9467) 1s 65 | 66 | 如果结果如下,则代表开启失败,还需要做处理(后文会讲到): 67 | 68 | fail: alertmanager: runsv not running 69 | fail: gitaly: runsv not running 70 | fail: gitlab-monitor: runsv not running 71 | fail: gitlab-workhorse: runsv not running 72 | fail: logrotate: runsv not running 73 | fail: nginx: runsv not running 74 | fail: node-exporter: runsv not running 75 | fail: postgres-exporter: runsv not running 76 | fail: postgresql: runsv not running 77 | fail: prometheus: runsv not running 78 | fail: redis: runsv not running 79 | fail: redis-exporter: runsv not running 80 | fail: sidekiq: runsv not running 81 | fail: unicorn: runsv not running 82 | 83 | 如果成功开启了 GitLab 服务,接下来就可以打开你的浏览器,输入“127.0.0.1”或者你在局域网中的 ip 进入 GitLab 的服务界面了。局域网内的其它机器也可以通过你的局域网 IP 访问你机器上的 GitLab 服务,这样就形成了一个私有的 Git 版本管理。 84 | 85 | 成功后的服务界面: 86 | 87 | ![截图](./pics/深度截图_选择区域_20180728134235.png) 88 | 89 | ## 二、常见问题 90 | 91 | ### (1)服务开启失败 92 | 93 | 错误结果显示如下: 94 | 95 | fail: alertmanager: runsv not running 96 | fail: gitaly: runsv not running 97 | fail: gitlab-monitor: runsv not running 98 | fail: gitlab-workhorse: runsv not running 99 | fail: logrotate: runsv not running 100 | fail: nginx: runsv not running 101 | fail: node-exporter: runsv not running 102 | fail: postgres-exporter: runsv not running 103 | fail: postgresql: runsv not running 104 | fail: prometheus: runsv not running 105 | fail: redis: runsv not running 106 | fail: redis-exporter: runsv not running 107 | fail: sidekiq: runsv not running 108 | fail: unicorn: runsv not running 109 | 110 | 这说明 runsv 服务未开启,通过一下命令开启即可: 111 | 112 | systemctl start gitlab-runsvdir.service 113 | 114 | systemctl status gitlab-runsvdir.service 115 | 116 | sudo gitlab-ctl start 117 | 118 | ### (2)打开页面时显示 502 错误 119 | 120 | 界面提示“Whoops, GitLab is taking too much time to respond.”,这说明 GitLab 此时占用了过多的内存资源。你需要对服务器进行扩容,或者清理掉一些不需要的且占内存的服务。 121 | 122 | ### (3)GitLab 占用内存过高 123 | 124 | 这个问题基本没有啥办法,只能是扩展内存了,因为开启服务确确实实需要占据大量内存。 -------------------------------------------------------------------------------- /Notes/对于JavaScript排序算法的一些其他测试.md: -------------------------------------------------------------------------------- 1 | # 对于JavaScript实现排序算法的一些其他测试 2 | 3 | 在我的[上一篇文章](https://juejin.im/post/5b06ba5051882538953ac7e5)中,总结了六种排序算法的JavaScript实现,以及每种算法的思想,掘金上的许多盆友提出了一些好的想法或者优化的方法,这里针对这些方法做一些新的测试,以验证盆友们的说法。此外,非常感谢大家仔细阅读我的文章,你们的意见让我进步很大,同时意识到自身的许多不足,我还是会继续努力的。 4 | 5 | 首先还是说明一下,为了方便测试,专门写了一个随机数组生成函数,代码如下: 6 | 7 | exports.generateArray = function(length) { 8 | let arr = Array(length); 9 | for(let i=0; i a - b); 25 | console.timeEnd('RawSort'); 26 | 27 | 针对10000000(一千万)条数据进行从小到大的排序,耗时为11s: 28 | 29 | RawSort: 11069.679ms 30 | 31 | 针对10000(一万)条数据进行排序,耗时0.023秒: 32 | 33 | RawSort: 23.382ms 34 | 35 | **测试快排:** 36 | 37 | 代码如下: 38 | 39 | function quickSort(arr) { 40 | let left = 0, 41 | right = arr.length - 1; 42 | console.time('QuickSort'); 43 | main(arr, left, right); 44 | console.timeEnd('QuickSort'); 45 | return arr; 46 | function main(arr, left, right) { 47 | if(arr.length === 1) { 48 | return; 49 | } 50 | let index = partition(arr, left, right); 51 | if(left < index - 1) { 52 | main(arr, left, index - 1); 53 | } 54 | if(index < right) { 55 | main(arr, index, right); 56 | } 57 | } 58 | function partition(arr, left, right) { 59 | let pivot = arr[Math.floor((left + right) / 2)]; 60 | while(left <= right) { 61 | while(arr[left] < pivot) { 62 | left++; 63 | } 64 | while(arr[right] > pivot) { 65 | right--; 66 | } 67 | if(left <= right) { 68 | [arr[left], arr[right]] = [arr[right], arr[left]]; 69 | left++; 70 | right--; 71 | } 72 | } 73 | return left; 74 | } 75 | } 76 | 77 | console.log(quickSort(generateArray(10000000))); 78 | 79 | 针对10000000(一千万)条数据从小到大进行排序,耗时3.6s: 80 | 81 | QuickSort: 3632.852ms 82 | 83 | 针对10000(一万)条数据进行排序,耗时0.012s: 84 | 85 | QuickSort: 12.482ms 86 | 87 | 可以看出来不管是大数据量还是小数据量,都是手动实现的快排更加快速,但是优势不是很大。猜测原因可能是原生方法实际上还是要调用底层的快排来实现排序,比手动实现的快排多了一层封装导致速度有稍微下降。 88 | 89 | 针对原生方法,还去了解了一下,发现V8引擎是不稳定排序,它根据数组长度来选择排序算法的。当数组长度在10以下时,会采用插入排序;数组长度在10以上时,会采用快速排序,详情参考:[Array.prototype.sort()方法到底是如何排序的?](https://www.jianshu.com/p/bcb6a8f4c114)。 90 | 91 | 由以上结论可以发现,当前端要排序的数据量比较大(千万级,当然基本不太可能)时,最好还是使用手动实现的快排,速度会比较快。数据量不大时,完全可以使用`Array.prototype.sort`进行排序,毕竟一千万条数据的时间差也不超过两秒。虽然原生方法可以满足我们的要求,但是这也绝不是前端工程师不学习排序和算法的理由。 92 | 93 | ## 二、冒泡排序写错了? 94 | 95 | 上一篇文章中,我在第二层循环内也是写的从头到尾的遍历,但是[@雪之祈舞 96 | ](https://juejin.im/user/5a773713f265da4e710f344e)提出来,可以进行优化,因为没遍历一次数据,符合要求的数据就自动排序到末尾了(比如第一轮排序最大值就到了最后一个位置),所以在之后的遍历中,都可以忽略数组的后i项,这是正确的做法。接下来进行对比: 97 | 98 | **上一篇文章中的写法:** 99 | 100 | function bubbleSort(arr) { 101 | console.time('BubbleSort'); 102 | let len = arr.length; 103 | let count = 0; 104 | arr.forEach(() => { 105 | // 这里的i arr[i+1]) { 108 | let tmp = arr[i+1]; 109 | arr[i] = tmp; 110 | arr[i+1] = arr[i] 111 | } 112 | count++; 113 | } 114 | }); 115 | console.timeEnd('BubbleSort'); 116 | return count; 117 | } 118 | 119 | console.log(bubbleSort(generateArray(20000))); 120 | 121 | 测试排序20000(两万)条数据,耗时1.27s: 122 | 123 | BubbleSort: 1277.692ms 124 | 125 | 而改进代码后: 126 | 127 | function bubbleSort(arr) { 128 | console.time('BubbleSort'); 129 | let len = arr.length; 130 | let count = 0; 131 | arr.forEach(() => { 132 | for(let i=0; i arr[i+1]) { 134 | let tmp = arr[i+1]; 135 | arr[i] = tmp; 136 | arr[i+1] = arr[i] 137 | } 138 | count++; 139 | } 140 | }); 141 | console.timeEnd('BubbleSort'); 142 | return count; 143 | } 144 | 145 | console.log(bubbleSort(generateArray(20000))); 146 | 147 | 测试20000(两万)条数据,耗时0.64s: 148 | 149 | BubbleSort: 638.900ms 150 | 151 | 可以发现几乎缩短了一半的时间。 152 | 153 | ## 三、选择排序不够优秀! 154 | 155 | 这里确实是我懒的缘故,最优秀的做法是每次遍历找出最小值(或者最大值)保存到一个变量中,而我是每次发现小于(或大于)参考值的元素就会将它们进行对调,这样虽然时间复杂度没变,但是变量交换花费的时间较多,造成了性能下降,感谢用户[@ly578269725](https://juejin.im/user/5860e66261ff4b0063070a8d)的指导,由于我的懒惰可能会导致别人的误会,我表示抱歉。下面是实测结果: 156 | 157 | **懒惰的写法:** 158 | 159 | function selectionSort(arr) { 160 | console.time('SelectionSort'); 161 | let len = arr.length; 162 | let count = 0; 163 | arr.forEach((item, index) => { 164 | for(let i=index; i arr[index]) { 166 | [arr[index], arr[i]] = [arr[i], arr[index]]; 167 | } 168 | count++; 169 | } 170 | }); 171 | console.log(arr); 172 | console.timeEnd('SelectionSort'); 173 | return count; 174 | } 175 | 176 | console.log(selectionSort(generateArray(30000))); 177 | 178 | 对20000(两万)条数据进行排序,耗时5.2s: 179 | 180 | SelectionSort: 5227.515ms 181 | 182 | 而改良后: 183 | 184 | function selectionSort(arr) { 185 | console.time('SelectionSort'); 186 | let len = arr.length; 187 | let count = 0; 188 | arr.forEach((item, index) => { 189 | let min = index; 190 | for(let i=index; i { 23 | fn(e); 24 | }) 25 | } 26 | }; 27 | return { 28 | on, 29 | emit 30 | }; 31 | }(); -------------------------------------------------------------------------------- /PubSub/Listener.js: -------------------------------------------------------------------------------- 1 | const ee = require('./EventEmitter'); 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MyMemorandum 2 | 这是一个日程表仓库,我用它的issues功能来规划我的学习、工作和生活。它的仓库功能我也会用来存储我的一些练手项目。 3 | 4 | * NodeReptile 5 | * [爬虫项目地址](./NodeReptile) 6 | * [前端项目地址](https://github.com/zhongdeming428/ZLZPData) 7 | * [Demo](https://zhongdeming428.github.io/ZLZPData) 8 | * [Underscore源码阅读系列](./UnderscoreSourceCode/README.md) 9 | * [Docker & Docker-Compose 简单示例](https://github.com/zhongdeming428/docker-demo) 10 | 11 | * 《图解 HTTP》笔记系列 12 | * [图解 HTTP 笔记(一)——了解 Web 及网络基础](https://juejin.im/post/5d2f1d59f265da1bbd4bab11) 13 | * [图解 HTTP 笔记(二)——简单的 HTTP 协议](https://juejin.im/post/5d2f1df5f265da1bc4148955) 14 | * [图解 HTTP 笔记(三)——HTTP 报文内的 HTTP 信息](https://juejin.im/post/5d2f2067f265da1b9163c9d7) 15 | * [图解 HTTP 笔记(四)——HTTP 状态码](https://juejin.im/post/5d2f20b6f265da1bbe5e39dc) 16 | * [图解 HTTP 笔记(五)——Web 服务器](https://juejin.im/post/5d2f2148f265da1bbb041117) 17 | * [图解 HTTP 笔记(六)——HTTP 首部](https://juejin.im/post/5d309279f265da1bb13f6a22) 18 | * [图解 HTTP 笔记(七)——HTTPS](https://juejin.im/post/5d3092d06fb9a07ee63f9d74) 19 | * [图解 HTTP 笔记(八)——常见 Web 攻击技术](https://juejin.im/post/5d309327f265da1b672145d2) 20 | * [汇总篇](https://juejin.im/post/5d309be1e51d45599e019e7c) -------------------------------------------------------------------------------- /ReactCarousel/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"] 3 | } -------------------------------------------------------------------------------- /ReactCarousel/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /ReactCarousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /ReactCarousel/npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/usr/bin/node', '/usr/bin/npm', 'run', 'build' ] 3 | 2 info using npm@3.5.2 4 | 3 info using node@v8.10.0 5 | 4 verbose run-script [ 'prebuild', 'build', 'postbuild' ] 6 | 5 info lifecycle ReactCarousel@1.0.0~prebuild: ReactCarousel@1.0.0 7 | 6 silly lifecycle ReactCarousel@1.0.0~prebuild: no script for prebuild, continuing 8 | 7 info lifecycle ReactCarousel@1.0.0~build: ReactCarousel@1.0.0 9 | 8 verbose lifecycle ReactCarousel@1.0.0~build: unsafe-perm in lifecycle true 10 | 9 verbose lifecycle ReactCarousel@1.0.0~build: PATH: /usr/share/npm/bin/node-gyp-bin:/media/zhongdeming428/新加卷/我的资料/MyMemorandum/ReactCarousel/node_modules/.bin:/usr/lib/jdk1.8.0_191/bin:/usr/local/java//bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin 11 | 10 verbose lifecycle ReactCarousel@1.0.0~build: CWD: /media/zhongdeming428/新加卷/我的资料/MyMemorandum/ReactCarousel 12 | 11 silly lifecycle ReactCarousel@1.0.0~build: Args: [ '-c', 'webpack --config webpack.prod.conf.js' ] 13 | 12 info lifecycle ReactCarousel@1.0.0~build: Failed to exec build script 14 | 13 verbose stack Error: ReactCarousel@1.0.0 build: `webpack --config webpack.prod.conf.js` 15 | 13 verbose stack spawn ENOENT 16 | 13 verbose stack at ChildProcess. (/usr/share/npm/lib/utils/spawn.js:17:16) 17 | 13 verbose stack at emitTwo (events.js:126:13) 18 | 13 verbose stack at ChildProcess.emit (events.js:214:7) 19 | 13 verbose stack at maybeClose (internal/child_process.js:925:16) 20 | 13 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:209:5) 21 | 14 verbose pkgid ReactCarousel@1.0.0 22 | 15 verbose cwd /media/zhongdeming428/新加卷/我的资料/MyMemorandum/ReactCarousel 23 | 16 error Linux 4.15.0-43-generic 24 | 17 error argv "/usr/bin/node" "/usr/bin/npm" "run" "build" 25 | 18 error node v8.10.0 26 | 19 error npm v3.5.2 27 | 20 error file sh 28 | 21 error code ELIFECYCLE 29 | 22 error errno ENOENT 30 | 23 error syscall spawn 31 | 24 error ReactCarousel@1.0.0 build: `webpack --config webpack.prod.conf.js` 32 | 24 error spawn ENOENT 33 | 25 error Failed at the ReactCarousel@1.0.0 build script 'webpack --config webpack.prod.conf.js'. 34 | 25 error Make sure you have the latest version of node.js and npm installed. 35 | 25 error If you do, this is most likely a problem with the ReactCarousel package, 36 | 25 error not with npm itself. 37 | 25 error Tell the author that this fails on your system: 38 | 25 error webpack --config webpack.prod.conf.js 39 | 25 error You can get information on how to open an issue for this project with: 40 | 25 error npm bugs ReactCarousel 41 | 25 error Or if that isn't available, you can get their info via: 42 | 25 error npm owner ls ReactCarousel 43 | 25 error There is likely additional logging output above. 44 | 26 verbose exit [ 1, true ] 45 | -------------------------------------------------------------------------------- /ReactCarousel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactCarousel", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack-dev-server --open --config webpack.dev.conf.js", 9 | "build": "webpack --config webpack.prod.conf.js" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "babel-core": "^6.26.3", 16 | "babel-loader": "^7.1.5", 17 | "babel-preset-env": "^1.7.0", 18 | "babel-preset-react": "^6.24.1", 19 | "clean-webpack-plugin": "^0.1.19", 20 | "css-loader": "^1.0.0", 21 | "html-webpack-plugin": "^3.2.0", 22 | "style-loader": "^0.23.1", 23 | "svg-url-loader": "^2.3.2", 24 | "webpack": "^4.22.0", 25 | "webpack-cli": "^3.1.2", 26 | "webpack-dev-server": "^3.1.9", 27 | "webpack-merge": "^4.1.4" 28 | }, 29 | "dependencies": { 30 | "react": "^16.5.2", 31 | "react-dom": "^16.5.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ReactCarousel/src/assets/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ReactCarousel/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Carousel from './Carousel'; 3 | import Dialog from './Dialog'; 4 | import Message from './Message'; 5 | 6 | const options = { 7 | width: '66%', 8 | height: '20%', 9 | // imgs: ['http://i1.letvimg.com/lc05_qmt/201710/15/17/00/defzxn4eqx6m/169.jpg','http://img.mp.itc.cn/upload/20161020/8a8a49fcc094473b96460973aa3b59fe_th.png','http://www.sxdaily.com.cn/NMediaFile/2013/1105/SXRB201311051455000090242434446.jpg','http://news.xinhuanet.com/sports/2017-10/15/1121806284_15080784732771n.jpg'], 10 | // urls: ['http://i1.letvimg.com/lc05_qmt/201710/15/17/00/defzxn4eqx6m/169.jpg','http://img.mp.itc.cn/upload/20161020/8a8a49fcc094473b96460973aa3b59fe_th.png','http://www.sxdaily.com.cn/NMediaFile/2013/1105/SXRB201311051455000090242434446.jpg','http://news.xinhuanet.com/sports/2017-10/15/1121806284_15080784732771n.jpg'], 11 | imgs: [], 12 | urls: [], 13 | timeDuration: 1000, 14 | autoSwipe: true, 15 | showBtn: true 16 | }; 17 | 18 | class App extends React.Component { 19 | constructor(props) { 20 | super(); 21 | this.state = { 22 | visible: true 23 | }; 24 | } 25 | componentDidMount() { 26 | 27 | } 28 | render() { 29 | return
30 | {/* 31 | 38 | */} 39 | 40 | 41 | 42 | 43 | 44 |
45 | } 46 | } 47 | 48 | export default App; -------------------------------------------------------------------------------- /ReactCarousel/src/components/Carousel.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const goNext = function() { 4 | if (this.state.cur === this.state.imgs.length - 1) return; 5 | this.setState({ 6 | cur: this.state.cur + 1 7 | }); 8 | let timeDuration = this.state.timeDuration; 9 | if (this.state.cur === this.state.imgs.length - 2) { 10 | this.setState({ 11 | cur: 0, 12 | timeDuration: 0 13 | }) 14 | window.setTimeout(() => { 15 | this.setState({ 16 | timeDuration: timeDuration 17 | }); 18 | }, 100); 19 | } 20 | } 21 | 22 | const goPrev = function() { 23 | let timeDuration = this.state.timeDuration; 24 | if (this.state.cur === 0) { 25 | this.setState({ 26 | cur: this.state.imgs.length - 1, 27 | timeDuration: 0 28 | }); 29 | window.setTimeout(() => { 30 | this.setState({ 31 | timeDuration: timeDuration 32 | }); 33 | }, 100); 34 | return; 35 | } 36 | this.setState({ 37 | cur: this.state.cur - 1 38 | }); 39 | } 40 | 41 | class Carousel extends React.Component { 42 | constructor(props) { 43 | super(); 44 | this.props = props; 45 | this.state = { 46 | cur: 0, 47 | width: this.props.width, 48 | height: this.props.height, 49 | imgs: [this.props.imgs[this.props.imgs.length - 1], ...this.props.imgs, this.props.imgs[0]], 50 | urls: [this.props.urls[this.props.urls.length - 1], ...this.props.urls, this.props.urls[0]], 51 | showBtn: this.props.showBtn, 52 | autoSwipe: this.props.autoSwipe, 53 | timeDuration: this.props.timeDuration 54 | }; 55 | this.goNext = goNext.bind(this); 56 | this.goPrev = goPrev.bind(this); 57 | } 58 | render() { 59 | return
60 |
61 | { 62 | this.state.imgs.map((img, i) => ) 63 | } 64 |
65 | { 66 | this.state.showBtn ?
: null 67 | } 68 | { 69 | this.state.showBtn ?
: null 70 | } 71 |
72 | } 73 | } 74 | 75 | export default Carousel; -------------------------------------------------------------------------------- /ReactCarousel/src/components/Dialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Dialog extends React.Component { 4 | constructor() { 5 | super(); 6 | this.modalHandler = (e) => { 7 | this.setState({ 8 | data: e.detail.data, 9 | visible: true 10 | }); 11 | }; 12 | this.state = { 13 | data: { 14 | title: '', 15 | content: '' 16 | }, 17 | visible: false 18 | }; 19 | this.close = this.close.bind(this); 20 | this.modalClick = this.modalClick.bind(this); 21 | } 22 | render() { 23 | return
24 |
25 |
{ this.state.data.title }+
26 |
27 | { 28 | this.state.data.content 29 | } 30 |
31 |
32 |
33 | } 34 | componentDidMount() { 35 | document.addEventListener('modal', this.modalHandler); 36 | } 37 | componentWillUnmount() { 38 | document.removeEventListener('modal',this.modalHandler); 39 | } 40 | close() { 41 | this.setState({ 42 | visible: false 43 | }) 44 | } 45 | static show(data) { 46 | document.dispatchEvent(new CustomEvent('modal', { 47 | detail: { 48 | data 49 | } 50 | })); 51 | } 52 | modalClick() { 53 | if (this.props.clickModal2Hide) { 54 | this.close(); 55 | } 56 | } 57 | } 58 | 59 | export default Dialog; -------------------------------------------------------------------------------- /ReactCarousel/src/components/Message.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const commonStyle = { 4 | minWidth: '150px', 5 | minHeight: '48px', 6 | backgroundColor: '#fff', 7 | padding: '5px 10px', 8 | boxSizing: 'border-box', 9 | borderRadius: '10px', 10 | position: 'fixed', 11 | top: '10px', 12 | left: '50%', 13 | transform: 'translateX(-50%)', 14 | display: 'flex', 15 | justifyContent: 'space-around', 16 | alignItems: 'center', 17 | boxShadow: '0px 5px 3px #c3c3c3', 18 | animation: 'bounceIn .5s ease' 19 | }; 20 | 21 | const svgs = [ 22 | 23 | 24 | 26 | , 27 | 28 | 29 | 31 | , 32 | 33 | 34 | 36 | 38 | 40 | , 41 | 42 | 43 | 45 | 47 | 48 | ] 49 | 50 | class Message extends React.Component { 51 | constructor() { 52 | super(); 53 | this.state = { 54 | text: '', 55 | visible: false, 56 | timer: null, 57 | ico: '' 58 | }; 59 | this.changeStyle = this.changeStyle.bind(this); 60 | Message.warning = this.warning.bind(this); 61 | Message.success = this.success.bind(this); 62 | Message.info = this.info.bind(this); 63 | Message.error = this.error.bind(this); 64 | } 65 | render() { 66 | return this.state.visible ?
67 | { svgs[this.state.ico] } 68 | 69 | { this.state.text } 70 | 71 |
:null 72 | } 73 | warning(text) { 74 | this.changeStyle(text, 0); 75 | } 76 | success(text) { 77 | this.changeStyle(text, 1); 78 | } 79 | info(text) { 80 | this.changeStyle(text, 2); 81 | } 82 | error(text) { 83 | this.changeStyle(text, 3); 84 | } 85 | changeStyle(text, ico) { 86 | this.setState({ 87 | text, 88 | visible: false, 89 | ico 90 | }, () => { 91 | window.clearTimeout(this.state.timer); 92 | let timer = window.setTimeout(() => { 93 | this.setState({ 94 | visible: false 95 | }); 96 | }, 2000); 97 | this.setState({ 98 | timer, 99 | visible: true 100 | }); 101 | }); 102 | } 103 | } 104 | 105 | export default Message -------------------------------------------------------------------------------- /ReactCarousel/src/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .content { 7 | width: 100%; 8 | height: 100%; 9 | } 10 | .btn { 11 | position: absolute; 12 | width: 30px; 13 | height: 30px; 14 | border-radius: 15px; 15 | background-color: aliceblue; 16 | z-index: 99; 17 | top: 50%; 18 | transform: translateY(-50%); 19 | margin: 4px; 20 | } 21 | .next-btn { 22 | right: 0; 23 | } 24 | 25 | .modal { 26 | position: fixed; 27 | top: 0; 28 | left: 0; 29 | width: 100%; 30 | height: 100%; 31 | background-color: rgba(0, 0, 0, 0.6); 32 | z-index: 9998; 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | } 37 | .dialog { 38 | background-color: white; 39 | border-radius: 5px; 40 | overflow: hidden; 41 | } 42 | .dialog-title { 43 | box-sizing: border-box; 44 | width: 100%; 45 | height: 48px; 46 | padding: 0 16px; 47 | border-bottom: 0.5px solid #c3c3c3; 48 | display: flex; 49 | justify-content: space-between; 50 | align-items: center; 51 | } 52 | .dialog-close { 53 | font-size: 32px; 54 | color: #c3c3c3; 55 | cursor: pointer; 56 | transform: rotate(45deg); 57 | user-select: none; 58 | } 59 | .dialog-close:hover { 60 | color: red; 61 | } 62 | .dialog-content { 63 | min-width: 300px; 64 | } 65 | 66 | 67 | 68 | 69 | @keyframes bounceIn { 70 | from { 71 | opacity: 0; 72 | transform: translate(-50%, 50px); 73 | } 74 | to { 75 | opacity: 1; 76 | transform: translate(-50%, 0); 77 | } 78 | } -------------------------------------------------------------------------------- /ReactCarousel/src/main.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import React from 'react'; 3 | import App from './components/App'; 4 | import './main.css'; 5 | 6 | ReactDOM.render(, document.getElementById('app')); -------------------------------------------------------------------------------- /ReactCarousel/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const htmlWebpackPlugin = require('html-webpack-plugin'); 3 | const cleanWebpackPlugin = require('clean-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: path.resolve(__dirname, './src/main.js'), 7 | output: { 8 | filename: '[name].[hash].js', 9 | path: path.resolve(__dirname, './dist/') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.css$/, 15 | use: ['style-loader', 'css-loader'] 16 | }, 17 | { 18 | test: /\.js$/, 19 | use: 'babel-loader', 20 | exclude: /node_modules/ 21 | }, 22 | { 23 | test: /\.svg$/, 24 | use: 'svg-url-loader' 25 | } 26 | ] 27 | }, 28 | plugins: [ 29 | new htmlWebpackPlugin({ 30 | template: './index.html' 31 | }), 32 | new cleanWebpackPlugin(['dist']) 33 | ] 34 | }; -------------------------------------------------------------------------------- /ReactCarousel/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('./webpack.base.conf'); 2 | const merge = require('webpack-merge'); 3 | module.exports = merge(baseConfig, { 4 | mode: 'development', 5 | devtool: 'inline-source-map', 6 | devServer: { 7 | port: 9000, 8 | contentBase: './dist/' 9 | } 10 | }); -------------------------------------------------------------------------------- /ReactCarousel/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const baseConfig = require('./webpack.base.conf'); 3 | module.exports = merge(baseConfig, { 4 | mode: 'production' 5 | }); -------------------------------------------------------------------------------- /SortExamples/BubbleSort.js: -------------------------------------------------------------------------------- 1 | const generateArray = require('./Global').generateArray; 2 | 3 | function bubbleSort(arr) { 4 | console.time('BubbleSort'); 5 | let len = arr.length; 6 | let count = 0; 7 | arr.forEach((v, j) => { 8 | for(let i=0; i arr[i+1]) { 10 | let tmp = arr[i+1]; 11 | arr[i+1] = arr[i] 12 | arr[i] = tmp; 13 | // [arr[i], arr[i+1]] = [arr[i+1], arr[i]]; 14 | } 15 | count++; 16 | } 17 | }); 18 | console.timeEnd('BubbleSort'); 19 | console.log(arr); 20 | return count; 21 | } 22 | 23 | // The worst result for bubbleSort. 24 | // O(n^2). 25 | console.log(bubbleSort(generateArray(10000))); -------------------------------------------------------------------------------- /SortExamples/Global.js: -------------------------------------------------------------------------------- 1 | exports.generateArray = function(length) { 2 | let arr = Array(length); 3 | for(let i=0; i0; i--) { 7 | [arr[i], arr[0]] = [arr[0], arr[i]]; 8 | heapify(arr, i, 0); 9 | } 10 | console.timeEnd('HeapSort'); 11 | return arr; 12 | function buildHeap(arr) { 13 | let mid = Math.floor(arr.length / 2); 14 | for(let i=mid; i>=0; i--) { 15 | heapify(arr, arr.length, i); 16 | } 17 | return arr; 18 | } 19 | function heapify(arr, heapSize, i) { 20 | let left = 2 * i + 1, 21 | right = 2 * i + 2, 22 | largest = i; 23 | if(left < heapSize && arr[left] < arr[largest]) { 24 | largest = left; 25 | } 26 | if(right < heapSize && arr[right] < arr[largest]) { 27 | largest = right; 28 | } 29 | if(largest !== i) { 30 | [arr[largest], arr[i]] = [arr[i], arr[largest]]; 31 | arguments.callee(arr, heapSize, largest); 32 | } 33 | return arr; 34 | } 35 | } 36 | 37 | console.log(heapSort(generateArray(3))); -------------------------------------------------------------------------------- /SortExamples/InsertionSort.js: -------------------------------------------------------------------------------- 1 | const generateArray = require('./Global').generateArray; 2 | 3 | function insertionSort(arr) { 4 | console.time('InsertionSort'); 5 | let len = arr.length; 6 | let count = 0; 7 | for(let i=1; i 0 && arr[j-1] > tmp) { 11 | arr[j] = arr[j-1]; 12 | j--; 13 | count++; 14 | } 15 | arr[j] = tmp; 16 | } 17 | console.log(arr); 18 | console.timeEnd('InsertionSort'); 19 | return count; 20 | } 21 | 22 | console.log(insertionSort(generateArray(100000))); -------------------------------------------------------------------------------- /SortExamples/MergeSort.js: -------------------------------------------------------------------------------- 1 | const generateArray = require('./Global').generateArray; 2 | 3 | function mergeSort(arr) { 4 | console.time('MergeSort'); 5 | let count = 0; 6 | console.log(main(arr)); 7 | console.timeEnd('MergeSort'); 8 | return count; 9 | function main(arr) { 10 | if(arr.length === 1) return arr; 11 | 12 | let mid = Math.floor(arr.length/2); 13 | let left = arr.slice(0, mid); 14 | let right = arr.slice(mid); 15 | return merge(arguments.callee(left), arguments.callee(right)); 16 | } 17 | function merge(left, right) { 18 | let il = 0, 19 | rl = 0, 20 | result = []; 21 | while(il < left.length && rl < right.length) { 22 | count++; 23 | if(left[il] < right[rl]) { 24 | result.push(left[il++]); 25 | } 26 | else { 27 | result.push(right[rl++]); 28 | } 29 | } 30 | return result.concat(left.slice(il)).concat(right.slice(rl)); 31 | } 32 | } 33 | 34 | console.log(mergeSort(generateArray(10000000))); -------------------------------------------------------------------------------- /SortExamples/PythonImplementation/BubbleSort.py: -------------------------------------------------------------------------------- 1 | from Global import generateList 2 | 3 | def bubbleSort(l): 4 | for index, item in enumerate(l): 5 | for i in range(0, len(l) - index - 1): 6 | if l[i] > l[i + 1]: 7 | tmp = l[i] 8 | l[i] = l[i + 1] 9 | l[i + 1] = tmp 10 | return l 11 | 12 | print(bubbleSort(generateList(10000))) -------------------------------------------------------------------------------- /SortExamples/PythonImplementation/Global.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | def generateList(count): 4 | list = [] 5 | for index in range(count): 6 | list.append(random.uniform(0,1)) 7 | return list 8 | -------------------------------------------------------------------------------- /SortExamples/PythonImplementation/HeapSort.py: -------------------------------------------------------------------------------- 1 | from Global import generateList 2 | 3 | def heapSort(l): 4 | def heapify(l, iNode, heapSize): 5 | left = 2 * iNode + 1 6 | right = 2 * iNode + 2 7 | largest = iNode 8 | if left < heapSize and l[left] > l[largest]: 9 | largest = left 10 | if right l[largest]: 11 | largest = right 12 | if largest != iNode: 13 | tmp = l[iNode] 14 | l[iNode] = l[largest] 15 | l[largest] = tmp 16 | heapify(l, largest, heapSize) 17 | return l; 18 | def buildHeap(l, iNode, heapSize): 19 | for i in range(iNode, -1, -1): 20 | heapify(l, i, heapSize) 21 | return l 22 | def main(l): 23 | mid = int(len(l) / 2) - 1 24 | buildHeap(l, mid, len(l)) 25 | for i in range(len(l) - 1, 0, -1): 26 | tmp = l[0] 27 | l[0] = l[i] 28 | l[i] = tmp 29 | buildHeap(l, 0, i) 30 | return l 31 | 32 | return main(l) 33 | 34 | 35 | print(heapSort(generateList(1000))) -------------------------------------------------------------------------------- /SortExamples/PythonImplementation/InsertionSort.py: -------------------------------------------------------------------------------- 1 | from Global import generateList 2 | 3 | def insertionSort(l): 4 | for i in range(1, len(l)): 5 | j = i 6 | tmp = l[i] 7 | while l[j-1] > tmp and j - 1 >= 0: 8 | l[j] = l[j-1] 9 | j = j - 1 10 | l[j] = tmp 11 | return l 12 | 13 | print(insertionSort(generateList(10000))) -------------------------------------------------------------------------------- /SortExamples/PythonImplementation/MergeSort.py: -------------------------------------------------------------------------------- 1 | from Global import generateList 2 | 3 | def mergeSort(l): 4 | def merge(left, right): 5 | result = [] 6 | i, j = 0, 0 7 | ll, rl = len(left), len(right) 8 | while i < ll and j < rl: 9 | if left[i] < right[j]: 10 | result.append(left[i]) 11 | i = i + 1 12 | else : 13 | result.append(right[j]) 14 | j = j + 1 15 | result.extend(left[i:]) 16 | result.extend(right[j:]) 17 | return result 18 | def main(l): 19 | if len(l) == 1: 20 | return l 21 | mid = int(len(l) / 2) 22 | left = l[0:mid] 23 | right = l[mid:] 24 | return merge(main(left), main(right)) 25 | return main(l) 26 | 27 | print(mergeSort(generateList(10000))) -------------------------------------------------------------------------------- /SortExamples/PythonImplementation/QuickSort.py: -------------------------------------------------------------------------------- 1 | from Global import generateList 2 | 3 | def quickSort(l): 4 | def partition(l, left, right): 5 | mid = int((left + right) / 2) 6 | pivot = l[mid] 7 | while left <= right: 8 | while l[left] < pivot: 9 | left = left + 1 10 | while l[right] > pivot: 11 | right = right - 1 12 | if left <= right: 13 | tmp = l[left] 14 | l[left] = l[right] 15 | l[right] = tmp 16 | right = right - 1 17 | left = left + 1 18 | return left 19 | def main(l, left, right): 20 | if len(l) == 1: 21 | return l 22 | index = partition(l, left, right) 23 | if index - 1 > left: 24 | main(l, left, index - 1) 25 | if index < right: 26 | # 注意这里main函数的第一个参数不需要是截取后的list,那样的话下标会溢出。 27 | main(l, index, right) 28 | return l 29 | return main(l, 0, len(l) - 1) 30 | 31 | print(quickSort(generateList(100))) 32 | 33 | -------------------------------------------------------------------------------- /SortExamples/PythonImplementation/SelectionSort.py: -------------------------------------------------------------------------------- 1 | from Global import generateList 2 | 3 | def selectionSort(l): 4 | for index, val in enumerate(l): 5 | min = index 6 | for i in range(index + 1, len(l)): 7 | if l[i] < l[min]: 8 | min = i 9 | if min != index: 10 | tmp = l[min] 11 | l[min] = l[index] 12 | l[index] = tmp 13 | return l 14 | 15 | print(selectionSort(generateList(10))) -------------------------------------------------------------------------------- /SortExamples/PythonImplementation/__pycache__/Global.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/SortExamples/PythonImplementation/__pycache__/Global.cpython-35.pyc -------------------------------------------------------------------------------- /SortExamples/QuickSort.js: -------------------------------------------------------------------------------- 1 | const generateArray = require('./Global').generateArray; 2 | 3 | function quickSort(arr) { 4 | let left = 0, 5 | right = arr.length - 1; 6 | console.time('QuickSort'); 7 | main(arr, left, right); 8 | console.timeEnd('QuickSort'); 9 | return arr; 10 | function main(arr, left, right) { 11 | if(arr.length === 1) { 12 | return; 13 | } 14 | let index = partition(arr, left, right); 15 | if(left < index - 1) { 16 | main(arr, left, index - 1); 17 | } 18 | if(index < right) { 19 | main(arr, index, right); 20 | } 21 | } 22 | function partition(arr, left, right) { 23 | let pivot = arr[Math.floor((left + right) / 2)]; 24 | while(left <= right) { 25 | while(arr[left] < pivot) { 26 | left++; 27 | } 28 | while(arr[right] > pivot) { 29 | right--; 30 | } 31 | if(left <= right) { 32 | [arr[left], arr[right]] = [arr[right], arr[left]]; 33 | left++; 34 | right--; 35 | } 36 | } 37 | return left; 38 | } 39 | } 40 | 41 | // console.log(quickSort([2, 1, 4, 2, 3, 6, 9, 8])); 42 | console.log(quickSort(generateArray(10000000))); -------------------------------------------------------------------------------- /SortExamples/RawSort.js: -------------------------------------------------------------------------------- 1 | const generateArray = require('./Global').generateArray; 2 | 3 | const arr = generateArray(10000); 4 | console.time('RawSort'); 5 | arr.sort((a, b) => a - b); 6 | console.timeEnd('RawSort'); 7 | -------------------------------------------------------------------------------- /SortExamples/SelectionSort.js: -------------------------------------------------------------------------------- 1 | const generateArray = require('./Global').generateArray; 2 | 3 | function selectionSort(arr) { 4 | console.time('SelectionSort'); 5 | let len = arr.length; 6 | let count = 0; 7 | arr.forEach((item, index) => { 8 | for(let i=index; i arr[index]) { 10 | [arr[index], arr[i]] = [arr[i], arr[index]]; 11 | } 12 | count++; 13 | } 14 | }); 15 | console.log(arr); 16 | console.timeEnd('SelectionSort'); 17 | return count; 18 | } 19 | 20 | console.log(selectionSort(generateArray(20000))); -------------------------------------------------------------------------------- /SortExamples/_QuickSort.js: -------------------------------------------------------------------------------- 1 | const generateArray = require('./Global').generateArray; 2 | 3 | function quickSort(arr) { 4 | if(arr.length <= 1) return arr; 5 | return [ 6 | ...quickSort(arr.slice(1).filter(item => item < arr[0])), 7 | arr[0], 8 | ...quickSort(arr.slice(1).filter(item => item >= arr[0])) 9 | ]; 10 | } 11 | 12 | console.time('_QuickSort'); 13 | let res = quickSort(generateArray(1000000)); 14 | console.timeEnd('_QuickSort'); 15 | console.log(res); -------------------------------------------------------------------------------- /SortExamples/_SelectionSort.js: -------------------------------------------------------------------------------- 1 | const generateArray = require('./Global').generateArray; 2 | 3 | function selectionSort(arr) { 4 | console.time('SelectionSort'); 5 | let len = arr.length; 6 | let count = 0; 7 | arr.forEach((item, index) => { 8 | let min = index; 9 | for(let i=index; i= 0) 17 | continue; 18 | else { 19 | if(arr2.indexOf(arr1[i]) >= 0) 20 | result.push(arr1[i]); 21 | } 22 | } 23 | return result; 24 | } 25 | 26 | 以上代码实现了求两个数组交集的功能。 27 | 28 | 如果涉及到多个数组呢?那就是Underscore的实现方法了。 29 | 30 | 以下是Underscore的源码(附注释): 31 | 32 | // Produce an array that contains every item shared between all the 33 | // passed-in arrays. 34 | //获取传入的多个数组的交集,之所以只有一个形参,是因为该函数使用第一个数组参数作为基准。 35 | _.intersection = function (array) { 36 | //将要返回的结果数组。 37 | var result = []; 38 | //传入数组的个数。 39 | var argsLength = arguments.length; 40 | //遍历第一个数组参数。 41 | for (var i = 0, length = getLength(array); i < length; i++) { 42 | //当前项。 43 | var item = array[i]; 44 | //如果结果数组中已有该项,那么直接跳过当前循环,进入下一轮循环中。 45 | if (_.contains(result, item)) continue; 46 | var j; 47 | //从第二个参数开始,遍历每一个参数。 48 | for (j = 1; j < argsLength; j++) { 49 | //一旦有一个参数数组不包含item,就退出循环。 50 | if (!_.contains(arguments[j], item)) break; 51 | } 52 | //如果所有参数数组都包含item项,就把item放入result。 53 | if (j === argsLength) result.push(item); 54 | } 55 | return result; 56 | }; 57 | 58 | 可以看到该函数一次接受多个数组,但是只有一个形参(array),该参数表示接收到的第一个数组,Underscore使用它作为参考,遍历该数组,然后依次判断剩余参数数组是否包含当前项,如果全部包含则该项为交集元素,推入结果数组当中。 59 | 60 | ## 2 数组并集函数——union 61 | 62 | 数组的并集是指包含指定的多个数组的所有元素的数组,求多个数组的并集即为求一个包含所有数组的所有元素的数组。 63 | 64 | 这里最直接的实现方法就是遍历所有数组参数,然后针对数组的每一项,放入到结果数组中(如果已经存在于结果数组中那么久不再添加)。 65 | 66 | var union = function() { 67 | var arrays = arguments; 68 | var length = arguments.length; 69 | var result = []; 70 | var i; 71 | for(i = 0; i < length; i++) { 72 | var arr = arrays[i]; 73 | var arrLength = arrays[i].length; 74 | for(var j = 0; j < arrLength; j++) { 75 | if(result.indexOf(arr[j]) < 0) { 76 | result.push(arr[j]); 77 | } 78 | } 79 | } 80 | return result; 81 | } 82 | 83 | 在阅读Underscore源码的时候,感觉它的实现方法十分巧妙。 84 | 85 | Underscore中已经有了很多工具方法,所以可以拿来直接使用,比如[restArgs](https://github.com/zhongdeming428/MyMemorandum/blob/master/UnderscoreSourceCode/notes/%E7%90%86%E8%A7%A3Underscore%E4%B8%AD%E7%9A%84restArgs%E5%87%BD%E6%95%B0.md)、[flatten](https://github.com/zhongdeming428/MyMemorandum/blob/master/UnderscoreSourceCode/notes/%E7%90%86%E8%A7%A3Underscore%E4%B8%AD%E7%9A%84flatten%E5%87%BD%E6%95%B0.md)、[uniq](https://github.com/zhongdeming428/MyMemorandum/blob/master/UnderscoreSourceCode/notes/%E7%90%86%E8%A7%A3Underscore%E4%B8%AD%E7%9A%84uniq%E5%87%BD%E6%95%B0.md)。为什么强调这几个方法呢?因为使用这几个方法就可以实现数组求并集。 86 | 87 | 我们的union方法是接受多个数组作为参数的,而restArgs可以把多个数组参数合并到一个数组中作为参数;然后通过flatten函数,我们可以把得到的这个数组参数展开,展开之后得到的数组就是包含所有数组参数的所有元素的一个数组了,但是这个数组中有冗余项,我们必须对其进行去重;这时候使用我们的uniq工具函数就可以对其进行去重了。 88 | 89 | 经过这三个函数的处理,我们得到的数组就是多个数组参数的并集! 90 | 91 | Underscore源码: 92 | 93 | // Produce an array that contains the union: each distinct element from all of 94 | // the passed-in arrays. 95 | _.union = restArgs(function (arrays) { 96 | return _.uniq(flatten(arrays, true, true)); 97 | }); 98 | 99 | 这样的实现是不是很简介大气? 100 | 101 | ## 3 数组差集函数——difference 102 | 103 | 数组的差集是指由数组A中所有不属于数组B的元素所组成的一个数组。 104 | 105 | 直接的实现方法就是遍历前者,然后判断每个元素是否属于后者,如果不属于,那么就推入结果数组。 106 | 107 | 简单实现: 108 | 109 | var difference = function(arr1, arr2) { 110 | var length = arr1.length; 111 | var i; 112 | var result = []; 113 | for(i = 0; i < length; i++) { 114 | if(arr2.indexOf(arr1[i]) < 0) { 115 | result.push(arr1[i]); 116 | } 117 | } 118 | return result; 119 | } 120 | 121 | Underscore的实现(附注释): 122 | 123 | // Take the difference between one array and a number of other arrays. 124 | // Only the elements present in just the first array will remain. 125 | //数组求差集函数。 126 | //通过restArgs函数把第二个数组开始的所有参数数组合并到一个数组。 127 | _.difference = restArgs(function (array, rest) { 128 | //使用flatten展开rest数组。 129 | rest = flatten(rest, true, true); 130 | //使用filter函数过滤array数组达到求差集的目的,判断条件就是value是否属于rest。 131 | return _.filter(array, function (value) { 132 | return !_.contains(rest, value); 133 | }); 134 | }); 135 | 136 | 更多Underscore源码解析:[GitHub](https://github.com/zhongdeming428/MyMemorandum/tree/master/UnderscoreSourceCode) 137 | -------------------------------------------------------------------------------- /UnderscoreSourceCode/notes/理解Underscore中的_.template函数.md: -------------------------------------------------------------------------------- 1 | # 理解Underscore中的_.template函数 2 | 3 | Underscore中提供了_.template函数实现模板引擎功能,它可以将JSON数据源中的数据对应的填充到提供的字符串中去,类似于服务端渲染的模板引擎。接下来看一下Underscore是如何实现模板引擎的。 4 | 5 | ## 工具准备 6 | 7 | 首先是_.template函数的配置项,Underscore源码中配置了默认的配置项: 8 | 9 | _.templateSettings = { 10 | // 执行JavaScript语句,并将结果插入。 11 | evaluate: /<%([\s\S]+?)%>/g, 12 | // 插入变量的值。 13 | interpolate: /<%=([\s\S]+?)%>/g, 14 | // 插入变量的值,并进行HTML转义。 15 | escape: /<%-([\s\S]+?)%>/g 16 | }; 17 | 18 | 每一项的意思都写在了注释中,修改不同项的正则表达式,可以修改你传入的字符串模板中的占位符。默认的占位符: 19 | 20 | * <% %> : 表示执行JavaScript语句。 21 | * <%= %> : 表示插入变量的值。 22 | * <%- %> : 表示对插入值进行HTML转义后再插入。 23 | 24 | 源码中还写了一个不可能匹配的正则表达式: 25 | 26 | // 一个不可能有匹配项的正则表达式。 27 | var noMatch = /(.)^/; 28 | 29 | 一个JSON(类字典),用于映射转义字符到转义后的字符: 30 | 31 | var escapes = { 32 | "'": "'", 33 | '\\': '\\', 34 | '\r': 'r', 35 | '\n': 'n', 36 | '\u2028': 'u2028', 37 | '\u2029': 'u2029' 38 | }; 39 | 40 | 以及一个匹配转义字符的正则表达式和一个转义函数。 41 | 42 | // 匹配需要转义字符的正则表达式。 43 | var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; 44 | 45 | // 返回字符对应的转义后的字符。 46 | var escapeChar = function (match) { 47 | return '\\' + escapes[match]; 48 | }; 49 | 50 | 接下来会使用到这些变量。 51 | 52 | ## 实现_.template 53 | 54 | 实现原理大致如下: 55 | 56 | * 使用正则匹配传入字符串中的所有占位符,并读取占位符中的变量名或JavaScript语句。 57 | * 构造一个字符串,用于定义渲染函数,把读取到的变量名或JavaScript语句嵌入到字符串中,使得在使用渲染函数时,变量会被具体的值替代。如果变量名所代表的值需要转义,则还需使用`_.escape`函数对其进行转义,同样写入字符串中。 58 | * 通过步骤二构造的字符串构造渲染函数,定义一个闭包,闭包负责调用这个渲染函数,返回闭包即可。 59 | 60 | 如何匹配传入字符串中的占位符是一个问题,因为一个字符串中可能包含多种或者多个占位符,这里用了`String.prototype.replace`方法的一种不常用的方法。 61 | 62 | 通常,我们至少用`String.prototype.replace`的简单用法,即第一个参数为要替换的字符串,第二个参数为用于替换它的新字符串,该函数返回替换结果,不改变原字符串。如果要将字符串中的指定字符串**全部替换**,那么第一个参数应该传入正则表达式,并且采用全局匹配模式`g`。很多人不知道`String.prototype.replace`还有更加灵活的第三种用法,即第二个参数传递为一个函数,这个函数的返回结果作为替代指定字符串的新字符串,且至少接收一个参数: 63 | 64 | * 如果对于第一个参数没有匹配结果,那么回调函数只接受一个参数,即为原字符串。 65 | * 如果有匹配结果,那么接收至少三个参数,依次为匹配到的字符串、匹配字符串的开始索引和原字符串。 66 | 67 | 可以打开浏览器输入如下代码回车进行验证: 68 | 69 | let str = 'abc'; 70 | str.replace(/a/g, function() { 71 | console.log(arguments); 72 | }); 73 | 74 | 如果有多个匹配结果,那么回调函数会被调用多次: 75 | 76 | let str = 'abcabc'; 77 | str.replace(/a/g, function() { 78 | console.log(arguments); 79 | }); 80 | 81 | 回车之后,在控制台可以看到两次打印结果。那么就相当于是进行了一个循环操作,这个循环会遍历匹配到的每一项,这样就可以对于匹配到的占位符进行适当的操作了。此外,`String.prototype.replace`函数还有一个很优秀的特性,如果第一个参数传递为正则表达式并且含有多个捕获组(及括号),那么每个捕获组所捕获的字符串都会作为参数传递给回调函数,所以说回调函数至少接收一个参数。其参数个数可以取决于正则表达式中的捕获组个数。验证以下代码: 82 | 83 | let str = 'abcabcabc'; 84 | str.replace(/(ab)|(c)/g, function() { 85 | console.log(arguments); 86 | }); 87 | 88 | 可以发现回调所接受的参数个数即为`3 + 正则中的捕获组个数`。基于这个特性,Underscore作者对字符串进行了很好的处理。 89 | 90 | 实现代码如下: 91 | 92 | _.template = function (text, settings, oldSettings) { 93 | // 如果第二个参数为null或undefined。。等,那么使用oldSettings作为settings。 94 | if (!settings && oldSettings) settings = oldSettings; 95 | // 如果三个参数齐整,那么使用整合后的对象作为settings。 96 | settings = _.defaults({}, settings, _.templateSettings); 97 | 98 | // Combine delimiters into one regular expression via alternation. 99 | // 匹配占位符的正则表达式,将配置项中的三个正则合并,每一个正则都是一个捕获组,如果配置项没有包含的话,就默认不匹配任何值。 100 | var matcher = RegExp([ 101 | (settings.escape || noMatch).source, 102 | (settings.interpolate || noMatch).source, 103 | (settings.evaluate || noMatch).source 104 | ].join('|') + '|$', 'g'); 105 | 106 | // Compile the template source, escaping string literals appropriately. 107 | var index = 0; 108 | var source = "__p+='"; 109 | // function回调作为string.replace的第二个参数会传递至少三个参数,如果有多余捕获的话,也会被作为参数依次传入。 110 | // string.replace只会返回替换之后的字符串,但是不会对原字符串进行修改,下面的操作实际上没有修改text,只是借用string.replace的回调函数完成新函数的构建。 111 | text.replace(matcher, function (match, escape, interpolate, evaluate, offset) { 112 | // 截取没有占位符的字符片段,并且转义其中需要转义的字符。 113 | source += text.slice(index, offset).replace(escapeRegExp, escapeChar); 114 | // 跳过占位符,为下一次截取做准备。 115 | index = offset + match.length; 116 | 117 | // 转义符的位置使用匹配到的占位符中的变量的值替代,构造一个函数的内容。 118 | if (escape) { 119 | // 不为空时将转义后的字符串附加到source。 120 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 121 | } else if (interpolate) { 122 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 123 | } else if (evaluate) { 124 | // 由于是直接执行语句,所以直接把evaluate字符串添加到构造函数的字符串中去就好。 125 | source += "';\n" + evaluate + "\n__p+='"; 126 | } 127 | 128 | // Adobe VMs need the match returned to produce the correct offset. 129 | // 正常来说没有修改原字符串text,所以不返回值没有关系,但是这里返回了原匹配项, 130 | // 根据注释的意思,可能是为了防止特殊环境下能够有一个正常的offset偏移量。 131 | return match; 132 | }); 133 | source += "';\n"; 134 | // source拼凑出了一个函数定义的所有内容,为后面使用Function构造函数做准备。 135 | 136 | // If a variable is not specified, place data values in local scope. 137 | // 指定作用域,以取得传入对象数据的所有属性。 138 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 139 | 140 | source = "var __t,__p='',__j=Array.prototype.join," + 141 | "print=function(){__p+=__j.call(arguments,'');};\n" + 142 | source + 'return __p;\n'; 143 | 144 | var render; 145 | try { 146 | // 通过new Function()形式构造函数对象。 147 | // new Function(param1, ..., paramN, funcBody) 148 | render = new Function(settings.variable || 'obj', '_', source); 149 | } catch (e) { 150 | e.source = source; 151 | throw e; 152 | } 153 | 154 | var template = function (data) { 155 | return render.call(this, data, _); 156 | }; 157 | 158 | // Provide the compiled source as a convenience for precompilation. 159 | var argument = settings.variable || 'obj'; 160 | // 为template函数添加source属性以便于进行预编译,以便于发现不可重现的错误。 161 | template.source = 'function(' + argument + '){\n' + source + '}'; 162 | 163 | return template; 164 | }; 165 | 166 | 具体注释都已经写在代码中。 167 | 168 | 可以发现,在`_.template`函数中,将配置项中的三个正则合并成了一个,并且每一个正则都构成了一个捕获组,这样回调就会接受6个参数,最后一个参数被作者忽略了。在回调中,作者分别对三种匹配项进行了处理,然后拼接到了source字符串中。 169 | 170 | 构造完source字符串之后,作者就使用了`new Function()`的语法构造了一个`render`函数,通过研究source字符串可以发现,`render`实际上相当于函数: 171 | 172 | function render(settings.variable || 'obj', _) { 173 | var __t, 174 | __p = '', 175 | __j = Array.prototype.join, 176 | print = function(){ 177 | __p+=__j.call(arguments,''); 178 | }; 179 | // 如果配置了variable属性就不需要使用with块了。 180 | with(obj || {}) { 181 | __p += '...' + ((__t=(" + /*需要转义的变量*/ + "))==null?'':_.escape(__t)) + ((__t=(" + /*变量*/ + "))==null?'':__t) + ... ; 182 | /*需要执行的JavaScript字符串*/; 183 | } 184 | return __p; 185 | } 186 | 187 | 构造完这个render函数,基本的工作也就完成了。 188 | 189 | 这里比较巧妙的点在于作者通过`String.prototype.replace`函数构造函数字符串,对于每一个特定的模板定制了一个特定的函数,这个函数会构造一个对应于模板的字符串,将变量填充进去,所以返回的字符串即为我们想要的字符串。 -------------------------------------------------------------------------------- /UnderscoreSourceCode/notes/理解Underscore中的bind函数.md: -------------------------------------------------------------------------------- 1 | # 理解Underscore中的_.bind函数 2 | 最近一直忙于实习以及毕业设计的事情,所以上周阅读源码之后本周就一直没有进展。今天在写完开题报告之后又抽空看了一眼Underscore源码,发现上次没有看明白的一个函数忽然就豁然开朗了,于是赶紧写下了这篇笔记。 3 | 4 | 关于如何绑定函数this指向,一直是JavaScript中的高频话题,面试时考官也喜欢问如何绑定函数this的指向,以及如何试现一个bind函数,今天我们就从Underscore源码来学习如何实现一个bind函数。 5 | 6 | ## 预备知识 7 | 8 | 在学习源码之前,我们最好先了解一下函数中this的指向,我在这个系列之前有写过一篇文章,比较完善的总结了一下JavaScript函数中this的指向问题,详情参见:[博客园](http://www.cnblogs.com/DM428/p/7515818.html)。 9 | 10 | 另外,在学习`_.bind`函数之前,我们需要先了解一下Underscore中的重要工具函数——`restArgs`。就在我的上一篇文章中就有介绍到:[理解Underscore中的restArgs函数](https://github.com/zhongdeming428/MyMemorandum/blob/master/UnderscoreSourceCode/notes/%E7%90%86%E8%A7%A3Underscore%E4%B8%AD%E7%9A%84restArgs%E5%87%BD%E6%95%B0.md)。 11 | 12 | ## 工具函数——executeBound 13 | 14 | 在学习`_.bind`函数之前,我们先来看一下Underscore中的另一个工具函数——executeBound。因为这是一个重要的工具函数,涉及到bind的实现。 15 | 16 | executeBound源码(附注释): 17 | 18 | // Determines whether to execute a function as a constructor 19 | // or a normal function with the provided arguments. 20 | //执行绑定函数,决定是否把一个函数作为构造函数或者普通函数调用。 21 | var executeBound = function (sourceFunc, boundFunc, context, callingContext, args) { 22 | //如果callingContext不是boundFunc的一个实例,则把sourceFunc作为普通函数调用。 23 | if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); 24 | //否则把sourceFunc作为构造函数调用。 25 | //baseCreate函数用于构造一个对象,继承指定的原型。 26 | //此处self就是继承了sourceFunc.prototype原型的一个空白对象。 27 | var self = baseCreate(sourceFunc.prototype); 28 | var result = sourceFunc.apply(self, args); 29 | //这里之所以要判断一下是因为如果构造函数有返回值并且返回值是一个对象,那么新构造的对象就会是返回值,而非this所指向的值。 30 | if (_.isObject(result)) return result; 31 | //只有在构造函数没有返回值或者返回值时非对象时,才返回this所指向的值。 32 | return self; 33 | }; 34 | 35 | 首先我们先看为什么在executeBound函数结尾需要判断一下result,原因已经写明在注释里,请大家一定仔细注意! 36 | 举一个帮助理解的例子: 37 | 38 | var A = function() { 39 | this.name = 'A'; 40 | return {}; 41 | } 42 | var B = function() { 43 | this.name = 'B'; 44 | } 45 | var C = function() { 46 | this.name = 'C'; 47 | return 'C'; 48 | } 49 | var a = new A(); 50 | var b = new B(); 51 | var c = new C(); 52 | 53 | 在浏览器中输出a、b、c,看看你会发现什么?然后再来仔细思考代码中注释的部分吧。 54 | 55 | 其次回到我们这篇文章的重点,这个函数的功能非常好理解,就是根据实际情况来决定是否把一个函数(sourceFunc)当做构造函数或者普通函数来调用。这个根据的条件就是看callingContext参数是否是boundFunc函数的一个实例。如果callingContext是boundFunc的一个实例,那么就把sourceFunc当做一个构造函数来调用,否则就当做一个普通函数来调用,使用Function.prototype.apply来改变sourceFunc中this的指向。 56 | 57 | 单独开这个函数可能会使我们变得疑惑,为什么要这么做呢?这个callingContext跟boundFunc是什么关系?为什么要根据这两个参数的关系来决定是否以构造函数的形式调用sourceFunc。 58 | 59 | 接下来我们根据实际情景来解析这段源码。 60 | 61 | 在Underscore源码中,使用`ctrl + F`键查找`executeBound`字段,共有三处结果。其中一处是上方源码所示的executeBound函数声明。另外两处是调用,其形式都如下所示: 62 | 63 | var bound = restArgs(function (callArgs) { 64 | return executeBound(func, bound, context, this, args.concat(callArgs)); 65 | }); 66 | 67 | 可以注意到实际调用时,第四个参数(callingContext)都是this,代表当前bound函数执行作用域,而第二个参数是bound自身,这样的写法着实奇怪。 68 | 69 | 其实考虑到我们的目的也就不难理解为什么这么写了,因为当我们把bound函数当做构造函数调用时,构造函数(此时也就是bound函数)内部的this会指向新构造的对象,而这个由bound函数新构造的对象自然就是bound函数的一个实例了,此时就会把sourceFunc当做构造函数调用。 70 | 71 | 接下来我们再看`_.bind`函数,一起深入理解该函数的同时,顺便理解一下executeBound函数中为什么要根据callingContext和boundFunc的关系来确定sourceFunc的调用方式。 72 | 73 | 74 | ## 理解_.bind函数 75 | 76 | 我们先看`_.bind`函数的源码(附注释): 77 | 78 | // Create a function bound to a given object (assigning `this`, and arguments, 79 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if 80 | // available. 81 | //将指定函数中的this绑定到指定上下文中,并传递一些参数作为默认参数。 82 | //其中args是默认参数,以后调用新的func时无需再次传递这些参数。 83 | _.bind = restArgs(function (func, context, args) { 84 | if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); 85 | var bound = restArgs(function (callArgs) { 86 | return executeBound(func, bound, context, this, args.concat(callArgs)); 87 | }); 88 | return bound; 89 | }); 90 | 91 | 我们看到在`_.bind`函数的内部定义了一个bound函数,然后返回了这个函数,即为闭包。闭包的好处即在于内部的函数是私有函数,可以访问外部函数作用域,在内部函数调用之前,整个外部函数的作用域都是存在且对于内部函数而言是可访问的。在restArgs函数的参数(即匿名函数)中并没有处理如何调用func,因为我们要根据情况来决定。当我们使用`_.bind`函数绑定一个函数的this时,会返回bound函数作为新的func函数,而bound函数会根据其调用的方式,来决定如何调用func,而此处的闭包能够保证在bound执行之前,func是一直存在的。当我们使用new来操作bound函数构造新的对象时,bound内的this指向新构造的对象(即为bound的新实例),executeBound函数内部就会把func当做构造函数来调用;如果以普通函数形式调用bound,那么内部的this会指向外部调用bound函数时的作用域,自然就不是bound的一个实例了,这就是为什么会给executeBound第四个参数传递this的原因。 92 | 93 | 口说无凭,我们自己写个代码探究一下闭包内部函数中this的指向问题: 94 | 95 | var test = function() { 96 | var bound = function() { 97 | this.name = 'bound'; 98 | console.log(this); 99 | } 100 | return bound; 101 | } 102 | var Bound = test(); 103 | var b = new Bound(); 104 | var b = Bound(); 105 | 106 | //bound { name: 'bound' } 107 | //window 108 | 109 | 大家可以将上面这段代码拷贝到浏览器控制台试一试,看看结果是不是跟上面的注释一样。 110 | 111 | ## 实现一个自己的bind函数 112 | 113 | 通过上面的学习,我们知道了原来bind函数还要考虑到特殊情况——被绑定过this的函数作为构造函数调用时的情况。 114 | 接下来我们手动实现一个简单的bind函数: 115 | 116 | var _bind = function(func, context) { 117 | var bound = function() { 118 | if(this instanceof bound) { 119 | var obj = new Object(); 120 | obj.prototype = func.prototype; 121 | obj.prototype.constructor = func; 122 | var res = func.call(obj); 123 | if(typeof res == 'function' || typeof res == 'object' && !!res) 124 | return res; 125 | else 126 | return obj 127 | } 128 | else { 129 | return func.call(context); 130 | } 131 | }; 132 | return bound; 133 | } 134 | 135 | 在阅读这篇文章之前,你会如何实现一个bind函数呢? 136 | 137 | 更多Underscore源码解读:[GitHub](https://github.com/zhongdeming428/MyMemorandum/tree/master/UnderscoreSourceCode) -------------------------------------------------------------------------------- /UnderscoreSourceCode/notes/理解Underscore中的flatten函数.md: -------------------------------------------------------------------------------- 1 | # 理解Underscore中的flatten函数 2 | 3 | 最近是在所在实习公司的第一个sprint,有个朋友又请假了,所以任务比较重,一直这么久都没怎么更新了,这个周末赖了个床,纠结了一会儿决定还是继续写这个系列,虽然比较乏味,但是学到的东西还是很多的。 4 | 5 | 之前主要是针对函数处理部分的API做解读,经过那些天的努力,基本已经解读完了,现在把重点移到数组上。对于数组处理API的解读,从这篇文章开始。 6 | 7 | flatten是一个很基础的函数,在Underscore中也算是一个工具函数,为了方便以后的讲解,今天先阅读flatten函数的源码。 8 | 9 | 首先,我们带着问题来阅读源码,如果你参加面试,考官让你手写一个展开数组的函数,你会怎么写? 10 | 11 | ## 实现一个flatten函数 12 | 13 | 我们接受的参数应该是一个数组,我们可以使用一个叫array的变量表示它,它的返回值应该是一个数组,使用result表示: 14 | 15 | function flatten(array) { 16 | var result = []; 17 | // ... 展开代码 18 | return result 19 | } 20 | 21 | 然后我们应该对传入的数组进行类型验证,如果不是数组,我们应该抛出一个类型异常: 22 | 23 | function flatten(array) { 24 | var result = []; 25 | if(Object.prototype.toString.call(array) !== '[object Array]') 26 | throw new TypeError('Please pass a array-type object as parameter to flatten function'); 27 | else { 28 | // ... 展开代码 29 | } 30 | return result 31 | } 32 | 33 | 这样就可以保证我们接收到的参数是一个数组,接下来我们应该遍历array参数,对于它的每一项,如果不是数组,我们就将其添加到result中,否则继续展开: 34 | 35 | function flatten(array) { 36 | var result = []; 37 | if(Object.prototype.toString.call(array) !== '[object Array]') 38 | throw new TypeError('Please pass a array-type object as parameter to flatten function'); 39 | else { 40 | for(var i = 0; i < array.length; i++) { 41 | if(Object.prototype.toString.call(array[i]) === '[object Array]') { 42 | // ... 继续展开。 43 | } 44 | else { 45 | result.push(array[i]); 46 | } 47 | } 48 | } 49 | return result 50 | } 51 | 52 | 当数组中的项还是一个数组时,我们应当如何展开呢? 53 | 由于不确定到底是嵌套了多少层数组,所以最好是使用递归来展开,但是有新的问题,我们的flatten函数返回一个数组结果,但是我们如何把递归结果返回给我们的result呢,是使用concat方法还是怎样? 54 | 55 | 由于函数中对象类型的参数是引用传值,所以我们可以把result传递给flatten自身,使其直接修改result即可: 56 | 57 | function flatten(array, result) { 58 | var result = result || []; 59 | if(Object.prototype.toString.call(array) !== '[object Array]') 60 | throw new TypeError('Please pass a array-type object as parameter to flatten function'); 61 | else { 62 | for(var i = 0; i < array.length; i++) { 63 | if(Object.prototype.toString.call(array[i]) === '[object Array]') { 64 | // ... 递归展开。 65 | arguments.callee(array[i], result); 66 | } 67 | else { 68 | result.push(array[i]); 69 | } 70 | } 71 | } 72 | return result 73 | } 74 | 75 | 以上函数,就基本实现了flatten的功能,再美化一下: 76 | 77 | var flatten = function(array, result) { 78 | var result = result || []; 79 | var length = array.length; 80 | var toString = Object.prototype.toString; 81 | var type = toString.call(array); 82 | if(type !== '[object Array]') 83 | throw new TypeError('The parameter you passed is not a array'); 84 | else { 85 | for(var i = 0; i < length; i++) { 86 | if(toString.call(array[i]) !== '[object Array]') { 87 | result.push(array[i]); 88 | } 89 | else { 90 | arguments.callee(array[i], result); 91 | } 92 | } 93 | } 94 | return result; 95 | } 96 | 97 | 大家可以把上面这段代码拷贝到控制台进行实验。 98 | 99 | ## Underscore中的flatten函数 100 | 101 | 通过我们自己亲手实现一个flatten函数,阅读Underscore源码就变得简单了。 102 | 下面是Underscore中flatten函数的源码(附注释): 103 | 104 | var flatten = function (input, shallow, strict, output) { 105 | output = output || []; 106 | var idx = output.length; 107 | //遍历input参数。 108 | for (var i = 0, length = getLength(input); i < length; i++) { 109 | var value = input[i]; 110 | if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { 111 | // Flatten current level of array or arguments object. 112 | //如果input数组的元素是数组或者类数组对象,根据是否shallow来展开,如果shallow为true,那么只展开一级。 113 | if (shallow) { 114 | var j = 0, len = value.length; 115 | while (j < len) output[idx++] = value[j++]; 116 | } else { 117 | //如果shallow为false,那么递归展开所有层级。 118 | flatten(value, shallow, strict, output); 119 | idx = output.length; 120 | } 121 | } else if (!strict) { 122 | //如果value不是数组或类数组对象,并且strict为false。 123 | //那么直接将value添加到输出数组,否则忽略value。 124 | output[idx++] = value; 125 | } 126 | } 127 | return output; 128 | }; 129 | 130 | Underscore实现的flatten更加强大,它支持类数组对象而不仅仅是数组,并且它多了两个参数——shallow和strict。 131 | 132 | 当shallow为true时,flatten只会把输入数组的数组子项展开一级,如果shallow为false,那么会全部展开。 133 | 134 | 当strict为false时,只要是非数组对象,flatten都会直接添加到output数组中;如果strict为true,那么会无视input数组中的非类数组对象。 -------------------------------------------------------------------------------- /UnderscoreSourceCode/notes/理解Underscore中的restArgs函数.md: -------------------------------------------------------------------------------- 1 | # 理解Underscore中的restArgs函数 2 | 3 | 虽然Underscore并没有在API手册中提及到restArgs函数,我们仍然可以通过`_.restArgs`接口使用restArgs函数。如果不去阅读源码,我们很难发现Underscore中还有这样的一个函数,对于这样的一个“没有存在感”的函数,我们为什么要使用并学习它呢? 4 | 5 | 这个函数虽然比较“低调”,但是它在Underscore中的存在感却一点也不低。在Underscore源码中,restArgs函数作为工具函数,参与多个公开API的实现,可谓劳苦功高。从其多次参与实现公开API可以看出,这是一个十分重要的函数,为了方便讲解后面的公开API,这里专门写一篇文章介绍restArgs工具函数。 6 | 7 | ## 为什么我们需要restArgs? 8 | 9 | 在现实中,我们可能有碰到过一些特殊情况,比如我们所写的函数不确定有多少个要传递的参数,这样在函数内部实现参数处理时就会比较棘手。 10 | 11 | 比如现在我们需要构建一个函数,这个函数接受至少两个参数,第一个是一个数组对象,第二个之后是一些值,我们的函数就需要把这些值添加到第一个参数的尾部。 12 | 13 | 代码实现: 14 | 15 | function appendToArray(arr) { 16 | if(arguments.length < 2) 17 | throw new Error('funciton a require at least 2 parameters!'); 18 | return arr.concat(Array.prototype.slice.call(arguments, 1)); 19 | } 20 | 21 | 现在我们需要实现另一个函数,该函数也是把参数附加到数组中,不过实现了过滤器功能,比如只把大于1的参数附加到数组中。 22 | 23 | 代码实现: 24 | 25 | function appendToArrayPlus(arr, filter) { 26 | if(arguments.length < 3) 27 | throw new Error('function a require at least 3 parameters!'); 28 | var params = Array.prototype.slice.call(arguments, 2); 29 | for(var i = 0; i < params.length; i++) { 30 | if(filter(params[i])) { 31 | arr.push(params[i]); 32 | } 33 | } 34 | return arr; 35 | } 36 | 37 | 可以看出来,我们在开发这两个函数时,做了重复的工作,那就是根据多余参数的开始序号来截断arguments对象。 38 | 39 | 这样的做法,在写一两个函数时没有什么问题,但是在开发框架时,需要写大量的参数个数不确定的函数,这就会使得冗余代码大量增加,并且多次直接操作arguments对象的做法并不十分优雅。 40 | 41 | 所以我们需要一个restArgs这样的工具函数,给它传递一个函数以及一个多余参数开始索引(startIndex)作为参数,它会返回一个函数,我们在调用返回的函数时,开始索引之后的多余参数会被放入到数组中,然后一并传递给restArgs的第一个参数函数调用(作为最后一个参数)。 42 | 43 | 有人会说,ES6中已经实现了rest params的功能,参考[阮老师教程](http://es6.ruanyifeng.com/#docs/function#rest-参数),但是我们知道一个框架的开发,必须考虑到兼容问题,很多低端浏览器并未实现ES6语法。所以在Underscore中,暂时还未使用ES6语法。 44 | 45 | ## Underscore的实现 46 | 47 | Underscore实现的源码(**附注释**): 48 | 49 | // Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html) 50 | // This accumulates the arguments passed into an array, after a given index. 51 | 52 | //restArgs用于把func的位于startIndex之后的参数归类为一个数组, 53 | //然后返回一个函数把这个数组结合startIndex之前的参数传递给func调用。 54 | var restArgs = function (func, startIndex) { 55 | //function.length表示function定义时,形式参数的个数。 56 | //注意此处是func.length,即传入的方法参数的形参个数而不是当前函数的参数个数,需要结合具体传入的参数来看。 57 | //当startIndex参数未传递时,默认func函数的最后一个参数开始为多余参数,会被整合到数组中。 58 | startIndex = startIndex == null ? func.length - 1 : +startIndex; 59 | return function () { 60 | //length表示构造的多余参数数组的长度,是实际的多余参数或者0。 61 | var length = Math.max(arguments.length - startIndex, 0), 62 | rest = Array(length), 63 | index = 0; 64 | //新建了一个rest数组,把位于startIndex索引之后的所有参数放入该数组。 65 | for (; index < length; index++) { 66 | rest[index] = arguments[index + startIndex]; 67 | } 68 | //将多余参数放入rest数组之后,直接用Function.prototype.call执行函数。 69 | switch (startIndex) { 70 | case 0: return func.call(this, rest); 71 | case 1: return func.call(this, arguments[0], rest); 72 | case 2: return func.call(this, arguments[0], arguments[1], rest); 73 | } 74 | //如果startIndex > 2,那么使用apply传递数组作为参数的方式执行func。 75 | //虽然调用方法发生了变化,但是还是会把rest数组放在传入的参数数组的最后。 76 | //这样做其实与之前的方法无异(switch部分可以删除),但是call的效率高于apply。 77 | 78 | //args数组用于盛放startIndex之前的非多余参数。 79 | var args = Array(startIndex + 1); 80 | for (index = 0; index < startIndex; index++) { 81 | args[index] = arguments[index]; 82 | } 83 | args[startIndex] = rest; 84 | return func.apply(this, args); 85 | }; 86 | }; 87 | 88 | 可以注意到两个重点: 89 | 90 | * 1 startIndex默认为func函数的形参个数减1,那么代表的含义就是当我们调用restArgs函数不传递第二个参数时,默认从最后一个形参开始即为多余参数。 91 | 92 | 比如: 93 | 94 | _.invoke = restArgs(function (obj, path, args) {...}); 95 | _.invoke(obj, path, 1, 2, 3); 96 | 97 | 以上代码中,如果我们给_.invoke传递这些参数,那么实际上执行的函数会是: 98 | 99 | function(obj, path, [1, 2, 3]) { 100 | // ... 101 | } 102 | 103 | 这样我们就可以在写函数`_.invoke`时,很方便的预处理args数组。 104 | 105 | * 2 switch中的内容只是后面`func.apply(this, args)`的一个特例,不写switch也完全可以实现功能,但是之所以要写这个switch,是因为Function.prototype.call的效率要高于Function.prototype.apply(具体请参考:[Why is call so much faster than apply?](https://stackoverflow.com/questions/23769556/why-is-call-so-much-faster-than-apply))。 106 | 107 | ## 结语 108 | 109 | 学习完这个内部函数之后,再学习其他API源码时,就会好理解许多,我们需要重点注意的一点就是当restArgs只接受一个函数作为参数时,表示默认从接受的最后一个参数开始(包括最后一个参数)即为多余参数。 110 | 111 | 其余Underscore源码解读文章:[GitHub](https://github.com/zhongdeming428/MyMemorandum/tree/master/UnderscoreSourceCode) -------------------------------------------------------------------------------- /UnderscoreSourceCode/notes/理解Underscore中的uniq函数.md: -------------------------------------------------------------------------------- 1 | # 理解Underscore中的uniq函数 2 | 3 | uniq函数,是Underscore中的一个数组去重函数,给它传递一个数组,它将会返回该数组的去重副本。 4 | 5 | ## 1 ES6版本去重 6 | 7 | 在ES6版本中,引入了一个新的数据结构——set,这是一种类似数组的数据结构,它有个最大的特点就是内部的每一个元素都是独一无二的,所以我们可以利用它来对数组进行去重: 8 | 9 | var uniq = function(array) { 10 | var set = new Set(array); 11 | return [...set]; 12 | } 13 | 14 | 这是目前而言最快速简介的数组去重方法。但是由于浏览器兼容问题,目前ES6还没有完全普及,这样的方法可能在老旧版本的浏览器当中无法起到作用。所以我们还是需要使用ES5来实现。 15 | 16 | ## 2 ES5版本去重 17 | 18 | 对于接受的数组,我们可以对其进行遍历,使用一个result数组存放独一无二的元素,对于传入数组的每一项,在result中进行检索,如果result中不存在,那么就推入result中,最后返回result即可: 19 | 20 | var uniq = function(array) { 21 | var result = []; 22 | var length = array.length; 23 | var i; 24 | for(i = 0; i < length; i++) { 25 | if(result.indexOf(array[i]) < 0) { 26 | result.push(array[i]); 27 | } 28 | } 29 | return result; 30 | }; 31 | 32 | 该函数已经能够比较简单的数值、字符串、布尔值等简单值了,但是如果是复杂对象的话,可能就达不到去重的目的,比如: 33 | 34 | var objArr = [{name: 'a'}, {name: 'a'}]; 35 | console.log(uniq(objArr)); 36 | 37 | 我们可能会希望返回值是[{name: 'a'}],但是由于连个对象引用值不相等,所以比较时,不会对这两个对象进行去重,导致最后返回的结果是两个都存在,这显然不是我们所期望的。 38 | 39 | 我们需要一个指定比较规则的函数。 40 | 41 | ## 3 规则定制版去重函数 42 | 43 | 我们无法预知用户传递的数组内元素的类型,所以我们最好能够让用户自定义比较规则,最好的办法就是让用户传递函数作为参数。 44 | 45 | 默认函数接受的参数即为数组中的某一项: 46 | 47 | var uniq = function(array, func) { 48 | var result = []; 49 | var length = array.length; 50 | var i; 51 | if(!func) { 52 | for(i = 0; i < length; i++) { 53 | if(result.indexOf(array[i]) < 0) { 54 | result.push(array[i]); 55 | } 56 | } 57 | } 58 | else { 59 | var seen = []; 60 | for(i = 0; i < length; i++) { 61 | if(seen.indexOf(func(array[i])) < 0) { 62 | seen.push(func(array[i])); 63 | result.push(array[i]); 64 | } 65 | } 66 | } 67 | return result; 68 | }; 69 | 70 | 在func没有被传递时,直接进行比较;如果传递了func函数,那么对于array中的每一项,使用func处理后的返回值再进行比较,这样就可以达到对象比较的目的。 71 | 72 | 再次使用对象进行实验: 73 | 74 | var objArr = [{id: 'a'}, {id: 'a'}, {id: 'b'}]; 75 | console.log(uniq(objArr, function(item) { 76 | return item.id; 77 | })); 78 | 79 | 输出结果中只有两个对象,说明达到了要求。 80 | 81 | 传递了这个自定义函数之后,去重的灵活性就大大的增加了。比如对于一个传递的对象数组,其中的每个对象都包含两个属性——name和age,我们需要比较这些对象,只有当name和age都相同的时候,我们才认为两个对象相同,那么: 82 | 83 | var persons = [{name: 'dm', age: 22}, {name: 'dm', age: 23}, {name: 'dm', age: 22}]; 84 | console.log(uniq(persons, function(item) { 85 | return item.name + item.age; 86 | })); 87 | 88 | 最后返回的结果能够符合我们去重的要求。 89 | 90 | 现在去重的问题解决了,可以提高一下效率吗? 91 | 92 | 如果我们得到的是一个有序的数组(无论是数组排序还是字符串排序),我们可以只比较相邻两项是否相同来去重,这样更加简单快速。 93 | 94 | ## 4 快速去重 95 | 96 | 我们可以给uniq函数新增一个参数——isSorted,代表传递的数组是否是有序数组。 97 | 98 | var uniq = function(array, isSorted, func) { 99 | var result = []; 100 | var length = array.length; 101 | var i; 102 | var seen = []; 103 | if(isSorted && !func) { 104 | for(i = 0; i< length; i++) { 105 | if(array[i] == seen) continue; 106 | else { 107 | result.push(array[i]); 108 | seen = array[i]; 109 | } 110 | } 111 | } 112 | else if(func){ 113 | for(i = 0; i < length; i++) { 114 | if(seen.indexOf(func(array[i])) < 0) { 115 | seen.push(func(array[i])); 116 | result.push(array[i]); 117 | } 118 | } 119 | } 120 | else{ 121 | for(i = 0; i < length; i++) { 122 | if(result.indexOf(array[i]) < 0) { 123 | result.push(array[i]); 124 | } 125 | } 126 | } 127 | return result; 128 | }; 129 | 130 | 这样的实现就比较完善了,其中重要的点是对于seen这个变量的运用。 131 | 132 | 以上代码的实现思想就是来源于Underscore,只不过实现得比Underscore更加简陋,相对而言不那么完善。 133 | 134 | ## 5 Underscore实现数组去重 135 | 136 | 以下就是Underscore的源码(附注释): 137 | 138 | // Produce a duplicate-free version of the array. If the array has already 139 | // been sorted, you have the option of using a faster algorithm. 140 | // The faster algorithm will not work with an iteratee if the iteratee 141 | // is not a one-to-one function, so providing an iteratee will disable 142 | // the faster algorithm. 143 | // Aliased as `unique`. 144 | //数组去重函数,使得数组中的每一项都是独一无二的。 145 | _.uniq = _.unique = function (array, isSorted, iteratee, context) { 146 | //如果没有传递isSorted参数(即传递值不是Boolean类型),那么默认为false,其余参数重新赋值。 147 | if (!_.isBoolean(isSorted)) { 148 | context = iteratee; 149 | iteratee = isSorted; 150 | isSorted = false; 151 | } 152 | //如果传递了iteratee,那么使用cb方法包装(确保返回一个函数),然后重新赋值。 153 | if (iteratee != null) iteratee = cb(iteratee, context); 154 | //保存结果。 155 | var result = []; 156 | //用于存放array的值便于下一次比较,或者用于存储computed值。 157 | var seen = []; 158 | //遍历array数组。 159 | for (var i = 0, length = getLength(array); i < length; i++) { 160 | //value表示当前项,computed表示要比较的项(有iteratee时是iteratee的返回值,无iteratee时是value自身)。 161 | var value = array[i], 162 | computed = iteratee ? iteratee(value, i, array) : value; 163 | if (isSorted && !iteratee) { 164 | //如果数组是有序的,并且没有传递iteratee,则依次比较相邻的两项是否相等。 165 | //!0===true,其余皆为false。 166 | if (!i || seen !== computed) result.push(value); 167 | //seen存放当前的项,以便于下一次比较。 168 | seen = computed; 169 | } else if (iteratee) { 170 | //如果传递了iteratee,那么seen就用于存放computed值,便于比较。 171 | //之所以不直接使用result存放computed值是因为computed只用于比较,result存放的值必须是原来数组中的值。 172 | if (!_.contains(seen, computed)) { 173 | seen.push(computed); 174 | result.push(value); 175 | } 176 | } else if (!_.contains(result, value)) { 177 | //isSorted为false并且iteratee为undefined。 178 | //可以理解为参数数组中是乱序数字,直接比较就好了。 179 | result.push(value); 180 | } 181 | } 182 | return result; 183 | }; 184 | 185 | 数组去重是一件说容易也容易,说简单也简单的事情,就看你怎么做了。 186 | 187 | 更多Underscore源码解读:[GitHub](https://github.com/zhongdeming428/MyMemorandum/tree/master/UnderscoreSourceCode) -------------------------------------------------------------------------------- /UnderscoreSourceCode/notes/理解Underscore中的去抖函数.md: -------------------------------------------------------------------------------- 1 | # 理解Underscore中的去抖函数 2 | 3 | 何为去抖函数?在学习Underscore去抖函数之前我们需要先弄明白这个概念。很多人都会把去抖跟节流两个概念弄混,但是这两个概念其实是很好理解的。 4 | 5 | 去抖函数(Debounce Function),是一个可以限制指定函数触发频率的函数。我们可以理解为**连续调用**同一个函数多次,只得到执行该函数一次的结果;但是隔一段时间再次调用时,又可以重新获得新的结果,具体这段时间有多长取决于我们的设置。这种函数的应用场景有哪些呢? 6 | 7 | 比如我们写一个DOM事件监听函数, 8 | 9 | window.onscroll = function(){ 10 | console.log('Got it!'); 11 | } 12 | 13 | 现在当我们滑动鼠标滚轮的时候,我们就可以看到事件被触发了。但是我们可以发现在我们滚动鼠标滚轮的时候,我们的控制台在不断的打印消息,因为window的scroll事件被我们不断的触发了。 14 | 15 | 在当前场景下,可能这是一个无伤大雅的行为,但是可以预见到,当我们的事件监听函数(Event Handler)涉及到一些复杂的操作时(比如Ajax请求、DOM渲染、大量数据计算),会对计算机性能产生多大影响;在一些比较老旧的机型或者较低版本的浏览器(尤其IE)中,很可能会导致死机情况的出现。所以这个时候我们就要想办法,在指定时间段内,只执行一定次数的事件处理函数。 16 | 17 | ## 理解去抖函数 18 | 19 | 说了一些概念和应用场景,但是还是很拗口,到底什么是去抖函数? 20 | 21 | 我们可以通过如下实例来理解: 22 | 23 | 假设有以下代码: 24 | 25 | //自己实现的简单演示代码,未实现immediate功能,欢迎改进。 26 | var debounce = function (callback, delay, immediate) { 27 | var timeout, result; 28 | return function () { 29 | var callNow; 30 | if (timeout) 31 | clearTimeout(timeout); 32 | callNow = !timeout && immediate; 33 | if (callNow) { 34 | result = callback.apply(this, Array.prototype.slice.call(arguments, 0)); 35 | timeout = {}; 36 | } 37 | else { 38 | timeout = setTimeout(() => { 39 | callback.apply(this, Array.prototype.slice.call(arguments, 0)); 40 | }, delay); 41 | } 42 | }; 43 | }; 44 | var s = debounce(() => { 45 | console.log('yes...'); 46 | }, 2000); 47 | window.onscroll = s; 48 | debounce函数就是我自己实现的一个简单的去抖函数,我们可以通过这段代码进行实验。 49 | 50 | 步骤如下: 51 | 52 | * 复制以上代码,打开浏览器,打开控制台(F12),然后粘贴代码并回车执行。 53 | * 连续不断的滚动鼠标,查看控制台有无输出。 54 | * 停止滚动鼠标,2s之内再次滚动鼠标,查看是否有输出。 55 | * 连续滚动之后停止2s以上,查看是否有输出。 56 | 57 | 通过以上步骤,我们可以发现当我们连续滚动鼠标时,控制台没有消息被打印出来,停止2s以内并再次滚动时,也没有消息输出;但是当我们停止的时间超过2s时,我们可以看到控制台有消息输出。 58 | 59 | 这就是去抖函数。在连续的触发中(无论时长),只能得到触发一次的效果。在指定时间长度内连续触发,最多只能得到一次触发的效果。 60 | 61 | ## underscore的实现 62 | 63 | underscore源码如下(附代码注释): 64 | 65 | // Returns a function, that, as long as it continues to be invoked, will not 66 | // be triggered. The function will be called after it stops being called for 67 | // N milliseconds. If `immediate` is passed, trigger the function on the 68 | // leading edge, instead of the trailing. 69 | //去抖函数,传入的函数在wait时间之后(或之前)执行,并且只会被执行一次。 70 | //如果immediate传递为true,那么在函数被传递时就立即调用。 71 | //实现原理:涉及到异步JavaScript,多次调用_.debounce返回的函数,会一次性执行完,但是每次调用 72 | //该函数又会清空上一次的TimeoutID,所以实际上只执行了最后一个setTimeout的内容。 73 | _.debounce = function (func, wait, immediate) { 74 | var timeout, result; 75 | 76 | var later = function (context, args) { 77 | timeout = null; 78 | //如果没有传递args参数,那么func不执行。 79 | if (args) result = func.apply(context, args); 80 | }; 81 | 82 | //被返回的函数,该函数只会被调用一次。 83 | var debounced = restArgs(function (args) { 84 | //这行代码的作用是清除上一次的TimeoutID, 85 | //使得如果有多次调用该函数的场景时,只执行最后一次调用的延时。 86 | if (timeout) clearTimeout(timeout); 87 | if (immediate) { 88 | ////如果传递了immediate并且timeout为空,那么就立即调用func,否则不立即调用。 89 | var callNow = !timeout; 90 | //下面这行代码,later函数内部的func函数注定不会被执行,因为没有给later传递参数。 91 | //它的作用是确保返回了一个timeout,并且保持到wait毫秒之后,才执行later, 92 | //清空timeout。而清空timeout是在immediate为true时,callNow为true的条件。 93 | //timeout = setTimeout(later, wait)的存在是既保证上升沿触发, 94 | //又保证wait内最多触发一次的必要条件。 95 | timeout = setTimeout(later, wait); 96 | if (callNow) result = func.apply(this, args); 97 | } else { 98 | //如果没有传递immediate,那么就使用_.delay函数延时执行later。 99 | timeout = _.delay(later, wait, this, args); 100 | } 101 | 102 | return result; 103 | }); 104 | 105 | //该函数用于取消当前去抖效果。 106 | debounced.cancel = function () { 107 | clearTimeout(timeout); 108 | timeout = null; 109 | }; 110 | 111 | return debounced; 112 | }; 113 | 114 | 可以看到underscore使用了闭包的方法,定义了两个私有属性:timeout和result,以及两个私有方法later和debounced。最终会返回debounced作为处理之后的函数。timeout用于接受并存储setTimeout返回的TimeoutID,result用于执行用户传入的func函数的执行结果,later方法用于执行传入的func函数。 115 | 116 | ### 实现原理 117 | 118 | 利用了JavaScript的异步执行机制,JavaScript会优先执行完所有的同步代码,然后去事件队列中执行所有的异步任务。 119 | 120 | 当我们不断的触发debounced函数时,它会不断的clearTimeout(timeout),然后再重新设置新的timeout,所以实际上在我们的同步代码执行完之前,每次调用debounced函数都会重置timeout。所以异步事件队列中的异步任务会不断刷新,直到最后一个debounced函数执行完。只有最后一个debounced函数设置的later异步任务会在同步代码执行之后被执行。 121 | 122 | 所以当我们在之前实验中不断的滚动鼠标时,实际上是在不断的调用debounced函数,不断的清除timeout对应的异步任务,然后又设置新的timeout异步任务。当我们停止的时间不超过2s时,timeout对应的异步任务还没有被触发,所以再次滚动鼠标触发debounced函数还可以清除timeout任务然后设置新的timeout任务。一旦停止的时间超过2s,最终的timeout对应的异步代码就会被执行。 123 | 124 | ## 总结 125 | 126 | * 去抖是限制函数执行频率的一种方法。 127 | * 去抖后的函数在指定时间内最多被触发一次,连续触发去抖后的函数只能得到一次的触发效果。 128 | * underscore去抖的实现依赖于JavaScript的异步执行机制,优先执行同步代码,然后执行事件队列中的异步代码。 129 | 130 | ## 参考 131 | * [underscore 函数去抖的实现](https://github.com/hanzichi/underscore-analysis/issues/21) 132 | * [JavaScript Debounce Function](https://davidwalsh.name/javascript-debounce-function) 133 | * [Stack Overflow:What does _.debounce do?](https://stackoverflow.com/questions/15927371/what-does-debounce-do) 134 | -------------------------------------------------------------------------------- /UnderscoreSourceCode/notes/理解Underscore中的节流函数.md: -------------------------------------------------------------------------------- 1 | # 理解Underscore中的节流函数 2 | 3 | 上一篇中讲解了Underscore中的去抖函数(`_.debounced`),这一篇就来介绍节流函数(`_.throttled`)。 4 | 5 | 经过上一篇文章,我相信很多人都已经了解了去抖和节流的概念。去抖,在一段连续的触发中只能得到触发一次的结果,在触发之后经过一段时间才可以得到执行的结果,并且必须在经过这段时间之后,才可以进入下一个触发周期。节流不同于去抖,节流是一段连续的触发至少可以得到一次触发结果,上限取决于设置的时间间隔。 6 | 7 | ## 1 理解函数节流 8 | 9 | 通过这张我手画的图,我相信可以更容易理解函数节流这个概念。 10 | 11 | ![throttle](./images/throttle.png) 12 | 13 | 在这张粗制滥造的手绘图中,从左往右的轴线表示时间轴,下方的粗蓝色线条表示不断的调用throttled函数(看做连续发生的),而上方的一个一个节点表示我们得到的执行func函数的结果。 14 | 15 | 从图上可以看出来,我们通过函数节流,成功的限制了func函数在一段时间内的调用频率,在实际中能够提高我们应用的性能表现。 16 | 17 | 接下来我们探究一下Underscore中_.throttle函数的实现。 18 | 19 | ## 2 Underscore的实现 20 | 21 | 我们在探究源码之前,先了解一下Underscore API手册中关于_.throttle函数的使用说明: 22 | 23 | >throttle_.throttle(function, wait, [options]) 24 | > 25 | >创建并返回一个像节流阀一样的函数,当重复调用函数的时候,最多每隔 wait毫秒调用一次该函数。对于想控制一些触发频率较高的事件有帮助。(注:详见:javascript函数的throttle和debounce) 26 | > 27 | >默认情况下,throttle将在你调用的第一时间尽快执行这个function,并且,如果你在wait周期内调用任意次数的函数,都将尽快的被覆盖。如果你想禁用第一次首先执行的话,传递{leading: false},还有如果你想禁用最后一次执行的话,传递{trailing: false}。 28 | > 29 | >var throttled = _.throttle(updatePosition, 100); 30 | > 31 | >$(window).scroll(throttled); 32 | 33 | 结合我画的那张示意图,应该比较好理解了。 34 | 35 | 如果传递的options参数中,leading为false,那么不会在throttled函数被执行时立即执行func函数;trailing为false,则不会在结束时调用最后一次func。 36 | 37 | **Underscore源码(附注释)**: 38 | 39 | // Returns a function, that, when invoked, will only be triggered at most once 40 | // during a given window of time. Normally, the throttled function will run 41 | // as much as it can, without ever going more than once per `wait` duration; 42 | // but if you'd like to disable the execution on the leading edge, pass 43 | // `{leading: false}`. To disable execution on the trailing edge, ditto. 44 | _.throttle = function (func, wait, options) { 45 | var timeout, context, args, result; 46 | var previous = 0; 47 | if (!options) options = {}; 48 | 49 | var later = function () { 50 | //previous===0时,下一次会立即触发。 51 | //previous===_.now()时,下一次不会立即触发。 52 | previous = options.leading === false ? 0 : _.now(); 53 | timeout = null; 54 | result = func.apply(context, args); 55 | if (!timeout) context = args = null; 56 | }; 57 | 58 | var throttled = function () { 59 | //获取当前时间戳(13位milliseconds表示)。 60 | //每一次调用throttled函数,都会重新获取now,计算时间差。 61 | //而previous只有在func函数被执行过后才回重新赋值。 62 | //也就是说,每次计算的remaining时间间隔都是每次调用throttled函数与上一次执行func之间的时间差。 63 | var now = _.now(); 64 | //!previous确保了在第一次调用时才会满足条件。 65 | //leading为false表示不立即执行。 66 | //注意是全等号,只有在传递了options参数时,比较才有意义。 67 | if (!previous && options.leading === false) previous = now; 68 | //计算剩余时间,now-previous为已消耗时间。 69 | var remaining = wait - (now - previous); 70 | context = this; 71 | args = arguments; 72 | //remaining <= 0代表当前时间超过了wait时长。 73 | //remaining > wait代表now < previous,这种情况是不存在的,因为now >= previous是永远成立的(除非主机时间已经被修改过)。 74 | //此处就相当于只判断了remaining <= 0是否成立。 75 | if (remaining <= 0 || remaining > wait) { 76 | //防止出现remaining <= 0但是设置的timeout仍然未触发的情况。 77 | if (timeout) { 78 | clearTimeout(timeout); 79 | timeout = null; 80 | } 81 | //将要执行func函数,重新设置previous的值,开始下一轮计时。 82 | previous = now; 83 | //时间达到间隔为wait的要求,立即传入参数执行func函数。 84 | result = func.apply(context, args); 85 | if (!timeout) context = args = null; 86 | //remaining>0&&remaining<=wait、不忽略最后一个输出、 87 | //timeout未被设置时,延时调用later并设置timeout。 88 | //如果设置trailing===false,那么直接跳过延时调用later的部分。 89 | } else if (!timeout && options.trailing !== false) { 90 | timeout = setTimeout(later, remaining); 91 | } 92 | return result; 93 | }; 94 | 95 | throttled.cancel = function () { 96 | clearTimeout(timeout); 97 | previous = 0; 98 | timeout = context = args = null; 99 | }; 100 | 101 | return throttled; 102 | }; 103 | 104 | 接下来,我们分三种情况分析Underscore源码: 105 | 106 | * 没有配置options选项时 107 | * options.leading === false时 108 | * options.trailing === false时 109 | 110 | ### 2.1 默认情况(options === undefined) 111 | 在默认情况下调用throttled函数时,options是一个空的对象`{}`,此时`options.leading!==false`并且`options.trailing!==false`,那么throttled函数中的第一个if会被忽略掉,因为options.leading === false永远不会满足。 112 | 113 | 此时,不断地调用throttled函数,会按照以下方式执行: 114 | 115 | * 用now变量保存当前调用时的时间戳,previous默认为0,计算remaining剩余时间,此时应该会小于0,满足了`if (remaining <= 0 || remaining > wait)`。 116 | 117 | * 清空timeout并清除其事件,为previous重新赋值以记录当前调用throttled函数的值。 118 | 119 | * 能够进入当前的if语句表示剩余时间不足或者是第一次调用throttled函数(且options.leading !== false),那么将会立即执行func函数,使用result记录执行后的返回值。 120 | 121 | * 下一次调用throttled函数时,重新计算当前时间和剩余时间,如果剩余时间不足那么仍然立即执行func,如此不断地循环。如果remaining时间足够(大于0),那么会进入else if语句,设置一个timeout异步事件,此时注意到timeout会被赋值,直到later被调用才回被赋值为null。这样做的目的就是为了防止不断进入else if条件语句重复设置timeout异步事件,影响性能,消耗资源。 122 | 123 | * 之后调用throttled函数,都会按照这样的方式执行。 124 | 125 | 通过上面的分析,我们可以发现,除非设置options.leading===false,否则第一次执行throttled函数时,条件语句`if (!previous && options.leading === false) previous = now;`不会被执行。间接导致remaining<0,然后进入if语句立即执行func函数。 126 | 127 | 接下来我们看看设置options.leading === false时的情况。 128 | 129 | ### 2.2 options.leading === false 130 | 131 | 设置options.leading为false时,执行情况与之前并没有太大差异,仅在于`if(!previous && options.leading === false)`语句。当options.leading为false时,第一次执行会满足这个条件,所以赋值previous=== now,间接使得remaining>0。 132 | 133 | 由于timeout此时为undefined,所以!timeout为true。设置later为异步任务,在remaining时间之后执行。 134 | 135 | 此后再不断的调用throttled方法,思路同2.1无异,因为!previous为false,所以`if(!previous && options.leading === false)`该语句不再判断,会被完全忽略。可以理解为设置判断!previous的目的就是在第一次调用throttled函数时,判断options.leading是否为false,之后便不再进行判断。 136 | 137 | ### 2.3 options.trailing === false 138 | 139 | 此时的区别在于else if中的执行语句。如果`options.trailing === false`成立,那么当remaining>0时间足够时,不会设置timeout异步任务。那么如何实现时间到就立即执行func呢?是通过不断的判断remaining,一旦`remaining <= 0`成立,那么就立即执行func。 140 | 141 | 接下来,我们手动实现一个简单的throttle函数。 142 | 143 | ## 实现一个简单的throttle函数 144 | 145 | 首先,我们需要多个throttled函数共享一些变量,比如previous、result、timeout,所以最好的方案仍然是使用闭包,将这些共享的变量作为throttle函数的私有变量。 146 | 147 | 其次,我们需要在返回的函数中不断地获取调用该函数时的时间戳now,不断地计算remaining剩余时间,为了实现trailing不等于false时的效果,我们还需要设置timeout。 148 | 149 | 最终代码如下: 150 | 151 | var throttle = function(func, wait) { 152 | var timeout, result, now; 153 | var previous = 0; 154 | 155 | return function() { 156 | now = +(new Date()); 157 | 158 | if(now - previous >= wait) { 159 | if(timeout) { 160 | clearTimeout(timeout); 161 | timeout = null; 162 | } 163 | previous = now; 164 | result = func.apply(this, arguments); 165 | } 166 | else if(!timeout) { 167 | timeout = setTimeout(function() { 168 | previous = now; 169 | result = func.apply(this, arguments); 170 | timeout = null; 171 | }, wait - now + previous); 172 | } 173 | return result; 174 | } 175 | } 176 | 177 | 可能大家发现了一个问题就是我的now变量也是共享的变量,而underscore中是throttled函数的私有变量,为什么呢? 178 | 179 | 我们可以注意到:underscore设置timeout时,调用的是另外一个throttle函数的私有函数,叫做later。later在更新previous的时候,使用的是`previous = options.leading === false ? 0 : _.now();`也就是通过`_.now`函数直接获取later被调用时的时间戳。而我使用的是`previous = now`,如果now做成throttled的私有变量,那么timeout的异步任务执行时,设置的previous仍然是过去的时间,而非异步任务被执行时的当前时间。这样做直接导致的结果就是previous相比实际值更小,remaining会更大,下一次func触发会来的更早! 180 | 181 | 下面这段代码是对上面代码的应用,大家可以直接拷贝到浏览器的控制台,回车然后在页面上滚动鼠标滚轮,看看这个函数实现了怎样的功能,更有利于你对这篇文章的理解! 182 | 183 | var throttle = function(func, wait) { 184 | var timeout, result, now; 185 | var previous = 0; 186 | 187 | return function() { 188 | now = +(new Date()); 189 | 190 | if(now - previous >= wait) { 191 | if(timeout) { 192 | clearTimeout(timeout); 193 | timeout = null; 194 | } 195 | previous = now; 196 | result = func.apply(this, arguments); 197 | } 198 | else if(!timeout) { 199 | timeout = setTimeout(function() { 200 | previous = now; 201 | result = func.apply(this, arguments); 202 | timeout = null; 203 | }, wait - now + previous); 204 | } 205 | return result; 206 | } 207 | } 208 | window.onscroll = throttle(()=>{console.log('yes')}, 2000); -------------------------------------------------------------------------------- /UnderscoreSourceCode/notes/理解Underscore的设计架构.md: -------------------------------------------------------------------------------- 1 | # 理解Underscore的设计架构 2 | 3 | 在一个多月的毕业设计之后,我再次开始了Underscore的源码阅读学习,断断续续也写了好些篇文章了,基本把一些比较重要的或者个人认为有营养的函数都解读了一遍,所以现在学习一下Underscore的整体架构。我相信很多程序员都会有一个梦想,那就是可以写一个自己的模块或者工具库,那么我们现在就来学习一下如果我们要写一个自己的Underscore,我们该怎么写? 4 | 5 | 大致的阅读了一下Underscore源码,可以发现其基本架构如下: 6 | 7 | ## 1 定义变量 8 | 9 | 在ES6之前,JavaScript开发者是无法通过let、const关键字模拟块作用域的,只有函数内部的变量会被认为是私有变量,在外部无法访问,所以大部分框架或者工具库的模式都是在立即执行函数里面定义一系列的变量,完成框架或者工具库的构建,这样做的好处就是代码不会污染全局作用域。Underscore也不例外,它也使用了经典的立即执行函数的模式: 10 | 11 | (function() { 12 | // ... 13 | }()) 14 | 15 | 此外,Underscore采用了经典的构造器模式,这使得用户可以通过`_(obj).function()`的方式使用Underscore的接口,因为任意创建的Underscore对象都具有原型上的所有方法。那么代码形式如下: 16 | 17 | (function() { 18 | var _ = function() { 19 | // ... 20 | }; 21 | }()) 22 | 23 | _是一个函数,但是在JavaScript中,函数也是一个对象,所以我们可以给_添加一系列属性,即Underscore中的一系列公开的接口,以便可以通过`_.function()`的形式调用这些接口。代码形式如下: 24 | 25 | (function() { 26 | var _ = function() { 27 | // ... 28 | }; 29 | _.each = function() { 30 | // ... 31 | }; 32 | // ... 33 | }()) 34 | 35 | _变量可以当做构造器构造一个Underscore对象,这个对象是标准化的,它具有规定的属性,比如:`_chain`、`_wrapped`以及所有Underscore的接口方法。Underscore把需要处理的参数传递给_构造函数,构造函数会把这个值赋给所构造对象的`_wrapped`属性,这样做的好处就是在之后以`_(obj).function()`形式调用接口时,可以直接到`_wrapped`属性中寻找要处理的值。这就使得在定义_构造函数的时候,需要对传入的参数进行包裹,此外还要防止多层包裹,以及为了防止增加new操作符,需要在内部进行对象构建,代码形式如下: 36 | 37 | (function() { 38 | var _ = function(obj) { 39 | // 防止重复包裹的处理,如果obj已经是_的实例,那么直接返回obj。 40 | if(obj instanceof _) { 41 | return obj; 42 | } 43 | // 判断函数中this的指向,如果this不是_的实例,那么返回构造的_实例。 44 | // 这里是为了不使用new操作符构造新对象,很巧妙,因为在通过new使用构造函数时,函数中的this会指向新构造的实例。 45 | if(!(this instanceof _)) { 46 | return new _(); 47 | } 48 | // 49 | this._wrapped = obj; 50 | }; 51 | _.each = function() { 52 | // ... 53 | }; 54 | // ... 55 | }()) 56 | 57 | 这一段的处理很关键也很巧妙。 58 | 59 | ## 2 导出变量 60 | 既然我们是在立即执行函数内定义的变量,那么_的生命周期也只存在于匿名函数的执行阶段,一旦函数执行完毕,这个变量所存储的数据也就被释放掉了,所以不导出变量的话实际上这段代码相当于什么都没做。那么该如何导出变量呢?我们知道函数内部可以访问到外部的变量,所以只要把变量赋值给外部作用域或者外部作用域变量就行了。通常为了方便实用,把变量赋值给全局作用域,不同的环境全局作用域名称不同,浏览器环境下通常为window,服务器环境下通常为global,根据不同的使用环境需要做不同的处理,比如浏览器环境下代码形式如下: 61 | 62 | (function() { 63 | var _ = function() { 64 | // ... 65 | }; 66 | _.each = function() { 67 | // ... 68 | }; 69 | // ... 70 | window._ = _; 71 | }()) 72 | 73 | 这样处理之后,在全局作用域就可以直接通过_使用Underscore的接口了。 74 | 75 | 但是仅仅这样处理还不够,因为Underscore面向环境很多,针对不同的环境要做不同的处理。接下来看Underscore源码。 76 | 77 | 首先,Underscore通过以下代码根据不同的环境获取不同的全局作用域: 78 | 79 | //获取全局对象,在浏览器中是self或者window,在服务器端(Node)中是global。 80 | //在浏览器控制台中输入self或者self.self,结果都是window。 81 | var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this || {}; 82 | root._ = _; 83 | 84 | 注释写在了代码中,如果既不是浏览器环境也不是Node环境的话,就获取值为this,通过this获取全局作用域,如果this仍然为空,就赋值给一个空的对象。感谢大神[@冴羽](https://github.com/mqyqingfeng)的指教,赋值给空对象的作用是防止在开发微信小程序时报错,因为在微信小程序这种特殊环境下,window和global都是undefined,并且强制开启了strict模式,这时候this也是undefined(严格模式下禁止this指向全局变量),所以指定一个空对象给root,防止报错,具体参考:[\`this\` is undefined in strict mode](https://github.com/jashkenas/underscore/pull/2641)。 85 | 86 | 这里值得学习的地方还有作者关于赋值的写法,十分简洁,尝试了一下,对于下面的写法: 87 | 88 | const flag = val1 && val2 && val3 || val4 && val5; 89 | 90 | 程序会从左到右依次判断val1、val2、val3的值,假设`||`把与运算分为许多组,那么: 91 | * 一旦当前判断组的某个值转换为Boolean值后为false,那么就跳转到下一组进行判断,直到最后一组,如果最后一组仍然有值被判断为false,那么为false的值被赋给flag。 92 | * 如果当前判断组所有的值转换后都为true,那么最后一个值会被赋给flag。 93 | 94 | 比如: 95 | 96 | const a = 1 && 2 && 3 || 2 && 3; 97 | // a === 3 98 | const b = 1 && false && 2 || 2 && 3; 99 | // b === 3 100 | const c = 1 && false && 2 || false && 2 101 | // c === false 102 | const d = 1 && false && 2 || 0 && 2 103 | // d === 0 104 | const e = 1 && false && 2 || 1 && 2 105 | // e === 2 106 | 107 | 除了要考虑给全局作用域赋值的差异以外,还要考虑JavaScript模块化规范的差异,JavaScript模块化规范包括AMD、CMD等。 108 | 109 | 通过以下代码兼容AMD规范: 110 | 111 | //兼容AMD规范的模块化工具,比如RequireJS。 112 | if (typeof define == 'function' && define.amd) { 113 | define('underscore', [], function () { 114 | return _; 115 | }); 116 | } 117 | 118 | 如果define是一个函数并且define.amd不为null或者undefined,那就说明是在AMD规范的工作环境下,使用define函数导出变量。 119 | 120 | 通过以下代码兼容CommonJS规范: 121 | 122 | //为Node环境导出underscore,如果存在exports对象或者module.exports对象并且这两个对象不是HTML DOM,那么即为Node环境。 123 | //如果不存在以上对象,把_变量赋值给全局环境(浏览器环境下为window)。 124 | if (typeof exports != 'undefined' && !exports.nodeType) { 125 | if (typeof module != 'undefined' && !module.nodeType && module.exports) { 126 | exports = module.exports = _; 127 | } 128 | exports._ = _; 129 | } else { 130 | root._ = _; 131 | } 132 | 133 | 此外,通过以上代码可以支持ES6模块的import语法。具体原理参考阮一峰老师的教程:[ES6 模块加载 CommonJS 模块](http://es6.ruanyifeng.com/#docs/module-loader#ES6-模块加载-CommonJS-模块)。如果既不是AMD规范也不是CommonJS规范,那么直接将_赋值给全局变量。这一点可以通过将Underscore源码复制到浏览器的控制台回车后再查看`_`和`_.prototype`的值得到结论。 134 | 135 | 导出变量之后,在外部就可以使用我们定义的接口了。 136 | 137 | ## 3 实现链式调用 138 | 139 | 许多出名的工具库都会提供链式调用功能,比如jQuery的链式调用:`$('...').css().click();`,Underscore也提供了链式调用功能:`_.chain(...).each().unzip();`。 140 | 141 | 链式调用基本都是通过返回原对象实现的,比如返回this,在Underscore中,可以通过`_.chain`函数开始链式调用,实现原理如下: 142 | 143 | // Add a "chain" function. Start chaining a wrapped Underscore object. 144 | //将传入的对象包装为链式调用的对象,将其标志位置位true。 145 | _.chain = function (obj) { 146 | var instance = _(obj); 147 | instance._chain = true; 148 | return instance; 149 | }; 150 | 151 | 它构造一个_实例,然后将其`_chain`链式标志位属性值为true代表链式调用,然后返回这个实例。这样做就是为了强制通过`_().function()`的方式调用接口,因为在_的原型上,所有接口方法与_的属性方法有差异,_原型上的方法多了一个步骤,它会对其父对象的`_chain`属性进行判断,如果为true,那么就继续使用`_.chain`方法进行链式调用的包装,在一部分在后续会继续讨论。 152 | 153 | ## 4 实现接口扩展 154 | 155 | 在许多出名的工具库中,都可以实现用户扩展接口,比如jQuery的`$.extend`和`$.fn.extend`方法,Underscore也不例外,其`_.mixin`方法允许用户扩展接口。 156 | 157 | 这里涉及到的一个概念就是mixin设计模式,mixin设计模式是JavaScript中最常见的设计模式,可以理解为把一个对象的属性拷贝到另外一个对象上,具体可以参考:[掺杂模式(mixin)](http://www.cnblogs.com/snandy/archive/2013/05/24/3086663.html)。 158 | 159 | 先看Underscore中`_.mixin`方法的源代码: 160 | 161 | _.mixin = function (obj) { 162 | // _.functions函数用于返回一个排序后的数组,包含所有的obj中的函数名。 163 | _.each(_.functions(obj), function (name) { 164 | // 先为_对象赋值。 165 | var func = _[name] = obj[name]; 166 | // 为_的原型添加函数,以增加_(obj).mixin形式的函数调用方法。 167 | _.prototype[name] = function () { 168 | // this._wrapped作为第一个参数传递,其他用户传递的参数放在后面。 169 | var args = [this._wrapped]; 170 | push.apply(args, arguments); 171 | // 使用chainResult对运算结果进行链式调用处理,如果是链式调用就返回处理后的结果, 172 | // 如果不是就直接返回运算后的结果。 173 | return chainResult(this, func.apply(_, args)); 174 | }; 175 | }); 176 | return _; 177 | }; 178 | 179 | 这段代码很好理解,就是对于传入的obj对象参数,将对象中的每一个函数拷贝到_对象上,同名会被覆盖。与此同时,还会把obj参数对象中的函数映射到_对象的原型上,为什么说是映射,因为并不是直接拷贝的,还进行了链式调用的处理,通过chainResult方法,实现了了链式调用,所以第三节中说_对象原型上的方法与_对象中的对应方法有差异,原型上的方法多了一个步骤,就是判断是否链式调用,如果是链式调用,那么继续通过`_.chain`函数进行包装。chainResult函数代码如下: 180 | 181 | // Helper function to continue chaining intermediate results. 182 | //返回一个链式调用的对象,通过判断instance._chain属性是否为true来决定是否返回链式对象。 183 | var chainResult = function (instance, obj) { 184 | return instance._chain ? _(obj).chain() : obj; 185 | }; 186 | 187 | 实现mixin函数之后,Underscore的设计者非常机智的运用了这个函数,代码中只可以看到为_自身定义的一系列函数,比如`_.each`、`_.map`等,但看不到为`_.prototype`所定义的函数,为什么还可以通过`_().function()`的形式调用接口呢?这里就是因为作者通过`_.mixin`函数直接将所有_上的函数映射到了`_.prototype`上,在`_.mixin`函数定义的下方,有一句代码: 188 | 189 | // Add all of the Underscore functions to the wrapper object. 190 | _.mixin(_); 191 | 192 | 这句代码就将所有的_上的函数映射到了`_.prototype`上,有点令我叹为观止。 193 | 194 | 通过`_.mixin`函数,用户可以为_扩展自定义的接口,下面的例子来源于[中文手册](http://www.bootcss.com/p/underscore/#mixin): 195 | 196 | _.mixin({ 197 | capitalize: function(string) { 198 | return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); 199 | } 200 | }); 201 | _("fabio").capitalize(); 202 | => "Fabio" 203 | 204 | ## 5 实现noConflict 205 | 206 | 在许多工具库中,都有实现noConflict,因为在全局作用域,变量名是独一无二的,但是用户可能引入多个类库,多个类库可能有同一个标识符,这时就要使用noConflict实现无冲突处理。 207 | 208 | 具体做法就是先保存原来作用域中该标志位的数据,然后在调用noConflict函数时,为全局作用域该标志位赋值为原来的值。代码如下: 209 | 210 | // Save the previous value of the `_` variable. 211 | //保存之前全局对象中_属性的值。 212 | var previousUnderscore = root._; 213 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 214 | // previous owner. Returns a reference to the Underscore object. 215 | _.noConflict = function () { 216 | root._ = previousUnderscore; 217 | return this; 218 | }; 219 | 220 | 在函数的最后,返回了Underscore对象,允许用户使用另外的变量存储。 221 | 222 | ## 6 为变量定义一系列基本属性 223 | 224 | 作为一个对象,应该有一些基本属性,比如toString、value等等,需要重写这些属性或者函数,以便使用时返回合适的信息。此外还需要添加一些版本号啊什么的属性。 225 | 226 | ## 7 总结 227 | 228 | 做完以上所有的工作之后,一个基本的工具库基本就搭建完成了,完成好测试、压缩等工作之后,就可以发布在npm上供大家下载了。想要写一个自己的工具库的同学可以尝试一下。 229 | 230 | 另外如果有错误之处或者有补充之处的话,欢迎大家不吝赐教,一起学习,一起进步! -------------------------------------------------------------------------------- /UnderscoreSourceCode/test.js: -------------------------------------------------------------------------------- 1 | //这个js文件用于测试underscore接口。 2 | 3 | const _ = require('./underscore.1.8.3.js'); 4 | 5 | // var a = { 6 | // name: 'test' 7 | // }; 8 | // a['test1'] = a; 9 | // var b = { 10 | // name: 'test' 11 | // }; 12 | // b['test1'] = b; 13 | 14 | // console.log(_.isEqual(a, b)); 15 | 16 | // var set1 = new Set([1,2,3,4]); 17 | // var set2 = new Set([1,2,3]); 18 | // console.log(_.isEqual(set1, set2)); 19 | // console.log(Array.prototype.slice.call(set1.values(), 0)); 20 | // console.log(set1.keys()); 21 | 22 | // var arr1 = [['name', 'zdm'], ['age', 22]]; 23 | // var arr2 = [['name', 'zdm'], ['age', 22], ['gender', 'male']]; 24 | // var map1 = new Map(arr1); 25 | // var map2 = new Map(arr2); 26 | 27 | // console.log(_.isEqual(map1, map2)); 28 | // console.log([...map2]); 29 | 30 | // var x = /.\w+/gi; 31 | // var y = /.\w+/ig; 32 | 33 | // console.log(_.isEqual(x, y)); 34 | 35 | // var a = _.debounce((content)=>{ 36 | // console.log('Yes!' + ' ' + content); 37 | // }, 1000, false); 38 | // a(1); 39 | // a(2); 40 | // a(3); 41 | // a(4); 42 | 43 | // var debounce = function(callback, delay, immediate){ 44 | // var timeout, result; 45 | // return function(){ 46 | // var callNow; 47 | // if(timeout) 48 | // clearTimeout(timeout); 49 | // callNow = !timeout && immediate; 50 | // if(callNow) { 51 | // result = callback.apply(this, Array.prototype.slice.call(arguments, 0)); 52 | // timeout = {}; 53 | // } 54 | // else { 55 | // timeout = setTimeout(()=>{ 56 | // callback.apply(this, Array.prototype.slice.call(arguments, 0)); 57 | // }, delay); 58 | // } 59 | // }; 60 | // }; 61 | 62 | // var b = debounce((x, y)=>{ 63 | // console.log(x + y); 64 | // console.timeEnd(); 65 | // }, 2000); 66 | // console.time(); 67 | // b(1,1); 68 | // b(2,2); 69 | // b(3,3); 70 | // b(4,4); 71 | 72 | // var a = _.throttle(()=>{ 73 | // console.log('yes'); 74 | // }, 100000, { 75 | // // leading:true, 76 | // // trailing:false 77 | // }); 78 | // while(1){ 79 | // a(); 80 | // } 81 | 82 | // var throttle = function(func, wait){ 83 | // var timeout, result, now; 84 | // var previous = 0; 85 | 86 | // return function() { 87 | // now = +(new Date()); 88 | 89 | // if(now - previous >= wait) { 90 | // if(timeout) { 91 | // clearTimeout(timeout); 92 | // timeout = null; 93 | // } 94 | // previous = now; 95 | // result = func.apply(this, arguments); 96 | // } 97 | // else if(!timeout) { 98 | // timeout = setTimeout(function() { 99 | // previous = now; 100 | // result = func.apply(this, arguments); 101 | // timeout = null; 102 | // }, wait - now + previous); 103 | // } 104 | // return result; 105 | // } 106 | // } 107 | // window.onscroll = throttle(()=>{console.log('yes')}, 2000); 108 | 109 | // var alert = ()=>{ 110 | // console.log('yes..'); 111 | // } 112 | // var b = _.before(3, alert); 113 | // b(); 114 | // b(); 115 | // b(); 116 | 117 | // var a = { 118 | // name: 'test', 119 | // sayName: function() { 120 | // console.log(this.name); 121 | // } 122 | // }; 123 | // // _.bindAll(a, 'sayName'); 124 | 125 | // var b = a.sayName; 126 | // name = 'window.test'; 127 | // b(); 128 | 129 | // function a(arr) { 130 | // if(arguments.length < 2) 131 | // throw new Error('funciton a require at least 2 parameters!'); 132 | // return arr.concat(Array.prototype.slice.call(arguments, 1)); 133 | // } 134 | 135 | // console.log(a([1, 2], 3)); 136 | 137 | // function a(arr, filter) { 138 | // if(arguments.length < 3) 139 | // throw new Error('function a require at least 3 parameters!'); 140 | // var params = Array.prototype.slice.call(arguments, 2); 141 | // for(var i = 0; i < params.length; i++) { 142 | // if(filter(params[i])) { 143 | // arr.push(params[i]); 144 | // } 145 | // } 146 | // return arr; 147 | // } 148 | // console.log(a([], (x) => { 149 | // return x > 1; 150 | // }, 1, 2, 3, 0)); 151 | // <<<<<<< HEAD 152 | 153 | 154 | // function A(name){ 155 | // this.name = name 156 | // } 157 | // var _A = _.bind(A); 158 | // console.log(new _A('zdm')); 159 | // console.log(new A('zdm')); 160 | 161 | // var _bind = function(func, context) { 162 | // var bound = function() { 163 | // if(this instanceof bound) { 164 | // var obj = new Object(); 165 | // obj.prototype = func.prototype; 166 | // obj.prototype.constructor = func; 167 | // var res = func.call(obj); 168 | // if(typeof res == 'function' || typeof res == 'object' && !!res) 169 | // return res; 170 | // else 171 | // return obj 172 | // } 173 | // else { 174 | // return func.call(context); 175 | // } 176 | // }; 177 | // return bound; 178 | // } 179 | 180 | // var test = {}; 181 | // var B = _bind(function() { 182 | // this.name = 'B'; 183 | // }, test); 184 | // var b = B(); 185 | // var bb = new B(); 186 | // console.log(test); 187 | // console.log(bb); 188 | 189 | 190 | // var flatten = function(array, result) { 191 | // var result = result || []; 192 | // var length = array.length; 193 | // var toString = Object.prototype.toString; 194 | // var type = toString.call(array); 195 | // if(type !== '[object Array]') 196 | // throw new TypeError('The parameter you passed is not a array'); 197 | // else { 198 | // for(var i = 0; i < length; i++) { 199 | // if(toString.call(array[i]) !== '[object Array]') { 200 | // result.push(array[i]); 201 | // } 202 | // else { 203 | // arguments.callee(array[i], result); 204 | // } 205 | // } 206 | // } 207 | // return result; 208 | // } 209 | 210 | // var arr1 = [1,2,3]; 211 | // var arr2 = [1,2,[1,2,[1,[1,2,3],2,3]]]; 212 | // var arr3 = [1,2,[3]] 213 | // // console.log(flatten(arr1)); 214 | // console.log(flatten(arr3)); 215 | 216 | // var intersection = function(arr1, arr2) { 217 | // var length = arr1.length; 218 | // var result = []; 219 | // var i; 220 | // for(i = 0; i < length; i++) { 221 | // if(result.indexOf(arr1[i]) >= 0) 222 | // continue; 223 | // else { 224 | // if(arr2.indexOf(arr1[i]) >= 0) 225 | // result.push(arr1[i]); 226 | // } 227 | // } 228 | // return result; 229 | // } 230 | 231 | // var arr1 = [1,2,3,5]; 232 | // var arr2 = [4,5,6,3]; 233 | // console.log(intersection(arr1, arr2)); 234 | 235 | // _.uniq([1,1,2], true); 236 | 237 | // var uniq = function(array) { 238 | // var set = new Set(array); 239 | // return [...set]; 240 | // } 241 | 242 | // var uniq = function(array, isSorted, func) { 243 | // var result = []; 244 | // var length = array.length; 245 | // var i; 246 | // var seen = []; 247 | // if(isSorted && !func) { 248 | // for(i = 0; i< length; i++) { 249 | // if(array[i] == seen) continue; 250 | // else { 251 | // result.push(array[i]); 252 | // seen = array[i]; 253 | // } 254 | // } 255 | // } 256 | // else if(func){ 257 | // for(i = 0; i < length; i++) { 258 | // if(seen.indexOf(func(array[i])) < 0) { 259 | // seen.push(func(array[i])); 260 | // result.push(array[i]); 261 | // } 262 | // } 263 | // } 264 | // else{ 265 | // for(i = 0; i < length; i++) { 266 | // if(result.indexOf(array[i]) < 0) { 267 | // result.push(array[i]); 268 | // } 269 | // } 270 | // } 271 | // return result; 272 | // }; 273 | // console.log(uniq([1,1,2,2,3,3,3,4], true)); 274 | // var objArr = [{id: 'a'}, {id: 'a'}, {id: 'b'}]; 275 | //var persons = [{name: 'dm', age: 22}, {name: 'dm', age: 23}, {name: 'dm', age: 22}]; 276 | 277 | 278 | // var union = function() { 279 | // var arrays = arguments; 280 | // var length = arguments.length; 281 | // var result = []; 282 | // var i; 283 | // for(i = 0; i < length; i++) { 284 | // var arr = arrays[i]; 285 | // var arrLength = arrays[i].length; 286 | // for(var j = 0; j < arrLength; j++) { 287 | // if(result.indexOf(arr[j]) < 0) { 288 | // result.push(arr[j]); 289 | // } 290 | // } 291 | // } 292 | // return result; 293 | // } 294 | 295 | // console.log(union([1,1,2], [2,3], [1,3,4])); 296 | 297 | // var difference = function(arr1, arr2) { 298 | // var length = arr1.length; 299 | // var i; 300 | // var result = []; 301 | // for(i = 0; i < length; i++) { 302 | // if(arr2.indexOf(arr1[i]) < 0) { 303 | // result.push(arr1[i]); 304 | // } 305 | // } 306 | // return result; 307 | // } 308 | // console.log(difference([1,1,1], [1,2])); 309 | 310 | // function __(obj) { 311 | // if(obj.wrapped != void 0) { 312 | // return obj; 313 | // } 314 | // if(!(this instanceof __)) { 315 | // return new __(obj); 316 | // } 317 | // this.wrapped = obj; 318 | // } 319 | 320 | // __.toString = function(obj) { 321 | // return obj.name; 322 | // }; 323 | 324 | // __.prototype.toString = function(obj) { 325 | // return obj.wrapped.name 326 | // }; 327 | 328 | // const tmp = __({name: 'none'}); 329 | // const name = __.toString({name: 'none'}); 330 | // console.log(tmp); 331 | 332 | // (function () { 333 | // var x = function() { 334 | // console.log('x'); 335 | // }; 336 | // x.name = 'x'; 337 | // global.x = x; 338 | // }()) 339 | // console.log(x); 340 | 341 | // const a = 1 && 2 && 3 || 2 && 3; 342 | // // a === 3 343 | // const b = 1 && false && 2 || 2 && 3; 344 | // // b === 3 345 | // const c = 1 && false && 2 || false && 2 346 | // // c === false 347 | // const d = 1 && false && 2 || 0 && 2 348 | // // d === 0 349 | // const e = 1 && false && 2 || 1 && 2 350 | // // e === 2 351 | 352 | const str = 'myzdmnameiszdm'.replace(/(zdm)|(name)/g, function() { 353 | console.log(arguments); 354 | return '钟德鸣' 355 | }); 356 | console.log(str); 357 | 358 | // var compiled = _.template("hello: <%- name %>"); 359 | // compiled({name: 'moe'}); -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/bounce-loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/bounce-loading.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/checkbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/checkbox.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/custom-scrollbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/custom-scrollbar.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/custom-text-selection.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/custom-text-selection.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/donut-loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/donut-loading.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/gradient-text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/gradient-text.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/hover-underline-animation-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/hover-underline-animation-1.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/hover-underline-animation-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/hover-underline-animation-2.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/hover-underline-animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/hover-underline-animation.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/not-selector.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/not-selector.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/overflow-scroll-gradient.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/overflow-scroll-gradient.gif -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/system-font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/system-font.png -------------------------------------------------------------------------------- /UsefulSnippet/CSSSnippet/gif/truncate-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSSSnippet/gif/truncate-text.png -------------------------------------------------------------------------------- /UsefulSnippet/CSharpTools.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/UsefulSnippet/CSharpTools.txt -------------------------------------------------------------------------------- /UsefulSnippet/JSSnippet/debounce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 函数去抖 3 | * @param {要去抖的函数} fn 4 | * @param {延迟时间(毫秒)} delay 5 | */ 6 | const debounce = function(fn, delay) { 7 | let timer = null; 8 | return function(context, ...args) { 9 | clearTimeout(timer); 10 | timer = setTimeout(() => { 11 | fn.apply(context, args); 12 | }, delay); 13 | } 14 | } -------------------------------------------------------------------------------- /UsefulSnippet/JSSnippet/throttle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 函数节流 3 | * @param {要节流的函数} fn 4 | * @param {没多久跑一次(毫秒)} delay 5 | */ 6 | const throttle = function(fn, delay) { 7 | let last = +new Date(); 8 | return function(context, ...args) { 9 | let now = +new Date(); 10 | if (now - last >= delay) { 11 | fn.apply(context, args); 12 | last = now; 13 | } 14 | }; 15 | } -------------------------------------------------------------------------------- /Webpack-React/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react"] 3 | } -------------------------------------------------------------------------------- /Webpack-React/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /Webpack-React/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const htmlWebpackPlugin = require('html-webpack-plugin'); 3 | const cleanWebpackPlugin = require('clean-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: { 7 | index: path.resolve(__dirname, '../src/index.js'), 8 | venders: ['react', 'react-dom', 'redux', 'react-redux'] 9 | }, 10 | output: { 11 | filename: '[name]_[hash].js', 12 | path: path.resolve(__dirname, '../dist/') 13 | }, 14 | resolve: { 15 | alias: { 16 | '@': path.resolve(__dirname, '../src'), 17 | 'components': path.resolve(__dirname, '../src/components') 18 | }, 19 | extensions: ['.json', '.js', '.jsx', '.css'] 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.jsx?$/, 25 | use: 'babel-loader', 26 | exclude: /node_modules/ 27 | }, 28 | { 29 | test: /\.css$/, 30 | use: ['style-loader', 'css-loader'] 31 | }, 32 | { 33 | test: /\.less$/, 34 | use: ['style-loader', 'css-loader', 'less-loader'] 35 | } 36 | ] 37 | }, 38 | plugins: [ 39 | new htmlWebpackPlugin({ 40 | inject: true, 41 | template: path.resolve(__dirname, '../index.html') 42 | }), 43 | new cleanWebpackPlugin(['dist']) 44 | ] 45 | } -------------------------------------------------------------------------------- /Webpack-React/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const baseConfig = require('./webpack.base.conf'); 3 | const path = require('path'); 4 | 5 | module.exports = merge(baseConfig, { 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | devServer: { 9 | port: 9999, 10 | open: true, 11 | contentBase: path.resolve(__dirname, '../dist') 12 | } 13 | }) -------------------------------------------------------------------------------- /Webpack-React/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Webpack-React/build/webpack.prod.conf.js -------------------------------------------------------------------------------- /Webpack-React/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Webpack React 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /Webpack-React/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Webpack-React", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack-dev-server --config ./build/webpack.dev.conf.js", 9 | "build": "webpack --config ./build/webpack.prod.conf.js" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "immutable": "^4.0.0-rc.12", 16 | "react": "^16.6.3", 17 | "react-dom": "^16.6.3", 18 | "react-redux": "^5.1.1", 19 | "redux": "^4.0.1" 20 | }, 21 | "devDependencies": { 22 | "babel-core": "^6.26.3", 23 | "babel-loader": "^7.1.5", 24 | "babel-preset-react": "^6.24.1", 25 | "clean-webpack-plugin": "^1.0.0", 26 | "css-loader": "^1.0.1", 27 | "html-webpack-plugin": "^3.2.0", 28 | "less": "^3.9.0", 29 | "less-loader": "^4.1.0", 30 | "style-loader": "^0.23.1", 31 | "webpack": "^4.26.1", 32 | "webpack-cli": "^3.1.2", 33 | "webpack-dev-server": "^3.1.10", 34 | "webpack-merge": "^4.1.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Webpack-React/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Header from 'components/Header'; 3 | import ListPanel from 'components/ListPanel'; 4 | import Footer from 'components/Footer'; 5 | 6 | class App extends Component { 7 | render() { 8 | return
9 |
10 | 11 |
12 |
13 | } 14 | } 15 | 16 | export default App; -------------------------------------------------------------------------------- /Webpack-React/src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Footer.less'; 3 | 4 | class Footer extends React.Component { 5 | render() { 6 | return
7 | 8 |
9 | } 10 | } 11 | 12 | export default Footer; -------------------------------------------------------------------------------- /Webpack-React/src/components/Footer.less: -------------------------------------------------------------------------------- 1 | .footer { 2 | background-color: lightblue; 3 | height: 3rem; 4 | } -------------------------------------------------------------------------------- /Webpack-React/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Header.less'; 3 | 4 | export default class Header extends React.Component { 5 | render() { 6 | return
7 |

TodoList

8 |
9 | } 10 | } -------------------------------------------------------------------------------- /Webpack-React/src/components/Header.less: -------------------------------------------------------------------------------- 1 | .header { 2 | background-color: lightblue; 3 | height: 4rem; 4 | padding: 1rem; 5 | box-sizing: border-box; 6 | } -------------------------------------------------------------------------------- /Webpack-React/src/components/ListPanel.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Webpack-React/src/components/ListPanel.css -------------------------------------------------------------------------------- /Webpack-React/src/components/ListPanel.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { todoItems } from '@/store/actions'; 4 | 5 | class _ListPanel extends React.Component { 6 | constructor() { 7 | super(); 8 | this.state = { 9 | newItem: '' 10 | }; 11 | this.addTodoItems = this.addTodoItems.bind(this); 12 | this.inputItem = this.inputItem.bind(this); 13 | } 14 | render() { 15 | return
16 |
17 | 18 | 19 |
20 |
21 | { 22 | this.props.items.map((item, index) => { 23 | return
24 | {item} 25 |
26 | }) 27 | } 28 |
29 |
30 | } 31 | addTodoItems() { 32 | this.props.addTodoItems(this.state.newItem); 33 | } 34 | inputItem(e) { 35 | this.setState({ 36 | newItem: e.target.value 37 | }); 38 | } 39 | } 40 | 41 | const mapState2Props = (state) => { 42 | return { 43 | items: state.todoItems 44 | }; 45 | } 46 | 47 | const mapDispatch2Props = dispatch => { 48 | return { 49 | addTodoItems(data) { 50 | dispatch(todoItems('ADD_TODOITEMS', data)); 51 | }, 52 | delTodoItems(data) { 53 | dispatch(todoItems('DELETE_TODOITEMS', data)); 54 | } 55 | }; 56 | }; 57 | 58 | const ListPanel = connect(mapState2Props, mapDispatch2Props)(_ListPanel); 59 | 60 | export default ListPanel; -------------------------------------------------------------------------------- /Webpack-React/src/index.css: -------------------------------------------------------------------------------- 1 | html, body, h1, h2, h3, h4, h5, h6, div, p { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | html { 7 | font-size: 16px; 8 | } 9 | 10 | 11 | h1 { 12 | color: lightcoral; 13 | } 14 | 15 | .stretch { 16 | width: 100% 17 | } -------------------------------------------------------------------------------- /Webpack-React/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | import store from '@/store/store'; 6 | import { Provider } from 'react-redux'; 7 | 8 | ReactDOM.render( 9 | 10 | , document.getElementById('app')); -------------------------------------------------------------------------------- /Webpack-React/src/store/actions.js: -------------------------------------------------------------------------------- 1 | export function todoItems(type, data) { 2 | return { 3 | type, data 4 | } 5 | } -------------------------------------------------------------------------------- /Webpack-React/src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { fromJS, List } from 'immutable'; 3 | 4 | function todoItems(todoItems = [], action) { 5 | let _todoItems = List(todoItems); 6 | let { type, data } = action; 7 | switch(type) { 8 | case 'ADD_TODOITEMS': _todoItems = _todoItems.push(data);break; 9 | case 'DELETE_TODOITEMS': _todoItems = _todoItems.splice(_todoItems.indexOf(data), 1);break; 10 | } 11 | console.log("_todoItems=>" + _todoItems); 12 | console.log("todoItems=>" + _todoItems.toJS()); 13 | return _todoItems.toJS(); 14 | } 15 | 16 | export default combineReducers({ 17 | todoItems 18 | }); -------------------------------------------------------------------------------- /Webpack-React/src/store/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import reducers from './reducers'; 3 | 4 | export default createStore(reducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()); -------------------------------------------------------------------------------- /Webpack-Vue/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }] 9 | ] 10 | } -------------------------------------------------------------------------------- /Webpack-Vue/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ -------------------------------------------------------------------------------- /Webpack-Vue/build/build.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const config = require('./webpack.prod.conf'); 3 | 4 | webpack(config, (err, stats) => { 5 | if (err || stats.hasErrors()) { 6 | // 在这里处理错误 7 | console.error(err); 8 | return; 9 | } 10 | // 处理完成 11 | console.log(stats.toString({ 12 | chunks: false, // 使构建过程更静默无输出 13 | colors: true // 在控制台展示颜色 14 | })); 15 | }); -------------------------------------------------------------------------------- /Webpack-Vue/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const AutoDllPlugin = require('autodll-webpack-plugin'); 5 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 6 | 7 | module.exports = { 8 | entry: { 9 | bundle: path.resolve(__dirname, '../src/index.js') 10 | }, 11 | output: { 12 | path: path.resolve(__dirname, '../dist'), 13 | filename: '[name].[hash].js' 14 | }, 15 | resolve: { 16 | extensions: ['*', '.js', '.json', '.vue'], 17 | alias: { 18 | 'vue$': 'vue/dist/vue.esm.js', 19 | '@': path.resolve(__dirname, '../src'), 20 | } 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.js$/, 26 | use: ['babel-loader'], 27 | exclude: /node_modules/ 28 | }, 29 | { 30 | test: /\.vue$/, 31 | loader: 'vue-loader' 32 | }, 33 | { 34 | test: /\.(png|svg|jpg|gif)$/, 35 | use: [ 36 | 'file-loader' 37 | ] 38 | }, 39 | { 40 | test: /\.(woff|woff2|eot|ttf|otf)$/, 41 | use: [ 42 | 'file-loader' 43 | ] 44 | } 45 | ] 46 | }, 47 | plugins: [ 48 | new HtmlWebpackPlugin({ 49 | template: path.resolve(__dirname, '../index.html') 50 | }), 51 | new AutoDllPlugin({ 52 | inject: true, // will inject the DLL bundle to index.html 53 | debug: true, 54 | filename: '[name]_[hash].js', 55 | path: './dll', 56 | entry: { 57 | vendor: ['vue', 'vue-router', 'vuex'] 58 | } 59 | }), 60 | new VueLoaderPlugin(), 61 | new webpack.optimize.SplitChunksPlugin() 62 | ] 63 | } -------------------------------------------------------------------------------- /Webpack-Vue/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const webpack = require('webpack'); 3 | const baseConfig = require('./webpack.base.conf'); 4 | const path = require('path'); 5 | 6 | module.exports = merge(baseConfig, { 7 | mode: 'development', 8 | devtool: 'inline-source-map', 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.css$/, 13 | use: [ 14 | 'vue-style-loader', 15 | 'css-loader', 16 | 'postcss-loader' 17 | ] 18 | }, 19 | { 20 | test: /\.styl(us)$/, 21 | use: ['vue-style-loader', 'css-loader', 'postcss-loader', 'stylus-loader'] 22 | }, 23 | ] 24 | }, 25 | devServer: { 26 | contentBase: path.resolve(__dirname, '../dist'), 27 | hot: true, 28 | open: true 29 | }, 30 | plugins: [ 31 | new webpack.HotModuleReplacementPlugin() 32 | ] 33 | }) -------------------------------------------------------------------------------- /Webpack-Vue/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const baseConfig = require('./webpack.base.conf'); 3 | const path = require('path'); 4 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 6 | 7 | module.exports = merge(baseConfig, { 8 | mode: 'production', 9 | devtool: 'source-map', 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.css$/, 14 | use: [ 15 | MiniCssExtractPlugin.loader, 16 | 'css-loader', 17 | 'postcss-loader' 18 | ] 19 | }, 20 | { 21 | test: /\.styl(us)$/, 22 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'stylus-loader'] 23 | }, 24 | ] 25 | }, 26 | plugins: [ 27 | new CleanWebpackPlugin(['dist/'], { 28 | root: path.resolve(__dirname, '../'), 29 | verbose: true, 30 | dry: false 31 | }), 32 | new MiniCssExtractPlugin({ 33 | filename: "[name].css", 34 | chunkFilename: "[id].css" 35 | }) 36 | ] 37 | }) -------------------------------------------------------------------------------- /Webpack-Vue/imgs/云之家图片20181012093335.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Webpack-Vue/imgs/云之家图片20181012093335.png -------------------------------------------------------------------------------- /Webpack-Vue/imgs/云之家图片20181012093521.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Webpack-Vue/imgs/云之家图片20181012093521.png -------------------------------------------------------------------------------- /Webpack-Vue/imgs/使用插件后的第二次打包.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Webpack-Vue/imgs/使用插件后的第二次打包.png -------------------------------------------------------------------------------- /Webpack-Vue/imgs/使用插件后第一次打包.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/Webpack-Vue/imgs/使用插件后第一次打包.png -------------------------------------------------------------------------------- /Webpack-Vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Webpack Vue Demo 5 | 6 | 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /Webpack-Vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-vue", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "node ./build/build.js", 9 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autodll-webpack-plugin": "^0.4.2", 15 | "autoprefixer": "^9.1.5", 16 | "babel-core": "^6.26.3", 17 | "babel-loader": "^7.1.5", 18 | "babel-polyfill": "^6.26.0", 19 | "babel-preset-env": "^1.7.0", 20 | "clean-webpack-plugin": "^0.1.19", 21 | "css-loader": "^1.0.0", 22 | "file-loader": "^2.0.0", 23 | "html-webpack-plugin": "^3.2.0", 24 | "mini-css-extract-plugin": "^0.4.4", 25 | "postcss-loader": "^3.0.0", 26 | "stylus": "^0.54.5", 27 | "stylus-loader": "^3.0.2", 28 | "vue-loader": "^15.4.2", 29 | "vue-style-loader": "^4.1.2", 30 | "vue-template-compiler": "^2.5.17", 31 | "webpack": "^4.20.2", 32 | "webpack-cli": "^3.1.2", 33 | "webpack-dev-server": "^3.1.9", 34 | "webpack-merge": "^4.1.4" 35 | }, 36 | "dependencies": { 37 | "vue": "^2.5.17", 38 | "vue-router": "^3.0.1", 39 | "vuex": "^3.0.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Webpack-Vue/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer') 4 | ] 5 | } -------------------------------------------------------------------------------- /Webpack-Vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 21 | -------------------------------------------------------------------------------- /Webpack-Vue/src/components/tab.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /Webpack-Vue/src/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | font-size: 16px; 5 | padding: 0; 6 | margin: 0 7 | } 8 | #app { 9 | width: 100%; 10 | height: 100%; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | } -------------------------------------------------------------------------------- /Webpack-Vue/src/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import App from './App'; 4 | 5 | import './index.css'; 6 | 7 | new Vue({ 8 | el: '#app', 9 | components: { 10 | App 11 | }, 12 | template: '' 13 | }); 14 | 15 | if (module.hot) { 16 | module.hot.accept(); 17 | } -------------------------------------------------------------------------------- /图解HTTP/1.md: -------------------------------------------------------------------------------- 1 | ## 图解 HTTP 笔记(一)——了解 Web 及网络基础 2 | 3 | > 本章内容:Web 建立在何种技术之上,HTTP 协议如何诞生并发展? 4 | 5 | ### 一、Web 基于 HTTP 通信 6 | 7 | Web 使用一种名为 HTTP (HyperText Transfer Protocol,超文本传输协议)的协议作为规范,协议指的是对一些规则的约定。可以说 Web 是建立在 HTTP 协议上通信的。 8 | 9 | ### 二、HTTP 的诞生 10 | 11 | #### 2.1 诞生背景 12 | 13 | 这一节主要讲 HTTP 的诞生背景,了解 HTTP 协议的诞生背景有利于我们学习理解 HTTP 协议。 14 | 15 | ![httpbackground](./pics/httpbackground.png) 16 | 17 | HTTP 起初的诞生是为了知识共享,最初设想的基本理念是借助多文档之间的相互关联形成的超文本,连成可互相参阅的 WWW(World Wide Web,万维网)。 18 | 19 | 现在已经提出了三项构建技术: 20 | 21 | * 把 SGML,即 `Standard Generalized Markup Language`(标准通用标记语言) 作为页面的文本标记语言的 HTML(HyperText Markup Language,超文本标记语言); 22 | * 作为文档传输协议的 HTTP; 23 | * 指定文档所在地址的 URL(Uniform Resource Location,统一资源定位符)。 24 | 25 | 可以看出来上面三项分别构成了知识共享的内容(HTML)、知识共享的方式(HTTP 传输)和知识共享的位置。 26 | 27 | #### 2.2 发展缓慢的 HTTP 28 | 29 | **HTTP/0.9** 30 | 31 | HTTP 诞生于 **1990** 年,此时还没有作为正式标准,此时的 HTTP 含有 HTTP/1.0 之前版本的意思,所以成为 HTTP/0.9。 32 | 33 | **HTTP/1.0** 34 | 35 | **1996 年 5 月**,HTTP 被正式作为标准,版本被命名为 HTTP/1.0。 36 | 37 | **HTTP/1.1** 38 | 39 | **1997 年 1 月**发布了 HTTP/1.1 版本,至今仍然是主流的 HTTP 协议。 40 | 41 | 由此可见,作为 Web 文档传输协议的 HTTP 协议版本更新十分缓慢,新一代的 HTTP 2.0 还在制定中,但是要大规模覆盖,还需要假以时日。 42 | 43 | > [HTTP 2.0]() 44 | > 45 | > [HTTP/2.0 相比1.0有哪些重大改进?]() 46 | 47 | HTTP 协议起初的诞生是为了解决文本传输的问题,现在已经超出了 Web 的界限,运用在许多场景。 48 | 49 | #### 2.3 网络基础 TCP/IP 50 | 51 | 计算机之间要进行通信,需要基于相同的方法,比如如何探测目标、哪一边先发起通信、使用什么语言通信、什么时候结束通信等等。所有的这一切都需要事先约定好,所以约定的规则就被称为协议(protocol)。 52 | 53 | 一种说法认为 TCP/IP 指的是 TCP 和 IP 两种协议,另一种说法认为是指在 IP 协议通信的过程中所用到的协议族的同称,该书偏向于后一种说法。 54 | 55 | ##### 2.3.1 TCP/IP 的分层管理 56 | 57 | TCP/IP 协议族最重要的一点就是分层管理,通常来说分为四层:应用层、传输层、网络层和数据链路层。 58 | 59 | 分层管理的最大好处就是将各个阶段的数据传输进行了隔离解耦,类似于编程时的模块化。处于某一层的应用只需要考虑该层所需要完成的任务,而不用管其他多余的事情。这样做使得各层协议的实现也变得自由了,要修改某一层的协议时,只需要修改该层的协议而不用涉及到其他层级的协议。 60 | 61 | 各层的作用如下: 62 | 63 | * **应用层**,决定向用户提供应用服务时的通信活动。HTTP、FTP(File Transfer Protocol,文件传输协议)和 DNS(Domain Name System,域名解析系统)都属于该层。 64 | * **传输层**,相对于上一层的应用层,该层提供处于网络连接中的两台计算机的数据传输。该层协议主要是 TCP(Transmission Control Protocol) 和 UDP(User Data Protocol)。 65 | * **网络层**,用于处理网络上流动的数据包(数据传输的最小单位),该层规定了通过怎样的路径到达对方计算机,并把数据传送给对方。该层协议主要是 IP 协议和 ARP(Address Solution Protocol) 协议。 66 | * **链路层**,用于处理网络连接的硬件部分,包括操作系统、设备驱动等,硬件上的范畴基本都在链路层的范围内。该层主要协议为以太网协议(Ethernet)。 67 | 68 | > 关于网络分层的更多内容: 69 | 70 | ##### 2.3.2 TCP/IP 通信传输流 71 | 72 | ![httpstream](./pics/httpstream.png) 73 | 74 | 利用 TCP/IP 通信时,会通过分层顺序与对方进行通信。发送方的数据流从上往下走,接收方的数据流从下往上走。 75 | 76 | 在传输过程的每一层中,都会对数据进行装箱和拆箱。发送方在发送数据时,在 HTTP 应用层会添加 HTTP 传输首部,在传输层会添加 TCP 传输首部,在链路层会添加以太网首部。接收方会在链路层收到传输的数据,然后从下层往上层开始拆箱。一层一层的去掉首部,最后剩下发送方最初发送的数据。到达应用层时就算真正接收到了客户端发送的数据了。 77 | 78 | ![httptransfer](./pics/httptransfer.png) 79 | 80 | 这种把数据信息包装起来的方法称为封装(encapsulate)。 81 | 82 | ##### 2.3.3 与 HTTP 关心密切的协议:IP、TCP 和 DNS 83 | 84 | **IP 协议** 85 | 86 | IP 协议负责网络传输,处于网络层。IP 不是 IP 地址,我们通常说的 IP 是一种协议。IP 地址指明了节点被分配到的地址,MAC 地址是网卡所属的固定地址,每块网卡出厂时,都有一个世界独一无二的MAC地址,长度是48个二进制位,用 12个十六进制位数表示。IP 地址可以和 MAC 地址配对,但是同一台机器 IP 地址可能会变,MAC 是固定不变的。 87 | 88 | IP 间的通信依赖 MAC 地址,实际生活中同一局域网内的网络通信比较少,大部分都是广域网的通信,数据需要经过多个节点路由的转发才能到达目的地。而在中转时,会利用下一站中转设备的 MAC 地址来搜索下一个中转目标。这时会采用 ARP 协议(Address Resolution Protocol),根据对方的 IP 地址即可查出对应的 MAC 地址,但是两台设备必须在同一个子网内。 89 | 90 | 数据在网络中的传输类似于现实生活中的快递运输,中转设备就类似于物流中转中心。快递到达一个中转站之后,中转站会判断下一个中转站的地址然后继续进行派送,直到到达客户所在的中转站。 91 | 92 | **TCP 协议** 93 | 94 | TCP 位于传输层,提供可靠的字节流服务。 95 | 96 | 字节流服务(Byte Stream Service)是指为了传输方便,把大块的数据切割成以报文段(segment)为单位的数据包进行管理。TCP 的可靠之处在于它会确保数据被送到了接收方。 97 | 98 | 为了确保数据被准确无误地送到了接收方,TCP 采用了三次握手(three-way handshaking)的策略。握手过程中包含了两个重要的标志(flag)——SYN(synchronize)和 ACK(acknowledgement)。 99 | 100 | 发送端会先发送一个带有 SYN 的数据包给对方,接收端接收到数据之后返回一个 带有 SYN/ACK 标志的数据包给发送端,最后发送端再传回一个带有 ACK 标志的数据包表示“握手”结束。如果握手意外结束,那么 TCP 协议会再次以相同的顺序发送相同的数据包。 101 | 102 | ![tcphandshaking](./pics/tcphandshaking.png) 103 | 104 | 除了以上三次握手,TCP 还有其他方法确保可靠性。 105 | 106 | **DNS** 107 | 108 | DNS 也位于应用层,它提供域名解析服务。能够把域名解析为 IP 地址。 109 | 110 | 各种协议与 HTTP 之间的关系: 111 | 112 | ![relationship](./pics/relationship.png) 113 | 114 | ##### 2.3.4 URI 和 URL 115 | 116 | URI(Uniform Resource Identifier,统一资源标识符)RFC2396 对名称中的三个单词做了解释: 117 | 118 | * Uniform,规定统一的格式可方便处理不同的资源,而不用根据上下文环境来识别资源指定的访问方式,另外加入新的协议(http、ftp)也更容易。 119 | * Resource,指任何可标识的东西。不仅限于文档、图片或服务。 120 | * Identifier,表示可标识的对象,也称为标识符。 121 | 122 | 除了 HTTP 外,URI 还可以使用 mailto、ftp、telnet 等协议方案。 123 | 124 | URI 用字符串标识某一互联网资源,而 URL 用字符串标识资源的地点。所以 URL 是 URI 的子集。 125 | 126 | **URI 的格式** 127 | 128 | ![urlformat](./pics/urlformat.png) 129 | 130 | 其中: 131 | 132 | * **登录信息(认证)**指定用户名密码作为从服务端获取资源时的登录信息,此项可选。 133 | * **服务器地址**,可以是域名、IP。 134 | * **服务器端口号**,指定服务器连接的网络端口号,此项可选,省略时用默认端口号。 135 | * **带层次的文件路径**,指定服务器上资源的文件路径。 136 | * **查询字符串**,通过查询字符串可以传入参数。 137 | * **片段标识符**,指定已获取资源中的子资源(文档内的某个位置)。 -------------------------------------------------------------------------------- /图解HTTP/2.md: -------------------------------------------------------------------------------- 1 | ## 图解 HTTP 笔记(二)——简单的 HTTP 协议 2 | 3 | > 本章主要以 HTTP 1.0 为例,讲解 HTTP 协议的基本结构。 4 | 5 | 在两台计算机之间使用 HTTP 协议进行通讯时,在一条通讯线路上必定有一端是客户端,另一端则是服务器端。 6 | 7 | 请求访问文本或图像等资源的一端成为客户端,而提供资源响应的一端成为服务器端。 8 | 9 | HTTP 协议规定,请求从客户端发出,最后服务器端响应该请求并返回。 10 | 11 | ### 一、请求和响应报文的组成 12 | 13 | 某个客户端发出的请求如下: 14 | 15 | ``` 16 | GET /index.htm HTTP/1.1 17 | Host: hackr.jp 18 | ``` 19 | 20 | 其中,GET 表示请求访问服务器的类型,称为方法(method)。随后的字符串`/index.htm`表示 请求访问的资源对象,也叫作请求 URI。最后的 HTTP/1.1 表示所使用的 HTTP 版本号,用于提示客户端所使用的 HTTP 版本。 21 | 22 | 所以上面这段请求头表示使用 GET 方法请求 hackr.jp 服务器上的 index.htm 资源,客户端使用的 HTTP 版本是 1.1。 23 | 24 | 总结而言:**请求报文是由请求方法、请求 URI、协议版本、可选的请求首部字段和内容实体构成的**。 25 | 26 | ![requestcontent](./pics/requestcontent.png) 27 | 28 | 上图解释了请求报文的组成。 29 | 30 | 接收到请求的服务器,返回了如下内容: 31 | 32 | ``` 33 | HTTP/1.1 200 OK 34 | Date: Tue, 10 Jul 2012 06:50:15 GMT 35 | Content-Length: 362 36 | Content-Type: text/html 37 | 38 | ...... 39 | ``` 40 | 41 | 其中,HTTP/1.1 表示服务器对应的 HTTP 版本;后面的 `200 OK` 表示请求的处理结果的状态码(status code)。下一行表示响应创建的时间,是响应首部字段(header field)的一个属性。空一行之后的内容表示资源主体的实体(entity body)。 42 | 43 | 总结:**响应报文基本上由协议版本、状态码(表示请求成功或者失败的数字代码)、用以解释状态码的原因短语、可选的响应首部字段以及实体主体构成**。 44 | 45 | ![responsecontent](./pics/responsecontent.png) 46 | 47 | 上图是响应报文的组成。 48 | 49 | ### 二、HTTP 是无状态协议 50 | 51 | HTTP 是一种不保存状态,即无状态(stateless)的协议。HTTP 自身不对请求和响应之间的通信状态进行保存。每次有新的请求建立时,就会有对应的响应产生,与之前或者之后的请求都没有任何关系。 52 | 53 | HTTP 的无状态特点,既有好处也有坏处。好处在于无状态的特点使得 HTTP 不用维护客户端状态,大大简化了协议内容和服务器的工作,确保了协议的可伸缩性。坏处在于随着 Web 的发展,网站应用越来越复杂,无状态的协议需要通过其他手段维持客户端状态(登录信息),比如 Cookie 技术,就是从 HTTP 1.1 开始引入的维持状态的手段。 54 | 55 | ### 三、告知服务器意图的 HTTP 方法 56 | 57 | HTTP 1.1 主要包含了以下可以使用的方法。 58 | 59 | * **GET** 方法用来请求访问已被 URI 识别的资源。指定的资源经过服务器解析后返回响应内容。 60 | * **POST **方法用来传输实体的主体。虽然用 GET 方法也可以传输实体的主体,但是一般不用 GET 方法。**虽说 POST 方法的功能和 GET 相似,但是 POST 的主要目的并不是获取响应的主体内容**。 61 | * **PUT** 方法主要用来传输文件,就像 FTP 的文件上传一样,要求在请求报文的主体中包含文件内容,然后保存到 URI 指定位置。由于 HTTP 1.1 的 PUT 方法不带验证机制,任何人都可以上传文件,所以具有安全隐患,一般不采用 PUT 方法。 62 | * **DELETE** 方法主要用于删除服务器上的文件,与 PUT 相反,DELETE 方法删除 URI 指定的服务器文件。同样由于存在安全隐患,所以一般不采用 DELETE 方法。 63 | * **HEAD** 方法主要用于确认 URI 的有效性及资源更新的日期时间等,不会返回报文的主体内容。 64 | * **OPTIONS** 方法用来查询针对请求 URI 指定的资源所支持的方法。 65 | * **TRACE** 方法用来追踪路径,发送请求时,在请求首部字段中加入 `Max-Forwards` 字段,值为数字,每经过一个服务器,该字段就会减一,当到达某个使该字段为 0 的服务器时,就会返回状态码为 200 OK 的响应。通过 TRACE 方法可以查询发送出去的请求是怎样被加工/篡改的。TRACE 方法本身使用场景较少,而且存在跨站追踪攻击的隐患,所以使用场景更加少了。 66 | * **CONNECT** 方法表示要求用隧道协议连接代理。在与代理服务器通信时,实现用隧道协议进行 TCP 通信,主要使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)。 67 | 68 | 需要注意的是方法名区分大小写,记得要使用大写字母。 69 | 70 | 下表是 HTTP 1.0 和 HTTP 1.1 支持的一些方法。 71 | 72 | | 方法 | 说明 | 支持的 HTTP 协议版本 | 73 | | :------ | ---------------------- | -------------------- | 74 | | GET | 获取资源 | 1.0/1.1 | 75 | | POST | 传输实体主体 | 1.0/1.1 | 76 | | PUT | 传输文件 | 1.0/1.1 | 77 | | HEAD | 获得报文首部 | 1.0/1.1 | 78 | | DELETE | 删除文件 | 1.0/1.1 | 79 | | OPTIONS | 询问支持的方法 | 1.1 | 80 | | TRACE | 追踪路径 | 1.1 | 81 | | CONNECT | 要求用隧道协议连接代理 | 1.1 | 82 | | LINK | 建立和资源之间的联系 | 1.0 | 83 | | UNLINK | 断开连接关系 | 1.0 | 84 | 85 | 其中比较常用的包括:`GET`、`POST`、`OPTIONS` 方法。 86 | 87 | ### 四、持久连接 88 | 89 | 我们知道 HTTP 协议基于 TCP 协议,而 TCP 协议每次建立之前都会进行“三次握手”,所以如果每次发送 HTTP 请求都要建立 TCP 连接的话,会造成过多的“握手”浪费服务器资源。而 HTTP 协议的初始版本中,每次 HTTP 通信都需要重新建立 TCP 请求,这是一个很大的缺点。 90 | 91 | 为了解决上述问题,HTTP 1.1 引入了持久连接(HTTP Persistent Connections,也成为 HTTP keep-alive 或者 HTTP connection reuse)。持久连接的特点是只要任意一方没有提出断开 TCP 连接,就会一直维持 TCP 的连接状态。 92 | 93 | 持久连接的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器的负载。**在 HTTP 1.1 中,所有的连接默认都是持久连接**。 94 | 95 | ### 五、管线化 96 | 97 | 持久连接使得多数请求以管线化(pipeline)方式发送成为可能。以前发送请求后需要等待响应之后才能发送下一个请求,但是管线化技术出现后,不同等待响应就可以发起下一个请求,这样能够做到多个请求并行发送,大大减少了页面加载的时间。 98 | 99 | ### 六、使用 Cookie 管理状态 100 | 101 | 之前提到过 HTTP 是无状态协议,我们通过 Cookie 维持客户端状态。 102 | 103 | Cookie 会根据从服务端发送的响应报文内的一个叫做 `Set-Cookie` 的首部字段信息,通知客户端保存 Cookie。当下次客户端再往该服务器发送请求时,客户端会自动在请求报文中加入 Cookie 值后发送出去。 104 | 105 | 服务器发现从客户端发送过来的 Cookie 之后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息。 106 | 107 | 设置 Cookie 的响应报文: 108 | 109 | ``` 110 | HTTP/1.1 200 OK 111 | Date: Thu, 12 Jul 2012 07:12:20 GMT 112 | Server: Apache 113 | 115 | Content-Type: text/plain; charset=UTF-8 116 | ``` 117 | 118 | 携带有 Cookie 信息的请求报文: 119 | 120 | ``` 121 | GET /image/ HTTP/1.1 122 | Host: hackr.jp 123 | Cookie: sid=1342077140226724 124 | ``` 125 | 126 | -------------------------------------------------------------------------------- /图解HTTP/3.md: -------------------------------------------------------------------------------- 1 | ## 图解 HTTP 笔记(三)—— HTTP 报文内的 HTTP 信息 2 | 3 | > 本章主要讲解请求和响应是如何运作的 4 | 5 | ### 一、HTTP 报文 6 | 7 | **用于 HTTP 协议交互的信息被称为 HTTP 报文,客户端的 HTTP 报文叫做请求报文,服务器端的叫做响应报文**。 8 | 9 | **HTTP 报文大致可分为报文首部和报文主体两块,两者通过空行划分(CR + LF),通常并不一定要有报文主体** 10 | 11 | > CR:Carriage Return,回车符,16 进制的 0x0d 12 | > 13 | > LF:Line Feed,换行符,16 进制的 0x0a 14 | 15 | ![httpbody](./pics/httpbody.png) 16 | 17 | 下图展示了请求报文和响应报文的结构: 18 | 19 | ![bodystructure](./pics/bodystructure.png) 20 | 21 | 其中: 22 | 23 | * **请求行** 包含用于请求的方法,请求 URL 和 HTTP 版本。 24 | * **状态行** 包含表明响应结果的状态码,原因短语和 HTTP 版本。 25 | * **首部字段** 包含表示请求和响应的各种条件和属性的各类首部。一般包括通用首部、请求首部、响应首部和实体首部。 26 | * **其他** 包含一些未在 RFC 中定义的首部(Cookie 等)。 27 | 28 | ### 二、通过编码提升传输速率 29 | 30 | HTTP 在传输时可以按照原始数据直接传输,也可以预先将数据进行压缩后再传输。编码压缩后可以减少传输的数据量,能够提升传输速率,但是会压缩过程会消耗更多的 CPU 资源。 31 | 32 | #### 2.1 报文主体和实体主体的差异 33 | 34 | * **报文(message)** 是 HTTP 通信过程中的基本单位,由八位组字节流组成,通过 HTTP 传输。 35 | * **实体(entity)** 作为请求或者响应的有效载荷数据被传输,其内容由实体首部和实体主体构成。 36 | 37 | HTTP 报文的主体用于传输实体的主体(请求或者响应的)。 38 | 39 | **通常情况下,报文主体就是实体主体。但是在进行编码压缩时,实体主体部分会被编码,导致与报文主体不同**。 40 | 41 | #### 2.2 压缩传输的内容编码 42 | 43 | 内容编码指明应用在实体内容上的编码格式,并保持实体信息原样压缩,压缩之后的内容在客户端被接受之后会进行解码还原。 44 | 45 | 常用的内容编码有一下几种: 46 | 47 | * gzip(GNU zip) 48 | * compress(UNIX 系统的标准压缩) 49 | * deflate(zlib) 50 | * identity(不进行编码) 51 | 52 | 现在还有一种新兴的优秀算法——Brotli,但是目前还没有被广泛采用。 53 | 54 | #### 2.3 分块传输编码 55 | 56 | 在传送大容量数据时,通过把数据分割成多块,能够让浏览器逐步显示页面。这种功能成为**分块传输编码(Chunked Transfer Coding)**。 57 | 58 | 分块传输会将实体主体分割成多个块(chunk)来传输,每一块都用十六进制来标记块的大小,而实体主体的最后一块会使用“0(CR+LF)”来标记。 59 | 60 | ### 三、发送多种数据的多部分对象集合 61 | 62 | 邮件附件能够同时传送多种内容的数据,是因为采用了 MIME(Multipurpose Internet Mail Extensions,多用途因特网邮件扩展)机制,它允许邮件处理文本、图片、视频等多种类型的数据。相应的,HTTP 也采纳了部分多部分对象集合。 63 | 64 | 多部分对象集合包含的对象如下: 65 | 66 | * **multipart/form-data**:在 Web 表单上传时使用。 67 | * **multipart/byteranges**:状态码 206(Partial Content,部分内容)响应报文包含了多个范围的内容时使用。 68 | 69 | 我们通过指定 `Content-Type` 请求头来使用多部分对象结合。 70 | 71 | ### 四、获取部分内容的范围请求 72 | 73 | 指定范围发送的请求叫做**范围请求(Range Request)**。 74 | 75 | 对于一份 10000 字节大小的资源,可以通过范围请求一次只请求 5001 ~ 10000 字节的资源。 76 | 77 | 执行范围请求时,会通过 Range 首部字段来指定资源的 byte 范围,比如: 78 | 79 | * 5000 - 10000 字节: 80 | 81 | ``` 82 | Range: bytes=5001-10000 83 | ``` 84 | 85 | * 5000 字节之后的所有内容: 86 | 87 | ``` 88 | Range: bytes=5000- 89 | ``` 90 | 91 | * 从一开始到 3000 字节和 5000 字节到 7000 字节的内容: 92 | 93 | ``` 94 | Range: bytes=0-3000,5000-7000 95 | ``` 96 | 97 | 针对范围请求,响应会返回状态码为 **206 Partial Content** 的响应报文。 98 | 99 | 对于多重范围的范围请求,响应会在首部字段 `Content-Type` 表明 `multipart/byteranges` 后返回。 100 | 101 | ### 五、内容协商返回最合适的内容 102 | 103 | 内容协商机制是指客户端和服务器端就响应的资源内容进行交涉,然后提供给客户端最为合适的资源。 104 | 105 | 内容协商会以语言、字符集、编码方式等为基准判断响应的资源。 106 | 107 | 包含在请求报文中的一些首部字段就是服务端响应的判断标准: 108 | 109 | * **Accept** 110 | * **Accept-Charset** 111 | * **Accept-Encoding** 112 | * **Accept-Language** 113 | * **Content-Language** -------------------------------------------------------------------------------- /图解HTTP/4.md: -------------------------------------------------------------------------------- 1 | ## 图解 HTTP 笔记(四)——HTTP 状态码 2 | 3 | > 本章主要内容是了解 HTTP 状态码的工作机制 4 | 5 | 状态码的职责是当客户端向服务器端发送请求时,描述返回的请求结果。借助状态码,我们可以了解这次请求是否在服务器端得到了正常的处理。 6 | 7 | 状态码从其含以上可以分为五种: 8 | 9 | | | 类别 | 原因短语 | 10 | | ---- | -------------------------------- | -------------------------- | 11 | | 1XX | Informational(信息性状态码) | 接受的请求正在处理 | 12 | | 2XX | Success(成功状态码) | 请求正常处理完毕 | 13 | | 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 | 14 | | 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 | 15 | | 5XX | Server Error(服务端错误状态码) | 服务器处理请求出错 | 16 | 17 | 下面介绍一下常用的一些状态码。 18 | 19 | ### 一、2XX 成功 20 | 21 | #### 1.1 200 OK 22 | 23 | 表示从客户端发送的请求被服务器正常处理了。 24 | 25 | #### 1.2 204 No Content 26 | 27 | 该状态码表示客户端发送的请求已经在服务器端正常处理了,但是没有返回的内容,响应报文中不包含实体的主体部分。 28 | 29 | 一般在只需要从客户端往服务器端发送信息,而服务器端不需要往客户端发送内容时使用。 30 | 31 | #### 1.3 206 Partial Content 32 | 33 | 该状态码表示客户端进行了范围请求,而服务器端执行了这部分的 GET 请求。响应报文中包含由 Content-Range 指定范围的实体内容。 34 | 35 | ### 二、3XX 重定向 36 | 37 | 3XX 响应状态码表示浏览器需要执行某些特殊的处理以正确处理请求。 38 | 39 | #### 2.1 301 Moved Permanently 40 | 41 | 永久性重定向。 42 | 43 | 该状态码表示请求的资源已经被分配了新的 URI,以后应使用资源指定的 URI。新的 URI 会在 HTTP 响应头中的 Location 首部字段指定。 44 | 45 | #### 2.2 302 Found 46 | 47 | 临时重定向。 48 | 49 | 该状态码表示请求的资源被分配到了新的 URI,希望用户(本次)能使用新的 URI 访问资源。 50 | 51 | 和 302 Moved Permanently 状态码相似,但是 302 代表的资源不是被永久重定向,只是临时性质的。 52 | 53 | #### 2.3 303 See Other 54 | 55 | 该状态码表示由于请求对应的资源存在着另一个 URI,应使用 GET 方法定向获取请求的资源。 56 | 57 | 303 状态码和 302 Found 状态码有着相似的功能,但是 303 状态码明确表示客户端应当采用 GET 方法获取资源。 58 | 59 | 60 | 61 | > 当 301、302、303 响应状态码返回时,几乎所有的浏览器都会把 POST 改成 GET,并删除请求报文内的主体,之后请求会再次自动发送。 62 | > 63 | > 301、302 标准是禁止将 POST 方法变成 GET 方法的,但实际大家都会这么做。 64 | 65 | #### 2.4 304 Not Modified 66 | 67 | 该状态码表示客户端发送附带条件的请求时(GET 请求包含 If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since 中任一首部)服务端允许访问请求访问资源,但因为请求没有满足条件,所以发生 304 Not Modified 重定向,直接使用客户端缓存的资源。 68 | 69 | #### 2.5 307 Temporary Redirect 70 | 71 | 临时重定向。 72 | 73 | 该状态码与 302 Found 有着相同含义,尽管 302 标准禁止 POST 变成 GET,但是实际使用时还是这样做了。 74 | 75 | **307 会遵守浏览器标准,不会从 POST 变成 GET**。但是对于处理请求的行为时,不同浏览器还是会出现不同的情况。 76 | 77 | ### 三、4XX 客户端错误 78 | 79 | 4XX 的结果表明客户端是发生错误的原因所在。 80 | 81 | #### 3.1 400 Bad Request 82 | 83 | 该状态码表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。 84 | 85 | #### 3.2 401 Unauthorized 86 | 87 | 该状态码表示请求需要通过认证。 88 | 89 | #### 3.3 403 Forbidden 90 | 91 | 该状态码表明请求资源的访问被服务器拒绝了,服务器端没有必要给出详细理由,但是可以在响应报文实体的主体中进行说明。 92 | 93 | #### 3.4 Not Found 94 | 95 | 该状态码表明服务器上无法找到请求的资源。 96 | 97 | ### 四、5XX 服务端错误 98 | 99 | 5XX 的响应结果表明服务端本身发生了错误。 100 | 101 | #### 4.1 500 Internet Server Error 102 | 103 | 该状态码表明服务端在执行请求时存在错误,也有可能是 Web 应用存在的 bug 或者某些临时故障。 104 | 105 | #### 4.2 503 Service Unavailable 106 | 107 | 该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 108 | 109 | -------------------------------------------------------------------------------- /图解HTTP/5.md: -------------------------------------------------------------------------------- 1 | ## 图解 HTTP 笔记(五)——Web 服务器 2 | 3 | > 该章的主要内容是讲解与 HTTP 协作的 Web 服务器 4 | 5 | ### 一、用单台虚拟主机实现多个域名 6 | 7 | 基于虚拟主机的功能,可以只使用一台物理机实现多个域名的网站部署。 8 | 9 | 在互联网上,域名通过 DNS 域名解析系统可以映射到具体的 IP 上,多个域名可以映射到同一个 IP,但是一个域名只能映射到一个 IP。如果服务器想知道请求来自于哪个域名,可以通过请求头中的 Host 首部字段获取。 10 | 11 | ### 二、代理、网关和隧道 12 | 13 | HTTP 通信时,除服务端和客户端以外,还有一些用于通信数据转发处理的**应用程序**,例如代理、网关和隧道,它们可以配合服务器工作。 14 | 15 | * **代理**是一种有转发功能的应用程序,扮演者服务器和客户端的中间人的角色。代理接受客户端的请求转发给服务器,然后接受服务器的响应转发给客户端。 16 | * **网关**是转发其他服务器通信数据的服务器,接收从客户端发送来的请求时,它就像自己拥有资源的源服务器一样对请求进行处理。 17 | * **隧道**是在相隔甚远的客户端和服务器之间进行中转并保持双方连接的应用程序。 18 | 19 | #### 2.1 代理 20 | 21 | ![webproxy](./pics/webproxy.png) 22 | 23 | 代理服务器的基本行为就是在客户端和服务端之间转发请求和响应,代理不改变请求 URI,会直接发送给持有资源的服务器(称为**源服务器**)。 24 | 25 | ![multihttpproxy](./pics/multihttpproxy.png) 26 | 27 | 每次通过代理转发请求或者响应时,会追加写入 Via 首部字段,该字段会标记处经过的代理主机信息。 28 | 29 | 使用代理服务器的理由包括利用缓存技术减少网络带宽的流量,组织内部针对特定网站的访问控制,获取访问日志等。 30 | 31 | 代理的使用方式可以按照是否使用缓存和是否修改报文来进行分类。 32 | 33 | **缓存代理**:代理转发响应时,会把资源保存在代理服务器上,当代理再接收到相同的资源请求时,会返回之前缓存的资源。 34 | 35 | **透明代理**:转发请求或者响应时,不对报文进行任何修改的代理类型称为透明代理,反之称为非透明代理。 36 | 37 | #### 2.2 网关 38 | 39 | ![httpgateway](./pics/httpgateway.png) 40 | 41 | 网关的工作机制和代理十分相似,但是网关可以使通信线路上的服务器提供非 HTTP 协议服务。 42 | 43 | 利用网关能提高通信的安全性,因为可以在客户端和网关之间的通信线路上加密以确保连接的安全性。 44 | 45 | #### 2.4 隧道 46 | 47 | 隧道可以按照要求建立起一条与其他服务器的通信线路,届时使用 SSL 等加密手段进行通信。**隧道的目的是确保客户端与服务端能够进行安全的通信**。 48 | 49 | ### 三、保存资源的缓存 50 | 51 | 缓存是指代理服务器或客户端本地磁盘内保存的资源副本。 52 | 53 | 利用缓存可以减少对源服务器的访问,可以减少通信流量和通信时间。 54 | 55 | 缓存服务器是代理服务器的一种,并归类在缓存代理类型中。 56 | 57 | #### 3.1 缓存的有效期限 58 | 59 | 即使存在缓存,也会因为客户端的要求、缓存的有效期等因素向源服务器确认资源的有效性。若缓存失效,缓存服务器会再次向源服务器获取“新的”资源。 60 | 61 | ![httpcache](./pics/httpcache.png) 62 | 63 | #### 3.2 客户端缓存 64 | 65 | 除了代理服务器内的缓存以外,客户端的浏览器也可以缓存。同样的客户端也会向源服务器确定资源的有效性,然后再返回有效的资源。 66 | 67 | -------------------------------------------------------------------------------- /图解HTTP/7.md: -------------------------------------------------------------------------------- 1 | ## 图解 HTTP 笔记(七)——HTTPS 2 | 3 | > 本章主要讲解 HTTPS 的基本原理,以及如何利用 HTTPS 防范 HTTP 通信过程中存在的伪装、窃听、篡改等问题 4 | 5 | ### 一、HTTP 的缺点 6 | 7 | HTTP 在通信过程中会面临以下三种安全问题: 8 | 9 | * 通信使用明文(不加密),内容可能会被窃听。 10 | * 不验证通信方的身份,可能会遭遇伪装。 11 | * 无法验证报文的完整性,可能已经被篡改。 12 | 13 | #### 1.1 窃听 14 | 15 | HTTP 本身不具备加密功能,所以传输过程中都是以明文方式发送。 16 | 17 | 由于在网络的传输过程中,我们所发送的信息要经过许多的网络节点和设备,在这个过程中这些设备是可能会拦截我们的信息并且进行窃听的,直接通过一些常用的抓包工具就可以窃听未加密的网络传输信息。 18 | 19 | **通过加密防止窃听** 20 | 21 | (一)**通信的加密**:为了防止传输内容被窃听,我们采取的方式之一就是通信加密,HTTP 本身没有加密机制,但是我们可以通过将 HTTP 和 SSL(Secure Socket Layer 安全套接层)或者 TLS(Transport Layer Security 安全传输协议)组合使用来加密 HTTP 传输内容。 22 | 23 | 用 SSL 建立安全通信线路以后,就可以在这条线路上进行 HTTP 通信了。与 SSL 组合使用的 HTTP 被称为 HTTPS(HTTP Secure)。 24 | 25 | (二)**内容的加密**:还有一种方式就是将参与通信的内容本身进行加密。这样的话就需要客户端对 HTTP 报文加密后再进行请求发送。由于该方式不同于 HTTPS 将整个通信线路加密的方式,所以内容仍然会有被篡改的风险。 26 | 27 | #### 1.2 伪装 28 | 29 | HTTP 协议本身并不会对通信的另一方进行身份验证,所以任何人都能对服务器发起请求。 30 | 31 | 不验证通信方可能就会存在各种安全隐患: 32 | 33 | * 客户端无法确认自己的请求是否发送到了目标服务器或者返回响应的服务器是否是目标服务器,有可能是伪装了的服务器。 34 | * 服务器无法确认向自己发起请求的客户端以及自己返回响应的客户端是否是目标中的客户端。 35 | * 无法确认通信方是否具备访问权限,因为某些服务器只想给特定的用户访问。 36 | * 即使是无意义的请求也会照单全收,使得服务器可能遭受到 DDoS 攻击。 37 | 38 | **通过查明对方证书来防止伪装** 39 | 40 | SSL 不仅提供加密处理,而且使用了一种称为证书的手段,可用于确认对方身份。 41 | 42 | **证书由第三方机构颁发,用以证明服务器和客户端是实际存在的**。 43 | 44 | ![httpcert](./pics/httpcert.png) 45 | 46 | 通过使用证书可以证明通信方就是意料中的服务器。对使用者而言,也减少了个人信息泄露的风险。 47 | 48 | 另外,客户端持有证书即可完成个人身份的认证,也可用于对网站的认证环节。 49 | 50 | #### 1.3 篡改 51 | 52 | HTTP 协议通常无法确认信息的完整性,一旦传输的信息被篡改,那么信息就失去了准确性,导致信息有误。比如你想在某个网站下载一个资源,而你的网络传输已经被别人所劫持,在你发起下载请求的时候,你所接收到的资源正在被人修改,所以你下载到的资源就不是你想要的那个了。 53 | 54 | 像这样,请求或者响应在传输途中遭攻击者拦截并篡改内容的攻击称为中间人攻击(Man-in-the-Middle attack, MITM)。 55 | 56 | **如何防止篡改** 57 | 58 | 之前的章节有提到过 Content-MD5 实体首部字段可用于确认实体内容是否完整,但是由于 Content-MD5 本身的值也可能被篡改,所以这个字段并不可靠,所以需要其他方法来确保传输的内容不被篡改。 59 | 60 | 通过其他散列算法来计算传输内容是否完整也不可靠,那么我们最终还是需要 HTTPS 来帮我们解决这个问题。SSL 提供认证和加密处理以及**摘要功能**。 61 | 62 | ### 二、HTTPS 63 | 64 | ``` 65 | HTTPS = HTTP + 加密 + 认证 + 摘要 66 | ``` 67 | 68 | #### 2.1 HTTPS 是身披 SSL 外壳的 HTTP 69 | 70 | HTTPS 并非是应用层的一种新协议。只是 HTTP 通信接口部分用 SSL 和 TLS 协议代替而已。 71 | 72 | 通常,HTTP 直接和 TCP 通信。当使用 SSL 的时候,就先和 SSL 通信,再由 SSL 和 TCP 通信了。所以简而言之,HTTPS 就是身披 SSL 外壳的 HTTP 协议。 73 | 74 | ![https](./pics/https.png) 75 | 76 | 在采用 SSL 以后,HTTP 就拥有了 HTTPS 的加密、证书和完整性保护功能了。 77 | 78 | > SSL 协议是独立于 HTTP 的协议,所以其他协议也可以采用 SSL 协议,它是当今世界上应用最广泛的网络安全技术 79 | 80 | #### 2.2 相互交换密钥的公开密钥加密技术 81 | 82 | 在讲 SSL 之前可以先了解一下加密方法,SSL 采用一种叫做公开密钥加密(Public-key Cryptography)的加密处理技术。 83 | 84 | 近代的加密方法中,加密算法是公开的,但是密钥是保密的。加密和解密都会用到密钥。没有密钥就无法对密钥进行解密。反过来说,任何人只要拿到了密钥就可以解密信息。如果密钥被攻击者获得,那加密也就失去了意义。 85 | 86 | ##### 2.2.1 共享密钥加密的困境 87 | 88 | **加密和解密使用同一个密钥的方式称为共享密钥加密(Common key crypto system),也被叫做对称密钥加密**。 89 | 90 | 采用共享密钥加密方式加密时,需要将密钥一起发送给通信方,所以有需要考虑密钥传输的安全性,需要设法安全地保管密钥,这便是共享密钥加密方式的困扰。 91 | 92 | ##### 2.2.2 使用两把密钥加密的公开密钥加密方式 93 | 94 | 公开密钥加密方式很好的解决了共享密钥加密方式的困扰。 95 | 96 | 公开密钥加密使用一对非对称的密钥。一把叫做私有密钥(private key),另一把叫做公开密钥(public key)。顾名思义,私有密钥不能被其他任何人知道,而公开密钥则可以仁任意传播,任何人都可以拿到。 97 | 98 | 使用公开密钥加密方式(非对称加密),发送密文的一方使用公钥进行加密处理,而接收方拿到被加密后的信息之后再使用自己的私钥进行解密。利用这种方式进行传输,就不需要发送密钥,也就不用担心密钥被攻击者拿走了。 99 | 100 | ##### 2.2.3 HTTPS 采用混合加密机制 101 | 102 | **HTTPS 采用共享密钥加密方式和公开密钥加密方式混用的加密方式**。 103 | 104 | 如果密钥可以被安全传输,则 HTTPS 会考虑采用共享密钥加密方式,否则将采用公开密钥加密方式。这是因为公开密钥加密方式的速度比共享密钥加密方式要慢。HTTPS 充分地利用了两者的优点,将多种方法组合起来用于通信。**在使用公开密钥加密方式交换密钥之后,之后的信息传输使用共享密钥加密方式**。 105 | 106 | #### 2.3 证明公开密钥正确性的证书 107 | 108 | 遗憾的是公开密钥加密方式本身也是有缺陷的,那就是无法证明公开的密钥本身是货真价实的。 109 | 110 | 为了解决上面说到的问题,可以使用由数字证书认证机构(CA,Certificate Authority)和其相关机构颁发的公开密钥证书。 111 | 112 | 数字证书认证机构处于客户端与服务端双方都信赖的第三方机构的立场上,威瑞新(VeriSign)就是其中一家非常有名的数字证书认证机构。 113 | 114 | 下面讲解一下数字证书认证机构的业务流程: 115 | 116 | 首先,服务器的运营人员会向数字机构提出公开密钥申请,CA 在认证申请者的身份信息之后,会对已申请的公开密钥进行数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公钥证书之后绑定在一起。 117 | 118 | 服务器会将这份 CA 颁发的公钥证书发送给客户端,以进行公钥加密方式通信,公钥证书也可叫做数字证书或者直接称为证书。 119 | 120 | 接到证书的客户端可使用 CA 的公钥对证书的数字签名进行认证,一旦验证通过,客户端便可确认两件事: 121 | 122 | * 认证服务器公钥的机构是真实有效的 CA 机构 123 | * 服务器的公钥是值得信任的 124 | 125 | 于是这就达到了确认公钥真实有效性的目的。 126 | 127 | 安全地转交 CA 机构的密钥给客户端是一件困难的事,因此多数浏览器会在内部植入常用认证机构的公钥。 128 | 129 | ![httpskey](./pics/httpskey.png) 130 | 131 | ##### 2.3.1 可证明组织真实性的 EV SSL 证书 132 | 133 | 证书的一个作用是证明作为通信一方的服务器是否符合规范,另一个作用是确认服务器运营商企业是否真实存在。能够证明企业真实性的正式就是 EV SSL 证书(Extended Validation SSL Certificate)。 134 | 135 | 该证书的目的是为了防止钓鱼攻击(Phishing)。 136 | 137 | ##### 2.3.2 用以确认客户端的客户端证书 138 | 139 | HTTPS 中还可以使用客户端证书对客户端进行认证。 140 | 141 | ### 三、Session 管理及 Cookie 应用 142 | 143 | 第八章要讲的内容不多,所以把最重要的一点挪到了这一篇笔记。 144 | 145 | 关于用户身份的认证,现在多数是采用表单认证,一般会采用 Cookie 来管理 Session(会话)。 146 | 147 | 大致流程如下: 148 | 149 | ![httpsession](./pics/httpsession.png) 150 | 151 | 具体步骤如下: 152 | 153 | * 客户端把用户的 ID 密码等登录信息放入报文的实体部分,通常用 POST 方法发送至服务器端。 154 | * 服务器生成并发放用来识别客户的 Session ID,这个 Session ID 同时会在服务器端保存,然后通过 Set-Cookie 字段绑定到客户端。顺便可以使用 `httponly` 属性来禁止 JavaScript 修改 Cookie,防止跨站脚本攻击。 155 | * 客户端把 Session ID 保存在本地 Cookie,下次访问时再带上。服务器端通过验证接收到的 Session ID 来识别用户,从数据库中可以顺便取到与用户相关的一系列信息。 -------------------------------------------------------------------------------- /图解HTTP/8.md: -------------------------------------------------------------------------------- 1 | ## 图解 HTTP 笔记(八)——常见 Web 攻击技术 2 | 3 | > 本章主要讲解 HTTP 通信过程中的一些常见 Web 攻击技术 4 | 5 | ### 一、跨站脚本攻击 6 | 7 | 跨站脚本攻击(Cross-Site Scripting, XSS)是指通过存在安全漏洞的 Web 网站注册用户的浏览器内运行非法的 HTML 标签或者 JavaScript 代码的一种攻击方式。动态创建的 HTML 可能存在安全漏洞。 8 | 9 | 该攻击可能造成以下影响: 10 | 11 | * 利用虚假输入表单骗取用户个人信息 12 | * 利用脚本窃取用户的 Cookie 值,被害者在不知情的情况下,帮助攻击者发送恶意请求 13 | * 显示伪造的文章或者图片 14 | 15 | ![xss](./pics/xss.png) 16 | 17 | 上图的表单输入以后直接把输入内容当做 HTML 展示在页面上,所以存在着安全漏洞,攻击者可以直接在输入框内编辑危险的代码,然后就会在页面上运行。 18 | 19 | ### 二、SQL 注入攻击 20 | 21 | SQL 注入(SQL Injection)是指针对 Web 应用使用的数据库,通过运行非法的 SQL 而产生的攻击。该安全隐患有可能引发极大的安全威胁,有时会直接导致个人信息及机密信息的泄露。 22 | 23 | SQL 注入可能会导致如下影响: 24 | 25 | * 非法查看或篡改数据库内的数据 26 | * 规避认证 27 | * 执行和数据库服务器业务关联的程序等 28 | 29 | 如果我们不对输入进行验证就直接拿去拼接 SQL 的话,是可能会被执行 SQL 注入攻击的。 30 | 31 | ### 三、目录遍历攻击 32 | 33 | 目录遍历(Directory Traversal)攻击是指对本无意公开的文件目录,通过非法截断其目录路径后,达成访问目的的一种攻击。这种攻击有时也被叫做路径遍历攻击(Path Traversal)。 34 | 35 | ### 四、会话劫持 36 | 37 | 会话劫持(Session Hijack)是指攻击者通过某种手段拿到了用户的会话 ID,并非法使用此会话 ID 伪装成用户,达到攻击的目的。 38 | 39 | 具备认证功能的 Web 应用,使用会话 ID 的会话管理机制,作为管理认证状态的主流方式。会话 ID 中记录客户端的 Cookie 等信息,服务器端将会话 ID 与认证状态进行一对一匹配管理。 40 | 41 | 攻击者可能通过以下方式获得会话 ID: 42 | 43 | * 通过非正规的生成方法推测会话 ID 44 | * 通过窃听或 XSS 攻击盗取会话 ID 45 | * 通过会话固定攻击(Session Fixation)强行获取会话 ID 46 | 47 | 通常情况下攻击者在发现网站存在的 XSS 攻击漏洞之后,会注入一段 JavaScript 代码,通过 `document.cookie` 盗取到会话 ID,之后植入到自己的浏览器,就可以伪装成被盗窃的用户访问被攻击的网站。 48 | 49 | ### 五、跨站点请求伪造 50 | 51 | 跨站点请求伪造(Cross-Site Request Forgeries,CSRF)攻击是指攻击者通过设置好的陷阱,强制对已完成认证的用户进行非预期的个人信息或设定信息等某些状态更新,属于被动攻击。 52 | 53 | 最常见的例子就是在一个需要登录才能进行操作的网站,攻击者在该网站伪造了一个可以触发危险操作的内容(比如 a 和 button 标签),用户在不经意间点击这些伪造的内容后就自动发起了请求,而这些请求如果是指向当前网站的话,用户的会话 ID 等信息也会被带上,使得被攻击者不经意之间完成了一次可能极为危险的操作。 54 | 55 | ### 六、点击劫持 56 | 57 | 点击劫持(Clickjacking)是指利用透明的按钮或链接做成陷阱,覆盖在 Web 页面之上。然后诱导用户在不知情的情况下点击那个链接访问内容的一种攻击手段。这种行为又称为界面伪装(UI Redressing)。 58 | 59 | 这种攻击最常见的案例就是某些小网站…… 60 | 61 | ### 七、DoS 攻击 62 | 63 | DoS 攻击(Denial of Service Attack)是一种让运行中的服务呈停止状态的攻击。有时也叫作停止服务攻击或者拒绝服务攻击。DoS 攻击的对象不仅限于 Web 网站,还包括网络设备及服务器等。 64 | 65 | DoS 主要有以下两种攻击方式: 66 | 67 | * 集中利用访问请求造成资源过载,资源用尽的同时,实际上服务也就呈停止状态 68 | * 通过攻击安全漏洞是服务停止 69 | 70 | 由此可见所有使得服务停止的攻击都可以称为 DoS 攻击。 71 | 72 | DoS 攻击在中国前端届比较著名的案例就是阮一峰老师的博客遭到过的一次攻击:[DDOS 攻击的防范教程]()。 -------------------------------------------------------------------------------- /图解HTTP/pics/bodystructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/bodystructure.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpbackground.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpbody.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpbody.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpcache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpcache.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpcert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpcert.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpconn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpconn.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpgateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpgateway.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpresstructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpresstructure.png -------------------------------------------------------------------------------- /图解HTTP/pics/https.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/https.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpsession.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpsession.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpskey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpskey.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpstream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpstream.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpstructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpstructure.png -------------------------------------------------------------------------------- /图解HTTP/pics/httptransfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httptransfer.png -------------------------------------------------------------------------------- /图解HTTP/pics/httpvia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/httpvia.png -------------------------------------------------------------------------------- /图解HTTP/pics/multihttpproxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/multihttpproxy.png -------------------------------------------------------------------------------- /图解HTTP/pics/relationship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/relationship.png -------------------------------------------------------------------------------- /图解HTTP/pics/requestcontent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/requestcontent.png -------------------------------------------------------------------------------- /图解HTTP/pics/responsecontent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/responsecontent.png -------------------------------------------------------------------------------- /图解HTTP/pics/tcphandshaking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/tcphandshaking.png -------------------------------------------------------------------------------- /图解HTTP/pics/urlformat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/urlformat.png -------------------------------------------------------------------------------- /图解HTTP/pics/webproxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/webproxy.png -------------------------------------------------------------------------------- /图解HTTP/pics/xss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhongdeming428/MyMemorandum/ea89c80a07034d219272d45901be1b6f12454bb5/图解HTTP/pics/xss.png -------------------------------------------------------------------------------- /图解HTTP/《图解 HTTP》读书笔记.md: -------------------------------------------------------------------------------- 1 | ## 《图解 HTTP》读书笔记 2 | 3 | 《图解 HTTP》一书是日本学者上野宣所著,2014 年由于均良先生翻译并在国内出版。因为作者使用十分生动的语言和浅显易懂的案例将 HTTP 协议讲解得深入浅出,所以深受开发者喜爱。现在在网上随手一搜都可以找到很多的电子书或者读书笔记,可见该书的畅销程度。 4 | 5 | 我本人由于之前在使用 Nodejs 开发后端项目的时候有过一些障碍(比如 302、301 重定向,401 认证失败、预检请求等等),所以想要找个时间系统的学习一下 HTTP 协议,也作为之后阅读 Nodejs 框架的前瞻;所以 《图解 HTTP》自然而然也就成为了我的首选教材,于是赶紧在网上买了一本,不久之后就诞生了这个系列的读书笔记。说是一个系列,其实就是八篇长度比较适中的文章,**对于书中的内容多半是精简摘抄,有些地方稍微有一点点拓展,所以看过书的童鞋就没有什么必要再浪费时间阅读了**。 6 | 7 | 全书一共有 11 个大章节,其中第一章主要介绍了网络基础知识,包括 HTTP 网络协议的诞生背景、TCP/IP 协议简介、网络协议的分层以及 DNS 服务等等。第二章主要讲解了 HTTP 协议的作用、工作方式以及一些特点。第三章主要讲解 HTTP 报文的结构。第四章主要讲解 HTTP 的一系列状态码,比如 2XX 代表响应成功、3XX 代表重定向、4XX 代表客户端请求错误造成响应失败、5XX 代表服务器错误导致响应失败。第五章主要讲解 Web 服务器的一些概念,比如网关、隧道、代理以及资源缓存等等。第六章主要讲解 HTTP 首部,包括常用的通用、请求、响应、实体首部字段以及一些其他的首部字段,这一章讲解的比较多,所以内容也相对而言比较多。第七章主要讲解 HTTPS 的组成、工作原理以及网络证书等知识,对于使用 Nodejs 开发而言这一节十分重要,尤其是对证书的知识感到困惑的童鞋需要好好阅读一下这一章。第八章主要讲解身份认证,现在多半基于表单认证,主要涉及到 cookie 以及 session 的知识。第九章主要讲解一些从 HTTP 扩展的协议,最重要的是 WebSocket 协议,但是我的笔记中这一章的内容基本没写。第十章主要讲解构建 Web 内容的技术,作为一名前端开发,我很自然的跳过了这一章~~。第十一章主要讲解一些常见的 Web 攻击技术,最常见的有 XSS 跨站攻击、SQL 注入攻击、CSRF 跨站点请求伪造、DoS 攻击等等。 8 | 9 | 我的笔记大概在七章之前是每章一篇 ,然后第十一章单独一篇,中间的两篇被忽略掉了。其中我认为最重要的是第四章 HTTP 状态码、第六章 HTTP 首部、第七章 HTTPS 的知识以及 HTTP 证书的基本原理。总而言之,这是一本关于 HTTP 的相当好的入门书籍,推荐指数五颗星。具体内容还需要大家自己阅读,文章如下: 10 | 11 | * [图解 HTTP 笔记(一)——了解 Web 及网络基础](https://juejin.im/post/5d2f1d59f265da1bbd4bab11) 12 | * [图解 HTTP 笔记(二)——简单的 HTTP 协议](https://juejin.im/post/5d2f1df5f265da1bc4148955) 13 | * [图解 HTTP 笔记(三)—— HTTP 报文内的 HTTP 信息](https://juejin.im/post/5d2f2067f265da1b9163c9d7) 14 | * [图解 HTTP 笔记(四)——HTTP 状态码](https://juejin.im/post/5d2f20b6f265da1bbe5e39dc) 15 | * [图解 HTTP 笔记(五)——Web 服务器](https://juejin.im/post/5d2f2148f265da1bbb041117) 16 | * [图解 HTTP 笔记(六)——HTTP 首部](https://juejin.im/post/5d309279f265da1bb13f6a22) 17 | * [图解 HTTP 笔记(七)——HTTPS](https://juejin.im/post/5d3092d06fb9a07ee63f9d74) 18 | * [图解 HTTP 笔记(八)——常见 Web 攻击技术](https://juejin.im/post/5d309327f265da1b672145d2) 19 | 20 | **所有文章的具体内容都可以在我的 [GitHub](https://github.com/zhongdeming428/MyMemorandum/tree/master/%E5%9B%BE%E8%A7%A3HTTP) 找到,有任何有异议的地方都可以直接评论或者提起 PR**。 21 | 22 | 最后,感谢您的阅读! 23 | 24 | --------------------------------------------------------------------------------