├── leetcode ├── readme.md ├── 146. LRU 缓存机制.md ├── 53.最大子序和.md ├── 112.路径总和.md ├── 136.只出现一次的数字.md ├── 206.反转链表.md ├── 771.宝石与石头.md ├── 70.爬楼梯.md ├── 283.移动零.md ├── 234.回文链表.md ├── 169.多数元素.md ├── 617.合并二叉树.md ├── 18.删除链表的节点.md ├── 78.子集.md ├── 101.对称二叉树.md ├── 14.最长公共前缀.md ├── 35.搜索插入位置.md ├── 155.最小栈.md ├── 1.两数之和.md ├── 88.合并两个有序数组.md ├── 21.合并两个有序链表.md ├── 20.有效的括号.md ├── 543.二叉树的直径.md ├── 160.相交链表.md ├── 5.最长回文子串.md ├── note.md └── easy │ └── readme.md ├── webpack ├── loader │ ├── app.js │ ├── loaders │ │ ├── vue-loader.js │ │ ├── css1px-loader.js │ │ ├── md-loader2.js │ │ ├── watermark-loader.js │ │ ├── convert2Webp.loader.js │ │ └── md-loader.js │ ├── postcss.config.js │ ├── src │ │ ├── index.txt │ │ ├── main.js │ │ ├── App.vue │ │ └── mdtest.md │ ├── static │ │ └── node-run-loader.jpg │ ├── public │ │ └── index.html │ ├── css1px.js │ ├── run-loader.js │ ├── package.json │ ├── webpack.config.js │ └── readme.md ├── plugin │ ├── dist │ │ ├── new-file.js │ │ ├── img │ │ │ └── logo.c92b85a.png │ │ └── index.html │ ├── .gitignore │ ├── postcss.config.js │ ├── src │ │ ├── assets │ │ │ └── logo.png │ │ ├── index.js │ │ ├── styles │ │ │ └── index.less │ │ └── components │ │ │ └── App.js │ ├── .babelrc │ ├── public │ │ ├── images │ │ │ └── logo.png │ │ └── index.html │ ├── .vscode │ │ └── launch.json │ ├── plugins │ │ ├── FileListPlugin.js │ │ ├── utils.js │ │ ├── MyWebpackPlugin.js │ │ ├── webpackConsoleLog.js │ │ ├── build-time.js │ │ ├── QiniuUpload.js │ │ └── compiler.js │ ├── package.json │ └── webpack.config.js ├── webpack │ ├── .gitignore │ ├── src │ │ ├── moduleB.js │ │ ├── moduleC.js │ │ ├── moduleA.js │ │ └── index.js │ ├── bin │ │ ├── my-webpack.js │ │ └── webpack.js │ ├── runWebpack.js │ ├── webpack.config.js │ ├── package.json │ ├── dist │ │ └── bundle.js │ └── myWebpack.js ├── readme.md └── webpack性能优化 │ └── readme.md ├── vue ├── Vue 依赖收集过程源码阅读以及实现.md ├── snabbdom.js使用及源码分析.md ├── https加密.md ├── readme.md ├── keeplive.md ├── nexttick原理.md ├── template编译.md ├── proxy.md ├── computed与watch.md ├── 整理.md ├── vue响应式原理.md ├── 公众号文章整理.md └── @vue │ └── composition-api.md ├── .gitignore ├── react ├── read-react-code │ └── readme.md └── 实现一个简单的react-hook里面的useState │ └── readme.md ├── handwrittenCode ├── 腾讯面试题:三个元素之和为指定数n的各个组合.js ├── toutiao.js ├── 大数相加.js ├── 腾讯面试题new一个函数发生了什么.js ├── hash去重.js ├── test.js ├── 字节面试题:Excel表格随机生成1000列.md ├── instanceof.js ├── 防抖节流.js ├── currying.js ├── 实现带有超时功能的Promise.js ├── 实现new.js ├── 数组全排列.js ├── 原生ajax封装成async-await调用.js ├── 苹果、梨、香蕉三个数组相互拼成字符串.js ├── call.js ├── 计算树的深度.js ├── 腾讯面试题3.js ├── js实现数组和链表之间相互转换.md ├── lastPromise.js ├── 实现一个函数,可以将数组转化为树状数据结构.js ├── 浏览器最大请求并非限制.js ├── 常见排序算法以及复杂度.md ├── 字节面试题:合并两个有序数组.md ├── 字节:二叉树完整路径之和.js ├── 发布订阅.js └── Promise.js ├── test4.js ├── 前端知识点 ├── 基本数据类型与引用数据类型.md ├── Map与WeakMap的区别.md ├── 前端项目负责人职责.md ├── 图文详情页秒开优化.md ├── 李兵.md ├── 面试题.md ├── 详解JavaScript中的Event Loop(事件循环).md ├── 性能优化相关.md ├── 错误数据上报.md ├── prototype和_proto_以及原型链的关系.md ├── HTTP2优点.md ├── 总结项目中碰到的难点.md ├── anying.md ├── 2022年4月19总结.md ├── 前端性能优化.md └── 项目总结.md ├── SECURITY.md ├── test5.js ├── test3.js ├── 性能优化 └── readme.md ├── 前端安全 ├── csrf │ └── readme.md ├── xss │ └── readme.md └── crsf.html ├── test2.js └── README.md /leetcode/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webpack/loader/app.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue/Vue 依赖收集过程源码阅读以及实现.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | todo.md 2 | /test.js -------------------------------------------------------------------------------- /react/read-react-code/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webpack/loader/loaders/vue-loader.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webpack/plugin/dist/new-file.js: -------------------------------------------------------------------------------- 1 | var a=1 -------------------------------------------------------------------------------- /webpack/webpack/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /webpack/plugin/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | wechat.md -------------------------------------------------------------------------------- /webpack/webpack/src/moduleB.js: -------------------------------------------------------------------------------- 1 | console.log("moduleB") -------------------------------------------------------------------------------- /webpack/webpack/src/moduleC.js: -------------------------------------------------------------------------------- 1 | console.log('moduleC') -------------------------------------------------------------------------------- /vue/snabbdom.js使用及源码分析.md: -------------------------------------------------------------------------------- 1 | https://www.cnblogs.com/tugenhua0707/p/11762585.html 2 | -------------------------------------------------------------------------------- /handwrittenCode/腾讯面试题:三个元素之和为指定数n的各个组合.js: -------------------------------------------------------------------------------- 1 | 2 | 一个长度足够长,只包含数字的数组中,找出三个元素之和为指定数n的各个组合 -------------------------------------------------------------------------------- /webpack/webpack/src/moduleA.js: -------------------------------------------------------------------------------- 1 | import moduleC from './moduleC.js' 2 | console.log("moduleA") -------------------------------------------------------------------------------- /handwrittenCode/toutiao.js: -------------------------------------------------------------------------------- 1 | ## 去除字符串里的 b,然后去除重复的 a 和 c 2 | aabbcc=>'' 3 | aabc=>'c' 4 | 5 | 6 | -------------------------------------------------------------------------------- /webpack/webpack/src/index.js: -------------------------------------------------------------------------------- 1 | import modulesA from './moduleA.js' 2 | import modulesB from './moduleB.js' -------------------------------------------------------------------------------- /webpack/loader/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } -------------------------------------------------------------------------------- /webpack/plugin/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } -------------------------------------------------------------------------------- /webpack/plugin/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxxqq/fe-blog/HEAD/webpack/plugin/src/assets/logo.png -------------------------------------------------------------------------------- /webpack/plugin/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"], 3 | "plugins": [ 4 | "transform-runtime" 5 | ] 6 | } -------------------------------------------------------------------------------- /webpack/plugin/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxxqq/fe-blog/HEAD/webpack/plugin/public/images/logo.png -------------------------------------------------------------------------------- /webpack/loader/src/index.txt: -------------------------------------------------------------------------------- 1 | .border-1px{border:1px solid #fff;border-radius:5px;}.border-1px-top{border-top:1px dashed green} -------------------------------------------------------------------------------- /webpack/loader/static/node-run-loader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxxqq/fe-blog/HEAD/webpack/loader/static/node-run-loader.jpg -------------------------------------------------------------------------------- /webpack/plugin/dist/img/logo.c92b85a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxxqq/fe-blog/HEAD/webpack/plugin/dist/img/logo.c92b85a.png -------------------------------------------------------------------------------- /leetcode/146. LRU 缓存机制.md: -------------------------------------------------------------------------------- 1 | 来源:力扣(LeetCode) 2 | 链接:https://leetcode-cn.com/problems/lru-cache 3 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 4 | -------------------------------------------------------------------------------- /test4.js: -------------------------------------------------------------------------------- 1 | // Promise.finally=()=>{ 2 | // new Promise 3 | // } 4 | 5 | // interface / type 6 | // box-sizing 7 | // 优化state变更组件渲染问题; 8 | // useMemo 9 | -------------------------------------------------------------------------------- /handwrittenCode/大数相加.js: -------------------------------------------------------------------------------- 1 | // 给两个大整数, 用字符串表示, 比如" 2154365543", "4332656442", 2 | // 都可能超过1万位, 写一个函数输出他们之和. 需要自己实现加法过程, 3 | // 不能用某些语言自带的高精度加法函数. 4 | let bigNumberSum = (a, b) => {} 5 | -------------------------------------------------------------------------------- /vue/https加密.md: -------------------------------------------------------------------------------- 1 | 讲的真好,层层递进!对称加密虽然性能好但有密钥泄漏的风险,非对称加密(2 组公钥+2 私钥双向传输)安全但性能低下,因此考虑用非对称加密来传输对称加密所需的密钥,然后进行对称加密,但是为了防止非对称过程产生的中间人攻击,需要对服务器公钥和服务器身份进行配对的数字认证,然后引入了 CA 数字签名+数字证书验证的方式!这是我梳理的整体逻辑流程。 2 | -------------------------------------------------------------------------------- /前端知识点/基本数据类型与引用数据类型.md: -------------------------------------------------------------------------------- 1 | ### 基本数据类型 2 | 3 | 基本的数据类型有:`undefined,boolean,number,string,null. 4 | 5 | ### 引用数据类型 6 | 7 | javascript 中除了上面的基本类型(number,string,boolean,null,undefined)之外就是引用类型了 8 | -------------------------------------------------------------------------------- /webpack/loader/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: h => h(App) 8 | }).$mount('#app') 9 | -------------------------------------------------------------------------------- /webpack/webpack/bin/my-webpack.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | let Webpack = require('../myWebpack.js'); 4 | const options = require('../webpack.config') 5 | new Webpack(options).run() 6 | 7 | -------------------------------------------------------------------------------- /webpack/plugin/src/index.js: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import ReactDOM from 'react-dom'; 3 | import App from './components/App'; 4 | // ReactDOM.render( , document.getElementById('root')); -------------------------------------------------------------------------------- /前端知识点/Map与WeakMap的区别.md: -------------------------------------------------------------------------------- 1 | Map 和 WeakMap 之间的主要区别: 2 | 3 | Map 对象的键可以是任何类型,但 WeakMap 对象中的键只能是对象引用; 4 | WeakMap 不能包含无引用的对象,否则会被自动清除出集合(垃圾回收机制),可以用来保存 DOM 节点,不容易造成内存泄漏; 5 | WeakMap 对象是不可枚举的,无法获取集合的大小。 6 | -------------------------------------------------------------------------------- /handwrittenCode/腾讯面试题new一个函数发生了什么.js: -------------------------------------------------------------------------------- 1 | // 1. 请写出以下程序的输出结果: 2 | function F() {} 3 | F.a = 100 4 | F.prototype.b = 10 5 | 6 | let z = new F() 7 | console.log(z.a) //undefined 8 | console.log(z.b) //8 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /前端知识点/前端项目负责人职责.md: -------------------------------------------------------------------------------- 1 | https://mmbiz.qpic.cn/mmbiz_png/tibUxowsg9P0fPcaicNENBSKdiadB7B1yiblx4X4JibjKcLxvbMzBibpGjsCFX5oB8T19vuL81n9utdcF7Z6l5UhGZ3g/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1 2 | 3 | 提高开发效率,保证开发质量,为保障项目高质量按时交付。 4 | -------------------------------------------------------------------------------- /vue/readme.md: -------------------------------------------------------------------------------- 1 | ### vue 计划写作方向 2 | 3 | 1. Vue 依赖收集过程源码阅读以及实现 4 | 2. Vue 响应式更新原理源码阅读以及实现 5 | 3. Vue@2 patch Diff 算法源码阅读以及实现 6 | 4. compute 和 watch 原理 7 | 5. nexttick 原理 8 | 6. keeplive 实现原理 9 | 7. vue Composition api 速成课 10 | -------------------------------------------------------------------------------- /前端知识点/图文详情页秒开优化.md: -------------------------------------------------------------------------------- 1 | ### 性能 2 | 3 | 页面加载时间 = 页面加载完成时间 - 页面开始加载时间 4 | 5 | 一般来说,WebView 渲染需要经过下面几个步骤 6 | 7 | 1. 解析 HTML 文件 8 | 2. 加载 JavaScript 和 CSS 文件 9 | 3. 解析并执行 JavaScript 10 | 4. 构建 DOM 结构 11 | 5. 加载图片等资源 12 | 6. 页面加载完毕 13 | 14 | ### 白屏 15 | 16 | ### 指标建立 17 | -------------------------------------------------------------------------------- /前端知识点/李兵.md: -------------------------------------------------------------------------------- 1 | 当我们讨论 Web 性能时,其实就是讨论 Web 应用速度,关于 Web 应用的速度,我们需要从两个阶段来考虑: 2 | 页面加载阶段; 3 | 页面交互阶段。 4 | 5 | 性能检测工具:Performance vs Audits 要想优化 Web 的性能,我们就需要有监控 Web 应用的性能数据,那怎么监控呢? 6 | Performance 和 Audits,它们能够准确统计页面在加载阶段和运行阶段的一些核心数据,诸如任务执行记录、首屏展示花费的时长等,有了这些数据我们就能很容易定位到 Web 应用的性能瓶颈 。 7 | -------------------------------------------------------------------------------- /vue/keeplive.md: -------------------------------------------------------------------------------- 1 | https://github.com/sisterAn/JavaScript-Algorithms/issues/9 2 | 聊聊 keep-alive 组件的使用及其实现原理 3 | https://github.com/answershuto/learnVue/blob/master/docs/%E8%81%8A%E8%81%8Akeep-alive%E7%BB%84%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8%E5%8F%8A%E5%85%B6%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86.MarkDown 4 | -------------------------------------------------------------------------------- /webpack/plugin/src/styles/index.less: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | line-height: 2; 5 | text-align: center; 6 | } 7 | 8 | body { 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | } 13 | 14 | .test { 15 | background: url('../assets/logo.png'); 16 | } -------------------------------------------------------------------------------- /vue/nexttick原理.md: -------------------------------------------------------------------------------- 1 | https://segmentfault.com/a/1190000020499713 2 | https://mp.weixin.qq.com/s/n3qHij5yB4r7sjCJ4Cf6Bw 3 | vue 异步更新原理图解 4 | https://mp.weixin.qq.com/s/p7blibUVF4YNBt7iqHnr9w 5 | https://juejin.im/post/6844903557372575752 6 | Vue 你必须知道的异步更新机制和 nextTick 原理 7 | https ://www.cnblogs.com/chanwahfung/p/13296293.html 8 | -------------------------------------------------------------------------------- /handwrittenCode/hash去重.js: -------------------------------------------------------------------------------- 1 | //hash去重 2 | function Deduplication(arr) { 3 | var result = []; 4 | var hashMap = {}; 5 | for (var i = 0; i < arr.length; i++) { 6 | var temp = arr[i] 7 | if (!hashMap[temp]) { 8 | hashMap[temp] = true 9 | result.push(temp) 10 | } 11 | } 12 | return result; 13 | } -------------------------------------------------------------------------------- /webpack/loader/loaders/css1px-loader.js: -------------------------------------------------------------------------------- 1 | const loaderUtils = require("loader-utils"); 2 | module.exports = function(content) { 3 | this.cacheable && this.cacheable(); 4 | // merge params and default config 5 | const options = loaderUtils.getOptions(this); 6 | console.log(content.split(/\r?\n/)) 7 | return content; 8 | 9 | }; -------------------------------------------------------------------------------- /handwrittenCode/test.js: -------------------------------------------------------------------------------- 1 | function fun(n, k) { 2 | console.log(k) 3 | return { 4 | fun: function (m) { 5 | return fun(m, n) 6 | }, 7 | } 8 | } 9 | var a = fun(0) 10 | a.fun(1) // undefined 11 | a.fun(2) // 0 12 | a.fun(3) // 0 13 | var b = fun(0).fun(1).fun(2).fun(3) 14 | var c = fun(0).fun(1) 15 | c.fun(2) // 16 | c.fun(3) // 17 | -------------------------------------------------------------------------------- /webpack/loader/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | vue demo 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /webpack/plugin/dist/index.html: -------------------------------------------------------------------------------- 1 | react demo
-------------------------------------------------------------------------------- /webpack/plugin/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | react demo 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /handwrittenCode/字节面试题:Excel表格随机生成1000列.md: -------------------------------------------------------------------------------- 1 | Excel 中每列的编号是字母 A B C ... Z AA AB ... ZZ AAA AAB...,写一个生成算法,生成前 1000 列 2 | 3 | ```js 4 | function encode(n) { 5 | let s = '' 6 | while (n > 0) { 7 | let m = n % 26 8 | if (m === 0) m = 26 9 | s = String.fromCharCode(m + 64) + s 10 | n = (n - m) / 26 11 | } 12 | return s 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /webpack/loader/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /前端知识点/面试题.md: -------------------------------------------------------------------------------- 1 | ```js 2 | Function.prototype.a = () => { 3 | console.log(1) 4 | } 5 | Object.prototype.b = () => { 6 | console.log(2) 7 | } 8 | function A() {} 9 | const a = new A() 10 | a.a() 11 | a.b() 12 | ``` 13 | 14 | new 15 | 执行上下文/作用域链/闭包 16 | this/call/apply/bind 17 | 原型/继承 18 | Promise 19 | 深浅拷贝 20 | 事件机制/Event Loop 21 | 函数式编程 22 | service worker/web worker 23 | -------------------------------------------------------------------------------- /webpack/readme.md: -------------------------------------------------------------------------------- 1 | 1. 常见 loader 源码简析,以及动手实现一个 md2html-loader 2 | 2. webpack 插件工作原理剖析 3 | 3. webpack 主流程源码阅读以及实现一个 webpack 4 | 4. [webpack 打包优化最佳实践(未完成)] 5 | -------------------------------------------------------------------------------- /vue/template编译.md: -------------------------------------------------------------------------------- 1 | 简而言之, 就是先转化成 AST 树, 再得到的 render 函数返回 VNode( Vue 的虚拟 DOM 节点), 详细步骤如下: 2 | 3 | 首先, 通过 compile 编译器把 template 编译成 AST 语法树( abstract syntax tree 即 源代码的抽象语法结构的树状表现形式), compile 是 createCompiler 的返回值, createCompiler 是用以创建编译器的。 另外 compile 还负责合并 option。 4 | 然后, AST 会经过 generate( 将 AST 语法树转化成 render funtion 字符串的过程) 得到 render 函数, render 的返回值是 VNode, VNode 是 Vue 的虚拟 DOM 节点, 里面有( 标签名、 子节点、 文本等等) 5 | -------------------------------------------------------------------------------- /webpack/loader/css1px.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const { runLoaders } = require("loader-runner"); 4 | 5 | runLoaders({ 6 | resource: "./src/index.txt", 7 | loaders: [path.resolve(__dirname, "./loaders/css1px-loader.js")], 8 | readResource: fs.readFile.bind(fs), 9 | }, 10 | (err, result) => 11 | (err ? console.error(err) : console.log(result)) 12 | ); -------------------------------------------------------------------------------- /handwrittenCode/instanceof.js: -------------------------------------------------------------------------------- 1 | function new_instance_of(leftVaule, rightVaule) { 2 | let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值 3 | leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值 4 | while (true) { 5 | if (leftVaule === null) { 6 | return false; 7 | } 8 | if (leftVaule === rightProto) { 9 | return true; 10 | } 11 | leftVaule = leftVaule.__proto__ 12 | } 13 | } -------------------------------------------------------------------------------- /webpack/loader/src/mdtest.md: -------------------------------------------------------------------------------- 1 | **加粗** 2 | 3 | ```js 4 | let a=1 5 | ``` 6 | 7 | `a` 8 | 9 | ![md-ast](http://cdn.ru23.com/fe-study/md-ast.png) 10 | 11 | *斜体字* 12 | 13 | [![alt text](http://path/to/img.jpg)](你的链接地址) 14 | 15 | [url](https://58fe.com) 16 | 17 | 1. 第一项: 18 | - 第一项嵌套的第一个元素 19 | - 第一项嵌套的第二个元素 20 | 2. 第二项: 21 | - 第二项嵌套的第一个元素 22 | - 第二项嵌套的第二个元素 23 | 24 | > 引用 25 | 26 | ~~text~~ 27 | 28 | ### 标题 29 | -------------------------------------------------------------------------------- /webpack/plugin/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Icon from '../assets/logo.png'; 4 | import '../styles/index.less' 5 | 6 | export default class App extends Component { 7 | render () { 8 | return ( 9 |
10 | 11 |

react demo

12 |
13 | ); 14 | } 15 | } 16 | 17 | ReactDOM.render(, document.getElementById('root')); 18 | -------------------------------------------------------------------------------- /react/实现一个简单的react-hook里面的useState/readme.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const myStates = [] 3 | let stateCalls = -1 4 | function useMyState(defaultValue) { 5 | stateCalls += 1 6 | const stateId = stateCalls 7 | if (myStates[stateId]) { 8 | return myStates[stateId] 9 | } 10 | const setMyValue = (value) => { 11 | myStates[stateId][0] = value 12 | renderWithMyHook() 13 | } 14 | const tuple = [defaultValue, setMyValue] 15 | myStates[stateId] = tuple 16 | return tuple 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /leetcode/53.最大子序和.md: -------------------------------------------------------------------------------- 1 | 来源:力扣(LeetCode)
2 | 链接:https://leetcode-cn.com/problems/maximum-subarray
3 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 4 | 5 | ```js 6 | /** 7 | * @param {number[]} nums 8 | * @return {number} 9 | */ 10 | var maxSubArray = function (nums) { 11 | let ans = nums[0] 12 | let sum = 0 13 | for (const num of nums) { 14 | if (sum > 0) { 15 | sum += num 16 | } else { 17 | sum = num 18 | } 19 | ans = Math.max(ans, sum) 20 | } 21 | return ans 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /webpack/plugin/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [{ 7 | "type": "node", 8 | "request": "launch", 9 | "name": "Launch Program", 10 | "skipFiles": [ 11 | "/**" 12 | ], 13 | "program": "${workspaceFolder}/webpack.config.js" 14 | }] 15 | } -------------------------------------------------------------------------------- /前端知识点/详解JavaScript中的Event Loop(事件循环).md: -------------------------------------------------------------------------------- 1 | 为了确保程序执行的一致性,javascript 选择只用一个主线程来执行代码 2 | 3 | 先执行 当前调用栈中的同步代码(一个宏任务); 4 | 调用栈为空后去检查是否有异步任务(微任务)需要执行; 5 | 如果有则 执行完当前异步代码(当前宏任务中微任务队列里的所有微任务), 6 | 再之后就是 从消息队列中取出下一个宏任务 去执行(重新维护一个调用栈),然后开始新一轮的 Event Lopp。 7 | 然后不同版本的 Node.js 的一个 Event Loop 区别: 8 | 9 | 如果是 Node 10 及其之前版本:宏任务队列当中有几个宏任务,是要等到宏任务队列当中的所有宏任务全部执行完毕才会去执行微队列当中的微任务。 10 | 如果是 Node 11 及之后版本:一旦执行一个阶段里对应宏队列当中的一个宏任务(setTimeout,setInterval 和 setImmediate 三者其中之一,不包括 I/O)就立刻执行微任务队列,执行完微队列当中的所有微任务再回到刚才的宏队列执行下一个宏任务。这就 跟浏览器端运行一致 了。 11 | -------------------------------------------------------------------------------- /webpack/webpack/runWebpack.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | let path = require('path'); 4 | let { resolve } = path; 5 | let webpackConfig = require(path.resolve('webpack.config.js')); 6 | let Webpack = require('./myWebpack.js'); 7 | 8 | const defaultConfig = { 9 | entry: 'src/index.js', 10 | output: { 11 | path: resolve(__dirname, '../dist'), 12 | filename: 'bundle.js' 13 | } 14 | } 15 | const config = { 16 | ...defaultConfig, 17 | ...webpackConfig 18 | } 19 | 20 | const options = require('./webpack.config') 21 | new Webpack(options).run() -------------------------------------------------------------------------------- /webpack/loader/loaders/md-loader2.js: -------------------------------------------------------------------------------- 1 | const marked = require("marked"); 2 | //将markdown文件转化成html字符串 3 | const loaderUtils = require("loader-utils"); 4 | module.exports = function (content) { 5 | this.cacheable && this.cacheable(); 6 | // merge params and default config 7 | const options = loaderUtils.getOptions(this); 8 | // 涉及到加载模块,异步loader 9 | try { 10 | marked.setOptions(options); 11 | return marked(content) 12 | } catch (err) { 13 | this.emitError(err); 14 | return null 15 | } 16 | 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /handwrittenCode/防抖节流.js: -------------------------------------------------------------------------------- 1 | // 防抖 2 | function debounce(fn, time) { 3 | let timer = null 4 | return function () { 5 | if (timer) { 6 | clearTimeout(timer) 7 | } 8 | timer = setTimeout(() => { 9 | fn.apply(this, arguments) 10 | }, time) 11 | } 12 | } 13 | 14 | // 节流 15 | function throttle(fn, time) { 16 | let canRun = true 17 | return function () { 18 | if (!canRun) { 19 | return 20 | } 21 | canRun = false 22 | setTimeout(() => { 23 | fn.apply(this, arguments) 24 | canRun = true 25 | }, time) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /webpack/webpack性能优化/readme.md: -------------------------------------------------------------------------------- 1 | 1. 减小打包后的文件大小 2 | 2. 首页按需引入文件,减少白屏时间 3 | 3. 优化 webpack 打包时间,提高开发效率 4 | 4. webpack4 splitChunks 合理拆包 5 | 5. webpack externals 6 | 7 | 引入 webpack 测速插件 `speed-measure-webpack-plugin`,因此我便将该插件引入项目中,先用来检测每个项目编译构建的速度,看看到底是哪个 loader、plugin 耗时久。测速后,发现主要是搜索解析文件,将 vue、es6 语法、sass/scss/less 文件转换成 js、css 的 loader,以及压缩打包资源的 plugin 耗时比较久。发现问题后,就可以针对性地进行优化,主要采取如下措施: 8 | 9 | 第三方依赖多,搜索解析文件慢:采用缓存策略,hard-source-webpack-plugin 为依赖模块提供中间缓存,极大程度地提升二次构建速度。 10 | loader 解析转换慢:配置 loader 的 test、include、exclude,缩小文件解析搜索范围。 11 | 压缩打包资源 plugin 执行慢:比如 uglify-webpack-plugin,可以开启缓存、多进程。 12 | -------------------------------------------------------------------------------- /leetcode/112.路径总和.md: -------------------------------------------------------------------------------- 1 | 来源:力扣(LeetCode)
2 | 链接:https://leetcode-cn.com/problems/path-sum
3 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 4 | 5 | ```js 6 | var hasPathSum = function (root, targetSum) { 7 | // 深度优先遍历 8 | if (root === null) { 9 | //1.刚开始遍历时 10 | //2.递归中间 说明该节点不是叶子节点 11 | return false 12 | } 13 | if (root.left === null && root.right === null) { 14 | return root.val - targetSum === 0 15 | } 16 | // 拆分成两个子树 17 | return ( 18 | hasPathSum(root.left, targetSum - root.val) || 19 | hasPathSum(root.right, targetSum - root.val) 20 | ) 21 | } 22 | ``` 23 | -------------------------------------------------------------------------------- /webpack/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | let path = require('path'); 2 | // let myPlugins = require('./plugins/p.js'); // 引入插件 3 | 4 | module.exports = { 5 | mode: 'development', 6 | entry: './src/index.js', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'dist') 10 | }, 11 | module: { 12 | rules: [{ 13 | test: /\.less$/, 14 | use: [ 15 | path.resolve(__dirname, 'loader', 'style-loader'), 16 | path.resolve(__dirname, 'loader', 'less-loader') 17 | ] 18 | }] 19 | }, 20 | plugins: [ 21 | // new myPlugins() 22 | ] 23 | } -------------------------------------------------------------------------------- /handwrittenCode/currying.js: -------------------------------------------------------------------------------- 1 | // 实现柯里化函数 2 | function currying(fn, ...args) { 3 | return args.length >= fn.length ? fn(...args) : (...args2) => currying(fn, ...args, ...args2) 4 | } 5 | console.log(currying(2, 2)(3)(4)) 6 | 7 | // https://zhuanlan.zhihu.com/p/33374547 8 | 9 | // 方法2 10 | function currying() { 11 | let args = [] 12 | 13 | args.push(...arguments) 14 | 15 | let fn = () => { 16 | args.push(...arguments) 17 | 18 | return fn 19 | } 20 | 21 | fn.toString = () => { 22 | return args.reduce((t, i) => t + i, 0) 23 | } 24 | 25 | return fn 26 | } 27 | console.log(currying(2, 2)(3)(4)) -------------------------------------------------------------------------------- /handwrittenCode/实现带有超时功能的Promise.js: -------------------------------------------------------------------------------- 1 | // 实现带有超时功能的 Promise 2 | async function executeWithTimeout(fn, ms) { 3 | let timeout = new Promise(function (reslove, reject) { 4 | setTimeout(() => { 5 | reject('timeout') 6 | }, ms) 7 | }) 8 | return Promise.race([timeout, fn()]) 9 | } 10 | async function fn1() { 11 | return new Promise((resolve, reject) => { 12 | setTimeout(() => { 13 | resolve() 14 | }, 400) 15 | }) 16 | } 17 | executeWithTimeout(fn1, 300) 18 | .then(() => { 19 | console.log('sucess') 20 | // ok 21 | }) 22 | .catch((err) => { 23 | console.log(1, err) 24 | // timeout 25 | }) 26 | -------------------------------------------------------------------------------- /vue/proxy.md: -------------------------------------------------------------------------------- 1 | ### 变化侦测 2 | 3 | vue2 中覆盖了 Array 原型中的 7 个方法,分别是:push、pop、shift、unshift、splice、sort、reverse,所以当直接通过索引改变数组时,vue 是追踪不到变化的。 4 | 5 | 所以在 vue2 中实现数据双向绑定,是通过 Object.definePropertyd 劫持各个属性的 getter、setter,在读取数据时触发 getter,修改数据时候触发 setter。 6 | 7 | 在 3 中改为用 Proxy,但是 Proxy 只能代理一层,对于深层的无法代理。vue3 中利用每次 set 被拦截之前都会拦截到 get 操作,所以 vue3 在 get 中直接对数据进行 reactive,这样就大大减少了递归 reactive 带来的性能消耗。 8 | 9 | 与 Object.defineProperty 对比优势: 10 | 11 | 1.可以直接监听对象而非属性 12 | 13 | 2.可以直接监听数组的变化 14 | 15 | 3.Proxy 有多达 13 种拦截方式,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的 16 | 17 | 4.Proxy 返回的是一个新对象,可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改 18 | -------------------------------------------------------------------------------- /leetcode/136.只出现一次的数字.md: -------------------------------------------------------------------------------- 1 | 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 2 | 3 | 说明: 4 | 5 | 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 6 | 7 | 示例 1: 8 | 9 | ``` 10 | 输入: [2,2,1] 11 | 输出: 1 12 | ``` 13 | 14 | 示例  2: 15 | 16 | ``` 17 | 输入: [4,1,2,1,2] 18 | 输出: 4 19 | ``` 20 | 21 | 来源:力扣(LeetCode)
22 | 链接:https://leetcode-cn.com/problems/single-number
23 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 24 | 25 | ```js 26 | /** 27 | * @param {number[]} nums 28 | * @return {number} 29 | */ 30 | var singleNumber = function (nums) { 31 | let ans = '' 32 | for (const num of nums) { 33 | ans ^= num 34 | console.log(ans) 35 | } 36 | return ans 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /handwrittenCode/实现new.js: -------------------------------------------------------------------------------- 1 | /* 2 | 思路:new 做了什么? 3 | 1. 创建一个对象,使得创建的对象的__proto__ === Fn.prototype 4 | 2. 调用Fn 的构造函数。传入的this 是创建的对象。 5 | 3. 如果Fn 的构造函数返回了一个对象,则返回这个对象,如果是普通类型的值,返回新建的对象。 6 | */ 7 | 8 | function person(options) { 9 | this.name = options.name 10 | // return this.name 或者 {} 可以自己试试啥效果 11 | } 12 | 13 | function myNew(fn, options) { 14 | var obj = {} 15 | obj.__proto__ = fn.prototype 16 | 17 | var res = fn.call(obj, options) 18 | 19 | if (typeof res === 'object') { 20 | return res 21 | } 22 | 23 | return obj 24 | } 25 | 26 | var p = myNew(person, { name: '小强' }) // person {name: "小强"} 27 | 28 | var p2 = new person({ name: '小强' }) // person {name: "小强"} -------------------------------------------------------------------------------- /leetcode/206.反转链表.md: -------------------------------------------------------------------------------- 1 | 来源:力扣(LeetCode)
2 | 链接:https://leetcode-cn.com/problems/reverse-linked-list
3 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 4 | 5 | ### 方案 1 6 | 7 | ```js 8 | var reverseList = function (head) { 9 | let prev = null 10 | cur = head 11 | while (cur) { 12 | const next = cur.next 13 | cur.next = prev 14 | prev = cur 15 | cur = next 16 | } 17 | return prev 18 | } 19 | ``` 20 | 21 | ### 方案 2 22 | 23 | ```js 24 | var reverseList = function (head) { 25 | let prev = null 26 | cur = head 27 | while (cur) { 28 | const next = cur.next 29 | cur.next = prev 30 | prev = cur 31 | cur = next 32 | } 33 | return prev 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /leetcode/771.宝石与石头.md: -------------------------------------------------------------------------------- 1 | 给定字符串`J`代表石头中宝石的类型,和字符串`S`代表你拥有的石头。 `S`中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。 2 | 3 | `J`中的字母不重复,`J`和  `S`中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。 4 | 5 | 示例 1: 6 | 7 | ``` 8 | 输入: J = "aA", S = "aAAbbbb" 9 | 输出: 3 10 | ``` 11 | 12 | 示例 2: 13 | 14 | ``` 15 | 输入: J = "z", S = "ZZ" 16 | 输出: 0 17 | ``` 18 | 19 | 思路:把石头里的宝石`replace`掉,长度相减,就是结果 20 | 21 | ```js 22 | /** 23 | * @param {string} J 24 | * @param {string} S 25 | * @return {number} 26 | */ 27 | var numJewelsInStones = function (J, S) { 28 | let newS = S 29 | for (let i = 0; i < J.length; i++) { 30 | newS = newS.replace(new RegExp(J[i], 'g'), '') 31 | } 32 | return S.length - newS.length 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /leetcode/70.爬楼梯.md: -------------------------------------------------------------------------------- 1 | 假设你正在爬楼梯。需要 n  阶你才能到达楼顶。 2 | 3 | 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 4 | 5 | 注意:给定 n 是一个正整数。 6 | 7 | 示例 1: 8 | 9 | 输入: 2 10 | 输出: 2 11 | 解释: 有两种方法可以爬到楼顶。 12 | 13 | 1. 1 阶 + 1 阶 14 | 2. 2 阶 15 | 示例 2: 16 | 17 | 输入: 3 18 | 输出: 3 19 | 解释: 有三种方法可以爬到楼顶。 20 | 21 | 1. 1 阶 + 1 阶 + 1 阶 22 | 2. 1 阶 + 2 阶 23 | 3. 2 阶 + 1 阶 24 | 25 | 来源:力扣(LeetCode)
26 | 链接:https://leetcode-cn.com/problems/climbing-stairs
27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | 29 | ```js 30 | var climbStairs = function (n) { 31 | let dp = [] 32 | dp[0] = 1 33 | dp[1] = 1 34 | for (let i = 2; i <= n; i++) { 35 | dp[i] = dp[i - 1] + dp[i - 2] 36 | } 37 | return dp[n] 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /前端知识点/性能优化相关.md: -------------------------------------------------------------------------------- 1 | vivo 商城架构升级-SSR 实战篇 2 | 3 | CSR 与 SSR 的对比 4 | 5 | 性能优化 6 | 7 | 自动化部署 8 | 9 | 容灾、降级 10 | 11 | 日志、监控 12 | ... 13 | 14 | 页面的首开率和白屏率确实能影响一个产品的用户留存 15 | SSR 是借用了 service 的能力去尽可能提高页面首屏性能。 16 | 17 | 从 app 的图文列表页点进去一些图文详情页或者理财师的个人主页,如果图文详情页渲染时间太久会导致用户流失,肯定会影响 app 的收益。 18 | 所以对于我们前端同学来说,提高首屏效率确实是我们前端领域亟待解决的问题。于是我们就开始对现有的图文详情页和个人主页进行性能改造。 19 | 20 | 首先来讲解一下几个常用的性能指标术语 21 | 22 | 首屏时间 fsp(first-screen-paint) 从开始下载(navigation)到首屏内容包含图片全部渲染完成的时间,直到用户可以看到首屏的全部内容, 23 | 计算方式分为两种 24 | 首次绘制时间 fmp(first-meaningful-paint) 页面的主要内容开始出现在屏幕上的时间点 25 | 可交互时间 ttl(time-to-interactive) 从开始下载(navigation)到屏幕首屏全部渲染完成的时间,渲染完成后,且页面没有长时间卡顿。 26 | 27 | 用户请求-webview 启动-请求 html、document-页面框架渲染-js 资源请求 28 | 29 | 缓存管理 30 | -------------------------------------------------------------------------------- /handwrittenCode/数组全排列.js: -------------------------------------------------------------------------------- 1 | class Permutation { 2 | constructor(arr) { 3 | this.arr = Array.from(arr) 4 | this.result = [] 5 | this.len = 0 6 | this.run(0) 7 | } 8 | 9 | run(index) { 10 | if (index == this.arr.length - 1) { 11 | this.result.push(Array.from(this.arr)) 12 | this.len++ 13 | return 14 | } 15 | for (let i = index; i < this.arr.length; i++) { 16 | ;[this.arr[index], this.arr[i]] = [this.arr[i], this.arr[index]] 17 | this.run(index + 1) 18 | ;[this.arr[index], this.arr[i]] = [this.arr[i], this.arr[index]] 19 | } 20 | } 21 | } 22 | 23 | let p = new Permutation(['A', 'B', 'C']) 24 | console.log(p.result) 25 | console.log(p.len) 26 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /webpack/loader/run-loader.js: -------------------------------------------------------------------------------- 1 | // 创建 run-loader.js 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const { runLoaders } = require("loader-runner"); 5 | const VueLoader = require('vue-loader') 6 | let options = ['mdtest.md', './loaders/md-loader']; 7 | // let options = ['main.js', 'babel-loader'] 8 | // let options = ['App.vue', VueLoader] 9 | 10 | runLoaders({ 11 | resource: `./src/${options[0]}`, 12 | // resource: `./src/index.css}`, 13 | loaders: [path.resolve(__dirname, options[1])], 14 | // loaders: [path.resolve(__dirname, "./loaders/css1px-loader.js")], 15 | readResource: fs.readFile.bind(fs), 16 | }, 17 | (err, result) => 18 | (err ? console.error(err) : console.log(result)) 19 | ); -------------------------------------------------------------------------------- /webpack/webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack", 3 | "version": "1.0.0", 4 | "description": "手写实现一个简单的webpack", 5 | "main": "index.js", 6 | "scripts": { 7 | "webpack":"webpack", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "bin": { 14 | "my-webpack": "./bin/my-webpack.js", 15 | "webpack": "./bin/webpack.js" 16 | }, 17 | "dependencies": { 18 | "@babel/core": "^7.10.2", 19 | "@babel/parser": "^7.10.2", 20 | "@babel/preset-env": "^7.10.2", 21 | "@babel/traverse": "^7.10.1" 22 | }, 23 | "devDependencies": { 24 | "webpack": "^4.43.0", 25 | "webpack-cli": "^3.3.11" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /leetcode/283.移动零.md: -------------------------------------------------------------------------------- 1 | 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 2 | 3 | 示例: 4 | 5 | ```js 6 | 输入: [0, 1, 0, 3, 12] 7 | 输出: [1, 3, 12, 0, 0] 8 | ``` 9 | 10 | 说明: 11 | 12 | 必须在原数组上操作,不能拷贝额外的数组。 13 | 尽量减少操作次数。 14 | 15 | 来源:力扣(LeetCode)
16 | 链接:https://leetcode-cn.com/problems/move-zeroes
17 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 18 | 19 | ```js 20 | /** 21 | * @param {number[]} nums 22 | * @return {void} Do not return anything, modify nums in-place instead. 23 | */ 24 | var moveZeroes = function (nums) { 25 | let j = 0 26 | for (let i = 0; i < nums.length; i++) { 27 | if (nums[i] !== 0) { 28 | if (i !== j) { 29 | ;[nums[i], nums[j]] = [nums[j], nums[i]] 30 | } 31 | j++ 32 | } 33 | } 34 | return nums 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /handwrittenCode/原生ajax封装成async-await调用.js: -------------------------------------------------------------------------------- 1 | const getJson = function (url, type, data) { 2 | const promise = new Promise(function (resolve, reject) { 3 | const handler = function () { 4 | if (this.readyState !== 4) { 5 | return 6 | } 7 | if (this.status == 200) { 8 | resolve(this.response) 9 | } else { 10 | reject(new Error(this.statusText)) 11 | } 12 | } 13 | const client = new XMLHttpRequest() 14 | client.open(type, url) 15 | client.onreadystatechange = handler 16 | client.responseType = 'json' 17 | if (type == 'get') { 18 | client.send(data) 19 | } else { 20 | client.setRequestHeader('Content-Type', 'application/json') 21 | client.send(JSON.stringify(data)) 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /vue/computed与watch.md: -------------------------------------------------------------------------------- 1 | computed 与 watch 2 | 计算属性顾名思义就是通过其他变量计算得来的另一个属性,计算属性具有缓存。计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。 3 | 侦听器 watch 是侦听一个特定的值,当该值变化时执行特定的函数。例如分页组件中,我们可以监听当前页码,当页码变化时执行对应的获取数据的函数 4 | 5 | computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值; 6 | watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作; 7 | 8 | 从属性名上,computed 是计算属性,也就是依赖其它的属性计算所得出最后的值。watch 是去监听一个值的变化,然后执行相对应的函数。 9 | 从实现上,computed 的值在 get 执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取 computed 的值时才会重新调用对应的 getter 来计算。watch 在每次监听的值变化时,都会执行回调。 10 | 其实从这一点来看,都是在依赖的值变化之后,去执行回调。很多功能本来就很多属性都可以用,只不过有更适合的。 11 | 12 | 如果一个值依赖多个属性(多对一),用 computed 肯定是更加方便的。 13 | 如果一个值变化后会引起一系列操作,或者一个值变化会引起一系列值的变化(一对多),用 watch 更加方便一些。 14 | 15 | watch 的回调里面会传入监听属性的新旧值,通过这两个值可以做一些特定的操作。computed 通常就是简单的计算。 16 | 下面是两者之间的互换 17 | -------------------------------------------------------------------------------- /leetcode/234.回文链表.md: -------------------------------------------------------------------------------- 1 | 请判断一个链表是否为回文链表。 2 | 3 | 示例 1: 4 | 5 | ``` 6 | 输入: 1->2 7 | 输出: false 8 | ``` 9 | 10 | 示例 2: 11 | 12 | ``` 13 | 输入: 1->2->2->1 14 | 输出: true 15 | ``` 16 | 17 | 进阶: 18 | 你能否用  O(n) 时间复杂度和 O(1) 空间复杂度解决此题? 19 | 20 | 来源:力扣(LeetCode)
21 | 链接:https://leetcode-cn.com/problems/palindrome-linked-list
22 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 23 | 24 | ```js 25 | const isPalindrome = (head) => { 26 | const vals = [] 27 | while (head) { 28 | // 丢进数组里 29 | vals.push(head.val) 30 | head = head.next 31 | } 32 | let start = 0, 33 | end = vals.length - 1 // 双指针 34 | while (start < end) { 35 | if (vals[start] != vals[end]) { 36 | // 理应相同,如果不同,不是回文 37 | return false 38 | } 39 | start++ 40 | end-- // 双指针移动 41 | } 42 | return true // 循环结束也没有返回false,说明是回文 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /handwrittenCode/苹果、梨、香蕉三个数组相互拼成字符串.js: -------------------------------------------------------------------------------- 1 | // 输入:[ ['a', 'b'], ['苹果', '梨', '香蕉'], ['红', '黑'] ]; 2 | // 输出: 3 | // ['a-苹果-红', 'a-苹果-黑', 'a-梨-红', 'a-梨-黑', 'a-香蕉-红', 'a-香蕉-黑', 4 | // 'b-苹果-红', 'b-苹果-黑', 'b-梨-红', 'b-梨-黑', 'b-香蕉-红', 'b-香蕉-黑' ]; 5 | 6 | 7 | // 思路: 数组两两拼接,组成一个新的数组,再和下一个数组拼接 8 | 9 | 10 | let list=[ ['a', 'b'], ['苹果', '梨', '香蕉'], ['红', '黑'] ] 11 | let arrMerge=()=>{ 12 | let result=[] 13 | if(list.length>1){ 14 | let res=[] 15 | for(let m=0;m1){ 23 | twoArrMerge(list) 24 | } 25 | result=res 26 | } else{ 27 | result=list 28 | } 29 | return list 30 | } 31 | console.log(arrMerge(list)) 32 | -------------------------------------------------------------------------------- /handwrittenCode/call.js: -------------------------------------------------------------------------------- 1 | // call 函数的实现步骤: 2 | 3 | // 1. 判断调用对象是否为函数, 即使我们是定义在函数的原型上的, 但是可能出现使用 call 等方式调用的情况。 4 | // 2. 判断传入上下文对象是否存在, 如果不存在, 则设置为 window。 5 | // 3. 处理传入的参数, 截取第一个参数后的所有参数。 6 | // 4. 将函数作为上下文对象的一个属性。 7 | // 5. 使用上下文对象来调用这个方法, 并保存返回结果。 8 | // 6. 删除刚才新增的属性。 9 | // 7. 返回结果。 10 | // https://juejin.im/post/5ef8377f6fb9a07e693a6061?#heading-72 11 | // call函数实现 12 | 13 | Function.prototype.myCall = function(context) { 14 | // 判断调用对象 15 | if (typeof this !== "function") { 16 | console.error("type error"); 17 | } 18 | 19 | // 获取参数 20 | let args = [...arguments].slice(1), 21 | result = null; 22 | 23 | // 判断 context 是否传入,如果未传入则设置为 window 24 | context = context || window; 25 | 26 | // 将调用函数设为对象的方法 27 | context.fn = this; 28 | 29 | // 调用函数 30 | result = context.fn(...args); 31 | 32 | // 将属性删除 33 | delete context.fn; 34 | 35 | return result; 36 | }; -------------------------------------------------------------------------------- /leetcode/169.多数元素.md: -------------------------------------------------------------------------------- 1 | 给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于  ⌊ n/2 ⌋  的元素。 2 | 3 | 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 4 | 5 | 示例  1: 6 | 7 | ``` 8 | 输入:[3,2,3] 9 | 输出:3 10 | ``` 11 | 12 | 示例  2: 13 | 14 | ``` 15 | 输入:[2,2,1,1,1,2,2] 16 | 输出:2 17 | ``` 18 | 19 | 进阶: 20 | 21 | 尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。 22 | 23 | 来源:力扣(LeetCode)
24 | 链接:https://leetcode-cn.com/problems/majority-element
25 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
26 | 27 | ```js 28 | /** 29 | * @param {number[]} nums 30 | * @return {number} 31 | */ 32 | var majorityElement = function (nums) { 33 | nums.sort((a, b) => a - b) 34 | return nums[Math.floor(nums.length / 2)] 35 | } 36 | ``` 37 | 38 | ```js 39 | var majorityElement = function (nums) { 40 | let half = nums.length / 2 41 | let obj = {} 42 | for (let num of nums) { 43 | obj[num] = (obj[num] || 0) + 1 44 | if (obj[num] > half) return num 45 | } 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /webpack/loader/loaders/watermark-loader.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const images = require("images"); 3 | const loaderUtils = require('loader-utils'); 4 | 5 | module.exports = function (content) { 6 | // 获取本loader对应的配置参数 7 | // 获取水印文件路径 8 | const { watermarkPath = '' } = this.query; 9 | // 获取需要处理的图片路径 10 | const url = loaderUtils.interpolateName(this, "[path][name].[ext]", { 11 | content: source, 12 | context: this.context 13 | }); 14 | // 给图片打上水印 15 | const file = images(path.resolve(this.context, url)) 16 | .draw(images(path.resolve(this.rootContext, watermarkPath)), 10, 10) 17 | .encode(path.extname(url) || 'png'); 18 | // 发射文件 19 | this.emitFile(url, file); 20 | // 返回数据 21 | const publicPath = `__webpack_public_path__ + ${JSON.stringify(url)}`; 22 | 23 | return `module.exports = ${publicPath};`; 24 | }; 25 | 26 | module.exports.raw = true; 27 | 28 | -------------------------------------------------------------------------------- /leetcode/617.合并二叉树.md: -------------------------------------------------------------------------------- 1 | 来源:力扣(LeetCode)
2 | 链接:https://leetcode-cn.com/problems/merge-two-binary-trees
3 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 4 | 5 | ```js 6 | /** 7 | * Definition for a binary tree node. 8 | * function TreeNode(val, left, right) { 9 | * this.val = (val===undefined ? 0 : val) 10 | * this.left = (left===undefined ? null : left) 11 | * this.right = (right===undefined ? null : right) 12 | * } 13 | */ 14 | /** 15 | * @param {TreeNode} root1 16 | * @param {TreeNode} root2 17 | * @return {TreeNode} 18 | */ 19 | var mergeTrees = function (root1, root2) { 20 | if (root1 == null && root2) { 21 | return root2 22 | } else if (root2 == null && root1) { 23 | return root1 24 | } else if (root1 && root2) { 25 | root1.val = root1.val + root2.val 26 | root1.left = mergeTrees(root1.left, root2.left) 27 | root1.right = mergeTrees(root1.right, root2.right) 28 | } 29 | return root1 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /handwrittenCode/计算树的深度.js: -------------------------------------------------------------------------------- 1 | const tree = { 2 | name: 'root', 3 | children: [ 4 | { name: '叶子1-1' }, 5 | { name: '叶子1-2' }, 6 | { 7 | name: '叶子2-1', 8 | children: [ 9 | { 10 | name: '叶子3-1', 11 | children: [ 12 | { 13 | name: '叶子4-1', 14 | }, 15 | ], 16 | }, 17 | ], 18 | }, 19 | ], 20 | } 21 | 22 | function getDepth(tree) { 23 | let depth = 0 24 | 25 | if (tree) { 26 | let arr = [tree] 27 | let temp = arr 28 | while (temp.length) { 29 | arr = temp 30 | temp = [] 31 | for (let i = 0; i < arr.length; i++) { 32 | if (arr[i].children && arr[i].children.length) { 33 | for (let j = 0; j < arr[i].children.length; j++) { 34 | temp.push(arr[i].children[j]) 35 | } 36 | } 37 | } 38 | depth++ 39 | } 40 | } 41 | 42 | return depth 43 | } 44 | console.log(getDepth(tree)) 45 | -------------------------------------------------------------------------------- /leetcode/18.删除链表的节点.md: -------------------------------------------------------------------------------- 1 | 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 2 | 3 | 返回删除后的链表的头节点。 4 | 5 | 注意:此题对比原题有改动 6 | 7 | 示例 1: 8 | 9 | ``` 10 | 输入: head = [4,5,1,9], val = 5 11 | 输出: [4,1,9] 12 | 解释: 给定你链表中值为  5  的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9. 13 | ``` 14 | 15 | 示例 2: 16 | 17 | ``` 18 | 输入: head = [4,5,1,9], val = 1 19 | 输出: [4,5,9] 20 | 解释: 给定你链表中值为  1  的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9. 21 | ``` 22 | 23 | 说明: 24 | 25 | 题目保证链表中节点的值互不相同 26 | 若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点 27 | 28 | 来源:力扣(LeetCode)
29 | 链接:https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof
30 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 31 | 32 | ```js 33 | var deleteNode = function (head, val) { 34 | if (head.val === val) return head.next 35 | let prev = head, 36 | node = prev.next 37 | while (node) { 38 | if (node.val === val) { 39 | prev.next = node.next 40 | } 41 | prev = node 42 | node = node.next 43 | } 44 | return head 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /webpack/plugin/plugins/FileListPlugin.js: -------------------------------------------------------------------------------- 1 | class FileListPlugin { 2 | apply(compiler) { 3 | // emit is asynchronous hook, tapping into it using tapAsync, you can use tapPromise/tap(synchronous) as well 4 | compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => { 5 | // Create a header string for the generated file: 6 | var filelist = 'In this build:\n\n' 7 | 8 | // Loop through all compiled assets, 9 | // adding a new line item for each filename. 10 | for (var filename in compilation.assets) { 11 | filelist += '- ' + filename + '\n' 12 | } 13 | 14 | // Insert this list into the webpack build as a new file asset: 15 | compilation.assets['filelist.md'] = { 16 | source: function() { 17 | return filelist 18 | }, 19 | size: function() { 20 | return filelist.length 21 | } 22 | } 23 | 24 | callback() 25 | }) 26 | } 27 | } 28 | 29 | module.exports = FileListPlugin; -------------------------------------------------------------------------------- /leetcode/78.子集.md: -------------------------------------------------------------------------------- 1 | 给你一个整数数组  nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 2 | 3 | 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 4 | 5 | 示例 1: 6 | 7 | ``` 8 | 输入:nums = [1,2,3] 9 | 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] 10 | ``` 11 | 12 | 示例 2: 13 | 14 | ``` 15 | 输入:nums = [0] 16 | 输出:[[],[0]] 17 | ``` 18 | 19 | 提示: 20 | 21 | ``` 22 | 1 <= nums.length <= 10 23 | -10 <= nums[i] <= 10 24 | nums 中的所有元素 互不相同 25 | ``` 26 | 27 | 来源:力扣(LeetCode)
28 | 链接:https://leetcode-cn.com/problems/subsets
29 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 30 | 31 | ```js 32 | const subsets = (nums) => { 33 | const res = [] 34 | 35 | const dfs = (index, list) => { 36 | if (index == nums.length) { 37 | // 指针越界 38 | res.push(list.slice()) // 加入解集 39 | return // 结束当前的递归 40 | } 41 | list.push(nums[index]) // 选择这个数 42 | dfs(index + 1, list) // 基于该选择,继续往下递归,考察下一个数 43 | list.pop() // 上面的递归结束,撤销该选择 44 | dfs(index + 1, list) // 不选这个数,继续往下递归,考察下一个数 45 | } 46 | 47 | dfs(0, []) 48 | return res 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /leetcode/101.对称二叉树.md: -------------------------------------------------------------------------------- 1 | 给定一个二叉树,检查它是否是镜像对称的。 2 | 3 | 例如,二叉树  [1,2,2,3,4,4,3] 是对称的。 4 | 5 | ``` 6 | 1 7 | / \ 8 | 2 2 9 | / \ / \ 10 | 3 4 4 3 11 | ``` 12 | 13 | 但是下面这个  [1,2,2,null,3,null,3] 则不是镜像对称的: 14 | 15 | ``` 16 | 1 17 | / \ 18 | 2 2 19 | \ \ 20 | 3 3 21 | ``` 22 | 23 | 来源:力扣(LeetCode)
24 | 链接:https://leetcode-cn.com/problems/symmetric-tree
25 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 26 | 27 | ```js 28 | /**递归 代码 29 | * @param {TreeNode} root 30 | * @return {boolean} 31 | */ 32 | var isSymmetric = function (root) { 33 | const check = (left, right) => { 34 | if (left == null && right == null) { 35 | return true 36 | } 37 | if (left && right) { 38 | return ( 39 | left.val === right.val && 40 | check(left.left, right.right) && 41 | check(left.right, right.left) 42 | ) 43 | } 44 | return false // 一个子树存在一个不存在,肯定不对称 45 | } 46 | if (root == null) { 47 | // 如果传入的root就是null,对称 48 | return true 49 | } 50 | return check(root.left, root.right) 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /test5.js: -------------------------------------------------------------------------------- 1 | // const primes = [] 2 | 3 | // function getPrimes(n) { 4 | // // let dList=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47] 5 | // const isPrimesFn = (num) => { 6 | // let isPrimes = true 7 | // if (num === 1) { 8 | // return false 9 | // } 10 | 11 | // for (let i = 2; i < num; i++) { 12 | // if (num % i === 0) { 13 | // isPrimes = false 14 | // } 15 | // } 16 | // return isPrimes 17 | // } 18 | 19 | // for (let m = 1; m < n; m++) { 20 | // if (isPrimesFn(m)) { 21 | // primes.push(m) 22 | // } 23 | // } 24 | // return primes 25 | // } 26 | 27 | // getPrimes(1000) 28 | 29 | // console.log(primes) 30 | 31 | function foo() { 32 | console.log(this.a) 33 | } 34 | function doFoo(fn) { 35 | fn() 36 | } 37 | var obj = { 38 | a: 2, 39 | foo: foo, 40 | } 41 | var a = 'global' 42 | doFoo(obj.foo) 43 | 44 | // var a = 10 45 | // ;(function () { 46 | // console.log(a) 47 | // a = 5 48 | // console.log(window.a) 49 | // console.log(a) 50 | // var a = 20 51 | // console.log(a) 52 | // })() 53 | -------------------------------------------------------------------------------- /handwrittenCode/腾讯面试题3.js: -------------------------------------------------------------------------------- 1 | // 生成长度为n的int型随机数组,数组元素范围为0~n-1, 2 | // 每个元素都是唯一的。只使用基本数据类型。 3 | 4 | let getRandomArray = (len) => { 5 | // let result = [] 6 | // for (let i = 0; i < n - 1; i++) { 7 | // let temp = [] 8 | // temp.push(i) 9 | // for (let l = n - 1; l < i; l--) { 10 | // temp.push(l) 11 | // } 12 | // console.log(temp) 13 | // } 14 | let result = [] 15 | 16 | for (let i = 0; i < len; i++) { 17 | result.push(i) 18 | } 19 | let swap = (a, b, array) => { 20 | let temp = '' 21 | temp = array[a] 22 | array[a] = array[b] 23 | array[b] = temp 24 | return JSON.parse(JSON.stringify(array)) 25 | } 26 | // console.log(swap(1, 2, result)) 27 | let list = [] 28 | for (let m = 0; m < len; m++) { 29 | for (let n = 0; n < len; n++) { 30 | list.push(swap(m, n, result)) 31 | // console.log(m, n, swap(m, n, result), list) 32 | } 33 | } 34 | console.log('list', list) 35 | let randomIndex = Math.random 36 | console.log(Math.random() * 10) 37 | // return list[] 38 | } 39 | 40 | getRandomArray(5) 41 | -------------------------------------------------------------------------------- /leetcode/14.最长公共前缀.md: -------------------------------------------------------------------------------- 1 | 示例 1: 2 | 3 | 编写一个函数来查找字符串数组中的最长公共前缀。 4 | 5 | 如果不存在公共前缀,返回空字符串  ""。 6 | 7 | 示例  1: 8 | 9 | ``` 10 | 输入: ["flower","flow","flight"] 11 | 输出: "fl" 12 | ``` 13 | 14 | 示例  2: 15 | 16 | ``` 17 | 输入: ["dog","racecar","car"] 18 | 输出: "" 19 | 解释: 输入不存在公共前缀。 20 | ``` 21 | 22 | 来源:力扣(LeetCode)
23 | 链接:https://leetcode-cn.com/problems/longest-common-prefix
24 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 25 | 26 | ##### 思路: 27 | 28 | 1. 先遍历数组 29 | 2. 再遍历数组的第一个字符串,用字符串中的每一个字符和数组中的每一项的对应的该字符串下标相比,不同则跳出循环,两两找出公共前缀,最终结果即为最长公共前缀的长度 j。 30 | 3. 截取字符串长度 j 的字符即为最长公共前缀 31 | 32 | ```js 33 | const strs = ['flower', 'flow', 'flight'] 34 | const longestCommonPrefix = function (strs) { 35 | if (strs === null || strs.length === 0) return '' 36 | let commonString = '' 37 | 38 | for (let i = 1; i < strs.length; i++) { 39 | let j = 0 40 | for (; j < strs[0].length && j < strs[i].length; j++) { 41 | if (strs[0][j] !== strs[i][j]) break 42 | } 43 | commonString = strs[0].substring(0, j) 44 | } 45 | return commonString 46 | } 47 | longestCommonPrefix(strs) 48 | ``` 49 | -------------------------------------------------------------------------------- /leetcode/35.搜索插入位置.md: -------------------------------------------------------------------------------- 1 | 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 2 | 3 | 你可以假设数组中无重复元素。 4 | 5 | 示例 1: 6 | 7 | ``` 8 | 输入: [1,3,5,6], 5 9 | 输出: 2 10 | ``` 11 | 12 | 示例  2: 13 | 14 | ``` 15 | 输入: [1,3,5,6], 2 16 | 输出: 1 17 | ``` 18 | 19 | 示例 3: 20 | 21 | ``` 22 | 输入: [1,3,5,6], 7 23 | 输出: 4 24 | ``` 25 | 26 | 示例 4: 27 | 28 | ``` 29 | 输入: [1,3,5,6], 0 30 | 输出: 0 31 | ``` 32 | 33 | 来源:力扣(LeetCode)
34 | 链接:https://leetcode-cn.com/problems/search-insert-position
35 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 36 | 37 | ### 思路:二分查找法 38 | 39 | 我们也可以用二分查找法找寻边界值,也就是说在有序数组中找到“正好大于(小于)目标数”的那个数。 40 | 如果当前找到的数大于目标数时,它可能就是我们要找的数,所以需要保留这个索引,也因此`if (target <= nums[mid])`时 `ans = mid;high=mid-1`; 而没有`low=mid+1`。 41 | 42 | ```js 43 | var searchInsert = function (nums, target) { 44 | var n = nums.length 45 | var low = 0, 46 | high = n - 1, 47 | ans = n 48 | while (low <= high) { 49 | let mid = Math.floor((low + high) / 2) 50 | if (target <= nums[mid]) { 51 | ans = mid 52 | high = mid - 1 53 | } else { 54 | low = mid + 1 55 | } 56 | } 57 | return ans 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /handwrittenCode/js实现数组和链表之间相互转换.md: -------------------------------------------------------------------------------- 1 | ### 数组转链表 2 | 3 | ```js 4 | //方法1 5 | let arrayToLink = (arr) => { 6 | if (arr.length) { 7 | return null 8 | } 9 | let node 10 | let head = { value: arr[0], next: null } 11 | let pnode = head 12 | for (let i = 0; i < ary.length; i++) { 13 | node = { value: ary[i], next: null } 14 | pnode.next = node 15 | pnode = node //将node赋值给pnode 16 | } 17 | return head 18 | } 19 | //方法2 递归 20 | let arrayToLink = (ary, start = 0) => { 21 | if (start === ary.length) { 22 | return null 23 | } 24 | var node = { 25 | value: ary[start], 26 | next: null, 27 | } 28 | var rest = arrayToLink(ary, start + 1) 29 | node.next = rest 30 | return node 31 | } 32 | arrayToLink([1, 2, 3, 4]) 33 | ``` 34 | 35 | ### 链表转数组 36 | 37 | ```js 38 | function linkToArray(head) { 39 | if (!head) { 40 | return [] 41 | } 42 | 43 | var result = [] 44 | var p = head 45 | 46 | while (p) { 47 | result.push(p.value) 48 | p = p.next 49 | } 50 | 51 | return result 52 | } 53 | 54 | function linkToArray(head) { 55 | if (!head) { 56 | return [] 57 | } 58 | var result = [head.value] 59 | var restValues = list2array(head.next) 60 | return result.concat(restValues) 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /webpack/loader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-vue", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server --mode development --open", 8 | "build": "webpack --mode production" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel-core": "^6.26.3", 15 | "babel-loader": "^7.1.4", 16 | "babel-preset-env": "^1.7.0", 17 | "clean-webpack-plugin": "^0.1.19", 18 | "css-loader": "^0.28.11", 19 | "file-loader": "^1.1.11", 20 | "html-loader": "^0.5.5", 21 | "html-webpack-plugin": "^3.2.0", 22 | "mini-css-extract-plugin": "^0.4.1", 23 | "node-sass": "^4.9.0", 24 | "postcss-loader": "^2.1.5", 25 | "sass-loader": "^7.0.3", 26 | "style-loader": "^0.21.0", 27 | "vue-loader": "^15.2.4", 28 | "vue-template-compiler": "^2.5.16", 29 | "webpack": "^4.14.0", 30 | "webpack-cli": "^3.0.8", 31 | "webpack-dev-server": "^3.1.4", 32 | "loader-runner": "^3.1.0" 33 | }, 34 | "dependencies": { 35 | "cwebp": "^2.0.5", 36 | 37 | "markdown-ast": "^0.2.1", 38 | "marked": "^0.8.2", 39 | "schema-utils": "^2.6.5", 40 | "vue": "^2.5.16" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /handwrittenCode/lastPromise.js: -------------------------------------------------------------------------------- 1 | //lastPromise实现 2 | //业务需求中,经常有 只需要最后一次请求的结果(比如搜索)编写一个高阶函数,传递旧请求方法(执行后返回 promise),返回一个新方法。 3 | //连续触发时,若上一次 promise 执行未结束则直接废弃,只有最后一次 promise 会触发then/reject。 4 | 5 | // 示例 6 | let count = 1 7 | let promiseFunction = () => 8 | new Promise((rs) => 9 | window.setTimeout(() => { 10 | rs(count++) 11 | }) 12 | ) 13 | 14 | // 1. 传递旧请求方法(执行后返回 promise),返回一个新方法 15 | // 2. 连续触发时,若上一次 promise 执行未结束则直接废弃,只有最后一次 promise 会触发then/reject 16 | /** 17 | * 只有最后一次promise会then与reject 18 | * @param {function} promiseFunction 19 | * promiseFunction 示例: () => fetch('data') 20 | */ 21 | 22 | const lastPromise = (promise) => { 23 | let hasCanceled_ = false 24 | const wrappedPromise = new Promise((resolve, reject) => { 25 | promise 26 | .then((val) => 27 | hasCanceled_ ? reject({ isCanceled: true }) : resolve(val) 28 | ) 29 | .catch((error) => 30 | hasCanceled_ ? reject({ isCanceled: true }) : reject(error) 31 | ) 32 | }) 33 | return { 34 | promise: wrappedPromise, 35 | cancel() { 36 | hasCanceled_ = true 37 | }, 38 | } 39 | } 40 | 41 | let lastFn = lastPromise(promiseFunction).promise 42 | 43 | lastFn().then(console.log) // 无输出 44 | lastFn().then(console.log) // 无输出 45 | lastFn().then(console.log) // 3 46 | -------------------------------------------------------------------------------- /webpack/plugin/plugins/utils.js: -------------------------------------------------------------------------------- 1 | const ensureArray = (thing) => { 2 | if (Array.isArray(thing)) 3 | return thing; 4 | if (thing == undefined) 5 | return []; 6 | return [thing]; 7 | } 8 | 9 | const createFilter = (include, exclude, options) => { 10 | const resolutionBase = options && options.resolve; 11 | const getMatcher = (id) => { 12 | return id instanceof RegExp ? 13 | id : { 14 | test: micromatch_1.matcher(getMatcherString(id, resolutionBase) 15 | .split(path.sep) 16 | .join('/'), { dot: true }) 17 | }; 18 | }; 19 | const includeMatchers = ensureArray(include).map(getMatcher); 20 | const excludeMatchers = ensureArray(exclude).map(getMatcher); 21 | return function(id) { 22 | if (typeof id !== 'string') 23 | return false; 24 | if (/\0/.test(id)) 25 | return false; 26 | id = id.split(path.sep).join('/'); 27 | for (let i = 0; i < excludeMatchers.length; ++i) { 28 | const matcher = excludeMatchers[i]; 29 | if (matcher.test(id)) 30 | return false; 31 | } 32 | for (let i = 0; i < includeMatchers.length; ++i) { 33 | const matcher = includeMatchers[i]; 34 | if (matcher.test(id)) 35 | return true; 36 | } 37 | return !includeMatchers.length; 38 | }; 39 | }; 40 | 41 | exports.createFilter = createFilter; -------------------------------------------------------------------------------- /前端知识点/错误数据上报.md: -------------------------------------------------------------------------------- 1 | 资源加载错误,通过 addEventListener('error', callback, true)在捕获阶段捕捉资源加载失败错误。 2 | 3 | js 执行错误,通过 window.onerror 捕捉 js 错误。 4 | 5 | 跨域的脚本会给出 "Script Error." 提示,拿不到具体的错误信息和堆栈信息。此时需要在 script 标签增加 crossorigin="anonymous"属性,同时资源服务器需要增加 CORS 相关配置,比如 Access-Control-Allow-Origin: \* 6 | 7 | promise 错误,通过 addEventListener('unhandledrejection', callback)捕捉 promise 错误,但是没有发生错误的行数,列数等信息,只能手动抛出相关错误信息。 8 | 9 | ```js 10 | // 在捕获阶段,捕获资源加载失败错误 11 | addEventListener( 12 | 'error', 13 | (e) => { 14 | const target = e.target 15 | if (target != window) { 16 | monitor.errors.push({ 17 | type: target.localName, 18 | url: target.src || target.href, 19 | msg: (target.src || target.href) + ' is load error', 20 | time: Date.now(), 21 | }) 22 | } 23 | }, 24 | true 25 | ) 26 | 27 | // 监听 js 错误 28 | window.onerror = function (msg, url, row, col, error) { 29 | monitor.errors.push({ 30 | type: 'javascript', 31 | row: row, 32 | col: col, 33 | msg: error && error.stack ? error.stack : msg, 34 | url: url, 35 | time: Date.now(), 36 | }) 37 | } 38 | 39 | // 监听 promise 错误 缺点是获取不到行数数据 40 | addEventListener('unhandledrejection', (e) => { 41 | monitor.errors.push({ 42 | type: 'promise', 43 | msg: (e.reason && e.reason.msg) || e.reason || '', 44 | time: Date.now(), 45 | }) 46 | }) 47 | ``` 48 | -------------------------------------------------------------------------------- /webpack/plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server --mode development", 8 | "dev:open": "webpack-dev-server --mode development --open", 9 | "build": "webpack --mode production" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "babel-core": "^6.26.3", 16 | "babel-loader": "^7.1.4", 17 | "babel-plugin-transform-runtime": "^6.23.0", 18 | "babel-polyfill": "^6.26.0", 19 | "babel-preset-env": "^1.7.0", 20 | "babel-preset-react": "^6.24.1", 21 | "clean-webpack-plugin": "^0.1.19", 22 | "css-loader": "^0.28.11", 23 | "file-loader": "^1.1.11", 24 | "html-loader": "^0.5.5", 25 | "html-webpack-plugin": "^3.2.0", 26 | "less": "^3.0.4", 27 | "less-loader": "^4.1.0", 28 | "mini-css-extract-plugin": "^0.4.1", 29 | "postcss-cssnext": "^3.1.0", 30 | "postcss-loader": "^2.1.5", 31 | "qiniu": "^7.3.0", 32 | "react": "^16.4.1", 33 | "react-dom": "^16.4.1", 34 | "style-loader": "^0.21.0", 35 | "webpack": "^4.14.0", 36 | "webpack-cli": "^3.0.8", 37 | "webpack-dev-server": "^3.1.4", 38 | "webpack-log": "^3.0.1" 39 | }, 40 | "dependencies": { 41 | "simple-log-webpack-plugin": "^0.1.3" 42 | } 43 | } -------------------------------------------------------------------------------- /webpack/webpack/dist/bundle.js: -------------------------------------------------------------------------------- 1 | (function(graph){ 2 | function require(moduleId){ 3 | function localRequire(relativePath){ 4 | return require(graph[moduleId].dependencies[relativePath]) 5 | } 6 | var exports = {}; 7 | (function(require,exports,code){ 8 | eval(code) 9 | })(localRequire,exports,graph[moduleId].code) 10 | return exports; 11 | } 12 | require('./src/index.js') 13 | })({"./src/index.js":{"dependencies":{"./moduleA.js":"./src/moduleA.js","./moduleB.js":"./src/moduleB.js"},"code":"\"use strict\";\n\nvar _moduleA = _interopRequireDefault(require(\"./moduleA.js\"));\n\nvar _moduleB = _interopRequireDefault(require(\"./moduleB.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }"},"./src/moduleA.js":{"dependencies":{"./moduleC.js":"./src/moduleC.js"},"code":"\"use strict\";\n\nvar _moduleC = _interopRequireDefault(require(\"./moduleC.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nconsole.log(\"moduleA\");"},"./src/moduleB.js":{"dependencies":{},"code":"\"use strict\";\n\nconsole.log(\"moduleB\");"},"./src/moduleC.js":{"dependencies":{},"code":"\"use strict\";\n\nconsole.log('moduleC');"}}) -------------------------------------------------------------------------------- /leetcode/155.最小栈.md: -------------------------------------------------------------------------------- 1 | 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 2 | 3 | push(x) —— 将元素 x 推入栈中。 4 | pop() —— 删除栈顶的元素。 5 | top() —— 获取栈顶元素。 6 | getMin() —— 检索栈中的最小元素。 7 | 8 | 示例: 9 | 10 | 输入: 11 | 12 | ``` 13 | ["MinStack","push","push","push","getMin","pop","top","getMin"] 14 | [[],[-2],[0],[-3],[],[],[],[]] 15 | ``` 16 | 17 | 输出: 18 | 19 | ``` 20 | [null,null,null,null,-3,null,0,-2] 21 | ``` 22 | 23 | 解释: 24 | 25 | ```js 26 | MinStack minStack = new MinStack(); 27 | minStack.push(-2); 28 | minStack.push(0); 29 | minStack.push(-3); 30 | minStack.getMin(); --> 返回 -3. 31 | minStack.pop(); 32 | minStack.top(); --> 返回 0. 33 | minStack.getMin(); --> 返回 -2. 34 | ``` 35 | 36 | 来源:力扣(LeetCode)
37 | 链接:https://leetcode-cn.com/problems/min-stack
38 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 39 | 40 | ```js 41 | var MinStack = function () { 42 | this.x_stack = [] 43 | this.min_stack = [Infinity] 44 | } 45 | 46 | MinStack.prototype.push = function () { 47 | this.x_stack.push(x) 48 | this.min_stack.push(Math.min(this.min_stack[this.min_stack.length - 1], x)) 49 | } 50 | MinStack.prototype.pop = function () { 51 | this.x_stack.pop() 52 | this.min_stack.pop() 53 | } 54 | MinStack.prototype.top = function () { 55 | return this.x_stack[this.x_stack.length - 1] 56 | } 57 | MinStack.prototype.getMin = function () { 58 | return this.min_stack[this.min_stack.length - 1] 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /前端知识点/prototype和_proto_以及原型链的关系.md: -------------------------------------------------------------------------------- 1 | ##### prototype 2 | 3 | ```js 4 | //构造函数 5 | function Person() {} 6 | console.dir(Person.prototype) 7 | ``` 8 | 9 | 原型 :实例“继承”那个对象的属性。在原型上定义的属性,通过“继承”,实例也拥有了这个属性。“继承”这个行为是在 new 操作符内部实现的。 10 | 11 | ##### proto 隐式原型 12 | 13 | 实例通过`__proto__`访问到原型,所以如果是实例,那么就可以通过这个属性直接访问到原型: 14 | 15 | ```js 16 | function Person() {} 17 | //实例化函数 18 | var person = new Person() 19 | console.log('person', person) 20 | console.log('Person', Person) 21 | console.log(person.__proto__ === Person.prototype) //true 22 | console.log(Person.prototype.constructor === Person) //true 23 | ``` 24 | 25 | ##### 原型链 26 | 27 | ```js 28 | function Person1() {} 29 | Person1.prototype.name = 'frank' 30 | function Person2() {} 31 | Person2.prototype = new Person1() 32 | let newPerson = new Person2() 33 | console.log(newPerson.name) //输出frank 34 | console.log(newPerson.__proto__) 35 | console.log(newPerson.__proto__.__proto__) 36 | console.log(newPerson.__proto__.__proto__.__proto__) 37 | ``` 38 | 39 | 原型同样也可以通过 `__proto__`访问到原型的原型 40 | 比方说这里有个构造函数 Person1 然后“继承”前者的有一个构造函数 Person2 ,然后 new Person1 得到实例 newPerson 41 | 42 | 当访问 newPerson 中的一个非自有属性的时候,就会通过 `__proto__` 作为桥梁连接起来的一系列原型、原型的原型、原型的原型的原型直到 Object 构造函数为止。 43 | 这个搜索的过程形成的链状关系就是原型链 44 | 45 | ![原型链](https://cdn.6fed.com/github/js-basis/%E5%8E%9F%E5%9E%8B%E9%93%BE.jpg) 46 | 47 | 参考 48 | [图解原型和原型链](https://juejin.im/post/5c8a692af265da2d8763b744) 49 | -------------------------------------------------------------------------------- /leetcode/1.两数之和.md: -------------------------------------------------------------------------------- 1 | 给定一个整数数组 nums  和一个整数目标值 target,请你在该数组中找出 和为目标值 的那   两个   整数,并返回它们的数组下标。 2 | 3 | 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 4 | 5 | 你可以按任意顺序返回答案。 6 | 7 | 示例 1: 8 | 9 | ``` 10 | 输入:nums = [2,7,11,15], target = 9 11 | 输出:[0,1] 12 | 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 13 | ``` 14 | 15 | 示例 2: 16 | 17 | ``` 18 | 输入:nums = [3,2,4], target = 6 19 | 输出:[1,2] 20 | ``` 21 | 22 | 示例 3: 23 | 24 | ``` 25 | 输入:nums = [3,3], target = 6 26 | 输出:[0,1] 27 | ``` 28 | 29 | 来源:力扣(LeetCode)
30 | 链接:https://leetcode-cn.com/problems/two-sum
31 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 32 | 33 | ```js 34 | /** 35 | * @param {number[]} nums 36 | * @param {number} target 37 | * @return {number[]} 38 | */ 39 | var twoSum = function (nums, target) { 40 | for (let i = 0; i < nums.length; i++) { 41 | let diff = target - nums[i] 42 | for (let j = i + 1; j < nums.length; j++) { 43 | if (diff == nums[j]) { 44 | return [i, j] 45 | } 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | ```js 52 | /** 53 | * @param {number[]} nums 54 | * @param {number} target 55 | * @return {number[]} 56 | */ 57 | var twoSum = function (nums, target) { 58 | var temp = [] 59 | for (var i = 0; i < nums.length; i++) { 60 | var dif = target - nums[i] 61 | if (temp[dif] != undefined) { 62 | return [temp[dif], i] 63 | } 64 | temp[nums[i]] = i 65 | } 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /test3.js: -------------------------------------------------------------------------------- 1 | // const a1 = [1, 3, 5, 7, 9] 2 | // const a2 = [1, 2, 3, 4, 5] 3 | // const findDiff = (arr1, arr2) => { 4 | // let ans = [] 5 | // for (let m = 0; m < arr1.length; m++) { 6 | // if (!arr2.includes(arr1[m])) { 7 | // ans.push(arr1[m]) 8 | // } 9 | // } 10 | // for (let m = 0; m < arr2.length; m++) { 11 | // if (!arr1.includes(arr2[m])) { 12 | // ans.push(arr2[m]) 13 | // } 14 | // } 15 | // console.log(ans) 16 | // } 17 | // // a1 = [100] 18 | // a1.push(1) 19 | // console.log(a1) 20 | // findDiff(a1, a2) 21 | // prmosise async 22 | // const r1 = 'A' && 'B' 23 | // const r2 = 0 && 'B' 24 | // const r3 = '' && 'B' 25 | // console.log(r1, r2, r3) 26 | let resList = [1, 1, 0] 27 | let index = -1 28 | const getPageResult = () => { 29 | index += 1 30 | return resList[index] 31 | } 32 | const request5 = () => { 33 | let start = 1 34 | let getRes = async () => { 35 | if (start > 5) { 36 | console.log('失败') 37 | return 38 | } 39 | let res = await getPageResult() 40 | if (res === 0) { 41 | console.log('成功') 42 | } else if (res === 1) { 43 | console.log('请求' + start) 44 | start += 1 45 | setTimeout(() => { 46 | getRes() 47 | }, 1000) 48 | } else if (res === 2) { 49 | console.log('失败') 50 | } 51 | } 52 | getRes() 53 | } 54 | request5() 55 | // 0 成功 56 | // 1 支付中 57 | // 2 失败 58 | -------------------------------------------------------------------------------- /handwrittenCode/实现一个函数,可以将数组转化为树状数据结构.js: -------------------------------------------------------------------------------- 1 | // 题目:实现一个函数,可以将数组转化为树状数据结构 2 | const arr = [ 3 | { id: 1, name: 'i1' }, 4 | { id: 2, name: 'i2', parentId: 1 }, 5 | { id: 4, name: 'i4', parentId: 3 }, 6 | { id: 3, name: 'i3', parentId: 2 }, 7 | { id: 8, name: 'i8', parentId: 7 }, 8 | ] 9 | 10 | /* 可以将数组转化为树状数据结构,要求程序具有侦测错误输入的能力*/ 11 | function buildTree(arr) { 12 | let tree = {} 13 | let temp = {} 14 | for (let i = 0; i++; i < arr.length) { 15 | temp[item.parentId] = item 16 | } 17 | for (let i = 0, len = list.length; i < len; i++) { 18 | let id = list[i].parentId 19 | if (id == parId) { 20 | result.push(list[i]) 21 | continue 22 | } 23 | } 24 | 25 | return tree 26 | } 27 | 28 | console.log(buildTree(arr)) 29 | 30 | //blog.csdn.net/susuzhe123/article/details/95353403 31 | 32 | function transfer(treeData) { 33 | if (!(!treeData.hasOwnProperty('name') || !treeData)) { 34 | let arr = [] 35 | let obj = {} 36 | obj.name = treeData.name 37 | obj.children = treeData.children.map((value) => { 38 | // [1] arr = arr.concat(transfer(value)) 39 | return value.name 40 | }) 41 | arr.push(obj) 42 | 43 | // 这段代码可由代码 [1] 替代,替代后父元素在子元素后 44 | treeData.children.forEach((value) => { 45 | arr = arr.concat(transfer(value)) 46 | }) 47 | // 48 | 49 | return arr 50 | } else { 51 | // 初始treeData是否为空树 52 | return [] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /handwrittenCode/浏览器最大请求并非限制.js: -------------------------------------------------------------------------------- 1 | class RequestLimit { 2 | constructor(limit) { 3 | // 定义一个自己的并发请求控制类在实例化时设置 limit 4 | this.limit = Number(limit) || 2 5 | this.blockQueue = [] 6 | this.currentReqNumber = 0 7 | } 8 | 9 | /** 10 | * 请求 11 | * @param {*} req 12 | */ 13 | async request(req) { 14 | // 为这个这个并发请求控制类实现一个 request 方法 15 | if (!req) { 16 | throw new Error('req is required.') 17 | } 18 | if (Object.prototype.toString.call(req) !== '[object Function]') { 19 | throw new Error('Req must be a function.') 20 | } 21 | if (this.currentReqNumber >= this.limit) { 22 | // 在 request 里判断如果当前请求数大于设置的 limit 程序进入阻塞状态 23 | await new Promise((resolve) => this.blockQueue.push(resolve)) // 阻塞队列增加一个 Pending 状态的 Promise 24 | } 25 | 26 | return this._handlerReq(req) // 在 request 请求里如果当前请求数小于设置的 limit,处理传入的请求 27 | } 28 | 29 | /** 30 | * 内部方法处理请求 31 | * @param {*} req 32 | */ 33 | async _handlerReq(req) { 34 | this.currentReqNumber++ // 在处理传入的请求开始时要对当前请求数做加 1 操作 35 | try { 36 | return await req() 37 | } catch (err) { 38 | return Promise.reject(err) 39 | } finally { 40 | this.currentReqNumber-- 41 | if (this.blockQueue.length) { 42 | // 每完成一个就从阻塞队列里剔除一个 43 | this.blockQueue[0]() // 将最先进入阻塞队列的 Promise 从 Pending 变为 Fulfilled 44 | this.blockQueue.shift() 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /性能优化/readme.md: -------------------------------------------------------------------------------- 1 | 用户卡顿,崩溃,白屏,不流畅等一些列问题 2 | 3 | 1. 页面整体加载时间 4 | 2. 用户可操作时间 5 | 3. 平滑和交互性 6 | 4. 可感知 7 | 5. 性能测定 8 | 9 | 常见的优化 10 | 11 | 长列表使用虚拟列表 12 | 基于 Service work、web worker 的 cache 13 | webpack analyze 分析 js 包大小 14 | 优化 css 动画 15 | Gzip 压缩 16 | 图片压缩 17 | 18 | 聊天框卡顿 19 | 拖拽卡顿 20 | 首屏渲染优化 21 | 22 | react 组件优化 减少层级 23 | 24 | FCP 25 | First Contentful Paint (FCP) 26 | TTFB 27 | Time to First Byte (TTFB) 28 | LCP 29 | Largest Contentful Paint (LCP): 衡量加载性能。为了提供一个好的用户体验,LCP 应该在 2.5 秒内。 30 | 31 | FID 32 | First Input Delay (FID): 衡量可交互性。为了提供一个好的用户体验,FID 应该在 100 毫秒内。 33 | 34 | 例如,Time to First Byte (TTFB) 和 First Contentful Paint (FCP) 都是关于加载性能的,两者都有助于诊断 LCP (缓慢的服务端响应,或者渲染阻塞的资源)。 35 | 36 | 同上,Total Blocking Time (TBT) 和 Time to Interactive (TTI) 则是影响 FID 的实验性指标,他们不属于核心,因为不能测试现场数据,不能反映用户为核心的关键结果。 37 | 38 | FP 是时间线上的第一个“时间点”,是指浏览器从响应用户输入网址地址,到浏览器开始显示内容的时间,简而言之就是浏览器第一次发生变化的时间。 39 | 40 | FCP(全称“First Contentful Paint”,翻译为“首次内容绘制”),是指浏览器从响应用户输入网络地址,在页面首次绘制文本,图片(包括背景图)、非白色的 canvas 或者 SVG 才算做 FCP,有些文章说 FCP 是首屏渲染事件,这其实是不对的。 41 | 42 | TTI,翻译为“可交互时间”表示网页第一次完全达到可交互状态的时间点。可交互状态指的是页面上的 UI 组件是可以交互的(可以响应按钮的点击或在文本框输入文字等),不仅如此,此时主线程已经达到“流畅”的程度,主线程的任务均不超过 50 毫秒。在一般的管理系统中,TTI 是一个很重要的指标。 43 | 44 | FMP(全称“First Meaningful Paint”,翻译为“首次有效绘制”表示页面的“主要内容”开始出现在屏幕上的时间点,它以前是我们测量用户加载体验的主要指标。本质上是通过一个算法来猜测某个时间点可能是 FMP,但是最好的情况也只有 77%的准确率,在 lighthouse6.0 的时候废弃掉了这个指标,取而代之的是 LCP 这个指标。 45 | 46 | LCP(全称“Largest Contentful Paint”)表示可视区“内容”最大的可见元素开始出现在屏幕上的时间点。 47 | 48 | https://mp.weixin.qq.com/s/rA2gv3nKoUfcchdYBAdykg 49 | -------------------------------------------------------------------------------- /leetcode/88.合并两个有序数组.md: -------------------------------------------------------------------------------- 1 | 来源:力扣(LeetCode)
2 | 链接:https://leetcode-cn.com/problems/merge-sorted-array
3 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 4 | 5 | ##### 🧠 解题思路 6 | 7 | 通过题意所示,我们可以使用暴破,双指针+额外存储空间来完成该题,但是这两种做法的空间和时间复杂度至少都是 O(m + n)O(m+n) 。 8 | 9 | 这个时候我们就要分析,为什么告诉我们 nums1.length >= m + n? 10 | 考查 原地修改 11 | 12 | 将空间复杂度降低到 O(1)。 13 | 因为这样不需要使用额外的数组空间了,我们完全可以把 nums2 也放入 nums1 中。 14 | 15 | 原地修改时,为了避免从前往后遍历导致原有数组元素被破坏掉,我们要选择从后往前遍历! 16 | 17 | 所以,我们总共需要创建三个指针,两个指针用于指向 ums1 和 nums2 的初始化元素数量的末位,也就是分别指向 m-1 和 n-1 的位置,还有一个指针,我们指向 nums1 数组末位即可。 18 | 19 | ##### 🎨 图解演示 20 | 21 | ``` 22 | nums1 [1,2,3,0,0,0] 23 | i k 24 | nums2 [2,5,6] 25 | j 26 | ``` 27 | 28 | ```js 29 | var merge = (nums1, m, nums2, n) => { 30 | let i = m - 1, 31 | j = n - 1, 32 | k = m + n - 1 33 | while (i >= 0 || j >= 0) { 34 | if (i < 0) { 35 | nums1[k--] = nums2[j--] 36 | console.log(1, i, j, nums1) 37 | } else if (j < 0) { 38 | nums1[k--] = nums1[i--] 39 | console.log(2, i, j, nums1) 40 | } else if (nums1[i] < nums2[j]) { 41 | nums1[k--] = nums2[j--] 42 | console.log(3, i, j, nums1) 43 | } else { 44 | nums1[k--] = nums1[i--] 45 | console.log(4, i, j, nums1) 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | ##### 参考 52 | 53 | 作者:demigodliu
54 | 链接:https://leetcode-cn.com/problems/merge-sorted-array/solution/ni-xiang-shuang-zhi-zhen-he-bing-liang-g-ucgj/
55 | 来源:力扣(LeetCode)
56 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 57 | -------------------------------------------------------------------------------- /webpack/loader/loaders/convert2Webp.loader.js: -------------------------------------------------------------------------------- 1 | const CWebp = require('cwebp').CWebp 2 | const { getOptions } = require("loader-utils"); 3 | const validateOptions = require("schema-utils"); 4 | /** 5 | * 普通图片转 .webp图片 6 | * @param {string | buffer} img 图片绝对路径或二进制流 7 | * @param {number} quality 生成 webp 图片的质量,默认75 8 | * @returns .webp 文件流 9 | */ 10 | async function convertToWebp(img, quality = 75) { 11 | let encoder = new CWebp(img) 12 | encoder.quality = quality 13 | let buffer = await encoder.toBuffer() 14 | return buffer 15 | } 16 | 17 | const schema = { 18 | "additionalProperties": true, 19 | "properties": { 20 | "quality": { 21 | "description": "quality factor (0:small..100:big), default=75", 22 | "type": "number" 23 | } 24 | }, 25 | "type": "object" 26 | } 27 | 28 | 29 | module.exports = async function (source) { 30 | if (source instanceof Buffer) { 31 | // 异步模式 32 | let callback = this.async() 33 | // 获取 options 34 | const options = getOptions(this) || {} 35 | // 验证 options 36 | validateOptions(schema, options, { 37 | name: 'webp Loader', 38 | baseDataPath: 'options' 39 | }) 40 | try { 41 | if (content instanceof Buffer) { 42 | // 普通图片转 .webp 43 | let buffer = await convertToWebp(content, options.quality) 44 | callback(null, buffer) 45 | //当然我本身也可以返回二进制数据提供给下一个loader 46 | } 47 | 48 | } catch (err) { 49 | callback(err) 50 | } 51 | } 52 | } 53 | moudle.exports.raw = true; //不设置,就会拿到字符串 54 | 55 | -------------------------------------------------------------------------------- /handwrittenCode/常见排序算法以及复杂度.md: -------------------------------------------------------------------------------- 1 | ### 冒泡排序 2 | 3 | ```js 4 | // 时间复杂度:O(N^2) 5860ms 6.25% 5 | // 空间复杂度:O(1) 45.2MB 46.88% 6 | const sortArray = (nums) => { 7 | for (let i = 0; i < nums.length - 1; i++) { 8 | let mark = true 9 | for (let j = 0; j < nums.length - 1 - i; j++) { 10 | if (nums[j] > nums[j + 1]) { 11 | ;[nums[j], nums[j + 1]] = [nums[j + 1], nums[j]] 12 | mark = false 13 | } 14 | } 15 | if (mark) return 16 | } 17 | } 18 | ``` 19 | 20 | ### 选择排序 21 | 22 | ```js 23 | // 时间复杂度:O(N^2) 1372ms 12.50% 24 | // 空间复杂度:O(1) 44.2MB 93.75% 25 | const sortArray = (nums) => { 26 | for (let i = 0, len = nums.length; i < len; i++) { 27 | for (let j = i + 1; j < len; j++) { 28 | // 将 nums[i] 和它后面的元素进行比较,使 nums[i] 一直维持最小 29 | if (nums[i] > nums[j]) { 30 | ;[nums[i], nums[j]] = [nums[j], nums[i]] 31 | } 32 | } 33 | } 34 | return nums 35 | } 36 | ``` 37 | 38 | ### 快速排序 39 | 40 | ```js 41 | // 时间复杂度:O(NlogN) 148ms 50% 42 | // 空间复杂度:O(logN) 45.6MB 46.88% 43 | const sortArray = (nums, left = 0, right = nums.length - 1) => { 44 | if (left >= right) return nums 45 | let i = left 46 | let j = right - 1 47 | while (i <= j) { 48 | if (nums[i] > nums[right]) { 49 | ;[nums[i], nums[j]] = [nums[j], nums[i]] 50 | j-- 51 | } else { 52 | i++ 53 | } 54 | } 55 | j++ 56 | ;[nums[j], nums[right]] = [nums[right], nums[j]] 57 | sortArray(nums, left, j - 1) 58 | sortArray(nums, j + 1, right) 59 | return nums 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /webpack/plugin/plugins/MyWebpackPlugin.js: -------------------------------------------------------------------------------- 1 | const getLogger = require('webpack-log'); 2 | const log = getLogger({ name: 'webpack-batman' }); 3 | class MyWebpackPlugin { 4 | constructor(options) {} 5 | apply(compiler) { 6 | console.log(compiler) 7 | debugger 8 | compiler.hooks.emit.tapAsync('MyWebpackPlugin', (compilation, callback) => { 9 | // console.log(compilation) 10 | let { assets, chunks, compiler, hooks, options } = compilation 11 | let compilation2 = { 12 | assets, 13 | chunks, 14 | compiler, 15 | hooks, 16 | options 17 | } 18 | 19 | // 4. 通过compiler对象可以注册对应的事件,全部的钩子都可以使用 20 | // 注册一个编译完成的钩子, 一般需要将插件名作为事件名即可 21 | compiler.hooks.done.tap('MyWebpackPlugin', (stats) => { 22 | // console.log('整个webpack打包结束了'); 23 | }) 24 | // compilation.chunks存放了代码块列表 25 | compilation.chunks.forEach(chunk => { 26 | // chunk包含多个模块,通过chunk.modulesIterable可以遍历模块列表 27 | for (const module of chunk.modulesIterable) { 28 | // module包含多个依赖,通过module.dependencies进行遍历 29 | module.dependencies.forEach(dependency => { 30 | // console.log(dependency); 31 | }); 32 | } 33 | }); 34 | // 修改或添加资源 35 | compilation.assets['new-file.js'] = { 36 | source() { 37 | return 'var a=1'; 38 | }, 39 | size() { 40 | return this.source().length; 41 | } 42 | }; 43 | callback(); 44 | 45 | }); 46 | } 47 | } 48 | 49 | module.exports = MyWebpackPlugin; -------------------------------------------------------------------------------- /前端知识点/HTTP2优点.md: -------------------------------------------------------------------------------- 1 | https://www.zhihu.com/question/34074946 2 | 3 | 1. 多路复用 4 | 5 | 多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。 6 | 7 | 2. 二进制分帧 8 | 9 | 突破 HTTP1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量 10 | 11 | 3. 首部压缩 12 | 13 | 4. 服务端推送(Server Push) 14 | ##### HTTP1.0 15 | HTTP1.0最早在网页使用是1996年,那时候只是使用较为简单的网络请求。有如下缺陷: 16 | 1、无法复用链接,完成即断开。 17 | 2、heead of line blocking:线头阻塞,导致请求之间互相影响。 18 | 19 | ##### HTTP1.1 20 | HTTP1.1则在1999年开始广泛应用于现在的各大浏览器网络请求,同时也是当前使用最广泛的HTTP协议。和HTTP1.0相比,有了如下改进: 21 | 1、**长连接**(默认开启Connection:keep-alive) 22 | 2、**host头处理**。随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且他们共享一个ip地址。HTTP1.1的请求消息和响应消息都应支持host头域,且请求消息中如果没有host头域会报告一个错误(400 Bad Request)。 23 | 3、**缓存处理**: 24 | - Cache-Control 25 | - Expires 26 | - Last-Modified 27 | - Etag 28 | 4、**带宽优化及网络连接的使用**:HTTP1.0存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传的功能;在HTTP1.1则在请求头引入了range头域,它只允许请求资源的某个部分,即返回码是206(Partial Content),这样就充分利用带宽和连接。 29 | 5、**错误通知的管理**:在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。 30 | 31 | ##### HTTP2.0 32 | 1、**多路复用**:即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。 33 | 2、**二进制格式**:HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。 34 | 3、**首部压缩**:HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。 35 | 4、**服务端推送(server push)**:例如网页有一个style.css的请求,在客户端收到style.css数据的同时,服务器端会将style.js的文件推送给客户端,当客户端再次尝试获取style.js时,就可以直接从缓存中获取,不用再发起请求了 36 | 参考:https://mp.weixin.qq.com/s/GICbiyJpINrHZ41u_4zT-A 37 | -------------------------------------------------------------------------------- /leetcode/21.合并两个有序链表.md: -------------------------------------------------------------------------------- 1 | ### 题目 2 | 3 | 来源:力扣(LeetCode)
4 | 链接:https://leetcode-cn.com/problems/merge-two-sorted-lists
5 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 6 | 7 | ```js 8 | /** 9 | * Definition for singly-linked list. 10 | * function ListNode(val, next) { 11 | * this.val = (val===undefined ? 0 : val) 12 | * this.next = (next===undefined ? null : next) 13 | * } 14 | */ 15 | /** 16 | * @param {ListNode} l1 17 | * @param {ListNode} l2 18 | * @return {ListNode} 19 | */ 20 | var mergeTwoLists = function (l1, l2) { 21 | if (!l1) { 22 | return l2 23 | } else if (!l2) { 24 | return l1 25 | } else if (l1.val < l2.val) { 26 | l1.next = mergeTwoLists(l1.next, l2) 27 | return l1 28 | } else { 29 | l2.next = mergeTwoLists(l2.next, l1) 30 | return l2 31 | } 32 | } 33 | ``` 34 | 35 | ### Test 36 | ```js 37 | var l1 = { 38 | val: 1, 39 | next: { 40 | val: 2, 41 | next: { 42 | val: 4, 43 | next: null 44 | } 45 | } 46 | } 47 | var l2 = { 48 | val: 1, 49 | next: { 50 | val: 3, 51 | next: { 52 | val: 4, 53 | next: null 54 | } 55 | } 56 | } 57 | var mergeTwoLists = function (l1, l2) { 58 | if (!l1) { 59 | return l2 60 | } else if (!l2) { 61 | return l1 62 | } else if (l1.val < l2.val) { 63 | l1.next = mergeTwoLists(l1.next, l2) 64 | return l1 65 | } else { 66 | l2.next = mergeTwoLists(l2.next, l1) 67 | return l2 68 | } 69 | } 70 | var total = mergeTwoLists(l1,l2) 71 | console.log(total) // ok 72 | ``` 73 | -------------------------------------------------------------------------------- /前端知识点/总结项目中碰到的难点.md: -------------------------------------------------------------------------------- 1 | 结合自己的工作经历,总结一下这些年项目里碰到的难点 2 | 3 | 第一个阶段,知识储备 4 | 业务逻辑很复杂而且多变 5 | 6 | 模块规范、模块开发工具链、模块发布和版本管理、在线管理、 7 | 8 | 富文本内容发布业务需求,光是一个富文本编辑器就很复杂,要实现各种功能和兼容性,更复杂的是要适应业务发展。做业务的时候,市场现有的编辑器是不满足产品需求的。从 0 到 1 开发编辑器肯定需要花费时间和精力。业务方肯定不会给出这么多时间,只能在阅读现有编辑器源码基础上重新开发。 9 | 10 | 数据管理系统, 11 | 封装动态表单, 12 | 后端按照业务需求进行配置即可产出内容发布表单。 13 | 14 | 当时涉及很多人,光前端就多个团队投入一共 4 个人(包括模块系统、可视化拖拽等),后端也非常多。而且时间要求特别紧,。我作为整个项目 PM 简直要累死,前期讨论可能性并设计功能、分工,然后要一遍遍的跟不同的人解释概念、流程,然后糅合在一起开发、处理新出现的问题。天天加班,每天一罐红牛,终于在双十一既定时间前糊弄出来一套可以用的了。这个我也觉得很难,已经超出了前端的范围了,除了前端之外还要了解后端系统的限制对方案进行调整、项目管理等。不过这套也有新的问题,比如交互操作多,体验比不上 HTML base 的 新榜微信编辑器-让你的图文编辑生动有趣 这种,然后模块扩展依赖开发,成本高,等等。这些新问题就像挖坑挖出来的,越挖越难越复杂。。。总结下来我觉得前端比较难的几个点:前端本身业务逻辑、实现方式比较多样、复杂,技术选型、方案设计很难,这要求你对多种技术框架、工具都有一定的了解面对不同业务需求进行抽象、设计、研发以及关联系统的自主研发(跨技术栈)比较难将业务需求、交互设计、数据等糅合在一起开发出来展现给用户,跟多方沟通打交道比较难,良好的沟通需要多种领域的知识 15 | 16 | 1. 兼容 PC 和移动端富文本编辑器碰到的难点 17 | 控制光标的状态和位置 18 | selection range 操作光标 19 | 20 | selection 对象表示用户选择的文本范围或插入符号的当前位置。 21 | let range = selection.getRangeAt(0); 22 | 表示第 0 个文本的区域 23 | 通过 rang 对象 24 | 25 | 需要考虑到节点发生变化,光标也要调到合适的地方,通过重新创造一个 range 对象并且删除原有的 ranges 来保证光标一定会变动到我们想要的位置。 26 | 27 | 修改文本格式 28 | document.execCommand 29 | backColor 30 | 修改文档的背景颜色 31 | bold 32 | 开启或关闭选中文字或插入点的粗体字效果 33 | copy 34 | 拷贝当前选中内容到剪贴板。 35 | createLink 36 | 将选中内容创建为一个锚链接。 37 | 去除所选的锚链接的标签 38 | unlink 39 | insertBrOnReturn 40 | 控制当按下 Enter 键时,是插入 br 标签还是把当前块元素变成两个。(IE 浏览器不支持) 41 | justifyCenter justifyLeft justifyRight 42 | 对光标插入位置或者所选内容进行中右左对齐。 43 | redo 44 | undo 45 | 撤销最近执行的命令。 46 | 47 | 移动端低端机 input 事件 48 | 当用户当前输入法状态是中文时,在未选择词组到输入框也会触发事件 49 | 50 | 自动获取焦点 51 | 52 | 2. 发这些垂直解决方案的时候,业务分析、技术选型、组件库架构设计、以及落地是非常难的。 53 | -------------------------------------------------------------------------------- /handwrittenCode/字节面试题:合并两个有序数组.md: -------------------------------------------------------------------------------- 1 | ### 方法二:双指针 2 | 3 | ```js 4 | var nums1 = [1, 2, 3, 4] 5 | var nums2 = [3, 4, 5] 6 | 7 | var merge = function (nums1, nums2) { 8 | var m = nums1.length 9 | var n = nums2.length 10 | var p1 = 0, 11 | p2 = 0 12 | var sorted = new Array(m + n).fill(0) 13 | var cur 14 | while (p1 < m || p2 < n) { 15 | if (p1 === m) { 16 | cur = nums2[p2++] 17 | } else if (p2 === n) { 18 | cur = nums1[p1++] 19 | } else if (nums1[p1] < nums2[p2]) { 20 | cur = nums1[p1++] 21 | } else { 22 | cur = nums2[p2++] 23 | } 24 | sorted[p1 + p2 - 1] = cur 25 | } 26 | console.log(sorted) 27 | } 28 | merge(nums1, nums2) 29 | ``` 30 | 31 | 复杂度分析 32 | 33 | 时间复杂度:O(m+n)。 34 | 指针移动单调递增,最多移动 m+n 次,因此时间复杂度为 O(m+n)。 35 | 36 | 空间复杂度:O(m+n)。 37 | 需要建立长度为 m+nm+n 的中间数组 sorted。。 38 | 39 | ### 方法二:逆向双指针 40 | 41 | ```js 42 | var merge = function (nums1, m, nums2, n) { 43 | let p1 = m - 1, 44 | p2 = n - 1 45 | let tail = m + n - 1 46 | var cur 47 | while (p1 >= 0 || p2 >= 0) { 48 | if (p1 === -1) { 49 | cur = nums2[p2--] 50 | } else if (p2 === -1) { 51 | cur = nums1[p1--] 52 | } else if (nums1[p1] > nums2[p2]) { 53 | cur = nums1[p1--] 54 | } else { 55 | cur = nums2[p2--] 56 | } 57 | nums1[tail--] = cur 58 | } 59 | } 60 | ``` 61 | 62 | 时间复杂度:O(m+n) 。 63 | 指针移动单调递减,最多移动 m+nm+n 次,因此时间复杂度为 O(m+n)。 64 | 65 | 空间复杂度:O(1)。 66 | 直接对数组 nums1 原地修改,不需要额外空间。 67 | 68 | ### 参考 69 | 70 | 作者:LeetCode-Solution 71 | 链接:https://leetcode-cn.com/problems/merge-sorted-array/solution/he-bing-liang-ge-you-xu-shu-zu-by-leetco-rrb0/
72 | 来源:力扣(LeetCode)
73 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 74 | -------------------------------------------------------------------------------- /前端安全/csrf/readme.md: -------------------------------------------------------------------------------- 1 | ### CSRF/XSRF(跨站请求伪造) 2 | 3 | CSRF,即 Cross Site Request Forgery,中译是跨站请求伪造,是一种劫持受信任用户向服务器发送非预期请求的攻击方式。通常情况下,CSRF 攻击是攻击者借助受害者的 Cookie 骗取服务器的信任,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器,从而在并未授权的情况下执行在权限保护之下的操作。 4 | 5 | ##### Cookie 6 | 7 | Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。Cookie 主要用于以下三个方面: 8 | 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) 9 | 个性化设置(如用户自定义设置、主题等) 10 | 浏览器行为跟踪(如跟踪分析用户行为等) 11 | 而浏览器所持有的 Cookie 分为两种: 12 | 13 | Session Cookie(会话期 Cookie):会话期 Cookie 是最简单的 Cookie,它不需要指定过期时间(Expires)或者有效期(Max-Age),它仅在会话期内有效,浏览器关闭之后它会被自动删除。 14 | Permanent Cookie(持久性 Cookie):与会话期 Cookie 不同的是,持久性 Cookie 可以指定一个特定的过期时间(Expires)或有效期(Max-Age) 15 | res.setHeader('Set-Cookie', ['mycookie=222', 'test=3333; expires=Sat, 21 Jul 2018 00:00:00 GMT;']); 16 | 上述代码创建了两个 Cookie:mycookie 和 test,前者属于会话期 Cookie,后者则属于持久性 Cookie 17 | 18 | ##### CSRF 攻击 19 | 20 | 使登录用户访问攻击者的网站,发起一个请求,由于 Cookie 中包含了用户的认证信息,当用户访问攻击者准备的攻击环境时,攻击者就可以对服务器发起 CSRF 攻击。 21 | 在这个攻击过程中,攻击者借助受害者的 Cookie 骗取服务器的信任,但并不能拿到 Cookie,也看不到 Cookie 的内容。而对于服务器返回的结果,由于浏览器同源策略的限制,攻击者也无法进行解析。(攻击者的网站虽然是跨域的,但是他构造的链接是源网站的,跟源网站是同源的,所以能够携带 cookie 发起访问)- 但是攻击者无法从返回的结果中得到任何东西,他所能做的就是给服务器发送请求,以执行请求中所描述的命令,在服务器端直接改变数据的值,而非窃取服务器中的数据。例如删除数据、修改数据,新增数据等,无法获取数据。 22 | 23 | ##### CSRF 攻击防范 24 | 25 | 验证码:验证码被认为是对抗 CSRF 攻击最简洁而有效的防御方法。从上述示例中可以看出,CSRF 攻击往往是在用户不知情的情况下构造了网络请求。而验证码会强制用户必须与应用进行交互,才能完成最终请求。因为通常情况下,验证码能够很好地遏制 CSRF 攻击。但验证码并不是万能的,因为出于用户考虑,不能给网站所有的操作都加上验证码。因此,验证码只能作为防御 CSRF 的一种辅助手段,而不能作为最主要的解决方案。 26 | 27 | Referer Check:根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。通过 Referer Check,可以检查请求是否来自合法的"源"。 28 | 29 | 添加 token 验证:要抵御 CSRF,关键在于在请求中放入攻击者所不能伪造的信息,并且该信息不存在于 Cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。 30 | -------------------------------------------------------------------------------- /handwrittenCode/字节:二叉树完整路径之和.js: -------------------------------------------------------------------------------- 1 | // https://leetcode-cn.com/problems/path-sum/ 2 | 3 | ;(() => { 4 | //用多维数组代表题中示例的树,这个数组是按“前序遍历”后排序 5 | let arrSouce = [6, [2, [-1], [3]], [3, [0]]] 6 | 7 | /** 8 | * 定义二叉树的结点。根结点和树的数据结构一样, 9 | * 因此只需要定义一个结点(Node)的数据结构即可 10 | */ 11 | class Node { 12 | /** 13 | * 构造函数 14 | * @param {Number} value 当前结点的值 15 | * @param {Node} left 左子结点 16 | * @param {Node} right 右子结点 17 | */ 18 | constructor(value, left, right) { 19 | if (value != undefined) { 20 | this.value = value 21 | if (left != undefined) this.left = left 22 | if (right != undefined) this.right = right 23 | } 24 | } 25 | } 26 | 27 | /** 28 | * 创建一个树 29 | * @param {Array} arr 一个代表二叉树的多维数组 30 | */ 31 | function makeBTree(arr) { 32 | if (arr) { 33 | if (arr.length == 1) { 34 | return new Node(arr[0]) 35 | } 36 | //递归,创建二叉树 37 | return new Node(arr[0], makeBTree(arr[1]), makeBTree(arr[2])) 38 | } 39 | } 40 | 41 | //创建示例中的二叉树,简洁多了! 42 | let bTree = makeBTree(arrSouce) 43 | 44 | /** 45 | * 主逻辑函数,与第一种解法里代码一样 46 | * @param {Node} node 树的结点 47 | * @param {Number} target 题目要求的目标值 48 | */ 49 | function hasPathSum(node, target) { 50 | //若根节点无子结点 51 | if (!node.left && !node.right) { 52 | //直接判断根结点额值是否等于target 53 | return node.value == target 54 | } 55 | //若有子结点 56 | else { 57 | //如有左结点 左侧递归 58 | if (!!node.left) 59 | return hasPathSum(node.left, /*关键*/ target - node.value) 60 | //如有右结点 右侧递归 61 | if (!!node.right) 62 | return hasPathSum(node.right, /*关键*/ target - node.value) 63 | } 64 | } 65 | 66 | console.log(hasPathSum(bTree, 7)) //>> true 67 | })() 68 | -------------------------------------------------------------------------------- /leetcode/20.有效的括号.md: -------------------------------------------------------------------------------- 1 | ### 题目描述: 2 | 3 | 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 4 | 5 | 有效字符串需满足: 6 | 7 | 左括号必须用相同类型的右括号闭合。 8 | 左括号必须以正确的顺序闭合。 9 | 注意空字符串可被认为是有效字符串。 10 | 11 | 示例: 12 | 输入: "()" 13 | 输出: true 14 | 输入: "()[]{}" 15 | 输出: true 16 | 输入: "(]" 17 | 输出: false 18 | 输入: "([)]" 19 | 输出: false 20 | 输入: "{[]}" 21 | 输出: true 22 | 23 | 来源:力扣(LeetCode)
24 | 链接:https://leetcode-cn.com/problems/valid-parentheses
25 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 26 | 27 | ##### 方法分析: 28 | 29 | 该题使用的堆栈(stack)的知识。栈具有先进后出(FILO)的特点。堆栈具有栈顶和栈底之分。所谓入栈,就是将元素压入(push)堆栈;所谓出栈,就是将栈顶元素弹出(pop)堆栈。先入栈的一定后出栈,所以可以利用堆栈来检测符号是否正确配对。 30 | 31 | ```js 32 | var isValid = function (s) { 33 | var rightSymbols = [] 34 | for (var i = 0; i < s.length; i++) { 35 | if (s[i] == '(') { 36 | rightSymbols.push(')') 37 | } else if (s[i] == '{') { 38 | rightSymbols.push('}') 39 | } else if (s[i] == '[') { 40 | rightSymbols.push(']') 41 | } else if (rightSymbols.pop() != s[i]) { 42 | return false 43 | } 44 | } 45 | return !rightSymbols.length 46 | } 47 | ``` 48 | 49 | ##### 解题思路 2: 50 | 51 | 1. 有效括号字符串的长度,一定是偶数! 52 | 2. 右括号前面,必须是相对应的左括号,才能抵消! 53 | 3. 右括号前面,不是对应的左括号,那么该字符串,一定不是有效的括号! 54 | 55 | ```js 56 | var isValid = function (s) { 57 | let stack = [] 58 | if (!s || s.length % 2) return false 59 | for (let item of s) { 60 | switch (item) { 61 | case '{': 62 | case '[': 63 | case '(': 64 | stack.push(item) 65 | break 66 | case '}': 67 | if (stack.pop() !== '{') return false 68 | break 69 | case '[': 70 | if (stack.pop() !== ']') return false 71 | break 72 | case '(': 73 | if (stack.pop() !== ')') return false 74 | break 75 | } 76 | } 77 | return !stack.length 78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /leetcode/543.二叉树的直径.md: -------------------------------------------------------------------------------- 1 | 给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。 2 | 3 | 示例 : 4 | 给定二叉树 5 | 6 | ``` 7 | 1 8 | / \ 9 | 2 3 10 | / \ 11 | 4 5 12 | ``` 13 | 14 | 返回  3, 它的长度是路径 [4,2,1,3] 或者  [5,2,1,3]。 15 | 16 | 注意:两结点之间的路径长度是以它们之间边的数目表示。 17 | 18 | 来源:力扣(LeetCode)
19 | 链接:https://leetcode-cn.com/problems/diameter-of-binary-tree
20 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 21 | 22 | ##### 方法 1 23 | 24 | ```js 25 | var diameterOfBinaryTree = function (root) { 26 | // 默认为1是因为默认了根节点自身的路径长度 27 | let ans = 1 28 | function depth(rootNode) { 29 | if (!rootNode) { 30 | // 如果不存在根节点,则深度为0 31 | return 0 32 | } 33 | // 递归,获取左子树的深度 34 | let left = depth(rootNode.left) 35 | // 递归,获取右子树的深度 36 | let right = depth(rootNode.right) 37 | /* 关键点1 38 | L+R+1的公式是如何而来? 39 | 等同于:左子树深度(节点个数) + 右子树深度(节点个数) + 1个根节点 40 | 便是这株二叉树从最左侧叶子节点到最右侧叶子节点的最长路径 41 | 类似于平衡二叉树的最小值节点到最大值节点的最长路径 42 | 之所以+1是因为需要经过根节点 43 | */ 44 | // 获取该树的最长路径和现有最长路径中最大的那个 45 | ans = Math.max(ans, left + right + 1) 46 | /* 关键点2 47 | 已知根节点的左右子树的深度, 48 | 则,左右子树深度的最大值 + 1, 49 | 便是以根节点为数的最大深度*/ 50 | return Math.max(left, right) + 1 51 | } 52 | depth(root) 53 | // 由于depth函数中已经默认加上数节点的自身根节点路径了,故此处需减1 54 | return ans - 1 55 | } 56 | ``` 57 | 58 | ##### 方法 2 59 | 60 | ```js 61 | function height(node) { 62 | //求树高 63 | if (!node) return 0 64 | return 1 + Math.max(height(node.left), height(node.right)) 65 | } 66 | 67 | var diameterOfBinaryTree = function (root) { 68 | if (!root) return 0 69 | let tempH = height(root.left) + height(root.right) 70 | return Math.max( 71 | tempH, 72 | diameterOfBinaryTree(root.left), 73 | diameterOfBinaryTree(root.right) 74 | ) 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /vue/整理.md: -------------------------------------------------------------------------------- 1 | https://juejin.im/post/5cd7dcff51882569562224a5 2 | https://juejin.im/post/5cef749451882530810e0626(Vue底层架构及其应用) 3 | https://juejin.im/post/5d346ef9e51d45108f2542cb 4 | https://juejin.im/post/5d70f28c6fb9a06af824fb89 5 | https://juejin.im/post/5e1af3156fb9a02ffe7025c1 6 | https://juejin.im/post/5cc8f394f265da038733ae77 (数据挟持) 7 | https://juejin.im/post/5cfa356c6fb9a07ecf7216a4 8 | https://juejin.im/post/5cfb6b1d518825035746e64d 9 | https://juejin.im/post/5d419241f265da03e61aee26 10 | https://juejin.im/post/5d36d39bf265da1ba6482e52 11 | https://juejin.im/post/5cf9b8da6fb9a07ed657c471 12 | https://juejin.im/post/5d70f28c6fb9a06af824fb89 13 | 14 | https://juejin.im/post/5be692936fb9a049e129b741 15 | https://juejin.im/post/5e1af3156fb9a02ffe7025c1 16 | https://juejin.im/post/5d2dbf5f5188256b432322f3 17 | https://juejin.im/post/5d3acac5e51d45772a49ae36 18 | https://juejin.im/post/5e81364a6fb9a03c3a087098 19 | https://juejin.im/post/5d0e0af2e51d4510b71da614 20 | https://juejin.im/post/5b80e60de51d4557b85fc8fc 21 | https://juejin.im/post/5cf633ad6fb9a07efe2da92d 22 | https://juejin.im/post/5bae435b6fb9a05d0d287c13 (响应式和源码目录) 23 | https://juejin.im/post/5cbc74b4f265da03a85abe5c(数据挟持) 24 | https://juejin.im/post/5b16c0415188257d42153bac (2.Vue 如何保证 UI 与状态同步) 25 | 神仙朱系列 26 | https://juejin.im/post/5da400c16fb9a04df90192e2 27 | SHERlocked93 28 | https://juejin.im/post/6844903630458486798#heading-3 29 | 字节跳动:实现一个简易版 vue2 30 | https://juejin.im/post/5dd89a416fb9a07aa6226e6d 31 | 32 | https://jiongks.name/blog/vue-code-review/ 33 | 34 | VUE 源码解析 35 | https://github.com/6fedcom/myblog 36 | https://github.com/sup-fiveyear/Notes/tree/master/vue%20%E6%89%8B%E5%86%99 37 | https://github.com/mcuking/blog 38 | https://github.com/sup-fiveyear/Notes/blob/master/notes-vue/$watch%E5%92%8Ccomputed.md 39 | https://github.com/lihongxun945/myblog 40 | https://github.com/6fedcom/vue2.0-source 41 | -------------------------------------------------------------------------------- /前端知识点/anying.md: -------------------------------------------------------------------------------- 1 | ### 深拷贝和浅拷贝 2 | 3 | a. React 中 setState 后发生了什么 4 | b. 虚拟 DOM 主要做了什么 5 | c. 虚拟 DOM 本身是什么(JS 对象) 6 | https://xixili.online/2020/03/18/%E5%89%8D%E7%AB%AF%E5%9F%BA%E6%9C%AC%E4%B9%8B%E8%99%9A%E6%8B%9Fdom%EF%BC%88virtual%20DOM%EF%BC%89%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/ 7 | d. react key 的作用? 8 | 9 | ### 介绍 Vuex,以及数据流的流程 10 | 11 | a. Redux 如何实现多个组件之间的通信,多个组件使用相同状态如何进行管理 12 | b. 多个组件之间如何拆分各自的 state,每块小的组件有自己的状态, 13 | 它们之间还有一些公共的状态需要维护,如何思考这块 14 | 15 | ### vue 16 | 17 | a. computed/watch 原理 18 | b. 数据绑定 19 | 20 | ### 了解过服务端渲染 SSR 么 21 | 22 | 页面的首开率和白屏率确实能影响一个产品的用户留存 23 | SSR 是借用了 service 的能力去尽可能提高页面首屏性能。 24 | 25 | 从 app 的图文列表页点进去一些图文详情页或者理财师的个人主页,如果图文详情页渲染时间太久会导致用户流失,肯定会影响 app 的收益。 26 | 所以对于我们前端同学来说,提高首屏效率确实是我们前端领域亟待解决的问题。于是我们就开始对现有的图文详情页和个人主页进行性能改造。 27 | 28 | 首先来讲解一下几个常用的性能指标术语 29 | 30 | 首屏时间 fsp(first-screen-paint) 从开始下载(navigation)到首屏内容包含图片全部渲染完成的时间,直到用户可以看到首屏的全部内容, 31 | 计算方式分为两种 32 | 首次绘制时间 fmp(first-meaningful-paint) 页面的主要内容开始出现在屏幕上的时间点 33 | 可交互时间 ttl(time-to-interactive) 从开始下载(navigation)到屏幕首屏全部渲染完成的时间,渲染完成后,且页面没有长时间卡顿。 34 | 35 | 用户请求-webview 启动-请求 html、document-页面框架渲染-js 资源请求 36 | 37 | ### 自己遇到过哪些跨域问题,又时如何解决的 38 | 39 | a. 介绍 jsnop 跨域方案,以及服务端要如何配合 40 | 41 | ### ES6 相关 42 | 43 | a. promise 的有几个状态 44 | b. promise、async 有什么区别 45 | c. promise.all()是如何实现的 46 | d. 对 async、await 的理解,内部原理 47 | e. 箭头函数的 this 指向哪里 48 | 49 | ### promise 和 setTimeout 执行的先后顺序? 50 | 51 | ### 什么是宏任务、微任务 52 | 53 | 工程篇 54 | 55 | ### webpack 的生命周期 56 | 57 | compile 开始编译 58 | make 从入口点分析模块及其依赖的模块,创建这些模块对象 59 | build-module 构建模块 60 | after-compile 完成构建 61 | seal 封装构建结果 62 | emit 把各个 chunk 输出到结果文件 63 | after-emit 完成输出 64 | 65 | a. webpack 打包的整个过程 66 | [option=>编译=>build](https://user-gold-cdn.xitu.io/2020/6/21/172d2bc7fd4e2447?imageslim) 67 | 68 | b. loader 和 plugin 有什么区别 69 | c. 常用的 loader 和 plugin 有哪些 70 | 71 | 综合 72 | 73 | ### 单页面 spa 应用有哪些缺点?做过哪些性能优化 74 | 75 | ### react 做过哪些性能优化 76 | 77 | ### 做过防抖和节流么?防抖和节流的区别 78 | 79 | ### 自己做那些前端性能优化 80 | 81 | ### 做过哪些技术方面比较有挑战的事情 82 | -------------------------------------------------------------------------------- /前端知识点/2022年4月19总结.md: -------------------------------------------------------------------------------- 1 | ### 介绍闭包以及闭包为什么没清除 2 | 3 | 闭包就是函数套函数 4 | 在闭包里面,内部的函数可以访问到外部函数作用域内的变量,但是外部的函数不能访问内部函数作用域内的变量 5 | 6 | 7 | 8 | a. 闭包的使用场景 9 | 10 | 1、可以读取内部函数的变量; 11 | 2、让这些变量的值始终保存在内存中,不会被调用后就被垃圾回收机制收回; 12 | 3、用来模块化代码(类块级作用域)。 13 | 14 | ### JSBridge 的通信原理 15 | 16 | #### JavaScript 调用 Native 的方式 17 | 18 | JavaScript 调用 Native 的方式,主要有两种:注入 API 和 拦截 URL SCHEME。 19 | 20 | ###### 注入 API 21 | 22 | 注入 API 方式的主要原理是,通过 WebView 提供的接口,向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。 23 | 24 | 对于 iOS 的 UIWebView,实例如下: 25 | 26 | ```js 27 | JSContext _context = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; 28 | context[@"postBridgeMessage"] = ^(NSArray \*calls) { 29 | // Native 逻辑 30 | }; 31 | 前端调用方式: 32 | window.postBridgeMessage(message); 33 | ``` 34 | 35 | ###### 拦截 URL SCHEME 36 | 37 | 先解释一下 URL SCHEME:URL SCHEME 是一种类似于 url 的链接,是为了方便 app 直接互相调用设计的,形式和普通的 url 近似,主要区别是 protocol 和 host 一般是自定义的, 38 | 39 | 例如: qunarhy://hy/url?url=ymfe.tech,protocol 是 qunarhy,host 则是 hy。 40 | 41 | 拦截 URL SCHEME 的主要流程是:Web 端通过某种方式(例如 iframe.src)发送 URL Scheme 请求,之后 Native 拦截到请求并根据 URL SCHEME(包括所带的参数)进行相关操作。 42 | 43 | 在时间过程中,这种方式有一定的缺陷: 44 | 45 | 1. 使用 iframe.src 发送 URL SCHEME 会有 url 长度的隐患。 46 | 47 | 2. 创建请求,需要一定的耗时,比注入 API 的方式调用同样的功能,耗时会较长。 48 | 49 | 因此:JavaScript 调用 Native 推荐使用注入 API 的方式 50 | 51 | #### Native 调用 JavaScript 的方式 52 | 53 | 相比于 JavaScript 调用 Native, Native 调用 JavaScript 较为简单,直接执行拼接好的 JavaScript 代码即可。 54 | 55 | 从外部调用 JavaScript 中的方法,因此 JavaScript 的方法必须在全局的 window 上。 56 | 57 | 对于 iOS 的 UIWebView,示例如下: 58 | 59 | ```js 60 | result = [uiWebview stringByEvaluatingJavaScriptFromString:javaScriptString]; 61 | ``` 62 | 63 | - javaScriptString 为 JavaScript 代码串 64 | 65 | 对于 iOS 的 WKWebView,示例如下: 66 | 67 | ``` 68 | [wkWebView evaluateJavaScript:javaScriptString completionHandler:completionHandler]; 69 | ``` 70 | -------------------------------------------------------------------------------- /leetcode/160.相交链表.md: -------------------------------------------------------------------------------- 1 | 来源:力扣(LeetCode)
2 | 链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists
3 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 4 | 5 | ### 方法 1:暴力法 6 | 7 | ##### 思路 8 | 9 | 对于链表 A 的每个节点,都去链表 B 中遍历一遍找看看有没有相同的节点。 10 | 11 | ##### 复杂度 12 | 13 | 时间复杂度:O(M \* N)O(M∗N), M, N 分别为两个链表的长度。 14 | 空间复杂度:O(1)O(1)。 15 | 16 | ```js 17 | var getIntersectionNode = function (headA, headB) { 18 | if (!headA || !headB) return null 19 | let pA = headA 20 | while (pA) { 21 | let pB = headB 22 | while (pB) { 23 | if (pA === pB) return pA 24 | pB = pB.next 25 | } 26 | pA = pA.next 27 | } 28 | } 29 | ``` 30 | 31 | ### 方法 2:哈希表 32 | 33 | ##### 思路 34 | 35 | 先遍历一遍链表 A,用哈希表把每个节点都记录下来(注意要存节点引用而不是节点值)。 36 | 再去遍历链表 B,找到在哈希表中出现过的节点即为两个链表的交点。 37 | 38 | ##### 复杂度 39 | 40 | 时间复杂度:O(M + N), M, N 分别为两个链表的长度。 41 | 空间复杂度:O(N),N 为链表 A 的长度。 42 | 43 | ```js 44 | var getIntersectionNode = function (headA, headB) { 45 | if (!headA || !headB) return null 46 | const hashmap = new Map() 47 | let pA = headA 48 | while (pA) { 49 | hashmap.set(pA, 1) 50 | pA = pA.next 51 | } 52 | let pB = headB 53 | while (pB) { 54 | if (hashmap.has(pB)) return pB 55 | pB = pB.next 56 | } 57 | } 58 | ``` 59 | 60 | ### 方法 3:双指针 61 | 62 | ##### 如果链表没有交点 63 | 64 | 两个链表长度一样,第一次遍历结束后 pA 和 pB 都是 null,结束遍历 65 | 两个链表长度不一样,两次遍历结束后 pA 和 pB 都是 null,结束遍历 66 | 67 | ##### 复杂度 68 | 69 | 时间复杂度:O(M + N) , M, N 分别为两个链表的长度。 70 | 空间复杂度:O(1)。 71 | 72 | ```js 73 | /** 74 | * Definition for singly-linked list. 75 | * function ListNode(val) { 76 | * this.val = val; 77 | * this.next = null; 78 | * } 79 | */ 80 | 81 | /** 82 | * @param {ListNode} headA 83 | * @param {ListNode} headB 84 | * @return {ListNode} 85 | */ 86 | var getIntersectionNode = function (headA, headB) { 87 | if (!headA || !headB) return null 88 | 89 | let pA = headA, 90 | pB = headB 91 | while (pA !== pB) { 92 | pA = pA === null ? headB : pA.next 93 | pB = pB === null ? headA : pB.next 94 | } 95 | return pA 96 | } 97 | ``` 98 | -------------------------------------------------------------------------------- /webpack/plugin/plugins/webpackConsoleLog.js: -------------------------------------------------------------------------------- 1 | const ColorHash = require('color-hash'); 2 | const colorHash = new ColorHash(); 3 | 4 | const Dependency = require('webpack/lib/Dependency'); 5 | 6 | class LogDependency extends Dependency { 7 | 8 | constructor(module) { 9 | super(); 10 | this.module = module; 11 | } 12 | }; 13 | 14 | LogDependency.Template = class { 15 | apply(dep, source) { 16 | 17 | const before = `;console.group('${source._source._name}');` 18 | const after = `;console.groupEnd();` 19 | const _size = source.size() 20 | 21 | source.insert(0, before) 22 | 23 | source.replace(_size, _size, after) 24 | 25 | } 26 | }; 27 | 28 | 29 | module.exports = class LogPlugin { 30 | 31 | constructor(opts) { 32 | this.options = { 33 | expression: /\blog\.(\w+)\b/ig, 34 | ...opts 35 | } 36 | this.plugin = { name: 'LogPlugin' } 37 | 38 | } 39 | 40 | doLog(module) { 41 | console.log(module._source) 42 | if (!module._source) return 43 | let _val = module._source.source(), 44 | _name = module.resource; 45 | 46 | const filedColor = colorHash.hex(module._buildHash) 47 | 48 | // 判断是否需要加入 49 | if (this.options.expression.test(_val)) { 50 | module.addDependency(new LogDependency(module)); 51 | } 52 | 53 | _val = _val.replace( 54 | this.options.expression, 55 | `console.log('%c$1字段:%c%o, %c%s', 'color: ${filedColor}', 'color: red', $1, 'color: pink', '${_name}')` 56 | ) 57 | 58 | return _val 59 | 60 | } 61 | 62 | apply(compiler) { 63 | 64 | compiler.hooks.compilation.tap(this.plugin, (compilation) => { 65 | 66 | // 注册自定义依赖模板 67 | compilation.dependencyTemplates.set( 68 | LogDependency, 69 | new LogDependency.Template() 70 | ); 71 | 72 | // modlue解析完毕钩子 73 | compilation.hooks.succeedModule.tap(this.plugin, module => { 74 | console.log(module) 75 | 76 | // 修改模块的代码 77 | module._source._value = this.doLog(module) 78 | 79 | 80 | }) 81 | }) 82 | 83 | } 84 | } -------------------------------------------------------------------------------- /webpack/loader/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const HtmlWebPackPlugin = require("html-webpack-plugin"); 4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 5 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 6 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 7 | 8 | module.exports = { 9 | mode: 'development', 10 | entry: [ 11 | path.join(__dirname, './src/main.js') 12 | ], 13 | module: { 14 | rules: [{ 15 | test: /\.vue$/, 16 | loader: 'vue-loader', 17 | 18 | }, 19 | { 20 | test: /\.(js)$/, 21 | exclude: /node_modules/, 22 | use: { 23 | loader: "babel-loader" 24 | } 25 | }, 26 | { 27 | test: /\.html$/, 28 | use: [{ 29 | loader: "html-loader", 30 | options: { 31 | minimize: true 32 | } 33 | }] 34 | }, 35 | { 36 | test: /\.scss$/, 37 | exclude: /node_modules/, 38 | use: [ 39 | path.resolve(__dirname, "./loaders/css1px-loader.js"), 40 | // 'style-loader', 41 | 'css-loader', 42 | 'postcss-loader', 43 | 'sass-loader', 44 | ] 45 | }, 46 | { 47 | test: /\.md$/, 48 | exclude: /node_modules/, 49 | use: [ 50 | { loader: "html-loader" }, 51 | { 52 | loader: path.resolve(__dirname, "./loaders/md-loader.js") 53 | } 54 | ] 55 | }, 56 | { 57 | test: /\.(png|svg|jpg|gif)$/, 58 | use: [{ 59 | loader: 'file-loader', 60 | options: { 61 | name: '[name].[ext]' 62 | } 63 | }] 64 | }, 65 | ] 66 | }, 67 | resolve: { 68 | alias: { 69 | 'vue$': 'vue/dist/vue.esm.js' 70 | } 71 | }, 72 | plugins: [ 73 | new VueLoaderPlugin(), 74 | new CleanWebpackPlugin(['dist']), 75 | new HtmlWebPackPlugin({ 76 | template: "./public/index.html", 77 | filename: "./index.html" 78 | }), 79 | new MiniCssExtractPlugin({ 80 | filename: "[name].css", 81 | chunkFilename: "[id].css" 82 | }), 83 | 84 | ] 85 | } -------------------------------------------------------------------------------- /test2.js: -------------------------------------------------------------------------------- 1 | const paginationAttr: any = { 2 | position: ['bottomRight'], 3 | pageSizeOptions: [10, 20, 50, 100], // 自定义每页显示多少条数据 4 | defaultPageSize: [10], // 默认每页显示10条数据 5 | showSizeChanger: true, // 显示下拉选项页码 6 | showQuickJumper: true, // 显示允许跳转至xx页 7 | }; 8 | 9 | const ContentOrderDetail: React.FC = (props) => { 10 | // 发送 action 11 | const params1 = { 12 | 'orderNo': props.location.query.transporterOrderNo, 13 | }; 14 | const [resData, setResData] = useState({}); 15 | const [resDeatilData, setResDeatilData] = useState([]); 16 | const [resOrderStatus, setResOrderStatus] = useState([]); 17 | const [statusEnums, setStatusEnums] = useState(''); 18 | 19 | // 订单详情 20 | function getData() { 21 | const orderNo = { 22 | 'orderNo': props.location.query.orderNo, 23 | }; 24 | queryOrderDetails(orderNo).then(async(res) => {// 异步加载数据 25 | setResData(res.data.data); 26 | console.log(resData); 27 | const dictRes = await ValueEnum.getTransportTypeText(resData.transportCondition, transportType); 28 | setStatusEnums(dictRes); 29 | setResDeatilData(res.data.data.details); 30 | }); 31 | } 32 | // 路由信息 33 | function queryOrderStatusData() { 34 | const orderNo = { 35 | 'order_id': props.location.query.orderNo, 36 | }; 37 | queryOrderStatus(orderNo).then((res) => {// 异步加载数据 38 | if (!res.data) { 39 | setResOrderStatus(res.data.data.feed); 40 | } 41 | }); 42 | } 43 | // 物流轨迹 44 | function queryOrderTrackData() { 45 | const orderNo = { 46 | 'orderNumber': props.location.query.orderNo, 47 | 'orderCreateTime': props.location.query.orderCreateTime, 48 | }; 49 | queryOrderTrack(orderNo).then((res) => {// 异步加载数据 50 | // setResDeatilData(res.data.data); 51 | }); 52 | } 53 | // 全程温控 54 | function queryOrderHumidityData() { 55 | const orderNo = { 56 | 'orderNumber': props.location.query.orderNo, 57 | 'orderCreateTime': props.location.query.orderCreateTime, 58 | }; 59 | queryOrderHumidity(orderNo).then((res) => {// 异步加载数据 60 | // setResDeatilData(res.data.data); 61 | }); 62 | } 63 | const [transportCondition,setTransportCondition]=useState('') 64 | useEffect(() => { 65 | (async function initForm() { 66 | 67 | })(); 68 | }, [resData]); 69 | useEffect(() => { 70 | getData(); 71 | queryOrderStatusData(); 72 | queryOrderTrackData(); 73 | queryOrderHumidityData(); 74 | }, []); 75 | // @ts-ignore 76 | return ( -------------------------------------------------------------------------------- /webpack/loader/loaders/md-loader.js: -------------------------------------------------------------------------------- 1 | const loaderUtils = require("loader-utils"); 2 | const md = require('markdown-ast'); //md通过正则匹配的方法把buffer转抽象语法树 3 | const hljs = require('highlight.js'); //代码高亮插件 4 | // 利用 AST 作源码转换 5 | class MdParser { 6 | constructor(content) { 7 | this.data = md(content); 8 | this.parse() 9 | } 10 | parse() { 11 | this.data = this.traverse(this.data); 12 | } 13 | traverse(ast) { 14 | console.log(ast) 15 | let body = ''; 16 | ast.map(item => { 17 | switch (item.type) { 18 | case "bold": 19 | body += `'${this.traverse(item.block)}'` 20 | break; 21 | case "break": 22 | body += '
' 23 | break; 24 | case "codeBlock": 25 | const highlightedCode = hljs.highlight(item.syntax, item.code).value 26 | body += highlightedCode 27 | break; 28 | case "codeSpan": 29 | body += `${item.code}` 30 | break; 31 | case "image": 32 | body += `${item.alt}` 33 | break; 34 | case "italic": 35 | body += ` ${this.traverse(item.block)}`; 36 | break; 37 | case "link": 38 | let linkString = this.traverse(item.block) 39 | body += `
${linkString}` 40 | break; 41 | case "list": 42 | item.type = (item.bullet === '-') ? 'ul' : 'ol' 43 | if (item.type !== '-') { 44 | item.startatt = (` start=${item.indent.length}`) 45 | } else { 46 | item.startatt = '' 47 | } 48 | body += '<' + item.type + item.startatt + '>\n' + this.traverse(item.block) + '\n' 49 | break; 50 | case "quote": 51 | let quoteString = this.traverse(item.block) 52 | body += '
\n' + quoteString + '
\n'; 53 | break; 54 | case "strike": 55 | body += `${this.traverse(item.block)}` 56 | break; 57 | case "text": 58 | body += item.text 59 | break; 60 | case "title": 61 | body += `${this.traverse(item.block)}` 62 | break; 63 | default: 64 | throw Error("error", `No corresponding treatment when item.type equal${item.type}`); 65 | } 66 | }) 67 | return body 68 | } 69 | } 70 | 71 | module.exports = function(content) { 72 | this.cacheable && this.cacheable(); 73 | const options = loaderUtils.getOptions(this); 74 | try { 75 | const parser = new MdParser(content); 76 | return parser.data 77 | } catch (err) { 78 | throw err; 79 | } 80 | }; -------------------------------------------------------------------------------- /webpack/webpack/myWebpack.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | const path = require("path") 3 | const parser = require("@babel/parser") //解析成ast 4 | const traverse = require("@babel/traverse").default //遍历ast 5 | const { transformFromAst } = require("@babel/core") //ES6转换ES5 6 | module.exports = class Webpack { 7 | constructor(options) { 8 | const { entry, output } = options 9 | this.entry = entry 10 | this.output = output 11 | this.modulesArr = [] 12 | } 13 | run() { 14 | const info = this.analysis(this.entry) 15 | this.modulesArr.push(info) 16 | for (let i = 0; i < this.modulesArr.length; i++) { 17 | const item = this.modulesArr[i] 18 | const { dependencies } = item; 19 | if (dependencies) { 20 | for (let j in dependencies) { 21 | this.modulesArr.push(this.analysis(dependencies[j])) 22 | } 23 | } 24 | } 25 | // console.log(this.modules) 26 | //数组结构转换 27 | const obj = {} 28 | this.modulesArr.forEach((item) => { 29 | obj[item.entryFile] = { 30 | dependencies: item.dependencies, 31 | code: item.code 32 | } 33 | }) 34 | this.emitFile(obj) 35 | } 36 | 37 | analysis(entryFile) { 38 | const conts = fs.readFileSync(entryFile, 'utf-8') 39 | const ast = parser.parse(conts, { 40 | sourceType: "module" 41 | }); 42 | // console.log(ast) 43 | const dependencies = {} 44 | traverse(ast, { 45 | ImportDeclaration({ node }) { 46 | const newPath = "./" + path.join( 47 | path.dirname(entryFile), 48 | node.source.value 49 | ) 50 | dependencies[node.source.value] = newPath 51 | // console.log(dependencies) 52 | } 53 | }) 54 | const { code } = transformFromAst(ast, null, { 55 | presets: ["@babel/preset-env"] 56 | }) 57 | return { 58 | entryFile, 59 | dependencies, 60 | code 61 | } 62 | } 63 | emitFile(code) { 64 | // console.log(code) 65 | //生成bundle.js =>./dist/main.js 66 | const filePath = path.join(this.output.path, this.output.filename) 67 | const newCode = JSON.stringify(code) 68 | const bundle = `(function(modules){ 69 | function require(moduleId){ 70 | function localRequire(relativePath){ 71 | return require(modules[moduleId].dependencies[relativePath]) 72 | } 73 | var exports = {}; 74 | (function(require,exports,code){ 75 | eval(code) 76 | })(localRequire,exports,modules[moduleId].code) 77 | return exports; 78 | } 79 | require('${this.entry}') 80 | })(${newCode})` 81 | fs.writeFileSync(filePath, bundle, 'utf-8') 82 | } 83 | } -------------------------------------------------------------------------------- /handwrittenCode/发布订阅.js: -------------------------------------------------------------------------------- 1 | function EventEmitter() { 2 | //缓存列表,存放订阅者列表 3 | var list = [] 4 | var instance 5 | 6 | //判断事件是否曾被订阅 7 | var on = function (name, fn, type = 0) { 8 | //订阅name事件,监听函数为fn,type = 0表示永久订阅 =1表示一次订阅 9 | if (!list[name]) { 10 | list[name] = [] 11 | } 12 | list[name].push([fn, type]) //监听函数插入该事件列表 13 | } 14 | 15 | //订阅一次触发后删除 16 | var once = function (name, fn, type = 1) { 17 | on(name, fn, type) 18 | } 19 | 20 | //发布 21 | var emit = function (name) { 22 | var fns = list[name] //取出事件 23 | var args = [].slice.call(arguments, 1) //获取参数列表 24 | 25 | if (!fns || fns.length == 0) { 26 | //判断监听函数是否存在 27 | return 28 | } 29 | 30 | for (var i = 0; i < fns.length, (fn = fns[i++]); ) { 31 | fn[0].apply(this, args) //调用监听函数fn 32 | 33 | if (fn[1] == 1) { 34 | //判断是否只订阅一次 35 | 36 | remove(name, fn[0], 1) //订阅一次触发后删除 37 | } 38 | } 39 | } 40 | 41 | //删除事件 42 | var remove = function (name, fn, type = 0) { 43 | if (!name) { 44 | //如果事件不存在,直接return 45 | return 46 | } 47 | var fns = list[name] //取出事件 48 | 49 | if (!fn) { 50 | list[name] = [] //未传入监听函数,取消全部 51 | } else { 52 | for (var i = 0; i < fns.length, (fn1 = fns[i]); i++) { 53 | if (fn === fn1[0] && type === fn1[1]) { 54 | fns.splice(i, 1) //找到对应函数,删除之 55 | } 56 | } 57 | } 58 | } 59 | return { 60 | //返回一个对象 61 | on, 62 | once, 63 | emit, 64 | remove, 65 | } 66 | } 67 | 68 | var emitter = new EventEmitter() 69 | var log = console.log 70 | emitter.on('someTask', log) //判断log事件是否被订阅 71 | emitter.emit('someTask', 1) //1 发布 72 | emitter.emit('someTask', 1, 2) //1,2 发布 73 | emitter.once('someTask', log) //只订阅一次log事件 74 | emitter.emit('someTask', 1) //1 75 | emitter.emit('onceTask', 1) //第二次不输出 76 | emitter.remove('someTask', log) //删除事件 77 | emitter.emit('someTask', 1) //删除事件后不输出 78 | class pubSub { 79 | constructor() { 80 | this.events = {} 81 | } 82 | subcribe(event, callback) { 83 | if (this.events[event]) { 84 | // 如果有人订阅过了,这个键已经存在,就往里面加就好了 85 | this.events[event].push(callback) 86 | } else { 87 | // 没人订阅过,就建一个数组,回调放进去 88 | this.events[event] = [callback] 89 | } 90 | } 91 | publish(event, ...args) { 92 | const subcribedEvents = this.events[event] 93 | if (subcribedEvents && subcribedEvents.length) { 94 | subcribedEvents.forEach((callback) => { 95 | callback.call(this, ...args) 96 | }) 97 | } 98 | } 99 | unsubscribe(event, callback) { 100 | const subscribedEvents = this.events[event] 101 | if (subscribedEvents && subscribedEvents.length) { 102 | this.events[event] = this.events[event].filter((cb) => cb !== callback) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /webpack/plugin/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const HtmlWebPackPlugin = require("html-webpack-plugin"); 4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 5 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 6 | const QiniuUpload = require('./plugins/QiniuUpload') 7 | const MyWebpackPlugin = require('./plugins/MyWebpackPlugin') 8 | const WebpackConsoleLog = require('./plugins/webpackConsoleLog') 9 | // const BuildTimePlugin = require('./plugins/build-time') 10 | module.exports = (env, argv) => { 11 | const devMode = argv.mode !== 'production' 12 | return { 13 | entry: [ 14 | "babel-polyfill", 15 | path.join(__dirname, './src/index.js') 16 | ], 17 | devServer: { 18 | port: 3000, //端口号 19 | }, 20 | output: { 21 | // 给输出的 JavaScript 文件名称加上 Hash 值 22 | filename: '[name]_[chunkhash:8].js', 23 | path: path.resolve(__dirname, './dist'), 24 | // 指定存放 JavaScript 文件的 CDN 目录 URL 25 | publicPath: '//wcdn.6fed.com/', 26 | }, 27 | module: { 28 | rules: [{ 29 | test: /\.(js|jsx)$/, 30 | exclude: /node_modules/, 31 | use: { 32 | loader: "babel-loader" 33 | } 34 | }, 35 | { 36 | test: /\.html$/, 37 | use: [{ 38 | loader: "html-loader", 39 | options: { 40 | minimize: true 41 | } 42 | }] 43 | }, 44 | { 45 | test: /\.less$/, 46 | use: [ 47 | devMode ? 'style-loader' : MiniCssExtractPlugin.loader, 48 | 'css-loader', 49 | 'postcss-loader', 50 | 'less-loader', 51 | ] 52 | }, 53 | { 54 | test: /\.(png|svg|jpg|gif)$/, 55 | use: [{ 56 | loader: 'file-loader', 57 | options: { 58 | limit: 1, 59 | name: 'img/[name].[hash:7].[ext]', 60 | publicPath: "http://wcdn.6fed.com" 61 | } 62 | }], 63 | } 64 | ] 65 | }, 66 | plugins: [ 67 | new CleanWebpackPlugin(['dist']), 68 | new HtmlWebPackPlugin({ 69 | template: "./public/index.html", 70 | filename: "index.html" 71 | }), 72 | new MiniCssExtractPlugin({ 73 | filename: "css/[name].css", 74 | chunkFilename: "[id].css" 75 | }), 76 | // new QiniuUpload({ 77 | // qiniu: { 78 | // accessKey: 'HCct3FpW17hnRMdsSCnogNeqtklD5nIiUa9hOrvi', 79 | // secretKey: '7Pp2QhmgJo0SdwpKCiuq5M1VMFHbZNj68mjLBwRz', 80 | // bucket: 'webpack-plugin-upload', 81 | // keyPrefix: 'webpack-plugins-statics/', 82 | // include: [ 83 | // 'css/**', 84 | // ], 85 | // exclude: [ 86 | // path.resolve(__dirname, "index.html") 87 | // ], 88 | // } 89 | // }), 90 | new MyWebpackPlugin(), 91 | // new BuildTimePlugin() 92 | ] 93 | } 94 | }; -------------------------------------------------------------------------------- /leetcode/5.最长回文子串.md: -------------------------------------------------------------------------------- 1 | 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设  s 的最大长度为 1000。 2 | 3 | 示例 1: 4 | 5 | 输入: "babad" 6 | 输出: "bab" 7 | 注意: "aba" 也是一个有效答案。 8 | 示例 2: 9 | 10 | 输入: "cbbd" 11 | 输出: "bb" 12 | 13 | 来源:力扣(LeetCode)
14 | 链接:https://leetcode-cn.com/problems/longest-palindromic-substring
15 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 16 | 17 | ```js 18 | function longestPalindrome(string: string): string { 19 | const n = string.length 20 | const dp: boolean[][] = [] 21 | //先创建一个二维数组 22 | for (let i = 0; i < n; i++) { 23 | dp[i] = [] 24 | } 25 | let ans = '' 26 | //l作为字符串的长度 27 | for (let l = 0; l < n; l++) { 28 | //注意 i才是起始位置 不能用l来作为起始位置遍历 29 | for (let i = 0; i + l < n; i++) { 30 | let j = i + l 31 | if (l === 0) { 32 | dp[i][j] = true 33 | } else if (l === 1) { 34 | dp[i][j] = string[i] === string[j] 35 | } else { 36 | dp[i][j] = dp[i + 1][j - 1] && string[i] === string[j] 37 | } 38 | //每当有回文字符串的时候跟ans比较,得出长度更大的回文串 39 | // j+1是因为substring的方法 40 | if (dp[i][j] && string.substring(i, j + 1).length > ans.length) { 41 | ans = string.substring(i, j + 1) 42 | } 43 | } 44 | } 45 | return ans 46 | } 47 | ``` 48 | 49 | 解题思路 50 | 两种情况 51 | 一种是回文子串长度为奇数(如 aba,中心是 b) 52 | 另一种回文子串长度为偶数(如 abba,中心是 b,b) 53 | 54 | 循环遍历字符串 对取到的每个值 都假设他可能成为最后的中心进行判断 55 | 56 | 作者:qing-chen-4a
57 | 链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/chao-jian-dan-de-zhong-xin-kuo-san-fa-yi-qini/
58 | 来源:力扣(LeetCode)
59 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 60 | 61 | ```js 62 | /** 63 | * @param {string} s 64 | * @return {string} 65 | */ 66 | var longestPalindrome = function (s) { 67 | if (s.length < 2) { 68 | return s 69 | } 70 | let res = '' 71 | for (let i = 0; i < s.length; i++) { 72 | // 回文子串长度是奇数 73 | helper(i, i) 74 | // 回文子串长度是偶数 75 | helper(i, i + 1) 76 | } 77 | 78 | function helper(m, n) { 79 | while (n < s.length && s[m] == s[n]) { 80 | m-- 81 | n++ 82 | } 83 | // 注意此处m,n的值循环完后 是恰好不满足循环条件的时刻 84 | // 此时m到n的距离为n-m+1,但是mn两个边界不能取 所以应该取m+1到n-1的区间 长度是n-m-1 85 | if (n - m - 1 > res.length) { 86 | // slice也要取[m+1,n-1]这个区间 87 | res = s.slice(m + 1, n) 88 | } 89 | } 90 | return res 91 | } 92 | ``` 93 | 94 | ##### 暴力枚举法 95 | 96 | ```js 97 | var longestPalindrome = function (s) { 98 | function isPalindrome(str) { 99 | var len = str.length 100 | var middle = parseInt(len / 2) 101 | for (var i = 0; i < middle; i++) { 102 | if (str[i] != str[len - i - 1]) { 103 | return false 104 | } 105 | } 106 | return true 107 | } 108 | 109 | var ans = '' 110 | var max = 0 111 | var len = s.length 112 | for (var i = 0; i < len; i++) { 113 | for (var j = i + 1; j < =len; j++) { 114 | var tmpStr = s.substring(i, j) 115 | if (isPalindrome(tmpStr) && tmpStr.length > max) { 116 | ans = s.substring(i, j) 117 | max = tmpStr.length 118 | } 119 | } 120 | } 121 | return ans 122 | } 123 | ``` 124 | -------------------------------------------------------------------------------- /webpack/plugin/plugins/build-time.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const pluginName = 'ModuleBuildTimeCalWebpackPlugin'; 3 | const NOOL = () => {}; 4 | 5 | class ModuleBuildTimeCalWebpackPlugin { 6 | constructor(options = {}) { 7 | /** 8 | * OPTIONS FORMAT 9 | * const options = { 10 | * filename: 'modules.json', 11 | * includeNodeModules: false, 12 | * callback: () => {}, 13 | * } 14 | */ 15 | this.options = options; 16 | this.modules = {}; 17 | // console.log('Plugin options', this.options); 18 | } 19 | 20 | handleFinishModules() { 21 | const { 22 | callback = NOOL, 23 | filename = 'modules.json', 24 | } = this.options; 25 | try { 26 | const data = JSON.stringify(this.modules); 27 | const writeToFilePath = `${process.cwd()}/${filename}}`; 28 | 29 | /** 30 | * OUTPUT FORMAT 31 | * modules.json = { 32 | * 'src/index.js': { 33 | * name: 'src/index.js', 34 | * start: 1, 35 | * end: 4, 36 | * time: 3, 37 | * loaders: [ ... ] 38 | * }, 39 | * ... 40 | * } 41 | */ 42 | fs.writeFile(writeToFilePath, data, 'utf8', callback); 43 | } catch (e) { 44 | console.error(`[${pluginName} ERROR]: `, e); 45 | } 46 | } 47 | 48 | handleBuildModule(module) { 49 | const { userRequest, loaders } = module; 50 | const { includeNodeModules } = this.options; 51 | console.log(1, module) 52 | const rawRequest = userRequest.replace(process.cwd(), ''); 53 | if (includeNodeModules || !rawRequest.includes('node_modules')) { 54 | this.modules[rawRequest] = { 55 | start: (new Date()).getTime(), 56 | loaders, 57 | name: rawRequest, 58 | }; 59 | } 60 | } 61 | 62 | handleSucceedModule(module) { 63 | const { userRequest, loaders } = module; 64 | const { includeNodeModules } = this.options; 65 | console.log(2, userRequest) 66 | const rawRequest = userRequest.replace(process.cwd(), ''); 67 | if (includeNodeModules || !rawRequest.includes('node_modules')) { 68 | this.modules[rawRequest].end = (new Date()).getTime(); 69 | const spend = this.modules[rawRequest].end - this.modules[rawRequest].start; 70 | this.modules[rawRequest].time = spend; 71 | } 72 | } 73 | 74 | apply(compiler) { 75 | console.log(compiler) 76 | debugger 77 | compiler.hooks.compilation.tap( 78 | pluginName, 79 | compilation => { 80 | console.log(compilation) 81 | debugger 82 | compilation.hooks.buildModule.tap( 83 | pluginName, 84 | this.handleBuildModule.bind(this) 85 | ); 86 | 87 | compilation.hooks.succeedModule.tap( 88 | pluginName, 89 | this.handleSucceedModule.bind(this)); 90 | 91 | compilation.hooks.finishModules.tap( 92 | pluginName, 93 | this.handleFinishModules.bind(this) 94 | ); 95 | } 96 | ); 97 | } 98 | } 99 | 100 | module.exports = ModuleBuildTimeCalWebpackPlugin; -------------------------------------------------------------------------------- /handwrittenCode/Promise.js: -------------------------------------------------------------------------------- 1 | class Promise { 2 | constructor(executor) { 3 | this.status = 'pending' // 默认状态 4 | this.value // resolve 成功时的值 5 | this.error // reject 失败时的值 6 | this.resolveQueue = []; // 成功存放的数组 7 | this.rejectQueue = []; // 失败存放法数组 8 | 9 | // 定义 resolve 10 | let resolve = (res) => { 11 | if (this.status === 'pending') { 12 | this.value = res 13 | this.status = 'resolved' 14 | // 一旦resolve执行,调用成功数组的函数 15 | this.resolveQueue.forEach(fn => fn()); 16 | } 17 | } 18 | 19 | // 定义 reject 20 | let reject = (err) => { 21 | if (this.status === 'pending') { 22 | this.error = err 23 | this.status = 'rejected' 24 | } 25 | } 26 | 27 | // 自动执行 28 | executor(resolve, reject) 29 | } 30 | 31 | // 声明 then 32 | then(onFullfilled, onRejected) { 33 | let promise2; 34 | if (this.status === 'resolved') { 35 | onFullfilled(this.value) 36 | } 37 | if (this.status === 'rejected') { 38 | onRejected(this.error) 39 | } 40 | // 当状态state为pending时 41 | if (this.status === "pending") { 42 | promise2 = new Promise((resolve, reject) => { 43 | this.resolveQueue.push(() => { 44 | let x = onFullfilled(this.value); 45 | resolvePromise(promise2, x, resolve, reject); 46 | }) 47 | this.rejectQueue.push(() => { 48 | let x = onRejected(this.error); 49 | resolvePromise(promise2, x, resolve, reject); 50 | }) 51 | }) 52 | } 53 | return promise2; 54 | }, 55 | catch (onRejected) { 56 | return this.then(null, onRejected) 57 | } 58 | } 59 | 60 | class Promise { 61 | constructor(executor) { 62 | this.status = "pending" 63 | this.value 64 | this.error 65 | this.resolveQuene = [] 66 | this.rejectQuene = [] 67 | let resolve = (res) => { 68 | if (this.status === 'pending') { 69 | this.value = res 70 | this.status = 'resolved' 71 | this.resolveQuene.forEach(fn => fn()) 72 | } 73 | } 74 | let reject = (err) => { 75 | if (this.status === 'pending') { 76 | this.value = res 77 | this.status = 'rejected' 78 | } 79 | } 80 | executor(resolve, reject) 81 | } 82 | then(onFullfilled, onRejected) { 83 | let promise2; 84 | if (this.status == 'resolved') { 85 | onFullfilled(this.value) 86 | } 87 | if (this.status == 'rejected') { 88 | onRejected(this.error) 89 | } 90 | if (this.status == 'pending') { 91 | promise2 = new Promise((resolve, reject) => { 92 | this.resolveQuene.push(() => { 93 | let x = onFullfilled(this.value) 94 | resolvePromise(promise2, x, resolve, reject); 95 | }) 96 | this.rejectQueue.push(() => { 97 | let x = onRejected(this.error); 98 | resolvePromise(promise2, x, resolve, reject); 99 | }) 100 | }) 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /前端安全/xss/readme.md: -------------------------------------------------------------------------------- 1 | ### XSS(跨站脚本攻击) 2 | 3 | XSS,即 Cross Site Script,中译是跨站脚本攻击;其原本缩写是 CSS,但为了和层叠样式表(Cascading Style Sheet)有所区分,因而在安全领域叫做 XSS。 4 | XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。 5 | 攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTML 和 Flash。有很多种方式进行 XSS 攻击,但它们的共同点为:将一些隐私数据像 cookie、session 发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作。 6 | XSS 攻击可以分为 3 类:反射型(非持久型)、存储型(持久型)、基于 DOM。 7 | 8 | ##### 反射型 9 | 10 | 反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器,这种攻击方式往往需要攻击者诱使用户点击一个恶意链接(攻击者可以将恶意链接直接发送给受信任用户,发送的方式有很多种,比如 email, 网站的私信、评论等,攻击者可以购买存在漏洞网站的广告,将恶意链接插入在广告的链接中),或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。最简单的示例是访问一个链接,服务端返回一个可执行脚本: 11 | 12 | ```js 13 | const http = require('http') 14 | function handleReequest(req, res) { 15 | res.setHeader('Access-Control-Allow-Origin', '*') 16 | res.writeHead(200, { 'Content-Type': 'text/html; charset=UTF-8' }) 17 | res.write('') 18 | res.end() 19 | } 20 | 21 | const server = new http.Server() 22 | server.listen(8001, '127.0.0.1') 23 | server.on('request', handleReequest) 24 | ``` 25 | 26 | ##### 存储型 27 | 28 | 存储型 XSS 会把用户输入的数据 "存储" 在服务器端,当浏览器请求数据时,脚本从服务器上传回并执行。这种 XSS 攻击具有很强的稳定性。比较常见的一个场景是攻击者在社区或论坛上写下一篇包含恶意 JavaScript 代码的文章或评论,文章或评论发表后,所有访问该文章或评论的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码 29 | // 例如在评论中输入以下留言 30 | // 如果请求这段留言的时候服务端不做转义处理,请求之后页面会执行这段恶意代码 31 | 32 | 33 | 34 | ##### 基于 DOM 35 | 36 | 基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击: 37 | 38 | ```html 39 |

XSS:

40 | 41 | 42 |
43 | 66 | ``` 67 | 68 | 点击 Submit 按钮后,会在当前页面插入一个链接,其地址为用户的输入内容。如果用户在输入时构造了如下内容: 69 | 'onclick=alert(/xss/)' 70 | 用户提交之后,页面代码就变成了: 71 | 72 | ```html 73 |
testLink 74 | ``` 75 | 76 | 此时,用户点击生成的链接,就会执行对应的脚本。 77 | 78 | ##### 5. XSS 攻击防范 79 | 80 | HttpOnly 防止劫取 Cookie:HttpOnly 最早由微软提出,至今已经成为一个标准。浏览器将禁止页面的 Javascript 访问带有 HttpOnly 属性的 Cookie。上文有说到,攻击者可以通过注入恶意脚本获取用户的 Cookie 信息。通常 Cookie 中都包含了用户的登录凭证信息,攻击者在获取到 Cookie 之后,则可以发起 Cookie 劫持攻击。所以,严格来说,HttpOnly 并非阻止 XSS 攻击,而是能阻止 XSS 攻击后的 Cookie 劫持攻击。 81 | 输入检查:不要相信用户的任何输入。对于用户的任何输入要进行检查、过滤和转义。建立可信任的字符和 HTML 标签白名单,对于不在白名单之列的字符或者标签进行过滤或编码。在 XSS 防御中,输入检查一般是检查用户输入的数据中是否包含 <,> 等特殊字符,如果存在,则对特殊字符进行过滤或编码,这种方式也称为 XSS Filter。而在一些前端框架中,都会有一份 decodingMap, 用于对用户输入所包含的特殊字符或标签进行编码或过滤,如 <,>,script,防止 XSS 攻击 82 | // vuejs 中的 decodingMap 83 | // 在 vuejs 中,如果输入带 script 标签的内容,会直接过滤掉 84 | 85 | ```js 86 | const decodingMap = { 87 | '<': '<', 88 | '>': '>', 89 | '"': '"', 90 | '&': '&', 91 | ' ': '\n', 92 | } 93 | ``` 94 | 95 | 输出检查:用户的输入会存在问题,服务端的输出也会存在问题。一般来说,除富文本的输出外,在变量输出到 HTML 页面时,可以使用编码或转义的方式来防御 XSS 攻击。例如利用 sanitize-html 对输出内容进行有规则的过滤之后再输出到页面中。 96 | -------------------------------------------------------------------------------- /webpack/plugin/plugins/QiniuUpload.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | const path = require('path'); 3 | const { createFilter } = require('./utils'); 4 | 5 | class qiniuUploadPlugin { 6 | // 七牛SDK mac对象 7 | mac = null; 8 | 9 | constructor(options) { 10 | // 读取传入选项 11 | this.options = options || {}; 12 | // 检查选项中的参数 13 | this.checkQiniuConfig(); 14 | // 初始化七牛mac对象 15 | 16 | this.mac = new qiniu.auth.digest.Mac( 17 | this.options.qiniu.accessKey, 18 | this.options.qiniu.secretKey 19 | ); 20 | } 21 | checkQiniuConfig() { 22 | // 配置未传qiniu,读取环境变量中的配置 23 | if (!this.options.qiniu) { 24 | 25 | this.options.qiniu = { 26 | accessKey: process.env.QINIU_ACCESS_KEY, 27 | secretKey: process.env.QINIU_SECRET_KEY, 28 | bucket: process.env.QINIU_BUCKET, 29 | keyPrefix: process.env.QINIU_KEY_PREFIX || '' 30 | }; 31 | } 32 | const qiniu = this.options.qiniu; 33 | if (!qiniu.accessKey || !qiniu.secretKey || !qiniu.bucket) { 34 | throw new Error('invalid qiniu config'); 35 | } 36 | } 37 | 38 | apply(compiler) { 39 | compiler.hooks.afterEmit.tapPromise('qiniuUploadPlugin', (compilation) => { 40 | return new Promise((resolve, reject) => { 41 | // 总上传数量 42 | const uploadCount = Object.keys(compilation.assets).length; 43 | // 已上传数量 44 | let currentUploadedCount = 0; 45 | // 七牛SDK相关参数 46 | const { include, exclude, bucket, keyPrefix } = this.options.qiniu 47 | const putPolicy = new qiniu.rs.PutPolicy({ scope: bucket }); 48 | const uploadToken = putPolicy.uploadToken(this.mac); 49 | const config = new qiniu.conf.Config(); 50 | config.zone = qiniu.zone.Zone_z1; 51 | const formUploader = new qiniu.form_up.FormUploader() 52 | const putExtra = new qiniu.form_up.PutExtra(); 53 | // 因为是批量上传,需要在最后将错误对象回调 54 | let globalError = null; 55 | console.log(Object.keys(compilation.assets)) 56 | 57 | // excludeDir 58 | // const includeExcludeFilter = createFilter(include, exclude); 59 | // const filter = id => extensionRegExp.test(id) && includeExcludeFilter(id); 60 | // 遍历编译资源文件 61 | 62 | for (const filename of Object.keys(compilation.assets)) { 63 | // 开始上传 64 | formUploader.putFile( 65 | uploadToken, 66 | keyPrefix + filename, 67 | path.resolve(compilation.outputOptions.path, filename), 68 | putExtra, 69 | (err) => { 70 | console.log(`uploade ${filename} result: ${err ? `Error:${err.message}` : 'Success'}`) 71 | currentUploadedCount++; 72 | if (err) { 73 | globalError = err; 74 | } 75 | if (currentUploadedCount === uploadCount) { 76 | globalError ? reject(globalError) : resolve(); 77 | } 78 | }); 79 | } 80 | }) 81 | }); 82 | } 83 | } 84 | 85 | module.exports = qiniuUploadPlugin; -------------------------------------------------------------------------------- /leetcode/note.md: -------------------------------------------------------------------------------- 1 | LeetCode 开了个会员,截了一些高频率题目分享给大家 2 | https://ac.nowcoder.com/discuss/292850 3 | 4 | LeetCode 最长回文字符串 https://zhuanlan.zhihu.com/p/260533819 5 | https://mp.weixin.qq.com/s/cEvygCxKCaeFL9h_fmCTig 6 | 7 | leetcode 8 | https://github.com/lf2021/Front-End-Interview/blob/master/07.%E7%AE%97%E6%B3%95%E5%88%B7%E9%A2%98/leetcode%E6%80%9D%E8%B7%AF.md 9 | 10 | LeetCode 题解 | 206. 反转链表 11 | https://mp.weixin.qq.com/s/dAltoydx7yf73Il7Zdibiw 12 | 13 | 看了这篇 LeetCode 的刷题心得,再也不用抄别人代码了 14 | https://mp.weixin.qq.com/s/ZwAY6LB_Y4E2LPwUzvs2JA 15 | 16 | LeetCode 岛屿数量 17 | https://mp.weixin.qq.com/s/ld0H2kPSDOvmXpOA8_6W0g 18 | 19 | 如何借助 5 道算法题入职 Leetcode 20 | https://mp.weixin.qq.com/s/NRyV7fdHLDJfvjlh_83qYQ 21 | 22 | 「算法与数据结构」链表的 9 个基本操作 23 | https://mp.weixin.qq.com/s/i5iqfDMRuFpS_N89c8Of1g 24 | 25 | 作为前端,我是如何在 Leetcode 算法比赛中进入前 100 的? 26 | https://mp.weixin.qq.com/s/MBaG0xX20MfHrXyVaeDO6g 27 | 28 | LeetCode CookBook 29 | https://mp.weixin.qq.com/s/qu_drrtR-dFtYk1xiiZ7UA 30 | 31 | 写给前端的算法进阶指南,我是如何两个月零基础刷 200 题 32 | https://mp.weixin.qq.com/s/7mJSpnHE319swy0LStpavQ 33 | 34 | 学好算法,有三重境界 35 | https://mp.weixin.qq.com/s/hDSONGkNMXK6JQLZvW0f0Q 36 | 37 | 前端食堂专辑 38 | https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&__biz=MzUxMjkwMjU1MQ==&scene=24&album_id=1379327217062526976#wechat_redirect 39 | 40 | 前端++头条面经 41 | https://mp.weixin.qq.com/s/lTZGyhY8SZa9Z0ZvcanKaA 42 | 43 | 「面试必问」leetcode 高频题精选 44 | https://mp.weixin.qq.com/s/N8ywdOS_XdjDkjfFiOQOFA 45 | 46 | LeetCode T876. 链表的中间结点[1] 47 | https://mp.weixin.qq.com/s/CD6UsVcocZm53RuQPiCgvA 48 | 49 | JavaScript 实现 LeetCode 第 350 题:两个数组的交集 II 50 | 两个数组的交集 II 51 | https://mp.weixin.qq.com/s/tZus_A-lGGYrhvwPwSSrSw 52 | 53 | 实战 LeetCode 系列(一) (题目+解析) 54 | https://mp.weixin.qq.com/s/qbcUO9MFnq4flNKThkdznA 55 | 56 | 前端面试必备-40 道 LeetCode 经典面试算法题 57 | https://mp.weixin.qq.com/s/vSyedV1gzH8nhos32LZoUg 58 | 59 | 实战 LeetCode - 前端面试必备二叉树算法 60 | https://mp.weixin.qq.com/s/lXXHdXdc6kotUclfDgFc6g 61 | 面试问算法,不懂这 6 大数据结构知识一定过不了!(附力扣 LeetCode 真题讲解) 62 | https://faxian.lagou.com/discover/3a16cd4697144008aa655aff266ce82e.html?ver=1561534036355&token=3585f417250fd2d3e8f23433dcc4763b0f3f2995413c898b&checkCode=1f3214e0-c025-41dd-83a9-f4a610017902&verify=d4a265b32c7897f257ed4e66892da52a&lagoufrom=noapp 63 | 进互联网名企要刷多少道算法题? 64 | https://mp.weixin.qq.com/s/Y37OI6pClpt_KeHp61YAHQ 65 | 66 | 【LeetCode】三数之和 67 | https://mp.weixin.qq.com/s/XsPG7fOyGe4Cy9E2Zv2ftA 68 | leetcode(3)——无重复字符的最长子串 69 | https://mp.weixin.qq.com/s/6w2X8nT7RVDNiIurUK8fEA 70 | leetcode(2)——两数相加 71 | https://mp.weixin.qq.com/s/4wYS5700yKFibVrGrXx4tA 72 | leetcode(1) —— 两数之和 73 | https://mp.weixin.qq.com/s/3Uc9luXNlHEdLia9w2fRhQ 74 | 75 | LeetCode 偶尔一题 —— 64. 最小路径和 76 | https://mp.weixin.qq.com/s/QZYemqpQhfwEE83_qIH0oA 77 | LeetCode - 234 - 回文链表(palindrome-linked-list) 78 | https://mp.weixin.qq.com/s/YHty2nhgGCkUCOlSOOEf1w 79 | LeetCode 偶尔一题 —— 268. 缺失数字 80 | https://mp.weixin.qq.com/s/GuHGgeghZO1Ucdrbm9tRGQ 81 | LeetCode - 219 - 存在重复元素 II(contains-duplicate-ii) 82 | https://mp.weixin.qq.com/s/g0g-M1ilbA7T-Bl7E7qrYg 83 | 84 | 【leetcode 系列】104. 二叉树的最大深度 85 | https://mp.weixin.qq.com/s/6-CulthBsbldybYZCXFbFA 86 | 87 | LeetCode 题解 | 458.可怜的小猪 88 | https://mp.weixin.qq.com/s/BlOZLKIHvWaFT9hd8k-lMg 89 | 90 | 【leetcode 系列】88.合并两个有序数组 91 | https://mp.weixin.qq.com/s/nu_xjkMI3w1nfIsaB1HfiA 92 | 93 | LeetCode - 205 - 同构字符串(isomorphic-strings) 94 | https://mp.weixin.qq.com/s/_kK-EzgnuNVn2aJVlkATUA 95 | 【leetcode 系列】002-整数反转 96 | https://mp.weixin.qq.com/s/5bBKxrca226gHQNDPzSBfQ 97 | 【leetcode 系列】001-两数之和 98 | https://mp.weixin.qq.com/s/1y_4KvlRBxBn51zqKq9ojQ 99 | 【leetcode 系列】003-回文数 100 | https://mp.weixin.qq.com/s/7RDZ1Wxp5dx3tkI1iB7DcQ 101 | LeetCode 题解 | 110.平衡二叉树 102 | https://mp.weixin.qq.com/s/lgXwHBi6htjr-QYhJigt4w 103 | 104 | LeetCode 题解 | 145.二叉树的后序遍历 105 | https://mp.weixin.qq.com/s/6urc2WJimaYfutYQTzr7_A 106 | 107 | https://leetcode-cn.com/problems/spiral-matrix/ 螺旋矩阵 108 | 109 | jsliang 系列 110 | https://juejin.cn/post/6844903903062917127 111 | https://juejin.cn/post/6844903889771167752 112 | 113 | 二叉树 easy 114 | https://juejin.cn/post/6844903887858565133 115 | 「leetcode」53.最大子序和 116 | https://juejin.cn/post/6844903935371804679 117 | -------------------------------------------------------------------------------- /vue/vue响应式原理.md: -------------------------------------------------------------------------------- 1 | ### 双向绑定原理 2 | 3 | vue 数据双向绑定是通过数据劫持和发布者-订阅者模式,回顾一下实现双向绑定的几个步骤: 4 | 5 | 1. 首先要对数据进行劫持监听,所以我们需要设置一个监听器 Observer,用来监听所有属性。 6 | 2. 属性发生变化的话,就需要告诉订阅者 Watcher 看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器 Dep 来专门收集这些订阅者,然后在监听器 Observer 和订阅者 Watcher 之间进行统一管理的。 7 | 3. 接着,我们还需要有一个指令解析器 Compile,对每个节点元素进行扫描和解析,将相关指令(如 v-model,v-on)对应初始化成一个订阅者 Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者 Watcher 接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。 8 | 9 | 监听器 Observer:用来劫持并通过 Object.defineProperty 监听所有属性(转变成 setter/getter 形式),如果属性发生变化,就通知订阅者。 10 | 11 | 订阅器 Dep:用来收集订阅者,对监听器 Observer 和 订阅者 Watcher 进行统一管理。 12 | 13 | 订阅者 Watcher:监听器 Observer 和解析器 Compile 之间通信的桥梁;每一个 Watcher 都绑定一个更新函数,watcher 可以收到属性的变化通知并执行相应的函数,从而更新视图。 14 | 15 | 解析器 Compile:可以扫描和解析每个节点的相关指令(v-model,v-on 等指令),如果节点存在 v-model,v-on 等指令,则解析器 Compile 初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)。 16 | 17 | 主要做的事情是: 18 | 19 | 在自身实例化时往属性订阅器(dep)里面添加自己。 20 | 自身有一个 update()方法。 21 | 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发解析器(Compile)中绑定的回调。 22 | 23 | ### Vue 的双向数据绑定原理是什么? 24 | 25 | vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤: 26 | 27 | 1、需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化 28 | 2、compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图 29 | 3、Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是: ① 在自身实例化时往属性订阅器(dep)里面添加自己 ② 自身必须有一个 update()方法 ③ 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调,则功成身退。 30 | 4、MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。 31 | 32 | ### 1.实现一个 Observer 33 | 34 | ```js 35 | function Observer(data) { 36 | this.data = data 37 | this.walk(data) 38 | } 39 | Observer.prototype = { 40 | walk(data) { 41 | //遍历,对这个对象的所有属性都进行监听 42 | Object.keys(data).forEach(() => { 43 | this.defineReactive(data, key, data[key]) 44 | }) 45 | }, 46 | defineReactive(data, key, val) { 47 | let dep = new Dep() 48 | let childObj = observe(val) 49 | Object.defineProperty(data, key, { 50 | enumerable: true, 51 | configurable: true, 52 | get: function getter() { 53 | if (Dep.target) { 54 | // 在这里添加一个订阅者 55 | console.log(Dep.target) 56 | dep.addSub(Dep.target) 57 | } 58 | return val 59 | }, 60 | set: function setter() { 61 | if (newVal === val) { 62 | return 63 | } 64 | val = newVal 65 | childObj = observe(newVal) 66 | dep.notify() 67 | }, 68 | }) 69 | }, 70 | } 71 | function observe(value, vm) { 72 | if (!value || typeof value !== 'object') { 73 | return 74 | } 75 | return new Observer(value) 76 | } 77 | // 消息订阅器Dep,订阅器Dep主要负责收集订阅者,然后在属性变化的时候执行对应订阅者的更新函数 78 | function Dep() { 79 | this.subs = [] 80 | } 81 | Dep.prototype = { 82 | addSub(sub) { 83 | this.subs.push(sub) 84 | }, 85 | notify() { 86 | this.subs.forEach((sub) => { 87 | sub.update() 88 | }) 89 | }, 90 | } 91 | Dep.target = null 92 | ``` 93 | 94 | ### 2.实现一个 Watcher 95 | 96 | ```js 97 | function Watcher(vm, exp, cb) { 98 | this.cb = cb 99 | this.vm = vm 100 | this.exp = exp 101 | this.value = this.get() // 将自己添加到订阅器的操作 102 | } 103 | Watcher.prototype = { 104 | update() { 105 | this.run() 106 | }, 107 | run() { 108 | let value = this.vm.data[this.exp] 109 | let oldVal = this.value 110 | if (value !== oldVal) { 111 | this.value = value 112 | this.cb.call(this.vm, value, oldVal) 113 | } 114 | }, 115 | get() { 116 | Dep.target = this // 缓存自己 117 | let value = this.vm.data[this.exp] // 强制执行监听器里的get函数 118 | Dep.target = null // 释放自己 119 | return value 120 | }, 121 | } 122 | ``` 123 | 124 | ### 3.实现一个 Compile 125 | 126 | ### Vue3.0 的 Proxy 相比于 defineProperty 的优势 127 | 128 | Object.defineProperty() 的问题主要有三个: 129 | 130 | 不能监听数组的变化 131 | 必须遍历对象的每个属性 132 | 必须深层遍历嵌套的对象 133 | 134 | Proxy 在 ES2015 规范中被正式加入,它有以下几个特点: 135 | 针对对象: 针对整个对象,而不是对象的某个属性,所以也就不需要对 keys 进行遍历。这解决了上述 Object.defineProperty() 第二个问题。 136 | 支持数组: Proxy 不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且标准的就是最好的。 137 | 138 | https://segmentfault.com/a/1190000006599500#comment-area 139 | -------------------------------------------------------------------------------- /webpack/plugin/plugins/compiler.js: -------------------------------------------------------------------------------- 1 | const { SyncHook, SyncBailHook, AsyncSeriesHook } = require("tapable"); 2 | class Compiler { 3 | constructor() { 4 | // 1. 定义生命周期钩子 5 | this.hooks = Object.freeze({ 6 | // ...更多hook就不列举了,有兴趣看源码 7 | done: new AsyncSeriesHook(["stats"]), 8 | beforeRun: new AsyncSeriesHook(["compiler"]), 9 | run: new AsyncSeriesHook(["compiler"]), 10 | emit: new AsyncSeriesHook(["compilation"]), 11 | afterEmit: new AsyncSeriesHook(["compilation"]), 12 | compilation: new SyncHook(["compilation", "params"]), 13 | beforeCompile: new AsyncSeriesHook(["params"]), 14 | compile: new SyncHook(["params"]), 15 | afterCompile: new AsyncSeriesHook(["compilation"]), 16 | watchRun: new AsyncSeriesHook(["compiler"]), 17 | failed: new SyncHook(["error"]), 18 | watchClose: new SyncHook([]), 19 | afterPlugins: new SyncHook(["compiler"]), 20 | entryOption: new SyncBailHook(["context", "entry"]) 21 | }); 22 | } 23 | 24 | transfer() { 25 | // 3. 在合适的时候 调用 26 | this.hooks.entryOption.call() //在 webpack 选项中的 entry 配置项 处理过之后,执行插件。 27 | this.hooks.afterPlugins.call() //设置完初始插件之后,执行插件。 28 | this.hooks.beforeRun.call() //compiler.run() 执行之前,添加一个钩子。 29 | this.hooks.run.call() //开始读取 records 之前,钩入(hook into) compiler。 30 | } 31 | newCompilation() { 32 | // 创建Compilation对象回调compilation相关钩子 33 | const compilation = new Compilation(this); 34 | //...一系列操作 35 | this.hooks.compilation.call(compilation, params); //compilation对象创建完成 36 | return compilation 37 | } 38 | watch() { 39 | //如果运行在watch模式则执行watch方法,否则执行run方法 40 | if (this.running) { 41 | return handler(new ConcurrentCompilationError()); 42 | } 43 | this.running = true; 44 | this.watchMode = true; 45 | return new Watching(this, watchOptions, handler); 46 | } 47 | run(callback) { 48 | if (this.running) { 49 | return callback(new ConcurrentCompilationError()); 50 | } 51 | this.running = true; 52 | process.nextTick(() => { 53 | this.emitAssets(compilation, err => { 54 | if (err) { 55 | // 在编译和输出的流程中遇到异常时,会触发 failed 事件 56 | this.hooks.failed.call(err) 57 | }; 58 | if (compilation.hooks.needAdditionalPass.call()) { 59 | // ... 60 | // done:完成编译 61 | this.hooks.done.callAsync(stats, err => { 62 | // 创建compilation对象之前 63 | this.compile(onCompiled); 64 | }); 65 | } 66 | this.emitRecords(err => { 67 | this.hooks.done.callAsync(stats, err => { 68 | 69 | }); 70 | }); 71 | }); 72 | }); 73 | 74 | this.hooks.beforeRun.callAsync(this, err => { 75 | // 假设我们想在 compiler.run() 之前处理逻辑,那么就要调用 beforeRun 钩子来处理: 76 | // compiler.hooks.beforeRun.tap( 77 | // 'testPlugin', 78 | // (comp) => { 79 | // // ... 80 | // } 81 | // ); 82 | this.hooks.run.callAsync(this, err => { 83 | this.readRecords(err => { 84 | this.compile(onCompiled); 85 | }); 86 | }); 87 | }); 88 | 89 | } 90 | compile(callback) { 91 | const params = this.newCompilationParams(); 92 | this.hooks.beforeCompile.callAsync(params, err => { 93 | this.hooks.compile.call(params); 94 | const compilation = this.newCompilation(params); 95 | this.hooks.make.callAsync(compilation, err => { 96 | process.nextTick(() => { 97 | compilation.finish(err => { 98 | // 封装构建结果(seal),不可再更改 99 | compilation.seal(err => { 100 | this.hooks.afterCompile.callAsync(compilation, err => { 101 | // 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程, 102 | // 不然运行流程将会一直卡在这不往下执行 103 | return callback(null, compilation); 104 | }); 105 | }); 106 | }); 107 | }); 108 | }); 109 | }); 110 | } 111 | emitAssets(compilation, callback) { 112 | const emitFiles = (err) => { 113 | //...省略一系列代码 114 | // afterEmit:文件已经写入磁盘完成 115 | this.hooks.afterEmit.callAsync(compilation, err => { 116 | if (err) return callback(err); 117 | return callback(); 118 | }); 119 | } 120 | 121 | // emit:资源输出之前,文件内容准备完成,准备生成文件,这是最后一次修改最终文件的机会 122 | this.hooks.emit.callAsync(compilation, err => { 123 | if (err) return callback(err); 124 | outputPath = compilation.getPath(this.outputPath, {}); 125 | mkdirp(this.outputFileSystem, outputPath, emitFiles); 126 | }); 127 | } 128 | } -------------------------------------------------------------------------------- /前端安全/crsf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | CSRF-demo 9 | 30 | 31 | 32 | 33 |
34 |
35 |

登陆

36 | 37 |
38 | 39 |
40 |
41 | 42 |
43 | 44 | 45 |
46 |

转账信息

47 |

当前账户余额为 0

48 | 49 | 50 |
51 |
52 | 53 | 听说点击这个链接的人都赚大钱了,你还不来看一下么 54 | 55 |
56 |
57 | 58 | 137 | 138 | -------------------------------------------------------------------------------- /webpack/webpack/bin/webpack.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // @ts-ignore 4 | process.exitCode = 0; 5 | console.log("bin:webpack") 6 | /** 7 | * @param {string} command process to run 8 | * @param {string[]} args commandline arguments 9 | * @returns {Promise} promise 10 | */ 11 | const runCommand = (command, args) => { 12 | const cp = require("child_process"); 13 | return new Promise((resolve, reject) => { 14 | const executedCommand = cp.spawn(command, args, { 15 | stdio: "inherit", 16 | shell: true 17 | }); 18 | 19 | executedCommand.on("error", error => { 20 | reject(error); 21 | }); 22 | 23 | executedCommand.on("exit", code => { 24 | if (code === 0) { 25 | resolve(); 26 | } else { 27 | reject(); 28 | } 29 | }); 30 | }); 31 | }; 32 | 33 | /** 34 | * @param {string} packageName name of the package 35 | * @returns {boolean} is the package installed? 36 | */ 37 | const isInstalled = packageName => { 38 | try { 39 | require.resolve(packageName); 40 | 41 | return true; 42 | } catch (err) { 43 | return false; 44 | } 45 | }; 46 | 47 | /** 48 | * @typedef {Object} CliOption 49 | * @property {string} name display name 50 | * @property {string} package npm package name 51 | * @property {string} binName name of the executable file 52 | * @property {string} alias shortcut for choice 53 | * @property {boolean} installed currently installed? 54 | * @property {boolean} recommended is recommended 55 | * @property {string} url homepage 56 | * @property {string} description description 57 | */ 58 | 59 | /** @type {CliOption[]} */ 60 | const CLIs = [ 61 | { 62 | name: "webpack-cli", 63 | package: "webpack-cli", 64 | binName: "webpack-cli", 65 | alias: "cli", 66 | installed: isInstalled("webpack-cli"), 67 | recommended: true, 68 | url: "https://github.com/webpack/webpack-cli", 69 | description: "The original webpack full-featured CLI." 70 | }, 71 | { 72 | name: "webpack-command", 73 | package: "webpack-command", 74 | binName: "webpack-command", 75 | alias: "command", 76 | installed: isInstalled("webpack-command"), 77 | recommended: false, 78 | url: "https://github.com/webpack-contrib/webpack-command", 79 | description: "A lightweight, opinionated webpack CLI." 80 | } 81 | ]; 82 | 83 | const installedClis = CLIs.filter(cli => cli.installed); 84 | 85 | if (installedClis.length === 0) { 86 | const path = require("path"); 87 | const fs = require("fs"); 88 | const readLine = require("readline"); 89 | 90 | let notify = 91 | "One CLI for webpack must be installed. These are recommended choices, delivered as separate packages:"; 92 | 93 | for (const item of CLIs) { 94 | if (item.recommended) { 95 | notify += `\n - ${item.name} (${item.url})\n ${item.description}`; 96 | } 97 | } 98 | 99 | console.error(notify); 100 | 101 | const isYarn = fs.existsSync(path.resolve(process.cwd(), "yarn.lock")); 102 | 103 | const packageManager = isYarn ? "yarn" : "npm"; 104 | const installOptions = [isYarn ? "add" : "install", "-D"]; 105 | 106 | console.error( 107 | `We will use "${packageManager}" to install the CLI via "${packageManager} ${installOptions.join( 108 | " " 109 | )}".` 110 | ); 111 | 112 | const question = `Do you want to install 'webpack-cli' (yes/no): `; 113 | 114 | const questionInterface = readLine.createInterface({ 115 | input: process.stdin, 116 | output: process.stderr 117 | }); 118 | questionInterface.question(question, answer => { 119 | questionInterface.close(); 120 | 121 | const normalizedAnswer = answer.toLowerCase().startsWith("y"); 122 | 123 | if (!normalizedAnswer) { 124 | console.error( 125 | "You need to install 'webpack-cli' to use webpack via CLI.\n" + 126 | "You can also install the CLI manually." 127 | ); 128 | process.exitCode = 1; 129 | 130 | return; 131 | } 132 | 133 | const packageName = "webpack-cli"; 134 | 135 | console.log( 136 | `Installing '${packageName}' (running '${packageManager} ${installOptions.join( 137 | " " 138 | )} ${packageName}')...` 139 | ); 140 | 141 | runCommand(packageManager, installOptions.concat(packageName)) 142 | .then(() => { 143 | require(packageName); //eslint-disable-line 144 | }) 145 | .catch(error => { 146 | console.error(error); 147 | process.exitCode = 1; 148 | }); 149 | }); 150 | } else if (installedClis.length === 1) { 151 | const path = require("path"); 152 | const pkgPath = require.resolve(`${installedClis[0].package}/package.json`); 153 | // eslint-disable-next-line node/no-missing-require 154 | const pkg = require(pkgPath); 155 | // eslint-disable-next-line node/no-missing-require 156 | require(path.resolve( 157 | path.dirname(pkgPath), 158 | pkg.bin[installedClis[0].binName] 159 | )); 160 | } else { 161 | console.warn( 162 | `You have installed ${installedClis 163 | .map(item => item.name) 164 | .join( 165 | " and " 166 | )} together. To work with the "webpack" command you need only one CLI package, please remove one of them or use them directly via their binary.` 167 | ); 168 | 169 | // @ts-ignore 170 | process.exitCode = 1; 171 | } 172 | -------------------------------------------------------------------------------- /前端知识点/前端性能优化.md: -------------------------------------------------------------------------------- 1 | ## 网站的性能的指标 2 | 3 | 白屏时间、首屏时间、整页时间、DNS 时间、CPU 占用率 4 | 5 | ##### 关于前端性能指标 6 | 7 | Performance API 监控 8 | 可以再浏览器控制台 直接输入 performance ,查看相关 API 9 | 10 | 常用的 11 | domComplete 页面加载 12 | const [{ domComplete }] = performance.getEntriesByType('navigation') 13 | 14 | ##### 确定统计起始点 (navigationStart vs fetchStart ) 15 | 16 | 页面性能统计的起始点时间,应该是用户输入网址回车后开始等待的时间。一个是通过 navigationStart 获取,相当于在 URL 输入栏回车或者页面按 F5 刷新的时间点;另外一个是通过 fetchStart,相当于浏览器准备好使用 HTTP 请求获取文档的时间。 17 | 18 | 从开发者实际分析使用的场景,浏览器重定向、卸载页面的耗时对页面加载分析并无太大作用;通常建议使用 fetchStart 作为统计起始点。 19 | 20 | ##### 首字节的时间 TTFB (Time To First Byte) 21 | 22 | 主文档返回第一个字节的时间,是页面加载性能比较重要的指标。对用户来说一般无感知,对于开发者来说,则代表访问网络后端的整体响应耗时。 23 | 24 | TTFB = responseStart - requestStart 25 | 26 | 首字节时间,是反映服务端响应速度的重要指标 27 | 浏览器开始收到服务器响应数据的时间 = 后台处理时间 + 重定向时间 28 | 29 | ##### 白屏时间 (first Paint Time) 30 | 31 | 白屏时间(first Paint Time)——用户从打开页面开始到页面开始有东西呈现为止 32 | 用户看到页面展示出现一个元素的时间。很多人认为白屏时间是页面返回的首字节时间,但这样其实并不精确,因为头部资源还没加载完毕,页面也是白屏。 33 | 34 | 相对来说具备「白屏时间」统计意义的指标,可以取 domLoading - fetchStart,此时页面开始解析 DOM 树,页面渲染的第一个元素也会很快出现。 35 | 36 | 从 W3C Navigation Timing Level 2 的方案设计,可以直接采用 [公式] ,此时页面资源加载完成,即将进入渲染环节。 37 | 38 | (chrome.loadTimes().firstPaintTime - chrome.loadTimes().startLoadTime)\*1000 39 | 40 | window.performance.timing.responseEnd – window.performance.timing.fetchStart 41 | 42 | ##### 首屏时间 43 | 44 | 用户浏览器首屏内所有内容都呈现出来所花费的时间 45 | 46 | 具备一定意义上的指标可以使用, [公式] ,甚至使用 [公式] ,此时页面 DOM 树已经解析完成并且显示内容。 47 | 48 | 以下给出统计页面性能指标的方法。 49 | 50 | ```js 51 | let times = {} 52 | let t = window.performance.timing 53 | 54 | // 优先使用 navigation v2 https://www.w3.org/TR/navigation-timing-2/ 55 | if (typeof win.PerformanceNavigationTiming === 'function') { 56 | try { 57 | var nt2Timing = performance.getEntriesByType('navigation')[0] 58 | if (nt2Timing) { 59 | t = nt2Timing 60 | } 61 | } catch (err) {} 62 | } 63 | 64 | //重定向时间 65 | times.redirectTime = t.redirectEnd - t.redirectStart 66 | 67 | //dns查询耗时 68 | times.dnsTime = t.domainLookupEnd - t.domainLookupStart 69 | 70 | //TTFB 读取页面第一个字节的时间 71 | times.ttfbTime = t.responseStart - t.navigationStart 72 | 73 | //DNS 缓存时间 74 | times.appcacheTime = t.domainLookupStart - t.fetchStart 75 | 76 | //卸载页面的时间 77 | times.unloadTime = t.unloadEventEnd - t.unloadEventStart 78 | 79 | //tcp连接耗时 80 | times.tcpTime = t.connectEnd - t.connectStart 81 | 82 | //request请求耗时 83 | times.reqTime = t.responseEnd - t.responseStart 84 | 85 | //解析dom树耗时 86 | times.analysisTime = t.domComplete - t.domInteractive 87 | 88 | //白屏时间 89 | times.blankTime = (t.domInteractive || t.domLoading) - t.fetchStart 90 | 91 | //domReadyTime 92 | times.domReadyTime = t.domContentLoadedEventEnd - t.fetchStart 93 | 94 | time.loadTime = t.loadEventEnd - t.navigationStart 95 | ``` 96 | 97 | ##### 用户可操作时间 98 | 99 | ——用户可以进行正常的点击、输入等操作,默认可以统计 domready 时间,因为通常会在这时候绑定事件操作 100 | 101 | #### 总下载时间 102 | 103 | 页面所有资源都加载完成并呈现出来所花的时间,即页面 onload 的时间 104 | 105 | ## 性能优化的三大方面 106 | 107 | 网络传输性能、页面渲染性能、JS 阻塞性能 108 | 109 | 重定向优化,重定向有 301(永久重定向)、302(临时重定向)、304(Not Modified)。前面两种重定向尽量避免。304 是用来做缓存的。重定向会耗时。 110 | 111 | DNS 的 Prefetch 112 | 113 | 网络性能优化措施 114 | 115 | https://zhuanlan.zhihu.com/p/82981365 116 | 117 | ## webpack 层面 118 | 119 | 1. 减小打包后的文件大小 120 | 2. 首页按需引入文件,减少白屏时间 121 | 3. webpack4 splitChunks 合理拆包 122 | 4. webpack externals 123 | 124 | ```js 125 | module.exports = { 126 | ··· 127 | externals: { 128 | 'vue': 'Vue', 129 | 'vuex': 'Vuex', 130 | 'vue-router': 'VueRouter', 131 | 'element-ui': 'ELEMENT', 132 | 'Axios':'axios' 133 | } 134 | }, 135 | ``` 136 | 137 | webpack-bundle-analyzer 可以构建出打包后的可视化界面,我们可以在可视化界面中找到体积过大、重复或者不需要的包去除或者替换它。 138 | speed-measure-webpack-plugin 可以直观的看出 webpack 构建期间每个插件和使用 loader 花费的时间 139 | 140 | 141 | ### 1. 重定向优化 142 | 重定向的类型分三种,301(永久重定向),302(临时重定向),304(Not Modified)。 143 | 304是用来优化缓存,非常有用,而前两种应该尽可能的避免,凡是遇到需要重定向跳转代码的代码, 144 | 可以把重定向之后的地址直接写到前端的html或JS中,可以减少客户端与服务端的通信过程,节省重定向耗时。 145 | ### 2. DNS优化 146 | 147 | DNS优化:一般来说,在前端优化中与 DNS 有关的有两点:一个是减少DNS的请求次数, 148 | 另一个就是进行DNS预获取(Prefetching ) 。 149 | 典型的一次DNS解析需要耗费 20-120 毫秒(移动端会更慢), 150 | 减少DNS解析的次数是个很好的优化方式,尽量把各种资源放在一个cdn域名上。 151 | DNS Prefetching 是让具有此属性的域名不需要用户点击链接就在后台解析, 152 | 而域名解析和内容载入是串行的网络操作,所以这个方式能减少用户的等待时间,提升用户体验 。 153 | 新版的浏览器会对页面中和当前域名(正在浏览网页的域名)不在同一个域的域名进行预获取, 154 | 并且缓存结果,这就是隐式的 DNS Prefetch。如果想对页面中没有出现的域进行预获取, 155 | 那么就要使用显示的 DNS Prefetch 了。下图是DNS Prefetch的方法: 156 | ```html 157 | 158 | 159 | 腾讯网 160 | 161 | 162 | 163 | 164 | 165 | ``` 166 | ### TCP请求优化: 167 | TCP请求优化:TCP的优化大都在服务器端,前端能做的就是尽量减少TCP的请求数, 168 | 也就是减少HTTP的请求数量。http 1.0 默认使用短连接,也是TCP的短连接, 169 | 也就是客户端和服务端每进行一次http操作,就建立一次连接, 170 | 任务结束就中断连接。这个过程中有3次TCP请求握手和4次TCP请求释放。 171 | 减少TCP请求的方式有两种,一种是资源合并,对于页面内的图片、css和js进行合并,减少请求量。 172 | 另一种使用长链接,使用http1.1,在HTTP的响应头会加上 Connection:keep-alive, 173 | 当一个网页打开完成之后,连接不会马上关闭,再次访问这个服务时,会继续使用这个长连接。 174 | 这样就大大减少了TCP的握手次数和释放次数。或者使用Websocket进行通信,全程只需要建立一次TCP链接。 175 | 176 | ### HTTP请求优化: 177 | 使用内容分发网络(CDN)和减少请求。使用CDN可以减少网络的请求时延, 178 | CDN的域名不要和主站的域名一样,这样会防止访问CDN时还携带主站cookie的问题, 179 | 对于网络请求,可以使用fetch发送无cookie的请求,减少http包的大小。 180 | 也可以使用本地缓存策略,尽量减少对服务器数据的重复获取。 181 | 182 | ### 渲染优化: 183 | 在浏览器端的渲染过程,如大型框架,vue和react, 184 | 它的模板其实都是在浏览器端进行渲染的,不是直出的html, 185 | 而是要走框架中相关的框架代码才能去渲染出页面, 186 | 这个渲染过程对于首屏就有较大的损耗,白屏的时间会有所增加。 187 | 在必要的情况下可以在服务端进行整个html的渲染,从而将整个html直出到我们的浏览器端, 188 | 而非在浏览器端进行渲染。 189 | 190 | 191 | 还有一个问题就是,在默认情况下,JavaScript 执行会“阻止解析器”, 192 | 当浏览器遇到一个 script 外链标记时,DOM 构建将暂停, 193 | 会将控制权移交给 JavaScript 运行时,等脚本下载执行完毕, 194 | 然后再继续构建 DOM。而且内联脚本始终会阻止解析器,除非编写额外代码来推迟它们的执行。 195 | 我们可以把 script 外链加入到页面底部,也可以使用 defer 或 async 延迟执行。 196 | defer 和 async 的区别就是 defer 是有序的,代码的执行按在html中的先后顺序, 197 | 而 async 是无序的,只要下载完毕就会立即执行。或者使用异步的编程方法,比如settimeout, 198 | 也可以使用多线webworker,它们不会阻碍 DOM 的渲染。 199 | 200 | -------------------------------------------------------------------------------- /vue/公众号文章整理.md: -------------------------------------------------------------------------------- 1 | https://github.com/F-E-P/Blogs 2 | https://github.com/xiaomuzhu/blog/issues/4 3 | https://juejin.im/post/5a9b8417518825558251ce15 4 | http://fengxu.ink/2018/03/09/vue-js%E5%8E%9F%E7%90%86%E5%88%9D%E6%8E%A2/ 5 | https://mp.weixin.qq.com/s/ivP5oyvfKDkxam2PzYtnUg 6 | https://juejin.im/post/5c6220e2f265da2d9617ecd6 7 | https://juejin.im/post/5c371fd76fb9a049e23236ad?from=groupmessage&isappinstalled=0 8 | https://github.com/fengshi123/blog 9 | Vue 源码解析:模版字符串转 AST 语法树: https://segmentfault.com/a/1190000017374492 10 | https://mp.weixin.qq.com/s/Ox2tD2iLuvC2P8HO-J7dHw 11 | https://github.com/DDFE/DDFE-blog/issues/8 12 | https://github.com/muwoo/blogs/blob/master/src/Vue/1.md 13 | https://mp.weixin.qq.com/s/IHRHeqpgdDQNPvJRhKp69w 14 | https://mp.weixin.qq.com/s/xADfHepDIkPQC1UecK_PHw 15 | https://mp.weixin.qq.com/s/s6aWLaIdE5Ue75FQg2GXFw 16 | http://hcysun.me/vue-design/zh/ 17 | http://hcysun.me/2017/03/03/Vue%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/http://hcysun.me/2017/03/03/Vue%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/ 18 | https://mp.weixin.qq.com/s/G6X5iTp5ElwtjzbPuvX7UQ 19 | https://mp.weixin.qq.com/s/i6m-rgb5a2NKc4EeVMtTng 20 | https://mp.weixin.qq.com/s/Pc1lMfJaPpSO3tN0H6LASA 21 | https://mp.weixin.qq.com/s/0PVFuD0MGDyxT89EIJbJyQ 22 | https://mp.weixin.qq.com/s/Jf3oaEca7Hc3Q14WaFdnmA 23 | https://mp.weixin.qq.com/s/pAAaJBIvnNZ_OKImL33Fag 24 | https://mp.weixin.qq.com/s/zmVAdaAO3nH_6ionqi1USg 25 | https://mp.weixin.qq.com/s/HvIz29XSSkaJHjBSDzBu1w 26 | https://mp.weixin.qq.com/s/N0ehY-Mzes6xWObBda2W4A 27 | https://mp.weixin.qq.com/s/9x__pkOdDITCUaVw0LkEQg 28 | https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzU2MzEwNTg4OA==&scene=124#wechat_redirect 29 | 15 vue 逐行源码分析 帖子 https://www.cnblogs.com/hao123456/p/10616356.html 30 | https://mp.weixin.qq.com/s/jOwovdmZiXF1OdE2qEJKDQ 31 | https://juejin.im/post/5df5d78151882512701d6a6c 32 | https://www.cnblogs.com/fron-tend/p/9713270.html?from=timeline#4078189 33 | https://github.com/F-E-P/Blogs 34 | https://github.com/berwin/Blog/issues/18 35 | https://mp.weixin.qq.com/s/kG1BZqAAKxGrLZ_IWGLpQg 36 | http://hcysun.me/2017/03http://hcysun.me/2017/03/03/Vue%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0//03/Vue%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/ 37 | https://mp.weixin.qq.com/s/BZjilt0Vh441QFmcbAs26w 38 | https://mp.weixin.qq.com/s/ffsS1PZ7YaWsqxsrPEAVbQ 39 | https://github.com/HcySunYang/vue-design 40 | https://juejin.im/post/5e7766a5e51d4526e262a710?from=timeline 41 | https://mp.weixin.qq.com/s/9cs6TO-aBcF7xiiPxSfvtg 42 | https://mp.weixin.qq.com/s/VWS62ExyE7Z5V7JZ99JABA 43 | https://mp.weixin.qq.com/s/8ni_i85wkZ5-c5zC3TkzLQ 44 | https://mp.weixin.qq.com/s/lppTdX0viIT0DyaAmDa9qw 45 | https://mp.weixin.qq.com/s/Dnl5Mr8Sbg_-K8etglTIRg 46 | https://mp.weixin.qq.com/s/tvjjkfdEf52KDFs0rAJMTQ 47 | https://mp.weixin.qq.com/s/RwVj_pvw_AF9S8t5H-4PCw 48 | https://mp.weixin.qq.com/s/kFp7rZiWzxbkaZ_4NgCL9w 49 | https://mp.weixin.qq.com/s/H3NU1zFRfRwHRS7f7oqaHg 50 | https://mp.weixin.qq.com/s/4g8XCx0olYaqY78q3ravIA 51 | https://mp.weixin.qq.com/s/Cu8IpLOg0p9B9TtWylR3rQ 52 | https://mp.weixin.qq.com/s/QI5oap5PJQKxoVKiWD6b8Q 53 | https://mp.weixin.qq.com/s/hTygoAan4yH3V4XV9iE1Pw 54 | https://mp.weixin.qq.com/s/uuGt0cZl3heQCyCGFzQBSg 55 | https://mp.weixin.qq.com/s/W7D067n3v-aulD9z35XtEg 56 | https://mp.weixin.qq.com/s/v3ZKAkCZTi-5QlxPDL_EcA 57 | https://mp.weixin.qq.com/s/An9ty9r3xAzY9hNH3SmOmQ 58 | https://mp.weixin.qq.com/s/zayuVpgaGjVb3otGQdPptA 59 | https://mp.weixin.qq.com/s/n09Sdw5jB4Dl4HSsZhjbWw 60 | https://mp.weixin.qq.com/s/9e13_N6lzHB_-BtyyqJkPQ 61 | https://mp.weixin.qq.com/s/Yxa9PID6QpgjMPf5gfTsVw 62 | https://mp.weixin.qq.com/s/IGMQlfkkcTotgB3xGzGrzA 63 | https://mp.weixin.qq.com/s/WM6qFDdsWa09D7q895wKvA 64 | https://mp.weixin.qq.com/s/UfQHF9h2Er5DZi7WE6Qt_w 65 | https://mp.weixin.qq.com/s/0TU2jiAI6Zy7RfZZv-p1eg 66 | https://mp.weixin.qq.com/s/aPEmHEXmyxLYqulvZdXzwA 67 | https://mp.weixin.qq.com/s/CnOHFkoDm7-N51ZkEZlZ9w 68 | https://zhuanlan.zhihu.com/p/79474076?utm_source=wechat_session&utm_medium=social&utm_oi=1124620575481712640 69 | https://mp.weixin.qq.com/s/An9ty9r3xAzY9hNH3SmOmQ 70 | https://mp.weixin.qq.com/s/WRgWquyg7CaQMYMUfJdDPA 71 | https://mp.weixin.qq.com/s/qvbm8lSwSPFz0MtlLD4CtQ 72 | https://mp.weixin.qq.com/s/gHfQgfq4IYZSur-0lYu16A 73 | https://mp.weixin.qq.com/s/PleN2iiKCheow_j7tovSjw 74 | https://zhuanlan.zhihu.com/p/53184632? 75 | utm_source=wechat_timeline&utm_medium=social&utm_oi=549484142702829568&from=timeline 76 | https://juejin.im/post/5e43f331e51d4526e651b33b 77 | https://mp.weixin.qq.com/s/RPTQg6Dz99ypoDr20cmlBg 78 | https://juejin.im/post/5dd89a416fb9a07aa6226e6d?from=groupmessage&isappinstalled=0 79 | https://hellogithub2014.github.io/2018/10/14/vue-sourcecode-1-entry/ 80 | https://zhuanlan.zhihu.com/p/81751167 81 | https://github.com/impeiran/Blog/issues/8 82 | https://www.cnblogs.com/tugenhua0707/ 83 | https://juejin.im/post/5e0dd467e51d45410f1232f5 84 | https://mp.weixin.qq.com/s/SWQxVihAa1HMsi5ylLc7OQ 85 | https://mp.weixin.qq.com/s/Oox8MrOuatdjo4-w1ukrOQ 86 | https://mp.weixin.qq.com/mp/homepage?__biz=MzUxNjQ1NjMwNw==&hid=1&sn=77b9eca3d06307f14d8806231c395ed2&scene=1&devicetype=android-28&version=27000665&lang=zh_CN&nettype=WIFI&ascene=1&wx_header=1 87 | 298 行代码带你理解 Vue 响应式原理和 next-Tick 原理,最后手写一个自己的小 demo 88 | https://juejin.im/post/5e929bdc51882573a94a2632 89 | http://akira.wang/vue%E7%AE%97%E6%98%AF%E5%BE%88%E9%80%9A%E4%BF%97%E7%9A%84%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%EF%BC%88%E9%9D%A2%E8%AF%95%E8%A3%85%E9%80%BC%E5%A4%A7%E6%B3%95%EF%BC%89/#more 90 | vue 的 diff 算法是怎么样的,大致流程的描述,虚拟 dom 是什么,diff 算法的时间复杂度,子节点是如何比较的等。 91 | vue 源码解析:https://jiongks.name/blog/vue-code-review/ 92 | 监听数组变化:https://mp.weixin.qq.com/s/VzTttU2IqZnwOvVB-SRkxA 93 | https://juejin.im/post/5a9b8417518825558251ce15 94 | v-model 源码:https://mp.weixin.qq.com/s/C0aqI0cBix1rCiB_LYCk_g 95 | vue2.0 源码学习之数据监听之 Observer、Watcher、Dep 的关系 96 | https://mp.weixin.qq.com/s/7VxtxSN1BSSlYzFKaqeLeA 97 | 【源码拾遗】从 vue-router 看前端路由的两种实现 98 | https://blog.csdn.net/HiSen_CSDN/article/details/105570779 99 | https://mp.weixin.qq.com/s/dgGMe_nW1S2PfF-2m8In0A 100 | https://mp.weixin.qq.com/s/U2pv8vXdUbAC5TpcZPH6Zw 101 | https://mp.weixin.qq.com/s/3o0bDX7gwCOtRjYG7bFULQ 102 | 103 | https://mp.weixin.qq.com/s/EZq45TRQGedPT7t-qoqQFQ 104 | frank:通过 observer 对 data 进行了监听,并且提供订阅某个数据项的变化的能力把 template 解析成一段 document fragment,然后解析其中的 directive,得到每一个 directive 所依赖的数据项及其更新方法。比如 v-text="message" 被解析之后 (这里仅作示意,实际程序逻辑会更严谨而复杂):所依赖的数据项 this.$data.message,以及相应的视图更新方法 node.textContent = this.$data.message 通过 watcher 把上述两部分结合起来,即把 directive 中的数据依赖订阅在对应数据的 observer 上,这样当数据变化的时候,就会触发 observer,进而触发相关依赖对应的视图更新方法,最后达到模板原本的关联效果。 105 | 数组的处理:https://mp.weixin.qq.com/s/a5G-V-mfv5NAJOy3Ba0rpQ 106 | https://hellogithub2014.github.io/archives/page/6/ 107 | 问题: Vue 内部怎么实现数据监听? 108 | 主要是 ES5 的 Object.defineProperty 监听对象的属性,但是数组类型的监听并不是通过这个 Object.defineProperty,而是通过重写能够引起数组变化的数组方法如 push pop 等方法达到响应式效果的。不监听数组下面变化的原因是 object.defineproperty 不能完全监听数组所有的变化,同时也是考虑监听下标属性情况下,性能的损耗还不如收益大。 109 | 拿 vue 来说,知道 watch 有 immediate 和 deep,并知道为什么要用;用 vue 写过 mixin,directive 和 render;拿 vue 做过模块化开发,了解里面的痛点;能基于脚手架个性化配置 webpack,能使用它分析页面性能,优化加载体验;独立负责过整个项目的开发;能熟练使用 vuex 和 vue-router; 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前端原理源码学习笔记 2 | 3 | --- 4 | 5 | ### webpack 系列 6 | 7 | 1. [常见 loader 源码简析,以及动手实现一个 md2html-loader](https://github.com/fxxqq/fe-blog/tree/master/webpack/loader) 8 | 2. [webpack 插件工作原理剖析](https://github.com/fxxqq/fe-blog/tree/master/webpack/plugin) 9 | 3. [webpack 主流程源码阅读以及实现一个 webpack](https://github.com/fxxqq/fe-blog/tree/master/webpack/webpack) 10 | 4. [webpack 打包优化实践](https://github.com/fxxqq/fe-blog/tree/master/webpack/webpack%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96) 11 | 12 | --- 13 | 14 | ### vue 系列写作方向 15 | 16 | **vue2** 17 | 18 | 1. Vue 依赖收集过程源码阅读以及实现 19 | 2. Vue 响应式更新原理源码阅读以及实现 20 | 3. Vue patch Diff 算法源码阅读以及实现 21 | 4. compute 和 watch 原理 22 | 5. [nexttick 原理](https://juejin.cn/post/6844903911673823246) 23 | 6. keeplive 实现原理 24 | 25 | **vue3** 26 | 27 | 1. [vue composition-api 速成课](https://github.com/fxxqq/fe-blog/tree/master/vue/@vue/composition-api.md) 28 | 29 | --- 30 | 31 | ##### react 系列 32 | 33 | 1. [React16 常用 api 解析以及原理剖析](https://github.com/fxxqq/fe-blog/tree/master/react/React16-commonly-used-API-analysis) 34 | 2. [实现一个简单的 useState](https://github.com/fxxqq/fe-blog/tree/master/react/实现一个简单的react-hook里面的useState) 35 | 36 | --- 37 | 38 | ### 微前端 39 | 1. [微前端项目难点解决](https://github.com/fxxqq/fe-blog/blob/master/micro-frontend/%E5%BE%AE%E4%B9%BE%E5%9D%A4%E9%A1%B9%E7%9B%AE%E9%9A%BE%E7%82%B9%E8%A7%A3%E5%86%B3.md) 40 | 41 | --- 42 | 43 | ### 前端安全 44 | 45 | 1. [xss](https://github.com/fxxqq/fe-blog/blob/master/前端安全/xss/readme.md) 46 | 2. [csrf](https://github.com/fxxqq/fe-blog/tree/master/前端安全/csrf/readme.md) 47 | 48 | --- 49 | 50 | ##### 大厂手写代码题 51 | 52 | 1. [hash 去重](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/hash%E5%8E%BB%E9%87%8D.js) 53 | 2. [防抖节流](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/%E9%98%B2%E6%8A%96%E8%8A%82%E6%B5%81.js) 54 | 3. [手写实现 promise](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/Promise.js) 55 | 4. [手写实现 instanceof](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/instanceof.js) 56 | 5. [new 的内部机制,自己实现一个 new](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/实现new.js) 57 | 6. [拼多多:实现柯里化函数](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/currying.js) 58 | 7. [拼多多:设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈](https://github.com/fxxqq/fe-blog/blob/master/leetcode/155.最小栈.md) 59 | 8. [快手:数组全排列](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/数组全排列.js) 60 | 9. [快手:浏览器最大请求并非限制](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/浏览器最大请求并非限制.js) 61 | 10. [头条:计算树的深度](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/计算树的深度.js) 62 | 11. [蚂蚁:编写高阶函数,连续触发时,若上一次 promise 执行未结束则直接废弃,只有最后一次 promise 会触发 then/reject](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/lastPromise.js) 63 | 12. [发布订阅.js](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/发布订阅.js) 64 | 13. [头条:原生 ajax 封装成 async await 调用](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/原生ajax封装成async-await调用.js) 65 | 14. [阿里:实现带有超时功能的 Promise](实现带有超时功能的Promise.js) 66 | 15. [阿里:实现一个函数,可以将数组转化为树状数据结构](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/实现一个函数,可以将数组转化为树状数据结构.js) 67 | 16. [腾讯:腾讯面试题 new 一个函数发生了什么](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/腾讯面试题new一个函数发生了什么.js) 68 | 17. [腾讯:三个元素之和为指定数 n 的各个组合](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/腾讯面试题:三个元素之和为指定数n的各个组合.js) 69 | 18. [腾讯:生成长度为 n 的 int 型随机数组,数组元素范围为 0~n-1](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/腾讯面试题3.js) 70 | 19. [蚂蚁:苹果、梨、香蕉三个数组相互拼成字符串问题](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/苹果、梨、香蕉三个数组相互拼成字符串.js) 71 | 20. [字节:Excel 表格随机生成 1000 列](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/字节面试题Excel表格随机生成1000列.md) 72 | 21. [字节:二叉树&完整路径和](https://github.com/fxxqq/fe-blog/blob/master/leetcode/112.路径总和.md) 73 | 22. [常见排序算法以及复杂度](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/常见排序算法以及复杂度.md) 74 | 23. [字节:合并两个有序数组](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/字节面试题:合并两个有序数组.md) 75 | 24. [js 实现数组和链表之间相互转换](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/js实现数组和链表之间相互转换.md) 76 | 25. [拼多多](合并线段) 77 | 78 | ### 前端知识点 79 | 80 | [prototype 和`_proto_`以及原型链的关系](https://github.com/fxxqq/fe-blog/blob/master/前端知识点/prototype和_proto_以及原型链的关系.md) 81 | 82 | [js 事件循环]() 83 | 84 | [前端性能优化](https://github.com/fxxqq/fe-blog/blob/master/前端知识点/前端性能优化.md) 85 | 86 | [Map 与 WeakMap 的区别](https://github.com/fxxqq/fe-blog/blob/master/前端知识点/Map与WeakMap的区别.md) 87 | 88 | --- 89 | 90 | ### leetCode Hot100 91 | 92 | ##### easy 93 | 94 | [1.两数之和](https://github.com/fxxqq/fe-blog/blob/master/leetcode/1.两数之和.md) 95 | 96 | [14.最长公共前缀](https://github.com/fxxqq/fe-blog/blob/master/leetcode/14.最长公共前缀.md) 97 | 98 | [18.删除链表的节点](https://github.com/fxxqq/fe-blog/blob/master/leetcode/18.删除链表的节点.md) 99 | 100 | [20.有效的括号](https://github.com/fxxqq/fe-blog/blob/master/leetcode/20.有效的括号.md) 101 | 102 | [21.合并两个有序链表](https://github.com/fxxqq/fe-blog/blob/master/leetcode/21.合并两个有序链表.md) 103 | 104 | [53.最大子序和](https://github.com/fxxqq/fe-blog/blob/master/leetcode/53.最大子序和.md) 105 | 106 | [70.爬楼梯](https://github.com/fxxqq/fe-blog/blob/master/leetcode/70.爬楼梯.md) 107 | 108 | [101.对称二叉树](https://github.com/fxxqq/fe-blog/blob/master/leetcode/101.对称二叉树.md) 109 | 110 | [112.路径总和](https://github.com/fxxqq/fe-blog/blob/master/leetcode/112.路径总和.md) 111 | 112 | [136.只出现一次的数字](https://github.com/fxxqq/fe-blog/blob/master/leetcode/136.只出现一次的数字.md) 113 | 114 | [155.最小栈](https://github.com/fxxqq/fe-blog/blob/master/leetcode/155.最小栈.md) 115 | 116 | [160.相交链表](https://github.com/fxxqq/fe-blog/blob/master/leetcode/160.相交链表md) 117 | 118 | [206.反转链表](https://github.com/fxxqq/fe-blog/blob/master/leetcode/206.反转链表.md) 119 | 120 | [234.回文链表](https://github.com/fxxqq/fe-blog/blob/master/leetcode/234.回文链表.md) 121 | 122 | [543.二叉树的直径](https://github.com/fxxqq/fe-blog/blob/master/leetcode/543.二叉树的直径.md) 123 | 124 | [617.合并二叉树](https://github.com/fxxqq/fe-blog/blob/master/leetcode/617.合并二叉树.md) 125 | 126 | [771.宝石与石头](https://github.com/fxxqq/fe-blog/blob/master/leetcode/771.宝石与石头.md) 127 | 128 | #### middle 129 | 130 | [5.最长回文子串](https://github.com/fxxqq/fe-blog/blob/master/leetcode/5.最长回文子串.md) 131 | 132 | ##### LeetCode 排序 解法题目 133 | 134 | [35.搜索插入位置(easy)](https://github.com/fxxqq/fe-blog/blob/master/leetcode/35.搜索插入位置.md) 135 | 136 | [88.合并两个有序数组(easy)](https://github.com/fxxqq/fe-blog/blob/master/leetcode/88.合并两个有序数组.md) 137 | 138 | 581.最短无序连续子数组(easy)https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray/ 139 | 140 | 1331.数组序号转换(easy)https://leetcode-cn.com/problems/rank-transform-of-an-array/ 141 | 142 | 56.合并区间(medium)https://leetcode-cn.com/problems/merge-intervals/ 143 | 144 | 215.数组中的第 K 个最大元素(medium) https://leetcode-cn.com/problems/kth-largest-element-in-an-array/ 145 | 146 | [912.排序数组(middle)](https://github.com/fxxqq/fe-blog/blob/master/handwrittenCode/常见排序算法以及复杂度.md) 147 | 148 | [146. LRU 缓存机制]() 149 | 150 | ##### LeetCode 字符串相关题目 151 | 152 | 验证回文串 153 | 154 | 分割回文串 155 | 156 | 单词拆分 157 | 158 | 反转字符串 159 | 160 | 161 | -------------------------------------------------------------------------------- /前端知识点/项目总结.md: -------------------------------------------------------------------------------- 1 | ### 2 | 3 | 项目实践 4 | 我接触前端已经快 5 年了,工作的这五年写过 jquery,require,ng1,react,vue。最近 3 年工作中主要接触 react 和 vue 比较多一点。所以对 vue 的原理了解比较多一点。上一次写 react 项目是在一年前。用的还是 react16 以前的版本,但是我对 react 新特性一直保持着关注的态度。富本文编辑器开发和 app 内嵌页面开发,学到了很多关于移动端调试的方法和编辑器的冷知识。1. 线上教育 react vue webrtc (前端工程化的东西)web 性能优化 中低端机 工作中主动做一些事情(组件库) 富文本编辑器 优化前端数据报表管理系统,对报表/用户/系统配置进行统一管理 5 | 6 | 学习方面 7 | 因为大学专业是软件工程,所以对 java 等都有一些了解,至少会装一些简单的环境和写一些常用的语法。因为兴趣爱好的原因,下班和周末会经常学习前端,平时也会在一些社区写文章,有一定量的粉丝,知乎掘金等平台前端粉丝过万。有想过做一个前端社区,阅读过优秀开源项目 egg-cnode 的源码,自己也尝试写过一些 node 项目。然后我也比较关注前端工程化的一些东西。比如落实公司组件库。解决在使用中碰到的一系列问题。有深入了解过 webpack 等。 8 | 9 | 未来规划 10 | 希望自己在接下来的时间内会更多的技术沉淀 1.技术深度方面, 有自己的技术积累和开源输出。 2. 技术广度方面, 3.培养一些软能力,有工程架构能力以及项目把控能力,调配各方资源依赖,推动项目的落地到上线。 11 | 12 | ### 项目难点 13 | 14 | 1. 富文本编辑器类的开发 15 | 快捷键 16 | 他人编辑引起的文本块变动如何不干扰到光标定位呢? 17 | 2. 开发体验和用户体验 18 | 19 | 性能优化。页面白屏、加载慢、动画卡顿、操作不流程等, 20 | 中、低端机 21 | 需要从网络层优化到代码逻辑,可能最后只是用户网络环境不好。为了减少服务器压力,通常会把更多的逻辑放在客户端实现,最常见的就是前后端分离后的前端渲染逻辑,所以就算不用优化数据库操作,还是要优化业务逻辑代码。 22 | 23 | 交互、动画、视觉效果、数据图表 ,如何高质量还原设计 UI,完成操作交互以及动画效果,还是我觉得前端最难的一点。 24 | 25 | 3. 前端工程自动化。 26 | 27 | 一个好的工程,必须有一个好的脚手架, 28 | 最大程度的减少开发者的工作量,从初始化项目、代码规范、mock 服务、单元测试、打包工具等。 29 | 从开始开发到最后的服务器部署,都应该有一套完善的解决方案。 30 | 31 | 业务开发的前端难点在于对业务的理解和把控能力; 32 | 平台开发的前端难点在于产品化的把控和推进能力。 33 | 34 | 业务逻辑开发本身并不是难点,谁都可以写。但是对于你自己负责的这块业务,后续业务的发展方向和潜力,你有去了解过吗? 35 | 当业务方提需求过来时你是只负责执行还是和业务方一起探讨更合理的方案? 36 | 你有没有给自己负责的产品提过一些建议?做过一些改善措施? 37 | 如果前端只是作为一个执行者,作为一种被调度的资源,那么即使最终项目取得了好的成绩,跟你有多大关系?你自己会有多大的成就感? 38 | 39 | 就是对业务的把控能力。业务方总是会催着上线,开发时间不断被压缩该怎么办? 40 | 进度不如预期怎么办?开发遇到瓶颈怎么办?发布新功能翻车了怎么办? 41 | 42 | 我们几次大的业务平台重构,前端基本要重新开发一遍(效果、交互完全不同) 43 | 跟后端大神们进行讨论推测未来业务可能会有大量表单而且需要完全的数据驱动,所以我们前端设计开发了 动态表单 技术产品然后后端有对应的接口进行解析和数据存储、表单生成服务,前端只需要开发组件,然后后端按照业务需求进行配置即可产出内容发布表单。 44 | 45 | 1. 加班 46 | 2. 仔细评估工时,提前预测风险 47 | 3. 解决问题后,要及时进行梳理总结。 48 | 4. 积累一定实践经验后,也可与领域专家交流,进一步提升自己的认知。 49 | 50 | ### 搭建组件库 51 | 52 | 打包体积小,高度可控 53 | 采用内部组件库安全性更高,防止嵌入攻击 54 | 构建和开发更灵活,且组合型更高 55 | 56 | 组件库的划分及设计思路 57 | 基础组件 UI 组件 58 | 59 | 业务型组件库 60 | 61 | 前端组件库百花齐放,antd、element ui 这些基础组件库已经很强大,使用于各种业务场景。但是这些基础组件的粒度是基于单个交互,而在交互与产品之间隔着各种各样的模块和业务场景,产品的汇聚源于各种基础组件在业务逻辑的沾粘下集成为一个个项目,一个团队或多或少会有项目或模块存在功能、交互流程的重复、本质上的同质化。 62 | 63 | 所以 antd、element ui 这类组件库是基于单个非连续性的交互组件,一个组件代表着一次人机无副作用的操作与响应,其不思考实体、用户、终端的状态,最小化的暴露和响应组件内部状态。对于连续性的交互通常来说与特点的业务场景有关,存在诸多的外部依赖,目前都是在各个业务模块由用户(coder)自行编写。 64 | 65 | 有没有一种方法解决连续性交互流程的共用问题? 66 | 67 | 解决的办法是组件封装包含业务场景的连续性交互流程,利用组件化将内部依赖通过接口映射到外部。 68 | 69 | 前端架构部门为业务部门提供业务型组件库能够有效提高开发效率. 70 | 71 | 组件库设计思路 72 | 73 | 组件是对一些具有相同业务场景和交互模式、交互流程代码的抽象,组件库首先应该保证各个组件的视觉风格和交互规范保持一致。组件库的 props 定义需要具备足够的可扩展性,对外提供组件内部的控制权,使组件内部完全受控。支持通过 children 自定义内部结构,预定义组件交互状态。保持组件具有统一的输入和输出,完整的 API. 74 | 75 | 组件库的开发我们需要考虑: 76 | 77 | 组件设计思路、需要解决的场景 78 | 组件代码规范 79 | 组件测试 80 | 组件维护,包括迭代、issue、文档、发布机制 81 | 82 | ### 83 | 84 | Canvas 的 save() 和 restore() 85 | 基本裁剪流程 86 | 裁剪框的绘制 87 | 裁剪框的移动和伸缩 88 | 旋转 89 | 考虑移动端的图片方位 拍摄信息 exif 90 | 输出裁剪图片 91 | 92 | 使用 Canvas.toBlob() 输出图片 93 | Canvas 的 getImageData() 和 putImageData() 94 | 上传至 CDN 95 | 96 | ### 虚拟 DOM 97 | 98 | 在说 diff 算法之前先来了解一下虚拟 DOM: 99 | 虚拟 DOM 只保留了真实 DOM 节点的一些基本属性,和节点之间的层次关系,它相当于建立在 javascript 和 DOM 之间的一层“缓存”。 100 | 虚拟 DOM 其实就是用一个对象来描述 DOM,通过对比前后两个对象的差异,最终只把变化的部分重新渲染,提高渲染的效率。 101 | 102 | 什么是 diff 算法 103 | React 需要同时维护两棵虚拟 DOM 树:一棵表示当前的 DOM 结构,另一棵在 React 状态变更将要重新渲染时生成。React 通过比较这两棵树的差异,决定是否需要修改 DOM 结构,以及如何修改。 104 | 简单来说 Diff 算法在虚拟 DOM 上实现,是虚拟 DOM 的加速器,提升性能的法宝。 105 | 106 | Vue 和 React 中 diff 算法区别 107 | vue 和 react 的 diff 算法,都是忽略跨级比较,只做同级比较。vue diff 时调动 patch 函数,参数是 vnode 和 oldVnode,分别代表新旧节点。 108 | 109 | vue 比对节点,当节点元素类型相同,但是 className 不同,认为是不同类型元素,删除重建,而 react 会认为是同类型节点,只是修改节点属性 110 | 111 | vue 的列表比对,采用从两端到中间的比对方式,而 react 则采用从左到右依次比对的方式。当一个集合,只是把最后一个节点移动到了第一个,react 会把前面的节点依次移动,而 vue 只会把最后一个节点移动到第一个。总体上,vue 的对比方式更高效。 112 | 113 | 只会做同级比较,不做跨级比较 114 | 比较后几种情况 115 | if (oldVnode === vnode),他们的引用一致,可以认为没有变化。 116 | if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text),文本节点的比较,需要修改,则会调用 Node.textContent = vnode.text。 117 | if( oldCh && ch && oldCh !== ch ), 两个节点都有子节点,而且它们不一样,这样我们会调用 updateChildren 函数比较子节点,这是 diff 的核心 118 | else if (ch),只有新的节点有子节点,调用 createEle(vnode),vnode.el 已经引用了老的 dom 节点,createEle 函数会在老 dom 节点上添加子节点。 119 | else if (oldCh),新节点没有子节点,老节点有子节点,直接删除老节点。 120 | 121 | ### 性能优化 122 | 123 | webpack 性能调优与 Gzip 原理 124 | 图片优化 125 | 126 | ssr 127 | 更好的搜索引擎优化(SEO) 128 | 更快的内容到达时间 (time-to-content) 129 | 130 | 性能:如何进行性能优化,提升 QPS,节约服务器资源? 131 | 132 | 1. 页面缓存 133 | 2. lru 组件缓存 134 | 135 | 容灾:如何做好容灾处理,实现自动降级? 136 | 137 | Node 服务器上启动一个服务,用来监测 Node 进程的 CPU 和内存使用率,设定一个阈值,当达到这个阈值时,停止 SSR,直接将 CSR 的入口文件 index.html 返回,实现降级。 138 | Nginx 降级策略 139 | 日志:如何接入日志,方便问题定位? 140 | 监控:如何对 Node 服务进行监控? 141 | 部署:如何打通公司 CI/CD 流程,实现自动化部署? 142 | 143 | ### 平常是怎么做继承 144 | 145 | 1. 原型链继承 146 | 父类构造函数中的引用类型(比如对象/数组),会被所有子类实例共享。 147 | 其中一个子类实例进行修改,会导致所有其他子类实例的这个值都会改变 148 | 2. 构造继承 149 | 解决了原型链继承中构造函数引用类型共享的问题,同时可以向构造函数传参(通过 call 传参) 150 | 所有方法都定义在构造函数中,每次都需要重新创建 151 | (对比原型链继承的方式,方法直接写在原型上,子类创建时不需要重新创建方法) 152 | 3. 组合继承 153 | 同时解决了构造函数引用类型的问题,同时避免了方法会被创建多次的问题 154 | 父类构造函数被调用了两次。 155 | 4. 寄生组合继承 156 | 这种方式就解决了组合继承中的构造函数调用两次,构造函数引用类型共享, 157 | 以及原型对象上存在多余属性的问题。是推荐的最合理实现方式 158 | 5. ES6 继承 159 | 160 | ### 介绍 JS 数据类型,基本数据类型和引用数据类型的区别。 161 | 162 | a. 他们分别储存在哪里? 163 | b. 栈和堆的区别?js 垃圾回收时,栈和堆的区别 164 | 答案: 165 | 基本数据 166 | 类型包括:undefined,null,number,boolean,string 167 | 168 | 1. 基本数据类型的值是不可变的 169 | 2. 基本数据类型不可以添加属性和方法 170 | 3. 基本数据类型的赋值是简单赋值 171 | 4. 基本数据类型的比较是值的比较 172 | 5. 基本数据类型是存放在栈区的 173 | 174 | 引用类型 175 | Object,Array,Function 176 | 177 | 1. 引用类型的值是可以改变的 178 | 2. 引用类型可以添加属性和方法 179 | 3. 引用类型的赋值是对象引用 180 | 4. 引用类型的比较是引用的比较 181 | 5. 引用类型是同时保存在栈区和堆区中的 182 | 183 | 栈优点:存取速度比堆快,仅次于直接位于 CPU 中的寄存器,数据可以共享; 184 | 栈缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。 185 | 栈中存放局部变量,内存的释放是系统控制实现的。(局部变量的存活时间是这个函数调用完之后) 186 | 堆的空间大,栈的空间小 187 | 堆中存放对象,需要手动释放内存。(垃圾回收机制) 188 | 189 | https://segmentfault.com/a/1190000008472264 190 | 191 | ### 发布-订阅和观察者模式的区别 192 | 193 | 订阅模式有一个调度中心,对订阅事件进行统一管理。 194 | 而观察者模式可以随意注册事件,调用事件,虽然实现原理都雷同,设计模式上有一定的差别, 195 | 实际代码运用中差别在于:订阅模式中,可以抽离出调度中心单独成一个文件, 196 | 可以对一系列的订阅事件进行统一管理。这样和观察者模式中的事件漫天飞就有千差万别了, 197 | 在开发大型项目的时候,订阅/发布模式会让业务更清晰! 198 | 199 | ### 路由原理 200 | 201 | 路由原理:前段路由实现本质是监听 URL 的变化,然后匹配路由规则显示相应页面,并且无须刷新。 202 | hash 模式: 203 | a: 点击或浏览器历史跳转时, 204 | 触发 onhashchange 事件, 205 | 然后根据路由规则匹配显示相应页面(遍历路由表,装载相应组件到 router-link); 206 | b: 手动刷新时,不会像服务器发送请求(不会触发 onhashchange), 207 | 触发 onload 事件,然后根据路由规则匹配显示相应页面; 208 | history 模式: 209 | a:跳转时会调用 history.pushState 方法, 210 | 根据 to 属性改变地址,并切换相应组件到 router-link; 211 | b:浏览器历史操作(前进,后退),只会改变地址栏(页面内容不会变), 212 | 不会切换组件,需要使用 popstate 方法来切换组件; 213 | c: 手动刷新,需要后端配合重定向,不然 404 214 | 215 | ### React 生命周期及自己的理解 216 | 217 | 挂载 218 | 219 | 1. constructor 220 | 初始化 state 对象 221 | 给自定义方法绑定 this 222 | 2. getDerivedStateFromProps 223 | 3. componentWillMount/UNSAFE_componentWillMount 224 | 4. render 225 | 5. componentDidMount 226 | 227 | 更新 228 | 229 | 1. componentWillReceiveProps/UNSAFE_componentWillReceiveProps 230 | 2. getDerivedStateFromProps 231 | 3. shouldComponentUpdate 232 | 4. componentWillUpdate/UNSAFE_componentWillUpdate 233 | 5. render 234 | 6. getSnapshotBeforeUpdate 235 | 7. componentDidUpdate 236 | 237 | 卸载 238 | 239 | 1. componentWillUnmount 240 | 241 | ### webrtc 242 | 243 | 获取音视频流或者其他数据; 244 | 获取 IP 地址和端口之类的网络信息,并与其他 WebRTC 客户端(peers)进行交换,以启用连接; 245 | 使用信令(signaling)来管理会话连接,并在发生错误时上报; 246 | 交换有关音视频流的客户端信息,例如分辨率、编解码器等信息; 247 | 建立连接并开始传输音视频流。 248 | -------------------------------------------------------------------------------- /vue/@vue/composition-api.md: -------------------------------------------------------------------------------- 1 | `Composition API` 将是 Vue 3 的核心功能,它具有许多更改和性能改进。我们可以在 `Vue 2` 中通过 npm 插件`@vue/composition-api` 使用它。 2 | 本人重点将带你了解: 3 | 4 | 1. `@vue/composition-api`常见 api 使用 5 | 2. vue3 代码逻辑提取和复用 6 | 3. 如何使用`provide+inject`替代`vuex`方案 7 | 8 | ### vue2 使用 composition-api 9 | 10 | 主文件 main.ts 或者 app.vue 添加 11 | 12 | ```js 13 | import Vue from 'vue' 14 | import VueCompositionAPI from '@vue/composition-api' 15 | Vue.use(VueCompositionAPI) 16 | ``` 17 | 18 | `Composition API` 不再传入 `data、mounted` 等参数, 19 | 通过引入的 `ref`、`onMounted `等方法实现数据的双向绑定、生命周期函数的执行。 20 | 21 | ### 核心语法 22 | 23 | `reactive`:接收一个普通对象然后返回该普通对象的响应式代理。 24 | 25 | `ref`:接受一个参数值并返回一个响应式且可改变的 `ref` 对象。`ref` 对象拥有一个指向内部值的单一属性 .value。 26 | 27 | `computed`:传入一个 `getter` 函数,返回一个默认不可手动修改的 `ref` 对象。 28 | 29 | `readonly`:传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。 30 | 31 | `watchEffect`:立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。可显式的调用返回值以停止侦听。 32 | 33 | `watch`:全等效于 2.x `this.\$watch` (以及 `watch` 中相应的选项)。 34 | 35 | #### setup 函数 36 | 37 | 现在要介绍的第一个 API 就是 `setup` 函数。 38 | `setup` 函数是一个新的组件选项。作为在组件内使用 `Composition API` 的入口点。 39 | 先看个简单 demo 40 | 41 | ```html 42 | 45 | 54 | ``` 55 | 56 | 1、调用时机 57 | 58 | 创建组件实例,然后初始化 `props` ,紧接着就调用 `setup` 函数。 59 | 从 vue2 生命周期钩子的视角来看,它会在 `beforeCreate` 钩子之后,`created` 之前被调用。 60 | 61 | 2、模板中使用 62 | 63 | 如果 `setup` 返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文。 64 | 65 | 3、渲染函数 / JSX 中使用 66 | 67 | `setup` 也可以返回一个函数,函数中也能使用当前 `setup` 函数作用域中的响应式数据: 68 | 69 | ```js 70 | import { h, ref, reactive } from '@vue/composition-api' 71 | 72 | export default { 73 | setup() { 74 | const count = ref(0) 75 | const object = reactive({ foo: 'bar' }) 76 | 77 | return () => h('div', [count.value, object.foo]) 78 | }, 79 | } 80 | ``` 81 | 82 | 4、两个参数 83 | `props`(注意 props 对象是响应式的), 84 | `context`(上下文对象,从原来 2.x 中 this 选择性地暴露了一些 `property`。) 85 | 86 | ```js 87 | const MyComponent = { 88 | setup(props, context) { 89 | let { 90 | attrs, 91 | emit, 92 | isServer, 93 | listeners, 94 | parent, 95 | refs, 96 | root, 97 | slots, 98 | ssrContext, 99 | } = context 100 | }, 101 | } 102 | ``` 103 | 104 | #### ref & reactive 105 | 106 | 在 `App.vue` 中,点击事件绑定了 `increase`,然后修改了 `count`, 107 | 但是页面并没有发生改变,这是因为 `setup` 函数返回的对象中 `count` 不是响应式数据, 108 | 那么如何创建响应式数据呢?此时就要掌握响应式系统 API,我们可以使用 `ref` 和 `reactive` 创建。 109 | 110 | ```html 111 | 116 | 117 | 131 | ``` 132 | 133 | 接受一个参数值并返回一个响应式且可改变的 `ref` 对象。 134 | `ref` 对象拥有一个指向内部值的单一属性 `.value`。 135 | 136 | 当 `ref` 作为渲染上下文的属性返回(即在 `setup()` 返回的对象中)并在模板中使用时, 137 | 它会自动解套,无需在模板内额外书写 `.value` 138 | 139 | Vue 本身已经有 "`ref`" 的概念了。 140 | 但只是为了在模板中获取 DOM 元素或组件实例 (“模板引用”)。 141 | 新的 `ref` 系统同时用于逻辑状态和模板引用。 142 | 143 | `reactive` 接收一个普通对象然后返回该普通对象的响应式代理。 144 | 145 | 响应式转换是“深层的”:会影响对象内部所有嵌套的属性。基于 ES2015 的 Proxy 实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象。 146 | 147 | 不要解构返回的代理对象,那样会使其失去响应性: 148 | 149 | ```html 150 | 153 | 154 | 164 | ``` 165 | 166 | #### toRef 和 toRefs 167 | 168 | 那如果我们真的想展开 `state` 的属性,在模板使用 count 而不是 state.count 的写法那怎么办呢?我们可以使用 `toRef` 和 `toRefs` 这两个 API,进行转换成 ref 对象,之前已经介绍了 ref 对象是可以直接在模板中使用的。 169 | 170 | `toRef` 可以用来为一个 `reactive` 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性。 171 | 172 | ```html 173 | 178 | 179 | 192 | ``` 193 | 194 | 把一个响应式对象转换成普通对象,该普通对象的每个 `property` 都是一个 `ref` ,和响应式对象 `property` 一一对应。 195 | 196 | #### computed & watch 197 | 198 | ```js 199 | const countDouble = computed(() => count.value * 2) 200 | watch( 201 | () => state.count, 202 | (count, prevCount) => { 203 | /* ... */ 204 | } 205 | ) 206 | ``` 207 | 208 | ### 代码逻辑提取和复用 209 | 210 | `Composition API` 的第一个明显优势是很容易提取逻辑。解决了 211 | 212 | #### 逻辑提取 213 | 214 | ```js 215 | export const useCount = (number) => { 216 | const count = ref(0) 217 | const increase = () => { 218 | count.value += 1 219 | } 220 | const reset = () => { 221 | count.value = 0 222 | } 223 | onMounted(() => { 224 | count.value = number 225 | }) 226 | return { 227 | count, 228 | increase, 229 | reset, 230 | } 231 | } 232 | ``` 233 | 234 | #### 代码复用 235 | 236 | ```js 237 | // 另外一个文件使用: 238 | const { count, increase } = useCount(1) 239 | console.log(count) //输出1 240 | increase() 241 | console.log(count) //输出2 242 | reset() 243 | console.log(count) //输出0 244 | ``` 245 | 246 | 有效的解决了 `mixins` 复用命名冲突,难以识别命名来自哪个 `mixin` 文件的问题。 247 | 248 | ### 替代 vuex 状态管理 249 | 250 | 状态 `store` 可以放在一个单一的文件或者目录里,比如设置一个全局组件可以只用的配置 `config` 251 | 252 | ```ts 253 | //context/config.ts 254 | import { provide, inject, ref, onMounted, readonly } from '@vue/composition-api' 255 | const configSymbol: symbol = Symbol() 256 | 257 | export const useProvider = { 258 | setup() { 259 | let config = ref(null) 260 | const configServer = async () => { 261 | // await 一些异步操作,比如api等 262 | config.value = { name: '名字' } 263 | } 264 | onMounted(async () => { 265 | await configServer() 266 | }) 267 | provide(configSymbol, { 268 | //导出只读的config只有函数内部可以修改状态 269 | config: readonly(config), 270 | }) 271 | }, 272 | } 273 | 274 | export const useInject = () => { 275 | return inject(configSymbol) 276 | } 277 | ``` 278 | 279 | 在最顶层的组件(例如 main.ts)上注入,config 就可以在所有的组件中使用 280 | 281 | ```js 282 | import { defineComponent } from '@vue/composition-api' 283 | import { useProvider } from './context/config' 284 | export default defineComponent({ 285 | setup() { 286 | useProvider() 287 | }, 288 | }) 289 | ``` 290 | 291 | 业务逻辑页面使用 config 292 | 293 | ```js 294 | import { useInject } from './context/config' 295 | const Components = { 296 | setup() { 297 | const { config } = useInject() 298 | console.log(config.value.name) //输出“名字” 299 | return { 300 | config, 301 | } 302 | }, 303 | } 304 | ``` 305 | -------------------------------------------------------------------------------- /leetcode/easy/readme.md: -------------------------------------------------------------------------------- 1 | 精选 100 道力扣(LeetCode)上最热门的题目,本篇文章只有 easy 级别的,适合初识算法与数据结构的新手和想要在短时间内高效提升的人。 2 | 3 | ## 1.两数之和 4 | 5 | https://leetcode-cn.com/problems/two-sum 6 | 7 | ##### 方法一 8 | 9 | ```js 10 | /** 11 | * @param {number[]} nums 12 | * @param {number} target 13 | * @return {number[]} 14 | */ 15 | var twoSum = function (nums, target) { 16 | for (let i = 0; i < nums.length; i++) { 17 | let diff = target - nums[i] 18 | for (let j = i + 1; j < nums.length; j++) { 19 | if (diff == nums[j]) { 20 | return [i, j] 21 | } 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | ##### 方法二 28 | 29 | ```js 30 | /** 31 | * @param {number[]} nums 32 | * @param {number} target 33 | * @return {number[]} 34 | */ 35 | var twoSum = function (nums, target) { 36 | var temp = [] 37 | for (var i = 0; i < nums.length; i++) { 38 | var dif = target - nums[i] 39 | if (temp[dif] != undefined) { 40 | return [temp[dif], i] 41 | } 42 | temp[nums[i]] = i 43 | } 44 | } 45 | ``` 46 | 47 | ## 14.最长公共前缀 48 | 49 | https://leetcode-cn.com/problems/longest-common-prefix 50 | 51 | ##### 思路: 52 | 53 | 1. 先遍历数组 54 | 2. 再遍历数组的第一个字符串,用字符串中的每一个字符和数组中的每一项的对应的该字符串下标相比,不同则跳出循环,两两找出公共前缀,最终结果即为最长公共前缀的长度 j。 55 | 3. 截取字符串长度 j 的字符即为最长公共前缀 56 | 57 | ```js 58 | const strs = ['flower', 'flow', 'flight'] 59 | const longestCommonPrefix = function (strs) { 60 | if (strs === null || strs.length === 0) return '' 61 | let commonString = '' 62 | 63 | for (let i = 1; i < strs.length; i++) { 64 | let j = 0 65 | for (; j < strs[0].length && j < strs[i].length; j++) { 66 | if (strs[0][j] !== strs[i][j]) break 67 | } 68 | commonString = strs[0].substring(0, j) 69 | } 70 | return commonString 71 | } 72 | longestCommonPrefix(strs) 73 | ``` 74 | 75 | ## 18.删除链表的节点 76 | 77 | https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof 78 | 79 | ```js 80 | var deleteNode = function (head, val) { 81 | if (head.val === val) return head.next 82 | let prev = head, 83 | node = prev.next 84 | while (node) { 85 | if (node.val === val) { 86 | prev.next = node.next 87 | } 88 | prev = node 89 | node = node.next 90 | } 91 | return head 92 | } 93 | ``` 94 | 95 | ## 20.有效的括号 96 | 97 | https://leetcode-cn.com/problems/valid-parentheses 98 | 99 | ##### 方法分析: 100 | 101 | 该题使用的堆栈(stack)的知识。栈具有先进后出(FILO)的特点。堆栈具有栈顶和栈底之分。所谓入栈,就是将元素压入(push)堆栈;所谓出栈,就是将栈顶元素弹出(pop)堆栈。先入栈的一定后出栈,所以可以利用堆栈来检测符号是否正确配对。 102 | 103 | ##### 解题思路: 104 | 105 | 1. 有效括号字符串的长度,一定是偶数! 106 | 2. 右括号前面,必须是相对应的左括号,才能抵消! 107 | 3. 右括号前面,不是对应的左括号,那么该字符串,一定不是有效的括号! 108 | 109 | ```js 110 | var isValid = function (s) { 111 | let stack = [] 112 | if (!s || s.length % 2) return false 113 | for (let item of s) { 114 | switch (item) { 115 | case '{': 116 | case '[': 117 | case '(': 118 | stack.push(item) 119 | break 120 | case '}': 121 | if (stack.pop() !== '{') return false 122 | break 123 | case '[': 124 | if (stack.pop() !== ']') return false 125 | break 126 | case '(': 127 | if (stack.pop() !== ')') return false 128 | break 129 | } 130 | } 131 | return !stack.length 132 | } 133 | ``` 134 | 135 | ## 21.合并两个有序链表 136 | 137 | https://leetcode-cn.com/problems/merge-two-sorted-lists 138 | 139 | ```js 140 | /** 141 | * Definition for singly-linked list. 142 | * function ListNode(val, next) { 143 | * this.val = (val===undefined ? 0 : val) 144 | * this.next = (next===undefined ? null : next) 145 | * } 146 | */ 147 | /** 148 | * @param {ListNode} l1 149 | * @param {ListNode} l2 150 | * @return {ListNode} 151 | */ 152 | var mergeTwoLists = function (l1, l2) { 153 | if (l1 === null) { 154 | return l2 155 | } else if (l2 === null) { 156 | return l1 157 | } else if (l1.val < l2.val) { 158 | l1.next = mergeTwoLists(l1.next, l2) 159 | return l1 160 | } else { 161 | l2.next = mergeTwoLists(l1, l2.next) 162 | return l2 163 | } 164 | } 165 | ``` 166 | 167 | ## 53.最大子序和 168 | 169 | https://leetcode-cn.com/problems/maximum-subarray 170 | 171 | ```js 172 | /** 173 | * @param {number[]} nums 174 | * @return {number} 175 | */ 176 | var maxSubArray = function (nums) { 177 | let ans = nums[0] 178 | let sum = 0 179 | for (const num of nums) { 180 | if (sum > 0) { 181 | sum += num 182 | } else { 183 | sum = num 184 | } 185 | ans = Math.max(ans, sum) 186 | } 187 | return ans 188 | } 189 | ``` 190 | 191 | ## 70.爬楼梯 192 | 193 | https://leetcode-cn.com/problems/climbing-stairs 194 | 195 | ```js 196 | var climbStairs = function (n) { 197 | let dp = [] 198 | dp[0] = 1 199 | dp[1] = 1 200 | for (let i = 2; i <= n; i++) { 201 | dp[i] = dp[i - 1] + dp[i - 2] 202 | } 203 | return dp[n] 204 | } 205 | ``` 206 | 207 | ## 101.对称二叉树 208 | 209 | https://leetcode-cn.com/problems/symmetric-tree 210 | 211 | ```js 212 | /**递归 代码 213 | * @param {TreeNode} root 214 | * @return {boolean} 215 | */ 216 | var isSymmetric = function (root) { 217 | const check = (left, right) => { 218 | if (left == null && right == null) { 219 | return true 220 | } 221 | if (left && right) { 222 | return ( 223 | left.val === right.val && 224 | check(left.left, right.right) && 225 | check(left.right, right.left) 226 | ) 227 | } 228 | return false // 一个子树存在一个不存在,肯定不对称 229 | } 230 | if (root == null) { 231 | // 如果传入的root就是null,对称 232 | return true 233 | } 234 | return check(root.left, root.right) 235 | } 236 | ``` 237 | 238 | ## 112.路径总和 239 | 240 | https://leetcode-cn.com/problems/path-sum 241 | 242 | ```js 243 | var hasPathSum = function (root, targetSum) { 244 | // 深度优先遍历 245 | if (root === null) { 246 | //1.刚开始遍历时 247 | //2.递归中间 说明该节点不是叶子节点 248 | return false 249 | } 250 | if (root.left === null && root.right === null) { 251 | return root.val - targetSum === 0 252 | } 253 | // 拆分成两个子树 254 | return ( 255 | hasPathSum(root.left, targetSum - root.val) || 256 | hasPathSum(root.right, targetSum - root.val) 257 | ) 258 | } 259 | ``` 260 | 261 | ## 136.只出现一次的数字 262 | 263 | https://leetcode-cn.com/problems/single-number 264 | 265 | ```js 266 | /** 267 | * @param {number[]} nums 268 | * @return {number} 269 | */ 270 | var singleNumber = function (nums) { 271 | let ans = '' 272 | for (const num of nums) { 273 | ans ^= num 274 | console.log(ans) 275 | } 276 | return ans 277 | } 278 | ``` 279 | 280 | ## 155.最小栈 281 | 282 | https://leetcode-cn.com/problems/min-stack 283 | 284 | ```js 285 | var MinStack = function () { 286 | this.x_stack = [] 287 | this.min_stack = [Infinity] 288 | } 289 | 290 | MinStack.prototype.push = function () { 291 | this.x_stack.push(x) 292 | this.min_stack.push(Math.min(this.min_stack[this.min_stack.length - 1], x)) 293 | } 294 | MinStack.prototype.pop = function () { 295 | this.x_stack.pop() 296 | this.min_stack.pop() 297 | } 298 | MinStack.prototype.top = function () { 299 | return this.x_stack[this.x_stack.length - 1] 300 | } 301 | MinStack.prototype.getMin = function () { 302 | return this.min_stack[this.min_stack.length - 1] 303 | } 304 | ``` 305 | 306 | ## 160.相交链表 307 | 308 | https://leetcode-cn.com/problems/intersection-of-two-linked-lists 309 | 310 | ### 方法 1:暴力法 311 | 312 | ##### 思路 313 | 314 | 对于链表 A 的每个节点,都去链表 B 中遍历一遍找看看有没有相同的节点。 315 | 316 | ##### 复杂度 317 | 318 | 时间复杂度:O(M \* N)O(M∗N), M, N 分别为两个链表的长度。 319 | 空间复杂度:O(1)O(1)。 320 | 321 | ```js 322 | var getIntersectionNode = function (headA, headB) { 323 | if (!headA || !headB) return null 324 | let pA = headA 325 | while (pA) { 326 | let pB = headB 327 | while (pB) { 328 | if (pA === pB) return pA 329 | pB = pB.next 330 | } 331 | pA = pA.next 332 | } 333 | } 334 | ``` 335 | 336 | ### 方法 2:哈希表 337 | 338 | ##### 思路 339 | 340 | 先遍历一遍链表 A,用哈希表把每个节点都记录下来(注意要存节点引用而不是节点值)。 341 | 再去遍历链表 B,找到在哈希表中出现过的节点即为两个链表的交点。 342 | 343 | ##### 复杂度 344 | 345 | 时间复杂度:O(M + N), M, N 分别为两个链表的长度。 346 | 空间复杂度:O(N),N 为链表 A 的长度。 347 | 348 | ```js 349 | var getIntersectionNode = function (headA, headB) { 350 | if (!headA || !headB) return null 351 | const hashmap = new Map() 352 | let pA = headA 353 | while (pA) { 354 | hashmap.set(pA, 1) 355 | pA = pA.next 356 | } 357 | let pB = headB 358 | while (pB) { 359 | if (hashmap.has(pB)) return pB 360 | pB = pB.next 361 | } 362 | } 363 | ``` 364 | 365 | ### 方法 3:双指针 366 | 367 | ##### 如果链表没有交点 368 | 369 | 两个链表长度一样,第一次遍历结束后 pA 和 pB 都是 null,结束遍历 370 | 两个链表长度不一样,两次遍历结束后 pA 和 pB 都是 null,结束遍历 371 | 372 | ##### 复杂度 373 | 374 | 时间复杂度:O(M + N) , M, N 分别为两个链表的长度。 375 | 空间复杂度:O(1)。 376 | 377 | ```js 378 | /** 379 | * Definition for singly-linked list. 380 | * function ListNode(val) { 381 | * this.val = val; 382 | * this.next = null; 383 | * } 384 | */ 385 | 386 | /** 387 | * @param {ListNode} headA 388 | * @param {ListNode} headB 389 | * @return {ListNode} 390 | */ 391 | var getIntersectionNode = function (headA, headB) { 392 | if (!headA || !headB) return null 393 | 394 | let pA = headA, 395 | pB = headB 396 | while (pA !== pB) { 397 | pA = pA === null ? headB : pA.next 398 | pB = pB === null ? headA : pB.next 399 | } 400 | return pA 401 | } 402 | ``` 403 | 404 | ## 206.反转链表 405 | 406 | ```js 407 | var reverseList = function (head) { 408 | let prev = null 409 | cur = head 410 | while (cur) { 411 | const next = cur.next 412 | cur.next = prev 413 | prev = cur 414 | cur = next 415 | } 416 | return prev 417 | } 418 | ``` 419 | 420 | ### 方案 2 421 | 422 | ```js 423 | var reverseList = function (head) { 424 | let prev = null 425 | cur = head 426 | while (cur) { 427 | const next = cur.next 428 | cur.next = prev 429 | prev = cur 430 | cur = next 431 | } 432 | return prev 433 | } 434 | ``` 435 | 436 | ## 234.回文链表 437 | 438 | https://leetcode-cn.com/problems/palindrome-linked-list 439 | 440 | ```js 441 | const isPalindrome = (head) => { 442 | const vals = [] 443 | while (head) { 444 | // 丢进数组里 445 | vals.push(head.val) 446 | head = head.next 447 | } 448 | let start = 0, 449 | end = vals.length - 1 // 双指针 450 | while (start < end) { 451 | if (vals[start] != vals[end]) { 452 | // 理应相同,如果不同,不是回文 453 | return false 454 | } 455 | start++ 456 | end-- // 双指针移动 457 | } 458 | return true // 循环结束也没有返回false,说明是回文 459 | } 460 | ``` 461 | 462 | ## 543.二叉树的直径 463 | 464 | https://leetcode-cn.com/problems/diameter-of-binary-tree 465 | 466 | ##### 方法 1 467 | 468 | ```js 469 | var diameterOfBinaryTree = function (root) { 470 | // 默认为1是因为默认了根节点自身的路径长度 471 | let ans = 1 472 | function depth(rootNode) { 473 | if (!rootNode) { 474 | // 如果不存在根节点,则深度为0 475 | return 0 476 | } 477 | // 递归,获取左子树的深度 478 | let left = depth(rootNode.left) 479 | // 递归,获取右子树的深度 480 | let right = depth(rootNode.right) 481 | /* 关键点1 482 | L+R+1的公式是如何而来? 483 | 等同于:左子树深度(节点个数) + 右子树深度(节点个数) + 1个根节点 484 | 便是这株二叉树从最左侧叶子节点到最右侧叶子节点的最长路径 485 | 类似于平衡二叉树的最小值节点到最大值节点的最长路径 486 | 之所以+1是因为需要经过根节点 487 | */ 488 | // 获取该树的最长路径和现有最长路径中最大的那个 489 | ans = Math.max(ans, left + right + 1) 490 | /* 关键点2 491 | 已知根节点的左右子树的深度, 492 | 则,左右子树深度的最大值 + 1, 493 | 便是以根节点为数的最大深度*/ 494 | return Math.max(left, right) + 1 495 | } 496 | depth(root) 497 | // 由于depth函数中已经默认加上数节点的自身根节点路径了,故此处需减1 498 | return ans - 1 499 | } 500 | ``` 501 | 502 | ##### 方法 2 503 | 504 | ```js 505 | function height(node) { 506 | //求树高 507 | if (!node) return 0 508 | return 1 + Math.max(height(node.left), height(node.right)) 509 | } 510 | 511 | var diameterOfBinaryTree = function (root) { 512 | if (!root) return 0 513 | let tempH = height(root.left) + height(root.right) 514 | return Math.max( 515 | tempH, 516 | diameterOfBinaryTree(root.left), 517 | diameterOfBinaryTree(root.right) 518 | ) 519 | } 520 | ``` 521 | 522 | 注:部分题解参考 LeetCode 最佳题解,有需要的同学可以自行去 LeetCode 官网查看。 523 | 524 | ## 617.合并二叉树 525 | 526 | https://leetcode-cn.com/problems/merge-two-binary-trees/ 527 | 528 | ```js 529 | var mergeTrees = function (root1, root2) { 530 | if (root1 == null && root2) { 531 | return root2 532 | } else if (root2 == null && root1) { 533 | return root1 534 | } else if (root1 && root2) { 535 | root1.val = root1.val + root2.val 536 | //递归合并每一个节点 537 | root1.left = mergeTrees(root1.left, root2.left) 538 | root1.right = mergeTrees(root1.right, root2.right) 539 | } 540 | return root1 541 | } 542 | ``` 543 | -------------------------------------------------------------------------------- /webpack/loader/readme.md: -------------------------------------------------------------------------------- 1 | 本文会带你简单的认识一下 webpack 的 loader,动手实现一个利用 md 转成抽象语法树,再转成 html 字符串的 loader。顺便简单的了解一下几个 style-loader,vue-loader,babel-loader 的源码以及工作流程。 2 | 3 | ### loader 简介 4 | 5 | webpack 允许我们使用 loader 来处理文件,loader 是一个导出为 function 的 node 模块。可以将匹配到的文件进行一次转换,同时 loader 可以链式传递。 6 | loader 文件处理器是一个 CommonJs 风格的函数,该函数接收一个 String/Buffer 类型的入参,并返回一个 String/Buffer 类型的返回值。 7 | 8 | ### loader 的配置的两种形式 9 | 10 | 方案 1: 11 | 12 | ```js 13 | 14 | // webpack.config.js 15 | module.exports = { 16 | ... 17 | module: { 18 | rules: [{ 19 | test: /.vue$/, 20 | loader: 'vue-loader' 21 | }, { 22 | test: /.scss$/, 23 | // 先经过 sass-loader,然后将结果传入 css-loader,最后再进入 style-loader。 24 | use: [ 25 | 'style-loader',//从JS字符串创建样式节点 26 | 'css-loader',// 把 CSS 翻译成 CommonJS 27 | { 28 | loader: 'sass-loader', 29 | options: { 30 | data: '$color: red;'// 把 Sass 编译成 CSS 31 | } 32 | } 33 | ] 34 | }] 35 | } 36 | ... 37 | } 38 | ``` 39 | 40 | 方法 2(右到左地被调用) 41 | 42 | ```js 43 | // module 44 | import Styles from 'style-loader!css-loader?modules!./styles.css' 45 | ``` 46 | 47 | 当链式调用多个 loader 的时候,请记住它们会以相反的顺序执行。取决于数组写法格式,从右向左或者从下向上执行。像流水线一样,挨个处理每个 loader,前一个 loader 的结果会传递给下一个 loader,最后的 Loader 将处理后的结果以 String 或 Buffer 的形式返回给 compiler。 48 | 49 | ### 使用 loader-utils 能够编译 loader 的配置,还可以通过 schema-utils 进行验证 50 | 51 | ```js 52 | import { getOptions } from 'loader-utils' 53 | import { validateOptions } from 'schema-utils' 54 | const schema = { 55 | // ... 56 | } 57 | export default function(content) { 58 | // 获取 options 59 | const options = getOptions(this) 60 | // 检验loader的options是否合法 61 | validateOptions(schema, options, 'Demo Loader') 62 | 63 | // 在这里写转换 loader 的逻辑 64 | // ... 65 | return content 66 | } 67 | ``` 68 | 69 | - content: 表示源文件字符串或者 buffer 70 | - map: 表示 sourcemap 对象 71 | - meta: 表示元数据,辅助对象 72 | 73 | ### 同步 loader 74 | 75 | 同步 loader,我们可以通过`return`和`this.callback`返回输出的内容 76 | 77 | ```js 78 | module.exports = function(content, map, meta) { 79 | //一些同步操作 80 | outputContent = someSyncOperation(content) 81 | return outputContent 82 | } 83 | ``` 84 | 85 | 如果返回结果只有一个,也可以直接使用 return 返回结果。但是,如果有些情况下还需要返回其他内容,如 sourceMap 或是 AST 语法树,这个时候可以借助 webpack 提供的 api `this.callback` 86 | 87 | ```js 88 | module.exports = function(content, map, meta) { 89 | this.callback( 90 | err: Error | null, 91 | content: string | Buffer, 92 | sourceMap?: SourceMap, 93 | meta?: any 94 | ); 95 | return; 96 | } 97 | ``` 98 | 99 | 第一个参数必须是 Error 或者 null 100 | 第二个参数是一个 string 或者 Buffer。 101 | 可选的:第三个参数必须是一个可以被这个模块解析的 source map。 102 | 可选的:第四个选项,会被 webpack 忽略,可以是任何东西【可以将抽象语法树(abstract syntax tree - AST)(例如 ESTree)作为第四个参数(meta),如果你想在多个 loader 之间共享通用的 AST,这样做有助于加速编译时间。】。 103 | 104 | ### 异步 loader 105 | 106 | 异步 loader,使用 this.async 来获取 callback 函数。 107 | 108 | ```js 109 | // 让 Loader 缓存 110 | module.exports = function(source) { 111 | var callback = this.async() 112 | // 做异步的事 113 | doSomeAsyncOperation(content, function(err, result) { 114 | if (err) return callback(err) 115 | callback(null, result) 116 | }) 117 | } 118 | ``` 119 | 120 | 详情请参考[官网 API](https://www.webpackjs.com/api/loaders/#%E5%90%8C%E6%AD%A5-loader) 121 | 122 | ### 开发一个简单的 md-loader 123 | 124 | ```js 125 | const marked = require('marked') 126 | 127 | const loaderUtils = require('loader-utils') 128 | module.exports = function(content) { 129 | this.cacheable && this.cacheable() 130 | const options = loaderUtils.getOptions(this) 131 | try { 132 | marked.setOptions(options) 133 | return marked(content) 134 | } catch (err) { 135 | this.emitError(err) 136 | return null 137 | } 138 | } 139 | ``` 140 | 141 | 上述的例子是通过现成的插件把 markdown 文件里的 content 转成 html 字符串,但是如果没有这个插件,改怎么做呢?这个情况下,我们可以考虑另外一种解法,借助 AST 语法树,来协助我们更加便捷地操作转换。 142 | 143 | ### 利用 AST 作源码转换 144 | 145 | `markdown-ast`是将 markdown 文件里的 content 转成数组形式的抽象语法树节点,操作 AST 语法树远比操作字符串要简单、方便得多: 146 | 147 | ```js 148 | const md = require('markdown-ast') //通过正则的方法把字符串处理成直观的AST语法树 149 | module.exports = function(content) { 150 | this.cacheable && this.cacheable() 151 | const options = loaderUtils.getOptions(this) 152 | try { 153 | console.log(md(content)) 154 | const parser = new MdParser(content) 155 | return parser.data 156 | } catch (err) { 157 | console.log(err) 158 | return null 159 | } 160 | } 161 | ``` 162 | 163 | **md 通过正则切割的方法转成抽象语树** 164 | ![md2ast](https://cdn.6fed.com/github/webpack/loader/md2ast.jpg) 165 | 166 | ```js 167 | const md = require('markdown-ast') //md通过正则匹配的方法把buffer转抽象语法树 168 | const hljs = require('highlight.js') //代码高亮插件 169 | // 利用 AST 作源码转换 170 | class MdParser { 171 | constructor(content) { 172 | this.data = md(content) 173 | console.log(this.data) 174 | this.parse() 175 | } 176 | parse() { 177 | this.data = this.traverse(this.data) 178 | } 179 | traverse(ast) { 180 | console.log('md转抽象语法树操作', ast) 181 | let body = '' 182 | ast.map((item) => { 183 | switch (item.type) { 184 | case 'bold': 185 | case 'break': 186 | case 'codeBlock': 187 | const highlightedCode = hljs.highlight(item.syntax, item.code).value 188 | body += highlightedCode 189 | break 190 | case 'codeSpan': 191 | case 'image': 192 | case 'italic': 193 | case 'link': 194 | case 'list': 195 | item.type = item.bullet === '-' ? 'ul' : 'ol' 196 | if (item.type !== '-') { 197 | item.startatt = ` start=${item.indent.length}` 198 | } else { 199 | item.startatt = '' 200 | } 201 | body += 202 | '<' + 203 | item.type + 204 | item.startatt + 205 | '>\n' + 206 | this.traverse(item.block) + 207 | '\n' 210 | break 211 | case 'quote': 212 | let quoteString = this.traverse(item.block) 213 | body += '
\n' + quoteString + '
\n' 214 | break 215 | case 'strike': 216 | case 'text': 217 | case 'title': 218 | body += `${item.text}` 219 | break 220 | default: 221 | throw Error( 222 | 'error', 223 | `No corresponding treatment when item.type equal${item.type}` 224 | ) 225 | } 226 | }) 227 | return body 228 | } 229 | } 230 | ``` 231 | 232 | [完整的代码参考这里](https://github.com/6fedcom/fe-blog/blob/master/webpack/loader/loaders/md-loader.js) 233 | 234 | **ast 抽象语法数转成 html 字符串** 235 | ![md2html](https://cdn.6fed.com/github/webpack/loader/md2html.png) 236 | 237 | ### loader 的一些开发技巧 238 | 239 | 1. 尽量保证一个 loader 去做一件事情,然后可以用不同的 loader 组合不同的场景需求 240 | 2. 开发的时候不应该在 loader 中保留状态。loader 必须是一个无任何副作用的纯函数,loader 支持异步,因此是可以在 loader 中有 I/O 操作的。 241 | 3. 模块化:保证 loader 是模块化的。loader 生成模块需要遵循和普通模块一样的设计原则。 242 | 4. 合理的使用缓存 243 | 合理的缓存能够降低重复编译带来的成本。loader 执行时默认是开启缓存的,这样一来, webpack 在编译过程中执行到判断是否需要重编译 loader 实例的时候,会直接跳过 rebuild 环节,节省不必要重建带来的开销。 244 | 但是当且仅当有你的 loader 有其他不稳定的外部依赖(如 I/O 接口依赖)时,可以关闭缓存: 245 | 246 | ```js 247 | this.cacheable && this.cacheable(false) 248 | ``` 249 | 250 | 5. `loader-runner` 是一个非常实用的工具,用来开发、调试 loader,它允许你不依靠 webpack 单独运行 loader 251 | `npm install loader-runner --save-dev` 252 | 253 | ```js 254 | // 创建 run-loader.js 255 | const fs = require('fs') 256 | const path = require('path') 257 | const { runLoaders } = require('loader-runner') 258 | 259 | runLoaders( 260 | { 261 | resource: './readme.md', 262 | loaders: [path.resolve(__dirname, './loaders/md-loader')], 263 | readResource: fs.readFile.bind(fs), 264 | }, 265 | (err, result) => (err ? console.error(err) : console.log(result)) 266 | ) 267 | ``` 268 | 269 | 执行 `node run-loader` 270 | 271 | ### 认识更多的 loader 272 | 273 | ##### style-loader 源码简析 274 | 275 | 作用:把样式插入到 DOM 中,方法是在 head 中插入一个 style 标签,并把样式写入到这个标签的 innerHTML 里 276 | 看下源码。 277 | 278 | 先去掉 option 处理代码,这样就比较清晰明了了 279 | ![style-loader](https://cdn.6fed.com/github/webpack/loader/style-loader.png) 280 | 返回一段 js 代码,通过 require 来获取 css 内容,再通过 addStyle 的方法把 css 插入到 dom 里 281 | 自己实现一个简陋的`style-loader.js` 282 | 283 | ```js 284 | module.exports.pitch = function (request) { 285 | const {stringifyRequest}=loaderUtils 286 | var result = [ 287 | //1. 获取css内容。2.// 调用addStyle把CSS内容插入到DOM中(locals为true,默认导出css) 288 | 'var content=require(' + stringifyRequest(this, '!!' + request) + ')’, 289 | 'require(' + stringifyRequest(this, '!' + path.join(__dirname, "addstyle.js")) + ')(content)’, 290 | 'if(content.locals) module.exports = content.locals’ 291 | ] 292 | return result.join(';') 293 | } 294 | ``` 295 | 296 | 需要说明的是,正常我们都会用 default 的方法,这里用到 pitch 方法。pitch 方法有一个官方的解释在这里 pitching loader。简单的解释一下就是,默认的 loader 都是从右向左执行,用 `pitching loader` 是从左到右执行的。 297 | 298 | ```js 299 | { 300 | test: /\.css$/, 301 | use: [ 302 | { loader: "style-loader" }, 303 | { loader: "css-loader" } 304 | ] 305 | } 306 | ``` 307 | 308 | 为什么要先执行`style-loader`呢,因为我们要把`css-loader`拿到的内容最终输出成 CSS 样式中可以用的代码而不是字符串。 309 | 310 | `addstyle.js` 311 | 312 | ```js 313 | module.exports = function(content) { 314 | let style = document.createElement('style') 315 | style.innerHTML = content 316 | document.head.appendChild(style) 317 | } 318 | ``` 319 | 320 | ##### babel-loader 源码简析 321 | 322 | 首先看下跳过 loader 的配置处理,看下 babel-loader 输出 323 | ![babel-loader-console](https://cdn.6fed.com/github/webpack/loader/babel-loader-console.png) 324 | 上图我们可以看到是输出`transpile(source, options)`的 code 和 map 325 | 再来看下`transpile`方法做了啥 326 | ![babel-loader-transpile](https://cdn.6fed.com/github/webpack/loader/babel-loader-transpile.png) 327 | babel-loader 是通过 babel.transform 来实现对代码的编译的, 328 | 这么看来,所以我们只需要几行代码就可以实现一个简单的 babel-loader 329 | 330 | ```js 331 | const babel = require('babel-core') 332 | module.exports = function(source) { 333 | const babelOptions = { 334 | presets: ['env'], 335 | } 336 | return babel.transform(source, babelOptions).code 337 | } 338 | ``` 339 | 340 | ##### vue-loader 源码简析 341 | 342 | vue 单文件组件(简称 sfc) 343 | 344 | ```vue 345 | 350 | 359 | 364 | ``` 365 | 366 | webpack 配置 367 | 368 | ```js 369 | const VueloaderPlugin = require('vue-loader/lib/plugin') 370 | module.exports = { 371 | ... 372 | module: { 373 | rules: [ 374 | ... 375 | { 376 | test: /\.vue$/, 377 | loader: 'vue-loader' 378 | } 379 | ] 380 | } 381 | 382 | plugins: [ 383 | new VueloaderPlugin() 384 | ] 385 | ... 386 | } 387 | ``` 388 | 389 | **VueLoaderPlugin** 390 | 作用:将在 webpack.config 定义过的其它规则复制并应用到 .vue 文件里相应语言的块中。 391 | 392 | `plugin-webpack4.js` 393 | 394 | ```js 395 | const vueLoaderUse = vueUse[vueLoaderUseIndex] 396 | vueLoaderUse.ident = 'vue-loader-options' 397 | vueLoaderUse.options = vueLoaderUse.options || {} 398 | // cloneRule会修改原始rule的resource和resourceQuery配置, 399 | // 携带特殊query的文件路径将被应用对应rule 400 | const clonedRules = rules.filter((r) => r !== vueRule).map(cloneRule) 401 | 402 | // global pitcher (responsible for injecting template compiler loader & CSS 403 | // post loader) 404 | const pitcher = { 405 | loader: require.resolve('./loaders/pitcher'), 406 | resourceQuery: (query) => { 407 | const parsed = qs.parse(query.slice(1)) 408 | return parsed.vue != null 409 | }, 410 | options: { 411 | cacheDirectory: vueLoaderUse.options.cacheDirectory, 412 | cacheIdentifier: vueLoaderUse.options.cacheIdentifier, 413 | }, 414 | } 415 | 416 | // 更新webpack的rules配置,这样vue单文件中的各个标签可以应用clonedRules相关的配置 417 | compiler.options.module.rules = [pitcher, ...clonedRules, ...rules] 418 | ``` 419 | 420 | 获取`webpack.config.js`的 rules 项,然后复制 rules,为携带了`?vue&lang=xx...query`参数的文件依赖配置 xx 后缀文件同样的 loader 421 | 为 Vue 文件配置一个公共的 loader:pitcher 422 | 将`[pitchLoder, ...clonedRules, ...rules]`作为 webapck 新的 rules。 423 | 424 | 再看一下`vue-loader`结果的输出 425 | ![vue-loader-result](https://cdn.6fed.com/github/webpack/loader/vue-loader-result.png) 426 | 当引入一个 vue 文件后,vue-loader 是将 vue 单文件组件进行 parse,获取每个 block 的相关内容,将不同类型的 block 组件的 Vue SFC 转化成 js module 字符串。 427 | 428 | ```js 429 | // vue-loader使用`@vue/component-compiler-utils`将SFC源码解析成SFC描述符,,根据不同 module path 的类型(query 参数上的 type 字段)来抽离 SFC 当中不同类型的 block。 430 | const { parse } = require('@vue/component-compiler-utils') 431 | // 将单个*.vue文件内容解析成一个descriptor对象,也称为SFC(Single-File Components)对象 432 | // descriptor包含template、script、style等标签的属性和内容,方便为每种标签做对应处理 433 | const descriptor = parse({ 434 | source, 435 | compiler: options.compiler || loadTemplateCompiler(loaderContext), 436 | filename, 437 | sourceRoot, 438 | needMap: sourceMap, 439 | }) 440 | 441 | // 为单文件组件生成唯一哈希id 442 | const id = hash(isProduction ? shortFilePath + '\n' + source : shortFilePath) 443 | // 如果某个style标签包含scoped属性,则需要进行CSS Scoped处理 444 | const hasScoped = descriptor.styles.some((s) => s.scoped) 445 | ``` 446 | 447 | 然后下一步将新生成的 js module 加入到 webpack 的编译环节,即对这个 js module 进行 AST 的解析以及相关依赖的收集过程。 448 | 449 | 来看下源码是怎么操作不同 type 类型(`template/script/style`)的,selectBlock 方法内部主要就是根据不同的 type 类型,来获取 descriptor 上对应类型的 content 内容并传入到下一个 loader 处理 450 | ![vue-loader-code](https://cdn.6fed.com/github/webpack/loader/vue-loader-code.png) 451 | 这三段代码可以把不同 type 解析成一个 import 的字符串 452 | 453 | ```js 454 | import { 455 | render, 456 | staticRenderFns, 457 | } from './App.vue?vue&type=template&id=7ba5bd90&' 458 | import script from './App.vue?vue&type=script&lang=js&' 459 | export * from './App.vue?vue&type=script&lang=js&' 460 | import style0 from './App.vue?vue&type=style&index=0&lang=scss&scope=true&' 461 | ``` 462 | 463 | **总结一下 vue-loader 的工作流程** 464 | 465 | 1. 注册`VueLoaderPlugin` 466 | 在插件中,会复制当前项目 webpack 配置中的 rules 项,当资源路径包含 query.lang 时通过 resourceQuery 匹配相同的 rules 并执行对应 loader 时 467 | 插入一个公共的 loader,并在 pitch 阶段根据 query.type 插入对应的自定义 loader 468 | 2. 加载\*.vue 时会调用`vue-loader`,.vue 文件被解析成一个`descriptor`对象,包含`template、script、styles`等属性对应各个标签, 469 | 对于每个标签,会根据标签属性拼接`src?vue&query`引用代码,其中 src 为单页面组件路径,query 为一些特性的参数,比较重要的有 lang、type 和 scoped 470 | 如果包含 lang 属性,会匹配与该后缀相同的 rules 并应用对应的`loaders` 471 | 根据 type 执行对应的自定义 loader,`template`将执行`templateLoader`、`style`将执行`stylePostLoader` 472 | 3. 在`templateLoader`中,会通过`vue-template-compiler`将`template`转换为`render`函数,在此过程中, 473 | 会将传入的`scopeId`追加到每个标签的上,最后作为 vnode 的配置属性传递给`createElemenet`方法, 474 | 在 render 函数调用并渲染页面时,会将`scopeId`属性作为原始属性渲染到页面上 475 | 4. 在`stylePostLoader`中,通过 PostCSS 解析 style 标签内容 476 | 477 | ### 文中涉及的 demo 源码 478 | 479 | [点击 github 仓库](https://github.com/6fedcom/fe-blog/tree/master/webpack/loader) 480 | 481 | ### 参考文献 482 | 483 | 1. [webpack 官网 loader api](https://www.webpackjs.com/api/loaders/) 484 | 2. [手把手教你写 webpack yaml-loader](https://mp.weixin.qq.com/s/gTAq5K5pziPT4tmiGqw5_w) 485 | 3. [言川-webpack 源码解析系列](https://github.com/lihongxun945/diving-into-webpack) 486 | 4. [从 vue-loader 源码分析 CSS Scoped 的实现](https://juejin.im/post/5d8627355188253f3a70c22c) 487 | --------------------------------------------------------------------------------