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