#2022#2021

数据结构-二叉树

--- title: 数据结构-二叉树 author: powerfulyang date: 2022-11-25 public: true tags: - Binary Tree - data-structure --- ## Binary Tree 1. 二叉树的第i层上节点数最多 2n-1。 2. 高度为k的二叉树中,最多有 2k-1 个节点。 3. 在任意一棵二叉树中,如果终端节点的度为 n,度为2的节点数为 m,则 n=m+1。 4. 二叉树的子树有左右之分,顺序不能颠倒。 5. 若采用连续储存的方式存放二叉树,则节点下标之间的关系: + 若某个节点的下标为 i ,则这个节点的父节点的下标为 i / 2。 + 若某个节点下标为 i ,且节点的度为2,则这个节点的左子节点的下标为 2 \* i + 1 ,右子节点的下标为 2 \* i + 2 。 ## Complete Binary Tree 树最后一层没有任何子节点,其余每一层的所有节点都有2个子节点。 1. 满二叉树的第i层的节点数为 2n-1 个。 2. 深度为k的满二叉树必有 2k-1 个节点 ,叶子数为 2k-1。 3. 满二叉树中不存在度为1的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层。 4. 具有n个节点的满二叉树的深度为 log2(n+1)。 ## Full Binary Tree 如果二叉树的深度为k,则除第k层外其余所有层节点的度都为2,且叶子节点从左到右依次存在。也即是,将满二叉树的最后一层从左到右依次删除若干节点就得到完全二叉树。满二叉树是一棵特殊的完全二叉树,但完全二叉树不一定是满二叉树。 1. 满二叉树是一棵特殊的完全二叉树,但完全二叉树不一定是满二叉树。 2. 在满二叉树中最下一层,连续删除若干个节点得到完全二叉树。 3. 在完全二叉树中,若某个节点没有左子树,则一定没有有子树。 4. 若采用连续储存的方式存放二叉树,则节点下标之间的关系(根节点下标为0): + 若某个节点的下标为 i ,则这个节点的父节点的下标为 i / 2。 + 若某个节点下标为 i ,且节点的度为2,则这个节点的左子节点的下标为 2 * i + 1 ,右子节点的下标为 2 * i + 2 。 + 除了根节点外,左子树的下标为基数,右子树的下标为偶数。
powerfulyangFri, Nov 25, 2022 2:31 PM

数据结构-堆

--- title: 数据结构-堆 author: powerfulyang date: 2022-11-25 public: true posterId: 47823 tags: - Heap - data-structure --- ## What is Heap? A Heap is a special Tree-based data structure in which the tree is a complete binary tree. + Heap is array-based data structure. + It is a complete binary tree. + Priority Queue is a heap. ## Types of Heap Generally, Heaps can be of two types: 1. **Max-Heap**: In a Max-Heap the key present at the root node must be greatest among the keys present at all of it’s children. The same property must be recursively true for all sub-trees in that Binary Tree. 2. **Min-Heap**: In a Min-Heap the key present at the root node must be minimum among the keys present at all of it’s children. The same property must be recursively true for all sub-trees in that Binary Tree. ## Implementation ```typescript class MaxHeap { private _heap: number[] = []; private swap(index1: number, index2: number) { [this._heap[index1], this._heap[index2]] = [this._heap[index2], this._heap[index1]]; } private heapifyUp() { // 重新排列 let lastItem = this._heap.length - 1; while (lastItem > 0) { // 保持 parentElement >= currentElemnt // heap is a complete binary tree // parentIndex = Math.floor((index - 1) / 2); const parentIndex = Math.floor((index - 1) / 2); if (this._heap[lastItem] > this._heap[parentIndex]) { this.swap(lastItem, parentIndex); lastItem = parentIndex; } else { lastItem = 0; } } } offer(element: number) { this._heap.push(element); this.heapifyUp(); } heapifyDown() { let index = 0; // complete binary tree // leftChildIndex = i * 2 + 1 // rightChildIndex = i * 2 + 2 while (index < this._heap.length) { const leftChildIndex = index * 2 + 1; const rightChildIndex = index * 2 + 2; let maxChildIndex = leftChildIndex; if (rightChildIndex < this._heap.length && this._heap[rightChildIndex] > this._heap[leftChildIndex]) { maxChildIndex = rightChildIndex; } if (this._heap[index] < this._heap[maxChildIndex]) { // 比较父节点和子节点的值,如果父节点的值比子节点的值小,则交换 this.swap(index, maxChildIndex); index = maxChildIndex; // 将子节点的索引赋值给循环的索引 } else { break; } } } poll() { const result = this._heap[0]; // 将最后一个元素放到第一个元素的位置,然后再弹出最后一个元素 // 维持 complete binary tree this._heap[0] = this._heap.pop(); // 把堆顶元素移动下去 this.heapifyDown(); return result; } } ```
powerfulyangFri, Nov 25, 2022 2:23 PM

阅读 React 官方文档 Part-3

--- title: 阅读 React 官方文档 Part-3 author: powerfulyang date: 2022-11-22 public: true tags: - React - Document - Diff --- ## [Diffing 算法](https://zh-hans.reactjs.org/docs/reconciliation.html#the-diffing-algorithm) ### 对比不同类型的元素 当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树。举个例子,当一个元素从 `<a>` 变成 `<img>`,从 `<Article>` 变成 `<Comment>`,或从 `<Button>` 变成 `<div>` 都会触发一个完整的重建流程。 当卸载一棵树时,对应的 DOM 节点也会被销毁。组件实例将执行 `componentWillUnmount()` 方法。当建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中。组件实例将执行 `UNSAFE_componentWillMount()` 方法,紧接着 `componentDidMount()` 方法。所有与之前的树相关联的 state 也会被销毁。 在根节点以下的组件也会被卸载,它们的状态会被销毁。比如,当比对以下更变时: ```jsx <div> <Counter /> </div> <span> <Counter /> </span> ``` React 会销毁 `Counter` 组件并且重新装载一个新的组件。 > **注意:** > > 这些方法被认为是过时的,在新的代码中应该[避免使用它们](https://zh-hans.reactjs.org/blog/2018/03/27/update-on-async-rendering.html): > > * `UNSAFE_componentWillMount()` ### 对比同一类型的元素 当对比两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。 ### 对比同类型的组件元素 当一个组件更新时,组件实例会保持不变,因此可以在不同的渲染时保持 state 一致。React 将更新该组件实例的 props 以保证与最新的元素保持一致,并且调用该实例的 `UNSAFE_componentWillReceiveProps()`、`UNSAFE_componentWillUpdate()` 以及 `componentDidUpdate()` 方法。 下一步,调用 `render()` 方法,diff 算法将在之前的结果以及新的结果中进行递归。 ### 对子节点进行递归 默认情况下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个 mutation。 在子元素列表末尾新增元素时,更新开销比较小。比如: ```jsx <ul> <li>first</li> <li>second</li> </ul> <ul> <li>first</li> <li>second</li> <li>third</li> </ul> ``` React 会先匹配两个 `<li>first</li>` 对应的树,然后匹配第二个元素 `<li>second</li>` 对应的树,最后插入第三个元素的 `<li>third</li>` 树。 如果只是简单的将新增元素插入到表头,那么更新开销会比较大。比如: ```jsx <ul> <li>Duke</li> <li>Villanova</li> </ul> <ul> <li>Connecticut</li> <li>Duke</li> <li>Villanova</li> </ul> ``` React 并不会意识到应该保留 `<li>Duke</li>` 和 `<li>Villanova</li>`,而是会重建每一个子元素。这种情况会带来性能问题。 ## Conclusion React 可以在每个 action 之后对整个应用进行重新渲染,得到的最终结果也会是一样的。在此情境下,重新渲染表示在所有组件内调用 render 方法,这不代表 React 会卸载或装载它们。React 只会基于以上提到的规则来决定如何进行差异的合并。 我们定期优化启发式算法,让常见用例更高效地执行。在当前的实现中,可以理解为一棵子树能在其兄弟之间移动,但不能移动到其他位置。在这种情况下,算法会重新渲染整棵子树。 由于 React 依赖启发式算法,因此当以下假设没有得到满足,性能会有所损耗。 1. 该算法不会尝试匹配不同组件类型的子树。如果你发现你在两种不同类型的组件中切换,但输出非常相似的内容,建议把它们改成同一类型。在实践中,我们没有遇到这类问题。 2. Key 应该具有稳定,可预测,以及列表内唯一的特质。不稳定的 key(比如通过 `Math.random()` 生成的)会导致许多组件实例和 DOM 节点被不必要地重新创建,这可能导致性能下降和子组件中的状态丢失。
powerfulyangTue, Nov 22, 2022 4:48 PM

阅读 React 官方文档 Part-2

--- title: 阅读 React 官方文档 Part-2 author: powerfulyang date: 2022-11-21 public: true tags: - React - Document --- ## 事件处理 你必须谨慎对待 JSX 回调函数中的 `this`,在 JavaScript 中,class 的方法默认不会[绑定](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind) `this`。如果你忘记绑定 `this.handleClick` 并把它传入了 `onClick`,当你调用这个函数的时候 `this` 的值为 `undefined`。 这并不是 React 特有的行为;这其实与 [JavaScript 函数工作原理](https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/)有关。通常情况下,如果你没有在方法后面添加 `()`,例如 `onClick={this.handleClick}`,你应该为这个方法绑定 `this`。 如果觉得使用 `bind` 很麻烦,这里有两种方式可以解决。你可以使用 [public class fields 语法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields#public_instance_fields) to correctly bind callbacks: ```jsx class LoggingButton extends React.Component { // This syntax ensures `this` is bound within handleClick. handleClick = () => { console.log('this is:', this); }; render() { return ( <button onClick={this.handleClick}> Click me </button> ); } } ``` 如果你没有使用 class fields 语法,你可以在回调中使用[箭头函数](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions): ```jsx class LoggingButton extends React.Component { handleClick() { console.log('this is:', this); } render() { // 此语法确保 `handleClick` 内的 `this` 已被绑定。 return ( <button onClick={() => this.handleClick()}> Click me </button> ); } } ``` 此语法问题在于每次渲染 `LoggingButton` 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。 ## [React.lazy](https://zh-hans.reactjs.org/docs/code-splitting.html#reactlazy) ### 避免兜底 任何组件都可能因渲染而暂停,甚至是已经展示给用户的组件。为了使屏幕内容始终一致,如果一个已经显示的组件暂停,React 必须隐藏它的树,直到最近的 `<Suspense>` 边界。然而,从用户的角度来看,这可能会使人很困惑。 参考这个标签切换的示例: ```jsx import React, { Suspense } from 'react'; import Tabs from './Tabs'; import Glimmer from './Glimmer'; const Comments = React.lazy(() => import('./Comments')); const Photos = React.lazy(() => import('./Photos')); function MyComponent() { const [tab, setTab] = React.useState('photos'); function handleTabSelect(tab) { setTab(tab); }; return ( <div> <Tabs onTabSelect={handleTabSelect} /> <Suspense fallback={<Glimmer />}> {tab === 'photos' ? <Photos /> : <Comments />} </Suspense> </div> ); } ``` 在这个示例中,如果标签从 `'photos'` 切换为 `'comments'`,但 `Comments` 会暂停,用户会看到屏幕闪烁。这符合常理,因为用户不想看到 `'photos'`,而 `Comments` 组件还没有准备好渲染其内容,而 React 为了保证用户体验的一致性,只能显示上面的 `Glimmer`,别无选择。 然而,有时这种用户体验并不可取。特别是在准备新 UI 时,展示 “旧” 的 UI 会体验更好。你可以尝试使用新的 [`startTransition`](https://zh-hans.reactjs.org/docs/react-api.html#starttransition) API 来让 React 实现这一点: ```jsx function handleTabSelect(tab) { startTransition(() => { setTab(tab); }); } ``` 此处代码会告知 React,将标签切换为 `'comments'` 不会标记为紧急更新,而是标记为需要一些准备时间的 [transition](https://zh-hans.reactjs.org/docs/react-api.html#transitions)。然后 React 会保留旧的 UI 并进行交互,当它准备好时,会切换为 `<Comments />`,具体请参阅 [Transitions](https://zh-hans.reactjs.org/docs/react-api.html#transitions) 以了解更多相关信息。 ## Context ### [React.createContext](https://zh-hans.reactjs.org/docs/context.html#reactcreatecontext) ```jsx const MyContext = React.createContext(defaultValue); ``` 创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 `Provider` 中读取到当前的 context 值。 **只有**当组件所处的树中没有匹配到 Provider 时,其 `defaultValue` 参数才会生效。此默认值有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 `undefined` 传递给 Provider 的 value 时,消费组件的 `defaultValue` 不会生效。 ### [Context.Provider](https://zh-hans.reactjs.org/docs/context.html#contextprovider) ```jsx <MyContext.Provider value={/* 某个值 */}> ``` 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。 Provider 接收一个 `value` 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。 当 Provider 的 `value` 值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件(包括 [.contextType](https://zh-hans.reactjs.org/docs/context.html#classcontexttype) 和 [useContext](https://zh-hans.reactjs.org/docs/hooks-reference.html#usecontext))的传播不受制于 `shouldComponentUpdate` 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。 通过新旧值检测来确定变化,使用了与 [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description) 相同的算法。 > **注意** > > 当传递对象给 `value` 时,检测变化的方式会导致一些问题:详见[注意事项](https://zh-hans.reactjs.org/docs/context.html#caveats)。 ## HOC 当你将 HOC 应用于组件时,原始组件将使用容器组件进行包装。这意味着新组件没有原始组件的任何静态方法。 ```jsx // 定义静态函数 WrappedComponent.staticMethod = function() {/*...*/} // 现在使用 HOC const EnhancedComponent = enhance(WrappedComponent); // 增强组件没有 staticMethod typeof EnhancedComponent.staticMethod === 'undefined' // true ``` 为了解决这个问题,你可以在返回之前把这些方法拷贝到容器组件上: ```jsx function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} // 必须准确知道应该拷贝哪些方法 :( Enhance.staticMethod = WrappedComponent.staticMethod; return Enhance; } ``` 但要这样做,你需要知道哪些方法应该被拷贝。你可以使用 [hoist-non-react-statics](https://github.com/mridgway/hoist-non-react-statics) 自动拷贝所有非 React 静态方法: ```jsx import hoistNonReactStatic from 'hoist-non-react-statics'; function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} hoistNonReactStatic(Enhance, WrappedComponent); return Enhance; } ``` 除了导出组件,另一个可行的方案是再额外导出这个静态方法。 ```jsx // 使用这种方式代替... MyComponent.someFunction = someFunction; export default MyComponent; // ...单独导出该方法... export { someFunction }; // ...并在要使用的组件中,import 它们 import MyComponent, { someFunction } from './MyComponent.js'; ```
powerfulyangMon, Nov 21, 2022 3:12 PM

如何写好一个二分查找

--- title: 如何写好一个二分查找 date: 2022-11-14 public: true tags: - BinarySearch - 二分查找 --- 第一个,最基本的二分查找算法: 因为我们初始化 right = nums.length - 1 所以决定了我们的「搜索区间」是 [left, right] 所以决定了 while (left <= right) 同时也决定了 left = mid + 1 和 right = mid - 1 因为我们只需找到一个 target 的索引即可 所以当 nums[mid] == target 时可以立即返回 ```javascript const BinarySearch = (nums: number[], target: number) => { // [left, right] let left = 0; let right = nums.length - 1; while (left <= right) { const mid = left + Math.floor((right - right) / 2); if (target > nums[mid]) { left = mid + 1; } else if (target < nums[mid]) { right = mid - 1; } else { return mid; } } return -1; } ``` 第二个,寻找左侧边界的二分查找: 因为我们初始化 right = nums.length 所以决定了我们的「搜索区间」是 [left, right) 所以决定了 while (left < right) 同时也决定了 left = mid + 1 和 right = mid 因为我们需找到 target 的最左侧索引 所以当 nums[mid] == target 时不要立即返回 而要收紧右侧边界以锁定左侧边界 ```javascript const BinarySearch = (nums: number[], target: number) => { // [left, right) let left = 0; let right = nums.length; while (left < right) { const mid = left + Math.floor((right - left) / 2); if (target > nums[mid]) { left = mid + 1; } else if (target < nums[mid]) { right = mid; } else { right = mid; } } if (nums[left] === target) { return left; } return -1; } ``` 第三个,寻找右侧边界的二分查找: 因为我们初始化 right = nums.length 所以决定了我们的「搜索区间」是 [left, right) 所以决定了 while (left < right) 同时也决定了 left = mid + 1 和 right = mid 因为我们需找到 target 的最右侧索引 所以当 nums[mid] == target 时不要立即返回 而要收紧左侧边界以锁定右侧边界 又因为收紧左侧边界时必须 left = mid + 1 所以最后无论返回 left 还是 right,必须减一 ```javascript const BinarySearch = (nums: number[], target: number) => { // [left, right) let left = 0; let right = nums.length; while (left < right) { const mid = left + Math.floor((right - left) / 2); if (target > nums[mid]) { left = mid + 1; } else if (target < nums[mid]) { right = mid; } else { left = mid + 1; } } if (nums[left - 1] === target) { return left - 1; } return -1; }; ```
powerfulyangMon, Nov 14, 2022 9:56 PM

周报

--- title: 周报 author: powerfulyang date: 2022-10-14 public: true tags: - 周报 - 2022-10-14 --- ## 2022-10-14 ### Concept 1. 关于 vertical-align 的使用,`Line-relative values` 不会改变基线,之后元素不会被影响。`Parent-relative values` 会改变基线,之后的元素默认会按照该元素对齐。 + Parent-relative values + baseline + sub + super + text-top + text-bottom + middle + <length> + <percentage> + Line-relative values + top + bottom 2. 关于 scrollIntoView 的使用。 #### vertical-align inline element 可以使用 vertical-align 竖直对齐 ```codepen <iframe height="300" style="width: 100%;" scrolling="no" title="vertical-align-demo" src="https://codepen.io/powerfulyang/embed/abGrBmY?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/powerfulyang/pen/abGrBmY"> vertical-align-demo</a> by powerfulyang (<a href="https://codepen.io/powerfulyang">@powerfulyang</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> <br/> ``` #### scrollIntoView Syntax: ```js scrollIntoView() scrollIntoView(alignToTop) scrollIntoView(scrollIntoViewOptions) ``` + alignToTop(Optional) + `true`, Corresponds to `scrollIntoViewOptions: {block: "start", inline: "nearest"}`. This is the default value. + `false`, Corresponds to `scrollIntoViewOptions: {block: "end", inline: "nearest"}`. + scrollIntoViewOptions(Optional) + `behavior`(Optional) Defines the transition animation. One of `auto` or `smooth`. Defaults to `auto`. + `block`(Optional) Defines vertical alignment. One of `start`, `center`, `end`, or `nearest`. Defaults to `start`. + `inline`(Optional) Defines horizontal alignment. One of `start`, `center`, `end`, or `nearest`. Defaults to `nearest`. ### 每日一题 #### Tree and List ```js const root = { pId: 0, id: 1, title: '1', children: [ { pId: 1, id: 2, title: '1-1', children: [ { pId: 2, id: 3, title: '1-1-1' } ] }, { pId: 1, id: 4, title: '1-2' } ] } const list = [ { pId: 0, id: 1, title: '1' }, { pId: 1, id: 2, title: '1-1' }, { pId: 2, id: 3, title: '1-1-1' }, { pId: 1, id: 4, title: '1-2' } ] function treeToList(t) { const list = []; const helperQueue = [t]; while (helperQueue.length) { const node = helperQueue.shift(); list.push(node); if (node.children) { helperQueue.push(...node.children); delete node.children; } } return list; } function listToTree(l) { const helperMap = new Map(); let root; for (const item of l) { helperMap.set(item.id, item); if (item.pId === 0) { root = item; } } for (const item of l) { const parent = helperMap.get(item.pId); if(parent) { if(!parent.children) { parent.children = [] } parent.children.push(item) } } return root; } ```
powerfulyangFri, Oct 14, 2022 6:12 PM

疑难杂症

--- title: 疑难杂症 author: powerfulyang date: 2022-10-11 tags: - 难题 - safari image 替换闪烁 --- ## Image, `decoding: async`, `loading: lazy` 用 framer-motion 写一个懒加载图片组件,Safari 上图片从一张模糊的图替换到高清图的过程。 + `decoding="async"` 会导致图片闪烁。 + `loading="lazy"` 会导致高清图不显示。 ## 企业微信登陆不成功 用户企微静态登陆不成功,其他人在他的电脑上登陆无异常,他在其他人电脑登陆无异常。 仅自己在自己电脑登陆突然不正常,以前也正常,那猜肯定是硬件问题。 **答案:C盘今天满过,IT 帮忙清理过,导致现在异常** ## Vary: Origin 导致的跨域问题 详见[浏览器缓存缓存了跨域头导致访问字体等文件异常](https://powerfulyang.com/post/yin-que-shi--Vary:-Origin--dao-zhi-de-huan-cun-cuo-luan-1) ## 时区老大难问题 SSR hydrate 导致的 Server 和 Client 不同,抑或数据库时区导致的问题。**用客户端 format 去解决** ## `white-space: pre` 和 innerHtml 一起使用导致问题 带有 white-sapce: pre 属性的标签,平时由于压缩工具帮忙去掉了空格和换行使我们忽略的问题,但是当使用 innerHtml 的时候`换行、空格`会生效,且 devtools 没有 padding and margin 但是高度就是异常的,让人摸不着头脑。 ```codepen <iframe height="300" style="width: 100%;" scrolling="no" title="white-sapce:pre and innerHtml cause the issue" src="https://codepen.io/powerfulyang/embed/LYmqpdO?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/powerfulyang/pen/LYmqpdO"> white-sapce:pre and innerHtml cause the issue</a> by powerfulyang (<a href="https://codepen.io/powerfulyang">@powerfulyang</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> <br/> ``` ## scrollIntoView 的 BUG `scrollIntoView` on container which has overflow:hidden cause abnormal scroll. ```codepen demo: click on body, see the bug. <iframe height="300" style="width: 100%;" scrolling="no" title="scrollIntoView with `overflow: hidden` cause abnormal scroll" src="https://codepen.io/powerfulyang/embed/GRdedaK?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/powerfulyang/pen/GRdedaK"> scrollIntoView with `overflow: hidden` cause abnormal scroll</a> by powerfulyang (<a href="https://codepen.io/powerfulyang">@powerfulyang</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> ``` 带锚点的链接初始化时,也会出现这种问题。 **不考虑兼容性的解决方案,`overflow: clip` 是最佳解决方案。** ## `判断滚动到底部`时会出现的问题 **在特定屏幕分辨率加缩放的情况下,scollTop 和 clientHeight 出现小数,导致总和可能与 scrollHeight 差零点几。** ```js // 最好使用如下代码兼容下 Math.ceil(this.scrollTop) + Math.ceil(this.clientHeight) >= Math.floor(this.scrollHeight) ```
powerfulyangTue, Oct 11, 2022 4:01 PM

给面试官准备的礼物

--- title: 给面试官准备的礼物 author: powerfulyang date: 2022-10-09 posterId: 47805 tags: - interview --- ## 遇到的手写题 ### 类似 'a.b.c' 转换成对应对象格式 ```typescript /** * 'a.b.c' to {a: {b: {c: null}}} */ export const strToObject = (str: string) => { const arr = str.split('.'); let obj = null; arr.reverse().forEach((item) => { obj = { [item]: obj }; }); return obj; }; ``` ### [类型体操科里化](https://github.com/type-challenges/type-challenges/blob/main/questions/00017-hard-currying-1/README.zh-CN.md) ```typescript type CurryingReturn<F> = F extends (...args: infer A) => infer Return ? A extends [infer G, ...infer Rest] ? (arg: G) => CurryingReturn<(...args: Rest) => Return> : Return : never; declare function Currying<F>(fn: F): CurryingReturn<F>; const curry = Currying((a: number, b: number, c: number) => a + b + c); curry(1)(2)(3); ``` ### [汉明距离总和](https://leetcode.cn/problems/total-hamming-distance/) ```typescript function totalHammingDistance(nums: number[]): number { let ans = 0; let n = nums.length; for (let i = 0; i < 30; i++) { let c = 0; for (const val of nums) { c += (val >> i) & 1; } ans += c * (n - c); } return ans; }; ``` ### [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) ```typescript function maxProfit(prices: number[]): number { let min = prices[0]; let maxProfit = 0; for (const price of prices) { min = Math.min(min, price); maxProfit = Math.max(maxProfit, price - min); } return maxProfit; }; ``` ### [链表随机节点](https://leetcode.cn/problems/linked-list-random-node/) >> [题解](https://leetcode.cn/problems/linked-list-random-node/solution/lian-biao-sui-ji-jie-dian-by-leetcode-so-x6it/) ```typescript /** * Definition for singly-linked list. * class ListNode { * val: number * next: ListNode | null * constructor(val?: number, next?: ListNode | null) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } * } */ class Solution { private head: ListNode = null; constructor(head: ListNode | null) { this.head = head; } getRandom(): number { // 从链表头开始,遍历整个链表,对遍历到的第 i 个节点,随机选择区间 [0,i) 内的一个整数,如果其等于 0,则将答案置为该节点值,否则答案不变。 let i = 1, ans = 0; for (let node = this.head; node != null; node = node.next) { if (Math.floor(Math.random() * i) === 0) { // 1/i 的概率选中(替换为答案) ans = node.val; } ++i; } return ans; } } /** * Your Solution object will be instantiated and called as such: * var obj = new Solution(head) * var param_1 = obj.getRandom() */ ``` ### 二叉树的迭代遍历 + 前序遍历 ```typescript export const preOrder = (root: TreeNode) => { const helperStack = [root]; const ans = []; while (helperStack.length) { const current = helperStack.pop(); ans.push(current.val); if (current.right) { helperStack.push(current.right); } if (current.left) { helperStack.push(current.left); } } return ans; } ``` + 中序遍历 ```typescript export const inOrder = (root: TreeNode) => { const helperStack = []; const ans = []; let current = root; while (helperStack.length || current) { if (current) { helperStack.push(current); current = current.left; } else { current = helperStack.pop(); ans.push(current.val); current = current.right; } } } ``` + 后序遍历 ```typescript export const postOrder = (root: TreeNode) => { const helperStack = []; const ans = []; let current = root; let previousHandled = null; while (helperStack.length || current) { while (current) { helperStack.push(current); current = current.left; } current = helperStack[helperStack.length - 1]; if (!current.right || current.right === previousHandled) { // 右边没有节点或右边的节点被处理过 current = helperStack.pop(); // 回退 ans.push(current.val); previousHandled = current; // 记录处理过的节点 current = null; } else { // 右边有节点且右边的节点没有被处理过 current = current.right; } } } ```
powerfulyangSun, Oct 9, 2022 6:20 PM

原型链

--- title: 原型链 author: powerfulyang date: 2022-10-06 posterId: 47800 tags: - prototype --- ## \_\_proto__ >`__proto__` 是 `[[Prototype]]` 的因历史原因而留下来的 getter/setter。 请注意,`__proto__` 与内部的 `[[Prototype]]` **不一样**。`__proto__` 是 `[[Prototype]]` 的 getter/setter。稍后,我们将看到在什么情况下理解它们很重要,在建立对 JavaScript 语言的理解时,让我们牢记这一点。 `__proto__` 属性有点过时了。它的存在是出于历史的原因,现代编程语言建议我们应该使用函数 `Object.getPrototypeOf/Object.setPrototypeOf` 来取代 `__proto__` 去 get/set 原型。 根据规范,`__proto__` 必须仅受浏览器环境的支持。但实际上,包括服务端在内的所有环境都支持它,因此我们使用它是非常安全的。 ```js let animal = { eats: true, walk() { alert("Animal walk"); } }; let rabbit = { jumps: true, __proto__: animal }; let longEar = { earLength: 10, __proto__: rabbit }; // walk 是通过原型链获得的 longEar.walk(); // Animal walk alert(longEar.jumps); // true(从 rabbit) ``` 现在,如果我们从 `longEar` 中读取一些它不存在的内容,JavaScript 会先在 `rabbit` 中查找,然后在 `animal` 中查找。 这里只有两个限制: 1. 引用不能形成闭环。如果我们试图在一个闭环中分配 `__proto__`,JavaScript 会抛出错误。 2. `__proto__` 的值可以是对象,也可以是 `null`。而其他的类型都会被忽略。 >几乎所有其他键/值获取方法都忽略继承的属性. 几乎所有其他键/值获取方法,例如 `Object.keys` 和 `Object.values` 等,都会忽略继承的属性。 它们只会对对象自身进行操作。**不考虑** 继承自原型的属性。 * 在 JavaScript 中,所有的对象都有一个隐藏的 `[[Prototype]]` 属性,它要么是另一个对象,要么就是 `null`。 * 我们可以使用 `obj.__proto__` 访问它(历史遗留下来的 getter/setter,这儿还有其他方法,很快我们就会讲到)。 * 通过 `[[Prototype]]` 引用的对象被称为“原型”。 * 如果我们想要读取 `obj` 的一个属性或者调用一个方法,并且它不存在,那么 JavaScript 就会尝试在原型中查找它。 * 写/删除操作直接在对象上进行,它们不使用原型(假设它是数据属性,不是 setter)。 * 如果我们调用 `obj.method()`,而且 `method` 是从原型中获取的,`this` 仍然会引用 `obj`。因此,方法始终与当前对象一起使用,即使方法是继承的。 * `for..in` 循环在其自身和继承的属性上进行迭代。所有其他的键/值获取方法仅对对象本身起作用。 ## prototype ```js let obj = {}; alert(obj.__proto__ === Object.prototype); // true ``` ```js let arr = [1, 2, 3]; // 它继承自 Array.prototype? alert( arr.__proto__ === Array.prototype ); // true // 接下来继承自 Object.prototype? alert( arr.__proto__.__proto__ === Object.prototype ); // true // 原型链的顶端为 null。 alert( arr.__proto__.__proto__.__proto__ ); // null ``` ```js function f() {} alert(f.__proto__ == Function.prototype); // true alert(f.__proto__.__proto__ == Object.prototype); // true,继承自 Object ``` ### 基本数据类型 最复杂的事情发生在字符串、数字和布尔值上。 正如我们记忆中的那样,它们并不是对象。但是如果我们试图访问它们的属性,那么临时包装器对象将会通过内建的构造器 `String`、`Number` 和 `Boolean` 被创建。它们提供给我们操作字符串、数字和布尔值的方法然后消失。 这些对象对我们来说是无形地创建出来的。大多数引擎都会对其进行优化,但是规范中描述的就是通过这种方式。这些对象的方法也驻留在它们的 prototype 中,可以通过 `String.prototype`、`Number.prototype` 和 `Boolean.prototype` 进行获取。 > 特殊值 `null` 和 `undefined` 比较特殊。它们没有对象包装器,所以它们没有方法和属性。并且它们也没有相应的原型。 ## Object.setPrototypeOf and Object.getPrototypeOf 使用 `obj.__proto__` 设置或读取原型被认为已经过时且不推荐使用(deprecated)了(已经被移至 JavaScript 规范的附录 B,意味着仅适用于浏览器)。 现代的获取/设置原型的方法有: * [Object.getPrototypeOf(obj)](https://developer.mozilla.org/zh/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) —— 返回对象 `obj` 的 `[[Prototype]]`。 * [Object.setPrototypeOf(obj, proto)](https://developer.mozilla.org/zh/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) —— 将对象 `obj` 的 `[[Prototype]]` 设置为 `proto`。 `__proto__` 不被反对的唯一的用法是在创建新对象时,将其用作属性:`{ __proto__: ... }`。 * [Object.create(proto, \[descriptors\])](https://developer.mozilla.org/zh/docs/Web/JavaScript/Reference/Global_Objects/Object/create) —— 利用给定的 `proto` 作为 `[[Prototype]]` 和可选的属性描述来创建一个空对象。 ### 原型历史 * 构造函数的 `"prototype"` 属性自古以来就起作用。这是使用给定原型创建对象的最古老的方式。 * 之后,在 2012 年,`Object.create` 出现在标准中。它提供了使用给定原型创建对象的能力。但没有提供 get/set 它的能力。一些浏览器实现了非标准的 `__proto__` 访问器,以为开发者提供更多的灵活性。 * 之后,在 2015 年,`Object.setPrototypeOf` 和 `Object.getPrototypeOf` 被加入到标准中,执行与 `__proto__` 相同的功能。由于 `__proto__` 实际上已经在所有地方都得到了实现,但它已过时,所以被加入到该标准的附件 B 中,即:在非浏览器环境下,它的支持是可选的。 * 之后,在 2022 年,官方允许在对象字面量 `{...}` 中使用 `__proto__`(从附录 B 中移出来了),但不能用作 getter/setter `obj.__proto__`(仍在附录 B 中)。 > 如果速度很重要,就请不要修改已存在的对象的 [[Prototype]] 从技术上来讲,我们可以在任何时候 get/set [[Prototype]]。但是通常我们只在创建对象的时候设置它一次,自那之后不再修改:rabbit 继承自 animal,之后不再更改。 并且,JavaScript 引擎对此进行了高度优化。用 Object.setPrototypeOf 或 obj.\_\_proto__= “即时”更改原型是一个非常缓慢的操作,因为它破坏了对象属性访问操作的内部优化。因此,除非你知道自己在做什么,或者 JavaScript 的执行速度对你来说完全不重要,否则请避免使用它。 ### 原型只能是对象或者 null ```js let obj = {}; let key = prompt("What's the key?", "__proto__"); obj[key] = "some value"; alert(obj[key]); // [object Object],并不是 "some value"! ``` ```js let obj = {}; let key = prompt("What's the key?", "__proto__"); obj[key] = new String("some value"); alert(obj[key]); // 这里却会是 "some value"! ``` `__proto__` 属性很特殊:它必须是一个对象或者 `null`。字符串不能成为原型。这就是为什么将字符串赋值给 `__proto__` 会被忽略。 ### 创建无原型对象 `Object.create(null)` 或 `{__proto__: null}` 创建的无原型的对象。 对象会从 `Object.prototype` 继承内建的方法和 `__proto__` getter/setter,会占用相应的键,且可能会导致副作用。原型为 `null` 时,对象才真正是空的。 ## [[HomeObject]] 当一个函数被定义为类或者对象方法时,它的 `[[HomeObject]]` 属性就成为了该对象。 然后 `super` 使用它来解析(resolve)父原型及其方法。 ```js let animal = { name: "Animal", eat() { // animal.eat.[[HomeObject]] == animal alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, name: "Rabbit", eat() { // rabbit.eat.[[HomeObject]] == rabbit super.eat(); } }; let longEar = { __proto__: rabbit, name: "Long Ear", eat() { // longEar.eat.[[HomeObject]] == longEar super.eat(); } }; // 正确执行 longEar.eat(); // Long Ear eats. ``` ### 应该是方法而不是函数属性 `[[HomeObject]]` 是为类和普通对象中的方法定义的。但是对于对象而言,方法必须确切指定为 `method()`,而不是 `"method: function()"`。 这个差别对我们来说可能不重要,但是对 JavaScript 来说却非常重要。 在下面的例子中,使用非方法(non-method)语法进行了比较。未设置 `[[HomeObject]]` 属性,并且继承无效: ```js let animal = { eat: function() { // 这里是故意这样写的,而不是 eat() {... // ... } }; let rabbit = { __proto__: animal, eat: function() { super.eat(); } }; rabbit.eat(); // 错误调用 super(因为这里没有 [[HomeObject]]) ``` ## Symbol.species 以给类添加一个特殊的静态 getter `Symbol.species`。如果存在,则应返回 JavaScript 在内部用来在 `map` 和 `filter` 等方法中创建新实体的 `constructor`。 如果我们希望像 `map` 或 `filter` 这样的内建方法返回常规数组,我们可以在 `Symbol.species` 中返回 `Array`,就像这样: ```js class PowerArray extends Array { isEmpty() { return this.length === 0; } // 内建方法将使用这个作为 constructor static get [Symbol.species]() { return Array; } } let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false // filter 使用 arr.constructor[Symbol.species] 作为 constructor 创建新数组 let filteredArr = arr.filter(item => item >= 10); // filteredArr 不是 PowerArray,而是 Array alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function ``` **内建类没有静态方法继承** 内建对象有它们自己的静态方法,例如 `Object.keys`,`Array.isArray` 等。 如我们所知道的,原生的类互相扩展。例如,`Array` 扩展自 `Object`。 ## Symbol.hasInstance `obj instanceof Class` 算法的执行过程大致如下: 1. 如果这儿有静态方法 `Symbol.hasInstance`,那就直接调用这个方法: 例如: ```js // 设置 instanceOf 检查 // 并假设具有 canEat 属性的都是 animal class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true:Animal[Symbol.hasInstance](obj) 被调用 ``` 2. 大多数 class 没有 `Symbol.hasInstance`。在这种情况下,标准的逻辑是:使用 `obj instanceOf Class` 检查 `Class.prototype` 是否等于 `obj` 的原型链中的原型之一。 换句话说就是,一个接一个地比较: ```js obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // 如果任意一个的答案为 true,则返回 true // 否则,如果我们已经检查到了原型链的尾端,则返回 false 在上面那个例子中,`rabbit.__proto__ === Rabbit.prototype`,所以立即就给出了结果。 而在继承的例子中,匹配将在第二步进行: class Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); alert(rabbit instanceof Animal); // true // rabbit.__proto__ === Animal.prototype(无匹配) // rabbit.__proto__.__proto__ === Animal.prototype(匹配!) ``` 这里还要提到一个方法 [objA.isPrototypeOf(objB)](https://developer.mozilla.org/zh/docs/Web/JavaScript/Reference/Global_Objects/object/isPrototypeOf),如果 `objA` 处在 `objB` 的原型链中,则返回 `true`。所以,可以将 `obj instanceof Class` 检查改为 `Class.prototype.isPrototypeOf(obj)`。 但是 `Class` 的 constructor 自身是不参与检查的!检查过程只和原型链以及 `Class.prototype` 有关。 创建对象后,如果更改 `prototype` 属性,可能会导致有趣的结果。 就像这样: ```js function Rabbit() {} let rabbit = new Rabbit(); // 修改了 prototype Rabbit.prototype = {}; // ...再也不是 rabbit 了! alert( rabbit instanceof Rabbit ); // false ``` ## Object.prototype.toString.call 内建的 `toString` 方法可以被从对象中提取出来,并在任何其他值的上下文中执行。其结果取决于该值。 * 对于 number 类型,结果是 `[object Number]` * 对于 boolean 类型,结果是 `[object Boolean]` * 对于 `null`:`[object Null]` * 对于 `undefined`:`[object Undefined]` * 对于数组:`[object Array]` * ……等(可自定义) ```js let s = Object.prototype.toString; alert( s.call(123) ); // [object Number] alert( s.call(null) ); // [object Null] alert( s.call(alert) ); // [object Function] ``` ### Symbol.toStringTag 可以使用特殊的对象属性 `Symbol.toStringTag` 自定义对象的 `toString` 方法的行为。 例如: ```js let user = { [Symbol.toStringTag]: "User" }; alert( {}.toString.call(user) ); // [object User] ```
powerfulyangThu, Oct 6, 2022 7:12 PM

constructor and new

--- title: constructor and new author: powerfulyang date: 2022-10-06 posterId: 47802 tags: - constructor - new - 构造函数 --- ## 构造函数 构造函数在技术上是常规函数。不过有两个约定: + 它们的命名以大写字母开头。 + 它们只能由 "new" 操作符来执行。 ```js function User(name) { this.name = name; this.isAdmin = false; } let user = new User("Jack"); alert(user.name); // Jack alert(user.isAdmin); // false ``` 当一个函数被使用 `new` 操作符执行时,它按照以下步骤: 1. 一个新的空对象被创建并分配给 `this`。 2. 函数体执行。通常它会修改 `this`,为其添加新的属性。 3. 返回 `this` 的值。 ## 构造器模式测试:new.target 在一个函数内部,我们可以使用 `new.target` 属性来检查它是否被使用 `new` 进行调用了。 对于常规调用,它为 undefined,对于使用 `new` 的调用,则等于该函数: ```js function User() { alert(new.target); } // 不带 "new": User(); // undefined // 带 "new": new User(); // function User { ... } ``` 它可以被用在函数内部,来判断该函数是被通过 `new` 调用的“构造器模式”,还是没被通过 `new` 调用的“常规模式”。 我们也可以让 `new` 调用和常规调用做相同的工作,像这样: ```js function User(name) { if (!new.target) { // 如果你没有通过 new 运行我 return new User(name); // ……我会给你添加 new } this.name = name; } let john = User("John"); // 将调用重定向到新用户 alert(john.name); // John ``` ## new 操作符做了什么 * 在内存中创建一个新对象。 * 将新对象内部的 \_\_proto\_\_ 赋值为构造函数的 prototype 属性。 * 将构造函数内部的 this 被赋值为新对象(即 this 指向新对象)。 * 执行构造函数内部的代码(给新对象添加属性)。 * 如果构造函数返回非空对象,则返回该对象。否则返回 this。 ### new 操作符的模拟实现 ```js function fakeNew() { // 创建新对象 var obj = Object.create(null); var Constructor = [].shift.call(arguments); // 将对象的 __proto__ 赋值为构造函数的 prototype 属性 obj.__proto__ = Constructor.prototype; // 将构造函数内部的 this 赋值为新对象 var ret = Constructor.apply(obj, arguments); // 返回新对象 return typeof ret === "object" && ret !== null ? ret : obj; } function Group(name, member) { this.name = name; this.member = member; } var group = fakeNew(Group, "hzfe", 17); ```
powerfulyangThu, Oct 6, 2022 5:12 PM

因缺失 Vary: Origin 导致的缓存错乱

--- title: '因缺失 Vary: Origin 导致的缓存错乱' date: 2022-09-16 posterId: 47789 tags: - 'Vary: Origin' - CORS --- 这些天把字体文件移动到 CDN 上时,遇到浏览器缓存异常的问题。 有两个站点分别是 https://powerfulyang.com 和 https://qa.powerfulyang.com ,都引用了跨域资源 https://static.powerfulyang.com/fonts/zpix.woff 文件。 但是发现访问 https://powerfulyang.com 之后再访问 https://qa.powerfulyang.com 会发现请求字体文件时报跨域错误,即 response header 中 Access-Control-Allow-Origin 的值是访问 https://powerfulyang.com 留下的缓存为 https://powerfulyang.com 。其中原因经查阅,了解到主要是 response header 中缺少了 Vary: Origin 这一部分,导致缓存没有按 Origin 分别缓存,只缓存了一份导致的问题。 ## 浏览器缓存机制 > Relate to https://zhuanlan.zhihu.com/p/38972475 由于浏览器里的缓存是以 URL 为 key 的,一个 URL 对应一个缓存,但是当浏览器访问了两个 URL 相同但 CORS 响应头不相同的资源。 比如在同一个浏览器下,先打开了 https://foo.taobao.com 上的一个页面,访问了我们的资源,这个资源被浏览器缓存了下来,和资源内容一起缓存的还有 Access-Control-Allow-Origin: https://foo.taobao.com 响应头。这时又打开 https://bar.taobao.com 上的一个页面,这个页面也要访问那个资源,这时它会读取本地缓存,读到的 Access-Control-Allow-Origin 头是缓存下的 https://foo.taobao.com 而不是自己想要的 https://bar.taobao.com ,这时就报跨域错误了。 ## 使用 Vary: Origin 让同一个 URL 有多份缓存 有一个 HTTP 响应头叫 `Vary`,vary 这个单词的意思是“变化”、“不同”的意思,`Vary` 响应头就是让同一个 URL 根据某个请求头的不同而使用不同的缓存。比如常见的 `Vary: Accept-Encoding` 表示客户端要根据 `Accept-Encoding` 请求头的不同而使用不同的缓存,比如 gizp 的缓存一份,未压缩的缓存为另一份。 >https://fetch.spec.whatwg.org/#cors-protocol-and-http-caches >However, if `Access-Control-Allow-Origin` is set to * or a static origin for a particular resource, then configure the server to always send `Access-Control-Allow-Origin` in responses for the resource — for non-CORS requests as well as CORS requests — and do not use `Vary`. **总结:最终选择了懒惰的方法,直接设置 Access-Control-Allow-Origin: \* 来解决不同 Origin 访问报跨域错误的问题。**
powerfulyangFri, Sep 16, 2022 11:34 PM

css selector

--- title: css selector author: powerfulyang date: 2022-09-08 tags: - ':has' - ':is' - ':where' - ':not' --- ## 伪类选择器 | 选择器 | 例子 | 例子描述 | | --- | --- | --- | | [:active](https://www.w3school.com.cn/cssref/selector_active.asp "CSS :active 选择器") | a:active | 选择活动的链接。 | | [:checked](https://www.w3school.com.cn/cssref/selector_checked.asp "CSS :checked 选择器") | input:checked | 选择每个被选中的 <input> 元素。 | | [:disabled](https://www.w3school.com.cn/cssref/selector_disabled.asp "CSS :disabled 选择器") | input:disabled | 选择每个被禁用的 <input> 元素。 | | [:empty](https://www.w3school.com.cn/cssref/selector_empty.asp "CSS :empty 选择器") | p:empty | 选择没有子元素的每个 <p> 元素。 | | [:enabled](https://www.w3school.com.cn/cssref/selector_enabled.asp "CSS :enabled 选择器") | input:enabled | 选择每个已启用的 <input> 元素。 | | [:first-child](https://www.w3school.com.cn/cssref/selector_first-child.asp "CSS :first-child 选择器") | p:first-child | 选择作为其父的首个子元素的每个 <p> 元素。 | | [:first-of-type](https://www.w3school.com.cn/cssref/selector_first-of-type.asp "CSS :first-of-type 选择器") | p:first-of-type | 选择作为其父的首个 <p> 元素的每个 <p> 元素。 | | [:focus](https://www.w3school.com.cn/cssref/selector_focus.asp "CSS :focus 选择器") | input:focus | 选择获得焦点的 <input> 元素。 | | [:hover](https://www.w3school.com.cn/cssref/selector_hover.asp "CSS :hover 选择器") | a:hover | 选择鼠标悬停其上的链接。 | | [:in-range](https://www.w3school.com.cn/cssref/selector_in-range.asp "CSS :in-range 选择器") | input:in-range | 选择具有指定范围内的值的 <input> 元素。 | | [:invalid](https://www.w3school.com.cn/cssref/selector_invalid.asp "CSS :invalid 选择器") | input:invalid | 选择所有具有无效值的 <input> 元素。 | | [:lang(*language*)](https://www.w3school.com.cn/cssref/selector_lang.asp "CSS :lang(language) 选择器") | p:lang(it) | 选择每个 lang 属性值以 "it" 开头的 <p> 元素。 | | [:last-child](https://www.w3school.com.cn/cssref/selector_last-child.asp "CSS :last-child 选择器") | p:last-child | 选择作为其父的最后一个子元素的每个 <p> 元素。 | | [:last-of-type](https://www.w3school.com.cn/cssref/selector_last-of-type.asp "CSS :last-of-type 选择器") | p:last-of-type | 选择作为其父的最后一个 <p> 元素的每个 <p> 元素。 | | [:link](https://www.w3school.com.cn/cssref/selector_link.asp "CSS :link 选择器") | a:link | 选择所有未被访问的链接。 | | [:not(*selector*)](https://www.w3school.com.cn/cssref/selector_not.asp "CSS :not(selector) 选择器") | :not(p) | 选择每个非 <p> 元素的元素。 | | [:nth-child(*n*)](https://www.w3school.com.cn/cssref/selector_nth-child.asp "CSS :nth-child(n) 选择器") | p:nth-child(2) | 选择作为其父的第二个子元素的每个 <p> 元素。 | | [:nth-last-child(*n*)](https://www.w3school.com.cn/cssref/selector_nth-last-child.asp "CSS :nth-last-child(n) 选择器") | p:nth-last-child(2) | 选择作为父的第二个子元素的每个<p>元素,从最后一个子元素计数。 | | [:nth-last-of-type(*n*)](https://www.w3school.com.cn/cssref/selector_nth-last-of-type.asp "CSS :nth-last-of-type(n) 选择器") | p:nth-last-of-type(2) | 选择作为父的第二个<p>元素的每个<p>元素,从最后一个子元素计数 | | [:nth-of-type(*n*)](https://www.w3school.com.cn/cssref/selector_nth-of-type.asp "CSS :nth-of-type(n) 选择器") | p:nth-of-type(2) | 选择作为其父的第二个 <p> 元素的每个 <p> 元素。 | | [:only-of-type](https://www.w3school.com.cn/cssref/selector_only-of-type.asp "CSS :only-of-type 选择器") | p:only-of-type | 选择作为其父的唯一 <p> 元素的每个 <p> 元素。 | | [:only-child](https://www.w3school.com.cn/cssref/selector_only-child.asp "CSS :only-child 选择器") | p:only-child | 选择作为其父的唯一子元素的 <p> 元素。 | | [:optional](https://www.w3school.com.cn/cssref/selector_optional.asp "CSS :optional 选择器") | input:optional | 选择不带 "required" 属性的 <input> 元素。 | | [:out-of-range](https://www.w3school.com.cn/cssref/selector_out-of-range.asp "CSS :out-of-range 选择器") | input:out-of-range | 选择值在指定范围之外的 <input> 元素。 | | [:read-only](https://www.w3school.com.cn/cssref/selector_read-only.asp "CSS :read-only 选择器") | input:read-only | 选择指定了 "readonly" 属性的 <input> 元素。 | | [:read-write](https://www.w3school.com.cn/cssref/selector_read-write.asp "CSS :read-write 选择器") | input:read-write | 选择不带 "readonly" 属性的 <input> 元素。 | | [:required](https://www.w3school.com.cn/cssref/selector_required.asp "CSS :required 选择器") | input:required | 选择指定了 "required" 属性的 <input> 元素。 | | [:root](https://www.w3school.com.cn/cssref/selector_root.asp "CSS :root 选择器") | root | 选择元素的根元素。 | | [:target](https://www.w3school.com.cn/cssref/selector_target.asp "CSS :target 选择器") | #news:target | 选择当前活动的 #news 元素(单击包含该锚名称的 URL)。 | | [:valid](https://www.w3school.com.cn/cssref/selector_valid.asp "CSS :valid 选择器") | input:valid | 选择所有具有有效值的 <input> 元素。 | | [:visited](https://www.w3school.com.cn/cssref/selector_visited.asp "CSS :visited 选择器") | a:visited | 选择所有已访问的链接。 | ### 链接伪类选择器 链接伪类选择器用于给超链接标签添加特殊的效果 ```css a:link {color: red;} /* 未访问的链接状态 */ a:visited {color: green;} /* 已访问的链接状态 */ a:hover {color: blue;} /* 鼠标滑过链接状态 */ a:active {color: yellow;} /* 鼠标按下去时的状态 */ ``` ### 结构(位置)伪类选择器 常用的结构伪类选择器: * **`E:first-child`** 选择器 表示**选择父元素中的第一个子元素 E**。简单点理解就是选择元素中的第一个子元素,记住是**子元素**,而不是后代元素。 举例: ```css ol > li:first-child{ color: red; } ``` * **`:last-child`** 选择器 选择的是元素的最后一个子元素。比如,需要改变的是列表中的最后一个“li”的背景色,就可以使用这个选择器。 举例: ```css ol > li:last-child{ color: red; } ``` * **`:nth-child(n)`** 选择器 用来定位某个**父元素**的**一个或多个特定的子元素**。其中“n”是其参数,不仅可以是整数值(1,2,3,4),也可以是表达式(2n+1、-n+5)和关键词(odd 奇数、even),但参数n的起始值始终是1,而不是0。也就是说,参数n的值为0时,选择器将选择不到任何匹配的元素。 举例: ```css ol li:nth-child(even){ background: orange; } ``` * **`E:empty`** 选择器 表示的就是空。用来选择没有任何内容的元素E,这里没有内容指的是一点内容都没有,**哪怕是一个空格**。 举例: ```css p{ background: orange; min-height: 30px; } p:empty { display: none; } ``` * `E:root` 选择器 从字面上我们就可以很清楚的理解是根选择器,它的意思就是匹配元素E所在文档的根元素。在HTML文档中,根元素始终是。 ```css :root{ background:pink; } /*等同于*/ html {background:pink;} ``` ### 目标伪类选择器 `:target` 选择器会突出显示当前活动的 HTML 锚,然后给它添加相应的样式。 ```html <h2><a href="#brand">Brand</a></h2> <div id="brand"> content for Brand </div> #brand:target { background: orange; color: #fff; } ``` 当点击链接时,锚点被激活,#brand 的 :target 样式会被应用。 ### 逻辑选择器 在 CSS 选择器家族中,新增这样一类比较新的选择器**逻辑选择器**,目前共有 4 名成员: * `:is` * `:where` * `:not` * `:has` #### :is 伪类选择器 `:is()` CSS伪类函数将选择器列表作为参数,并选择该列表中任意一个选择器可以选择的元素。 在之前,对于多个不同父容器的同个子元素的一些共性样式设置,可能会出现如下 CSS 代码: ``` header p:hover, main p:hover, footer p:hover {   color: red;   cursor: pointer; } ``` 而如今有了 `:is()` 伪类,上述代码可以改写成: ``` :is(header, main, footer) p:hover {   color: red;   cursor: pointer; } ``` 它并没有实现某种选择器的新功能,更像是一种语法糖,只是对原有功能的重新封装设计,实现了更容易的表达一个操作的语法,简化了某些复杂代码的写法。 ps: `:is` **不支持伪元素** 有个特例,不能用 `:is()` 来选取 `::before` 和 `::after` 两个伪元素。譬如: > 注意,仅仅是不支持伪元素,伪类,譬如 `:focus`、`:hover` 是支持的。 **`:is()` 的优先级是由它的选择器列表中优先级最高的选择器决定的。** #### :where 伪类选择器 和 `:is` 用法基本一致,唯一的区别在于`:where()` 的优先级**总是为 0**,而`:is()` 的优先级是由它的选择器列表中优先级最高的选择器决定的。 ### :not 伪类选择器 `:not` 伪类选择器用来匹配不符合一组选择器的元素。由于它的作用是防止特定的元素被选中,它也被称为反选伪类(negation pseudo-class)。 与 `:is()` 类似,`:not()` 选择器本身不会影响选择器的优先级,它的优先级是由它的选择器列表中优先级最高的选择器决定的。 #### :has 伪类选择器 **`:has()`** CSS [伪类](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Pseudo-classes)代表一个元素,其给定的选择器参数(相对于该元素的 [`:scope`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/:scope))至少匹配一个元素。 ```css // 下面的选择器只会匹配直接包含 <img> 子元素的 <a> 元素: a:has(> img) ``` ## 选择器 -> 组合器 + [后代组合器](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Descendant_combinator)([Descendant combinator](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Descendant_combinator)) ' '(空格)组合器选择前一个元素的后代节点。 **语法:**`A B` **例子:**`div span` 匹配所有位于任意 [`<div>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/div) 元素之内的 [`<span>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/span) 元素。 + [直接子代组合器](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Child_combinator)([Child combinator](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Child_combinator)) `>` 组合器选择前一个元素的直接子代的节点。 **语法**:`A > B` **例子**:`ul > li` 匹配直接嵌套在 [`<ul>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/ul) 元素内的所有 [`<li>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/li) 元素。 + [一般兄弟组合器](https://developer.mozilla.org/zh-CN/docs/Web/CSS/General_sibling_combinator)([General sibling combinator](https://developer.mozilla.org/zh-CN/docs/Web/CSS/General_sibling_combinator)) `~` 组合器选择兄弟元素,也就是说,后一个节点在前一个节点后面的任意位置,并且共享同一个父节点。 **语法**:`A ~ B` **例子**:`p ~ span` 匹配同一父元素下,[`<p>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/p) 元素后的所有 [`<span>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/span) 元素。 + [紧邻兄弟组合器](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Adjacent_sibling_combinator)([Adjacent sibling combinator](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Adjacent_sibling_combinator)) `+` 组合器选择相邻元素,即后一个元素紧跟在前一个之后,并且共享同一个父节点。 **语法:**`A + B` **例子:**`h2 + p` 会匹配所有紧邻在 [`<h2>` (en-US)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements "Currently only available in English (US)") 元素后的 [`<p>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/p) 元素。 + [列组合器](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Column_combinator)([Column combinator](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Column_combinator)) Experimental `||` 组合器选择属于某个表格行的节点。 **语法:** `A || B` **例子:** `col || td` 会匹配所有 [`<col>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/col) 作用域内的 [`<td>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/td) 元素。 + 交集选择器 交集选择器两个选择器直接连接构成 ```css h1.class { } .classA.classB { } ``` + 并集选择器, 并集选择器一定是英文逗号隔开 ```css /*选择器1,选择器2,选择器3......{属性:属性值;}*/ .top, #nav, p { color:red; } ``` ## 选择器 -> 属性选择器 example: ```css /* 存在 title 属性的<a> 元素 */ a[title] { color: purple; } /* 存在 href 属性并且属性值匹配"https://example.org"的<a> 元素 */ a[href="https://example.org"] { color: green; } /* 存在 href 属性并且属性值包含"example"的<a> 元素 */ a[href*="example"] { font-size: 2em; } /* 存在 href 属性并且属性值结尾是".org"的<a> 元素 */ a[href$=".org"] { font-style: italic; } /* 存在 class 属性并且属性值包含以空格分隔的"logo"的<a>元素 */ a[class~="logo"] { padding: 2px; } ``` `[attr]` 表示带有以 *attr* 命名的属性的元素。 `[attr=value]` 表示带有以 *attr* 命名的属性,且属性值为 *value* 的元素。 `[attr~=value]` 表示带有以 *attr* 命名的属性的元素,并且该属性是一个以空格作为分隔的值列表,其中至少有一个值为 *value*。 `[attr|=value]` 表示带有以 *attr* 命名的属性的元素,属性值为“value”或是以“value-”为前缀("`-`"为连字符,Unicode 编码为 U+002D)开头。典型的应用场景是用来匹配语言简写代码(如 zh-CN,zh-TW 可以用 zh 作为 value)。 `[attr^=value]` 表示带有以 *attr* 命名的属性,且属性值是以 **value** 开头的元素。 `[attr$=value]` 表示带有以 *attr* 命名的属性,且属性值是以 **value** 结尾的元素。 `[attr*=value]` 表示带有以 *attr* 命名的属性,且属性值至少包含一个 **value** 值的元素。 ## 所有 CSS 伪元素 | 选择器 | 例子 | 例子描述 | | --- | --- | --- | | [::after](https://www.w3school.com.cn/cssref/selector_after.asp) | p::after | 在每个 <p> 元素之后插入内容。 | | [::before](https://www.w3school.com.cn/cssref/selector_before.asp) | p::before | 在每个 <p> 元素之前插入内容。 | | [::first-letter](https://www.w3school.com.cn/cssref/selector_first-letter.asp) | p::first-letter | 选择每个 <p> 元素的首字母。 | | [::first-line](https://www.w3school.com.cn/cssref/selector_first-line.asp) | p::first-line | 选择每个 <p> 元素的首行。 | | [::selection](https://www.w3school.com.cn/cssref/selector_selection.asp) | p::selection | 选择用户选择的元素部分。 |
powerfulyangThu, Sep 8, 2022 5:19 PM

使用 tabIndex 控制焦点

--- title: 使用 tabIndex 控制焦点 author: powerfulyang date: 2022-09-08 posterId: 47788 tags: - tabIndex --- 标准 HTML 元素(如 <button> 或 <input>)免费内置了键盘可访问性。不过,如果您正在构建自定义交互组件,请使用 tabindex 属性确保它们可以通过键盘访问。 > 尽可能使用内置 HTML 元素,而不是构建您自己的自定义版本。例如,<button> 非常容易设置样式并且已经具有完整的键盘支持。这样您就无需管理 tabindex 或添加 ARIA 语义。 ## 检查您的控件是否可通过键盘访问 像 Lighthouse 这样的工具非常适合检测某些可访问性问题,但有些东西只能由人来测试。 尝试按 Tab 键浏览您的网站。您是否能够访问页面上的所有交互式控件?如果不能,您可能需要使用 [tabIndex](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/tabindex) 来提高这些控件的可聚焦性。 ## 将元素插入到 Tab 键顺序中 使用 tabindex="0" 将元素插入到自然的 Tab 键顺序中。例如: ```html <div tabindex="0">Focus me with the TAB key</div> ``` 要聚焦某个元素,请按 Tab 键或调用该元素的 focus() 方法。 ## 从 Tab 键顺序中删除元素 使用 `tabindex="-1"` 删除元素。例如: ```html <button tabindex="-1">Can't reach me with the TAB key!</button> ``` 这将从自然的 Tab 键顺序中删除一个元素,但仍然可以通过调用该元素的 `focus()` 方法来聚焦该元素。 请注意,对元素应用 tabindex="-1" 不会影响其子元素;如果它们本来就处在 Tab 键顺序中或由于 tabindex 值的原因,它们将保留在 Tab 键顺序中。 ## 避免 `tabindex > 0` 任何大于 0 的 `tabindex` 都会使元素跳到自然 Tab 键顺序的前面。如果有多个元素的 `tabindex` 大于0,则 Tab 键顺序从大于 0 的最低值开始,依次向上。 使用大于 0 的 `tabindex` 被认为是一种**反模式**,因为屏幕阅读器按 DOM 顺序导航页面,而不是按 Tab 键顺序。如果您需要某个元素在 Tab 键顺序中更早出现,则应将其移至 DOM 中较早的位置。 使用 Lighthouse 可以轻松识别 `tabindex` > 0 的元素。运行可访问性审计(Lighthouse > Options(选项)> Accessibility(可访问性))并查找“没有元素的 \[tabindex\] 值大于 0”审计的结果。
powerfulyangThu, Sep 8, 2022 12:29 PM

TCP 相关

--- title: TCP 相关 author: powerfulyang date: 2022-08-09 posterId: 47729 tags: - TCP - HTTP/1.0 - HTTP/1.1 - HTTP/2.0 - HTTP/3.0 --- ## TCP ### TCP 三次握手 ```js # SYN 是建立连接时的握手信号,TCP 中发送第一个 SYN 包的为客户端,接收的为服务端 # TCP 中,当发送端数据到达接收端时,接收端返回一个已收到消息的通知。这个消息叫做确认应答 ACK 假设有客户端A,服务端B。我们要建立可靠的数据传输。 SYN(=j) // SYN: A 请求建立连接 A ----------> B | ACK(=j+1) | // ACK: B 确认应答 A 的 SYN SYN(=k) | // SYN: B 发送一个 SYN A <----------- | | ACK(=k+1) -----------> B // ACK: A 确认应答 B 的包 ``` 1. 客户端发送 SYN 包(seq = j)到服务器,并进入 SYN\_SEND 状态,等待服务器确认。 2. 服务器收到 SYN 包,必须确认客户的 SYN(ACK = k + 1),同时自己也发送一个 SYN 包(seq = k),即 SYN+ACK 包,此时服务器进入 SYN\_RECV 状态。 3. 客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ACK = k + 1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。 ### TCP 四次挥手 ![source protected](47806) * 主动关闭方发送一个 FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(在 FIN 包之前发送出去的数据,如果没有收到对应的 ACK 确认报文,主动关闭方依然会重发这些数据),但此时主动关闭方还可以接受数据。 * 被动关闭方收到 FIN 包后,发送一个 ACK 给对方,确认序号为收到序号+1(与 SYN 相同,一个 FIN 占用一个序号)。 * 被动关闭方发送一个 FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。 * 主动关闭方收到 FIN 后,发送一个 ACK 给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。 ## HTTP HTTP (超文本传输协议,HyperText Transfer Protocol), ![source protected](47803) ### HTTP 1.0 ### HTTP 1.1
powerfulyangTue, Aug 9, 2022 1:37 PM

elasticsearch 指南

--- title: elasticsearch 指南 author: powerfulyang date: 2022-08-03 posterId: 47779 tags: - es - elasticsearch --- ## 去重 类似于 distinct 去重的字段不能是 text 类型。所以 mapping 要有 keyword。 ```js POST /logstash-*/_search { index: 'logstash-*', query: { match_all: {}, }, collapse: { field: 'container_name.keyword', }, } ```
powerfulyangWed, Aug 3, 2022 5:18 PM

和 Symbol 沾点边的东西

--- title: 和 Symbol 沾点边的东西 author: powerfulyang date: 2022-08-01 posterId: tags: - JavaScript - Object.freeze - Object.seal - Object.preventExtensions - Reflect.preventExtensions - Symbol - 隐式转换 --- ## Reflect 和 Object 的一些静态方法 ### Object.preventExtensions() 和 Reflect.preventExtensions() new properties cannot be added, existing properties can be removed **Object.preventExtensions() 与 Reflect.preventExtensions() 的不同点** 如果 Reflect.preventExtensions() 的 target 参数不是一个对象(是原始值),那么将造成一个 TypeError 异常。 对于 Object.preventExtensions() 方法, 非对象的 target 参数将被强制转换为对象。 ### Object.freeze() Freezing an object [prevents extensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) and makes existing properties non-writable and non-configurable. A frozen object can no longer be changed: new properties cannot be added, existing properties cannot be removed, their enumerability, configurability, writability, or value cannot be changed, and the object's prototype cannot be re-assigned. `freeze()` returns the same object that was passed in. ### Object.seal() Sealing an object [prevents extensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) and makes existing properties non-configurable. A sealed object has a fixed set of properties: new properties cannot be added, existing properties cannot be removed, their enumerability and configurability cannot be changed, and it's prototype cannot be re-assigned. Values of existing properties can still be changed as long as they are writable. `seal()` returns the same object that was passed in. ### Reflect 的静态方法 + Reflect.deleteProperty() The static **`Reflect.deleteProperty()`** method allows to delete properties. It is like the [`delete` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete) as a function. + Reflect.defineProperty() The static **`Reflect.defineProperty()`** method is like [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) but returns a [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean). + Reflect.getOwnPropertyDescriptor() The static **`Reflect.getOwnPropertyDescriptor()`** method is similar to [`Object.getOwnPropertyDescriptor()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor). It returns a property descriptor of the given property if it exists on the object, [`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined) otherwise. + Reflect.getPrototypeOf() The static **`Reflect.getPrototypeOf()`** method is almost the same method as [`Object.getPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf). It returns the prototype (i.e. the value of the internal `[[Prototype]]` property) of the specified object. + Reflect.has() The static **`Reflect.has()`** method works like the [`in` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in) as a function. + Reflect.ownKeys() The static **`Reflect.ownKeys()`** method returns an array of the `target` object's own property keys. + Reflect.setPrototypeOf() The static **`Reflect.setPrototypeOf()`** method is the same method as [`Object.setPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf), except for it's return type. It sets the prototype (i.e., the internal `[[Prototype]]` property) of a specified object to another object or to [`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null), and returns `true` if the operation was successful, or `false` otherwise. ### Reflect.setPrototypeOf() and Object.setPrototypeOf() Reflect.setPrototypeOf() is same as Object.setPrototypeOf(). ```typescript class Human {} class SuperHero {} // Set the instance properties Object.setPrototypeOf(SuperHero.prototype, Human.prototype); // Hook up the static properties Object.setPrototypeOf(SuperHero, Human); const superMan = new SuperHero(); ``` ### Reflect.ownKeys() and Object.keys() + The `Reflect.ownKeys` method returns an array of the `target` object's own property keys. It's return value is equivalent to **[Object.getOwnPropertyNames(target)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames).concat([Object.getOwnPropertySymbols(target)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols))**. + `Object.getOwnPropertyNames()` returns an array whose elements are strings corresponding to the enumerable and non-enumerable properties found directly in a given object `obj`. The ordering of the enumerable properties in the array is consistent with the ordering exposed by a [`for...in`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in) loop (or by [`Object.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys)) over the properties of the object. According to ES6, the non-negative integer keys of the object (both enumerable and non-enumerable) are added in ascending order to the array first, followed by the string keys in the order of insertion. + `Object.getOwnPropertySymbols()` is similar to [`Object.getOwnPropertyNames()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), you can get all symbol properties of a given object as an array of symbols. Note that [`Object.getOwnPropertyNames()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames) itself does not contain the symbol properties of an object and only the string properties. + `Object.keys()` returns an array whose elements are strings corresponding to the enumerable properties found directly upon `object`. The ordering of the properties is the same as that given by looping over the properties of the object manually. ## Symbol `symbol` 是唯一标识符的基本类型 + symbol 属性不参与 `for..in` 循环,Object.keys(obj) 也会忽略它们。 + 相反,Object.assign 会同时复制字符串和 symbol 属性,由于指向同一片内存当然会复制。 ### 全局 Symbol 该调用会检查全局注册表,如果有一个描述为 `key` 的 symbol,则返回该 symbol,否则将创建一个新 symbol(`Symbol(key)`),并通过给定的 `key` 将其存储在全局注册表中,key 只能为 string。 ```js // 从全局注册表中读取 let id = Symbol.for("id"); // 如果该 symbol 不存在,则创建它 // 再次读取(可能是在代码中的另一个位置) let idAgain = Symbol.for("id"); // 相同的 symbol alert( id === idAgain ); // true ``` 对于全局 symbol,`Symbol.for(key)` 按名字返回一个 symbol。相反,通过全局 symbol 返回一个名字,我们可以使用 `Symbol.keyFor(sym)`。 ```js // 通过 name 获取 symbol let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // 通过 symbol 获取 name alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id ``` `Symbol.keyFor` 内部使用全局 symbol 注册表来查找 symbol 的键。所以它不适用于非全局 symbol。如果 symbol 不是全局的,它将无法找到它并返回 `undefined`。 ### 隐式转换 `implicit conversion` **为了进行转换,JavaScript 尝试查找并调用三个对象方法:** 1. 调用 `obj[Symbol.toPrimitive](hint)` —— 带有 symbol 键 `Symbol.toPrimitive`(系统 symbol)的方法,如果这个方法存在的话, 2. 否则,如果 hint 是 `"string"` —— 尝试调用 `obj.toString()` 或 `obj.valueOf()`,无论哪个存在。 3. 否则,如果 hint 是 `"number"` 或 `"default"` —— 尝试调用 `obj.valueOf()` 或 `obj.toString()`,无论哪个存在。 关于所有原始转换方法,有一个重要的点需要知道,就是它们不一定会返回 “hint” 的原始值。 没有限制 `toString()` 是否返回字符串,或 `Symbol.toPrimitive` 方法是否为 `"number"` hint 返回数字。 唯一强制性的事情是:这些方法必须返回一个原始值,而不是对象。 由于历史原因,如果 `toString` 或 `valueOf` 返回一个对象,则不会出现 error,但是这种值会被忽略(就像这种方法根本不存在)。这是因为在 JavaScript 语言发展初期,没有很好的 “error” 的概念。 相反,`Symbol.toPrimitive` 更严格,它 **必须** 返回一个原始值,否则就会出现 error。 --- hint 可以理解为根据场景自动判断`更倾向于`转换成 `string` 还是 `number`,如果无法判断倾向于转化哪一个,就是 `default`。下面我们来`模拟`一下转化过程中获取 hint 值的过程 ```js const obj = { [Symbol.toPrimitive](hint) { console.log(`hint: ${hint}`); } }; const arr = [] arr[obj] // hint: string ``` ```js const obj = { [Symbol.toPrimitive](hint) { console.log(`hint: ${hint}`); } }; -obj // hint: number ``` ```js const a = [1, 2, 3] a[Symbol.toPrimitive] = function (hint) { console.log(`hint: ${hint}`); } '' + a // hint: default ```
powerfulyangMon, Aug 1, 2022 5:58 PM

JavaScript 中的值类型和引用类型

--- title: JavaScript 中的值类型和引用类型 author: powerfulyang date: 2022-08-01 posterId: tags: - JavaScript - Primitive Values - Reference Values --- JavaScript 的变量类型: + 值类型(基本类型):字符串(string)、数值(number)、布尔值(boolean)、undefined、null (这5种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值)(ECMAScript 2016新增了一种基本数据类型:symbol) + 引用类型:对象(Object)、数组(Array)、函数(Function) ## 值类型 值类型的特点: 1. 占用空间固定,保存在栈中(当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;栈中存储的是基础变量以及一些对象的引用变量,`基础变量的值是存储在栈中`,而引用变量存储在栈中的是`指向堆中的数组或者对象的地址`,这就是为何**修改引用类型总会影响到其他指向这个地址的引用变量。**) 2. **保存与复制的是值本身** 3. 使用 typeof 检测数据的类型 | Type | Result | | --- | --- | | [Undefined](https://developer.mozilla.org/en-US/docs/Glossary/undefined) | `"undefined"` | | [Null](https://developer.mozilla.org/en-US/docs/Glossary/Null) | `"object"` (see [below](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#typeof_null)) | | [Boolean](https://developer.mozilla.org/en-US/docs/Glossary/Boolean) | `"boolean"` | | [Number](https://developer.mozilla.org/en-US/docs/Glossary/Number) | `"number"` | | [BigInt](https://developer.mozilla.org/en-US/docs/Glossary/BigInt) | `"bigint"` | | [String](https://developer.mozilla.org/en-US/docs/Glossary/String) | `"string"` | | [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) | `"symbol"` | | [Function](https://developer.mozilla.org/en-US/docs/Glossary/Function) (implements \[\[Call\]\] in ECMA-262 terms; [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class) are functions as well) | `"function"` | | Any other object | `"object"` | In the first implementation of JavaScript, JavaScript values were represented as a type tag and a value. The type tag for objects was `0`. `null` was represented as the NULL pointer (`0x00` in most platforms). Consequently, `null` had `0` as type tag, hence the `typeof` return value `"object"`. ([reference](https://2ality.com/2013/10/typeof-null.html)) A fix was proposed for ECMAScript (via an opt-in), but [was rejected](https://web.archive.org/web/20160331031419/http://wiki.ecmascript.org:80/doku.php?id=harmony:typeof_null). It would have resulted in `typeof null === 'null'`. 4. 基本类型数据是值类型 ## 引用类型 引用类型的特点: 1. 占用空间不固定,保存在堆中(当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象`依然不会被销毁`,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。) 2. **保存与复制的是指向对象的一个指针** 3. 使用 instanceof 检测数据类型, 语法为: `object instanceof constructor` ```javascript const arr = [1, 2]; // 判断Object的prototype有没有在数组的原型链上 console.log(arr instanceof Object); // true // 数组arr的原型 const proto1 = Object.getPrototypeOf(arr); console.log(proto1); // [] // 数组arr的原型的原型 const proto2 = Object.getPrototypeOf(proto1); console.log(proto2); // [] // Object的prototype console.log(Object.prototype); // 判断arr的原型是否与Object的prototype相等 console.log(proto1 === Object.prototype); // false // 判断arr的原型的原型是否与Object的prototype相等 console.log(proto2 === Object.prototype); // true ``` The **`instanceof` operator** tests to see if the `prototype` property of a constructor appears anywhere in the prototype chain of an object. The return value is a boolean value. Its behavior can be customized with [`Symbol.hasInstance`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance). 4. 使用 new() 方法构造出的对象是引用型
powerfulyangMon, Aug 1, 2022 12:33 PM

TypeScript 相关

--- title: TypeScript 相关 author: powerflyang date: 2022-07-18 posterId: 47752 tags: - ts - TypeScript - infer --- Useful TypeScript features: + **infer** + **never** + **keyof + in** ## infer The `infer` keyword allows you to deduce a type from another type within a conditional type. Here’s an example: ```typescript const User = { id: 123, username: 'John', email: 'john@gmail.com', addons: [ { name: 'WriteAddon', id: 1 }, { name: 'TodoAddon', id: 2 } ] }; type UnpackArray<T> = T extends (infer R)[] ? R: T; type AddonType = UnpackArray<typeof User.addons>; // { name: string, id: number } ``` ## never The `never` type represents a value that is *never* observed. In a return type, this means that the function throws an exception or terminates the execution of the program. ## keyof + in ```typescript type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]>; }; ``` ## Triple-Slash Directives 最近遇到的一个问题,pnpm install 之后 `@types/node`, `@types/jest` 等都没有提示了,找了挺久似乎都没有思路,最后偶然尝试**在 d.ts 文件里加上 `/// <reference types="..." />` 就好了** ### `/// <reference types="..." />` Similar to a `/// <reference path="..." />` directive, which serves as a declaration of *dependency*, a `/// <reference types="..." />` directive declares a dependency on a package. The process of resolving these package names is similar to the process of resolving module names in an `import` statement. An easy way to think of triple-slash-reference-types directives are as an `import` for declaration packages. For example, including `/// <reference types="node" />` in a declaration file declares that this file uses names declared in `@types/node/index.d.ts`; and thus, this package needs to be included in the compilation along with the declaration file. Use these directives only when you’re authoring a `d.ts` file by hand. For declaration files generated during compilation, the compiler will automatically add `/// <reference types="..." />` for you; A `/// <reference types="..." />` in a generated declaration file is added *if and only if* the resulting file uses any declarations from the referenced package. For declaring a dependency on an `@types` package in a `.ts` file, use [`types`](https://www.typescriptlang.org/tsconfig#types) on the command line or in your `tsconfig.json` instead. See [using `@types`, `typeRoots` and `types` in `tsconfig.json` files](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#types-typeroots-and-types) for more details.
powerfulyangMon, Jul 18, 2022 11:09 AM

来点基础函数的解析

--- title: 来点基础函数的解析 author: powerflyang date: 2022-07-15 posterId: 47750 tags: - js - Math - Array --- ## Math ### Math 对象方法 | 方法 | 描述 | | --- | --- | | [abs(*x*)](https://www.w3school.com.cn/jsref/jsref_abs.asp "JavaScript abs() 方法") | 返回 x 的绝对值。 | | [ceil(*x*)](https://www.w3school.com.cn/jsref/jsref_ceil.asp "JavaScript ceil() 方法") | 返回 x,向上舍入为最接近的整数。 | | [exp(*x*)](https://www.w3school.com.cn/jsref/jsref_exp.asp "JavaScript exp() 方法") | 返回 Ex 的值。 | | [floor(*x*)](https://www.w3school.com.cn/jsref/jsref_floor.asp "JavaScript floor() 方法") | 返回 x,向下舍入为最接近的整数。 | | [log(*x*)](https://www.w3school.com.cn/jsref/jsref_log.asp "JavaScript log() 方法") | 返回 x 的自然对数。 | | [max(*x*, *y*, *z*, ..., *n*)](https://www.w3school.com.cn/jsref/jsref_max.asp "JavaScript max() 方法") | 返回值最高的数字。 | | [min(*x*, *y*, *z*, ..., *n*)](https://www.w3school.com.cn/jsref/jsref_min.asp "JavaScript min() 方法") | 返回值最小的数字。 | | [random()](https://www.w3school.com.cn/jsref/jsref_random.asp "JavaScript random() 方法") | 返回 0 到 1 之间的随机数。 | | [round(*x*)](https://www.w3school.com.cn/jsref/jsref_round.asp "JavaScript round() 方法") | 将 x 舍入为最接近的整数。 | | [sign(*x*)](https://www.w3school.com.cn/jsref/jsref_sign.asp "JavaScript sign() 方法") | 返回数的符号(检查它是正数、负数还是零)。 | | [trunc(*x*)](https://www.w3school.com.cn/jsref/jsref_trunc.asp "JavaScript trunc() 方法") | 返回数字 (x) 的整数部分。 | ## Array ### Array 属性 | 属性 | 描述 | | --- | --- | | [constructor](https://www.w3school.com.cn/jsref/jsref_constructor_array.asp "JavaScript Array constructor 属性") | 返回创建 Array 对象原型的函数。 | | [length](https://www.w3school.com.cn/jsref/jsref_length_array.asp "JavaScript Array length 属性") | 设置或返回数组中元素的数量。 | | [prototype](https://www.w3school.com.cn/jsref/jsref_prototype_array.asp "JavaScript Array prototype 属性") | 允许您向数组添加属性和方法。 | ### Array 方法 | 方法 | 描述 | | --- | --- | | [copyWithin()](https://www.w3school.com.cn/jsref/jsref_copywithin.asp "JavaScript Array copyWithin() 方法") | 将数组中的数组元素复制到指定位置或从指定位置复制。 | | [entries()](https://www.w3school.com.cn/jsref/jsref_entries.asp "JavaScript Array entries() 方法") | 返回键/值对数组迭代对象。 | | [lastIndexOf()](https://www.w3school.com.cn/jsref/jsref_lastindexof_array.asp "JavaScript Array lastIndexOf() 方法") | 在数组中搜索元素,从末尾开始,并返回其位置。 | | [reduceRight()](https://www.w3school.com.cn/jsref/jsref_reduceright.asp "JavaScript Array reduceRight() 方法") | 将数组的值减为单个值(从右到左)。 | | [reverse()](https://www.w3school.com.cn/jsref/jsref_reverse.asp "JavaScript Array reverse() 方法") | 反转数组中元素的顺序。 | | [shift()](https://www.w3school.com.cn/jsref/jsref_shift.asp "JavaScript Array shift() 方法") | 删除数组的第一个元素,并返回该元素。 | | [splice()](https://www.w3school.com.cn/jsref/jsref_splice.asp "JavaScript Array splice() 方法") | 从数组中添加/删除元素。 | | [unshift()](https://www.w3school.com.cn/jsref/jsref_unshift.asp "JavaScript Array unshift() 方法") | 将新元素添加到数组的开头,并返回新的长度。 | Array.prototype.flat: ```js it('flat', () => { expect([1, [2], 3].flat()).toEqual([1, 2, 3]); expect([1, [2, [3, [4]]]].flat(Infinity)).toEqual([1, 2, 3, 4]); }); ``` Array.prototype.flatMap: ```js it('flatMap', () => { expect([1, 2, 3].flatMap((x) => [x, x * 2])).toEqual([1, 2, 2, 4, 3, 6]); expect([1, 2, 3].flatMap(() => 4)).toEqual([4, 4, 4]); expect([1, 2, 3].flatMap(() => [4, 5])).toEqual([4, 5, 4, 5, 4, 5]); }); ``` ### Array.prototype.sort #### Parameters `compareFn` Optional Specifies a function that defines the sort order. If omitted, the array elements are converted to strings, then sorted according to each character's Unicode code point value. + `a` The first element for comparison. + `b` The second element for comparison. #### Description If `compareFn` is not supplied, all non-`undefined` array elements are sorted by converting them to strings and comparing strings in UTF-16 code units order. For example, "banana" comes before "cherry". In a numeric sort, 9 comes before 80, but because numbers are converted to strings, "80" comes before "9" in the Unicode order. All `undefined` elements are sorted to the end of the array. | `compareFn(a, b)` return value | sort order | | --- | --- | | \> 0 | sort `a` after `b` | | < 0 | sort `a` before `b` | | \=== 0 | keep original order of `a` and `b` |
powerfulyangFri, Jul 15, 2022 10:38 AM

KMP

--- title: KMP author: powerflyang date: 2022-07-13 posterId: 46091 tags: - string - indexOf - KMP - 前缀表 - next 数组 --- > 原文链接 [多图预警👊🏻详解 KMP 算法](https://leetcode.cn/problems/implement-strstr/solution/duo-tu-yu-jing-xiang-jie-kmp-suan-fa-by-w3c9c/) ## 前缀表(next 数组) next 数组存放的是当前长度下的 **最长相同前后缀** 的长度 以 `abcabf` 举例 1. `a` 时,最长前后缀长度是 0 > 因为是**缀**。总长度就只有 1 的话单独一个字母不算做**缀** > > 字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。 2. `ab` 时,很显然长度也是 0 3. `abc` 时,很显然长度也是 0 4. `abca` 时, `最长相同前后缀`长度就是 **1** 了,是 `a` 5. `abcab` 时, `最长相同前后缀`长度就是 **2** 了,是 `ab` 6. `abcabf` 时,没有 `最长相同前后缀`了,长度是0 > 字符串的前缀是指**不包含最后一个字符**的所有以**第一个字符开头**的连续子串; > 字符串的后缀是指**不包含第一个字符**的所有以**最后一个字符结尾**的连续子串。 ```typescript /** * 最大相同前后缀长度 * 双指针 * needle: abcdabc * next[0]: 0 * i = 1, j = 0, i->b j->a , next[1] = 0 * i = 2, j = 0, i->c j->a , next[2] = 0 * i = 3, j = 0, i->d j->a , next[3] = 0 * i = 4, j = 0, i->a j->a , next[4] = 1 * i = 5, j = 1, i->b j->b , next[5] = 2 * i = 6, j = 2, i->c j->c , next[6] = 3 * i = 7 break; */ export function getNext(needle: string) { const next = [0]; let i = 1; let j = 0; while (i < needle.length) { if (needle[i] === needle[j]) { next[i] = j + 1; i++; j++; } else if (j > 0) { j = next[j - 1]; } else { next[i] = 0; i++; } } return next; } ``` ### next 数组的应用 ```typescript export const ImplementStrStr = (haystack: string, needle: string): number => { if (needle.length === 0) { return 0; } const next = getNext(needle); let j = 0; for (let i = 0; i < haystack.length; i++) { while (j > 0 && haystack[i] !== needle[j]) { // ababbabababdabbac, abaaba next=[0,0,1,1,2,3] // 0123456789 // i = 2, j = 2 // i = 3,j = 0 // i = 4,j = 0 // i = 5,j = 1 // i = 6,j = 2 // i = 7,j = 3 // next 数组的作用 // i = 8,j = 1 // 最大相同前后缀可以直接跳过 j=3, aba 最大相同前后缀长度为1。 // i = 9,j = 2 // i = 10,j = 1 // ... j = next[j - 1]; } if (haystack[i] === needle[j]) { if (j === needle.length - 1) { return i - j; } j++; } } return -1; }; ```
powerfulyangWed, Jul 13, 2022 4:34 PM

递归的三种形式

--- title: 递归的三种形式 date: 2022-07-03 posterId: 46088 tags: - 递归 - Recursion --- ## Memorization 将计算结果保存,避免重复计算。 ## Divide and Conquer 分治,将大问题分解成小问题,各个击破,然后将小问题组合起来。 1. Divide 2. Conquer 3. Combine ## Backtracking 不断试错,知错就改。类似于暴力搜索, 但是会**剪枝优化**。 一般要求返回所有满足条件的解答。 例如 [第77题](https://leetcode.cn/problems/combinations/) ```typescript function backtrack(n: number, k: number, startIndex: number, path: number[], result: number[][]) { if (path.length === k) { result.push([...path]); return; } for (let i = startIndex; i <= n; i++) { path.push(i); backtrack(n, k, i + 1, path, result); path.pop(); } } /** * #77, https://leetcode.cn/problems/combinations/ */ export const Combine = (n: number, k: number): number[][] => { const result: number[][] = []; const path: number[] = []; backtrack(n, k, 1, path, result); return result; }; ``` + **递归函数的返回值以及参数** 一般定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。 还需要一个参数 startIndex, 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex。 + **回溯函数终止条件** + **剪枝优化** --- 上面题目的回溯函数终止条件为: **path.length === k**。 循环的最大次数如何确定: 1. startIndex 随着循环推移。 2. 已经选择的元素个数: path.size(); 3. 还需要的元素个数为: k - path.size(); 4. 在集合n中至多要从该起始位置至 n - (k - path.size()) + 1 遍历,**后面 (k - path.size - 1) 个元素是不需要遍历的**。 ```typescript function backtrack(n: number, k: number, startIndex: number, path: number[], result: number[][]) { if (path.length === k) { result.push([...path]); return; } for (let i = startIndex; i <= n - (k - path.length) + 1; i++) { path.push(i); backtrack(n, k, i + 1, path, result); path.pop(); } } /** * #77, https://leetcode.cn/problems/combinations/ */ export const Combine = (n: number, k: number): number[][] => { const result: number[][] = []; const path: number[] = []; backtrack(n, k, 1, path, result); return result; }; ```
powerfulyangSun, Jul 3, 2022 9:28 PM

浏览器缓存

--- title: 浏览器缓存 date: 2022-05-26 posterId: 46092 tags: - 浏览器缓存 --- ## 缓存分类 前端缓存: * 按缓存位置分类 (`memory cache`, `disk cache`, `Service Worker` 等) * 按失效策略分类 (`Cache-Control`, `ETag` 等) ### 换缓存位置分类 我们可以在 Chrome 的开发者工具中,Network -> Size 一列看到一个请求最终的处理方式:如果是大小 (多少 K, 多少 M 等) 就表示是网络请求,否则会列出 `from memory cache`, `from disk cache` 和 `from ServiceWorker`。 它们的优先级是:(由上到下寻找,找到即返回;找不到则继续) 1. `Memory Cache`, 以 Chrome 为例,Memory Cache 作为第一公民,位于 ServiceWorker 之上。 2. `ServiceWorker`, ServiceWorker 检查资源是否存在其缓存中,并根据其编程的缓存策略决定是否返回资源。这个操作不会自动发生,需要在注册的 ServiceWorker 中定义 `fetch` 事件去拦截并处理网络请求,这样才能命中 ServiceWorker 缓存而不是网络或者 HTTP 缓存。 3. Disk Cache 4. 网络请求 ![source protected](47814) MemoryCache 的存在会导致一个问题: **ServiceWorker 并不总是对资源有着控制权。** 如果确实对 ServiceWorker 资源控制权有很强的的要求,我们还是可以做点事情的。 MemoryCache 是受控于 **「强缓存」** 的,这意味着我们可以在 ServiceWorker 拦截资源的响应,并设置资源响应头来使资源从 MemoryCache 失效: ```yaml cache-control: max-age=0 ``` ```typescript self.addEventListener("fetch", (event) => { event.respondWith( (async function () { // 从 HTTP 缓存或者网络获取资源 const res = fetch(event.request); // 因为 Response 是一个流,只能用一次,所以这里要 clone 一下。 const newRes = res.clone(); // 改写资源响应头 return new Response(res.body, { ...newRes, headers: { 'cache-control': 'max-age=0' }}); })(); ); }); ``` #### memory cache `memory cache` 是内存中的缓存,(与之相对 `disk cache` 就是硬盘上的缓存)。按照操作系统的常理:先读内存,再读硬盘。`disk cache` 将在后面介绍 (因为它的优先级更低一些),这里先讨论 `memory cache`。 几乎所有的网络请求资源都会被浏览器自动加入到 `memory cache` 中。但是也正因为数量很大但是浏览器占用的内存不能无限扩大这样两个因素,`memory cache` 注定只能是个“短期存储”。常规情况下,浏览器的 TAB 关闭后该次浏览的 `memory cache` 便告失效 (为了给其他 TAB 腾出位置)。而如果极端情况下 (例如一个页面的缓存就占用了超级多的内存),那可能在 TAB 没关闭之前,排在前面的缓存就已经失效了。 刚才提过,几乎所有的请求资源 都能进入 `memory cache`,这里细分一下主要有两块: 1. `preloader` 熟悉浏览器处理流程的同学们应该了解,在浏览器打开网页的过程中,会先请求 `HTML` 然后解析。之后如果浏览器发现了 `js`, `css` 等需要解析和执行的资源时,它会使用`CPU` 资源对它们进行解析和执行。在古老的年代(大约 2007 年以前),“请求 `js/css`\--- 解析执行-- - 请求下一个 `js/css` - --解析执行下一个 `js/css`” 这样的“串行”操作模式在每次打开页面之前进行着。很明显在解析执行的时候,网络请求是空闲的,这就有了发挥的空间:我们能不能一边解析执行 `js/css`,一边去请求下一个(或下一批)资源呢? 这就是 `preloader` 要做的事情。不过 `preloader` 没有一个官方标准,所以每个浏览器的处理都略有区别。例如有些浏览器还会下载 css 中的 `@import` 内容或者 `<video>` 的 `poster` 等。 而这些被 `preloader` 请求够来的资源就会被放入 `memory cache` 中,供之后的解析执行操作使用。 2. `preload` (虽然看上去和刚才的 preloader 就差了俩字母)。实际上这个大家应该更加熟悉一些,例如`<link rel="preload">`。这些显式指定的预加载资源,也会被放入 `memory cache` 中。 `memory cache` 机制保证了一个页面中如果有两个相同的请求 (例如两个 `src` 相同的 `<img>`,两个 `href` 相同的 `<link>`)都实际只会被请求最多一次,避免浪费。 不过在匹配缓存时,除了匹配完全相同的 URL 之外,还会比对他们的类型,CORS 中的域名规则等。因此一个作为脚本 (script) 类型被缓存的资源是不能用在图片 (image) 类型的请求中的,即便他们 src 相等。 ~~在从 `memory cache` 获取缓存内容时,浏览器会忽视例如 `max-age=0`, `no-cache` 等头部配置。~~ **但是实际上在 Chrome@v103 的版本实验,`max-age=0` 和 `no-cache` 的情况是不会走 `memory cache`。在新的版本比如图片在内存中不会发多次请求了** 例如页面上存在几个相同 `src` 的图片,即便它们可能被设置为不缓存,但依然会从 `memory cache` 中读取。这是因为 `memory cache` 只是短期使用,大部分情况生命周期只有一次浏览而已。而 `max-age=0` 在语义上普遍被解读为**不要在下次浏览时使用**,所以和 `memory cache` 并不冲突。 但如果站长是真心不想让一个资源进入缓存,就连短期也不行,那就需要使用 `no-store`。存在这个头部配置的话,即便是 `memory cache` 也不会存储,自然也不会从中读取了。(后面的第二个示例有关于这点的体现) #### disk cache `disk cache` 也叫 `HTTP cache`,顾名思义是存储在硬盘上的缓存,因此它是持久存储的,是实际存在于文件系统中的。而且它允许相同的资源在跨会话,甚至跨站点的情况下使用,例如两个站点都使用了同一张图片。 `disk cache` 会严格根据 `HTTP` 头信息中的各类字段来判定哪些资源可以缓存,哪些资源不可以缓存;哪些资源是仍然可用的,哪些资源是过时需要重新请求的。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。绝大部分的缓存都来自 `disk cache`。 关于 `HTTP` 的协议头中的缓存字段,我们会在稍后进行详细讨论。凡是持久性存储都会面临容量增长的问题,`disk cache` 也不例外。在浏览器自动清理时,会有神秘的算法去把“最老的”或者“最可能过时的”资源删除,因此是一个一个删除的。不过每个浏览器识别“最老的”和“最可能过时的”资源的算法不尽相同,可能也是它们差异性的体现。 #### Service Worker 上述的缓存策略以及缓存/读取/失效的动作都是由浏览器内部判断 & 进行的,我们只能设置响应头的某些字段来告诉浏览器,而不能自己操作。举个生活中去银行存/取钱的例子来说,你只能告诉银行职员,我要存/取多少钱,然后把由他们会经过一系列的记录和手续之后,把钱放到金库中去,或者从金库中取出钱来交给你。 但 `Service Worker` 的出现,给予了我们另外一种更加灵活,更加直接的操作方式。依然以存/取钱为例,我们现在可以绕开银行职员,自己走到金库前(当然是有别于上述金库的一个单独的小金库),自己把钱放进去或者取出来。因此我们可以选择放哪些钱(缓存哪些文件),什么情况把钱取出来(路由匹配规则),取哪些钱出来(缓存匹配并返回)。当然现实中银行没有给我们开放这样的服务。 `Service Worker` 能够操作的缓存是有别于浏览器内部的 `memory cache` 或者 `disk cache` 的。我们可以从 Chrome 的 F12 中,`Application` -> `Cache Storage` 找到这个单独的“小金库”。除了位置不同之外,这个缓存是永久性的,即关闭 TAB 或者浏览器,下次打开依然还在(而 `memory cache` 不是)。有两种情况会导致这个缓存中的资源被清除:手动调用 `API cache.delete(resource)` 或者容量超过限制,被浏览器全部清空。如果 `Service Worker` 没能命中缓存,一般情况会使用 fetch() 方法继续获取资源。这时候,浏览器就去 `memory cache` 或者 `disk cache` 进行下一次找缓存的工作了。注意:经过 `Service Worker` 的 `fetch()` 方法获取的资源,即便它并没有命中 `Service Worker` 缓存,甚至实际走了网络请求,也会标注为`from ServiceWorker`。这个情况在后面的第三个示例中有所体现。请求网络 #### 网络请求 如果一个请求在上述 3 个位置都没有找到缓存,那么浏览器会正式发送网络请求去获取内容。之后容易想到,为了提升之后请求的缓存命中率,自然要把这个资源添加到缓存中去。具体来说: 1. 根据 `Service Worker` 中的 `handler` 决定是否存入 `Cache Storage` (额外的缓存位置)。 2. 根据 `HTTP` 头部的相关字段( `Cache-control`,`Pragma` 等)决定是否存入 `disk cache` 3. `memory cache` 保存一份资源的引用,以备下次使用。 --- **`Pragma`** 是一个在 HTTP/1.0 中规定的通用首部,这个首部的效果依赖于不同的实现,所以在“请求 - 响应”链中可能会有不同的效果。它用来向后兼容只支持 HTTP/1.0 协议的缓存服务器,那时候 HTTP/1.1 协议中的 Cache-Control 还没有出来。 **注意**:由于 Pragma 在 HTTP 响应中的行为没有确切规范,所以不能可靠替代 HTTP/1.1 中通用首部 Cache-Control,尽管在请求中,假如 Cache-Control 不存在的话,它的行为与 Cache-Control: no-cache 一致。建议只在需要兼容 HTTP/1.0 客户端的场合下应用 Pragma 首部。 > 前端页面和资源是否被浏览器缓存,一般是由服务器通过设置 http 响应头部去告诉浏览器的。响应头是有两对相关联的头的,一个是 HTTP/1.0 的 Expires 和 Last-Modified,另一对是 HTTP/1.1 增加的 Cache-Control 和 Etag。 ### 按失效策略分类 `memory cache` 是浏览器为了加快读取缓存速度而进行的自身的优化行为,不受开发者控制,也不受`HTTP` 协议头的约束,算是一个黑盒。`Service Worker` 是由开发者编写的额外的脚本,且缓存位置独立,出现也较晚,使用还不算太广泛。所以我们平时最为熟悉的其实是 `disk cache`,也叫 `HTTP cache` (因为不像 `memory cache`,它遵守 `HTTP` 协议头中的字段)。平时所说的强制缓存,对比缓存,以及 `Cache-Control` 等,也都归于此类。 #### 强缓存 强制缓存的含义是,当客户端请求后,会先访问缓存数据库看缓存是否存在。如果存在则直接返回;不存在则请求真的服务器,响应后再写入缓存数据库。 **强制缓存直接减少请求数,是提升最大的缓存策略。** 它的优化覆盖了文章开头提到过的请求数据的全部三个步骤。如果考虑使用缓存来优化网页性能的话,强制缓存应该是首先被考虑的。 可以造成强制缓存的字段是 `Cache-control` 和 `Expires`。 #### 协商缓存 当强制缓存失效(超过规定时间)时,就需要使用对比缓存,由服务器决定缓存内容是否失效。 流程上说,浏览器先请求缓存数据库,返回一个缓存标识。之后浏览器拿这个标识和服务器通讯。如果缓存未失效,则返回 HTTP 状态码 304 表示继续使用,于是客户端继续使用缓存;如果失效,则返回新的数据和缓存规则,浏览器响应数据后,再把规则写入到缓存数据库。 **对比缓存在请求数上和没有缓存是一致的**,但如果是 304 的话,返回的仅仅是一个状态码而已,并没有实际的文件内容,因此 **在响应体体积上的节省是它的优化点**。它的优化覆盖了文章开头提到过的请求数据的三个步骤中的最后一个:“响应”。通过减少响应体体积,来缩短网络传输时间。所以和强制缓存相比提升幅度较小,但总比没有缓存好。 对比缓存是可以和强制缓存一起使用的,作为在强制缓存失效后的一种后备方案。实际项目中他们也的确经常一同出现。 对比缓存有 2 组字段(不是两个): + Last-Modified & If-Modified-Since 1. 服务器通过 `Last-Modified` 字段告知客户端,资源最后一次被修改的时间,例如 ```yaml Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT ``` 2. 浏览器将这个值和内容一起记录在缓存数据库中。 3. 下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的 `Last-Modified` 的值写入到请求头的 `If-Modified-Since` 字段 4. 服务器会将 `If-Modified-Since` 的值与 `Last-Modified` 字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。 但是他还是有一定缺陷的: * 如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。 * 如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。 + Etag & If-None-Match + 为了解决上述问题,出现了一组新的字段 `Etag` 和 `If-None-Match` + `Etag` 存储的是文件的特殊标识(一般都是 hash 生成的),服务器存储着文件的 `Etag` 字段。之后的流程和 `Last-Modified` 一致,只是 `Last-Modified` 字段和它所表示的更新时间改变成了 `Etag` 字段和它所表示的文件 hash,把 `If-Modified-Since` 变成了 `If-None-Match`。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。 **Etag 的优先级高于 Last-Modified** ## [Cache directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#cache_directives "Permalink to Cache directives") The following table lists the standard `Cache-Control` directives: | Request | Response | | --- | --- | | `max-age` | `max-age` | | `max-stale` | \- | | `min-fresh` | \- | | \- | `s-maxage` | | `no-cache` | `no-cache` | | `no-store` | `no-store` | | `no-transform` | `no-transform` | | `only-if-cached` | \- | | \- | `must-revalidate` | | \- | `proxy-revalidate` | | \- | `must-understand` | | \- | `private` | | \- | `public` | | \- | `immutable` | | \- | `stale-while-revalidate` | | `stale-if-error` | `stale-if-error` | Note: Check the [compatibility table](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#browser_compatibility) for their support; user agents that don't recognize them should ignore them. This section lists directives that affect caching — both response directives and request directives. ### [Response Directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives "Permalink to Response Directives") #### `max-age` The `max-age=N` response directive indicates that the response remains [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) until *N* seconds after the response is generated. Cache-Control: max-age=604800 Indicates that caches can store this response and reuse it for subsequent requests while it's [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness). Note that `max-age` is not the elapsed time since the response was received; it is the elapsed time since the response was generated on the origin server. So if the other cache(s) — on the network route taken by the response — store the response for 100 seconds (indicated using the `Age` response header field), the browser cache would deduct 100 seconds from its [freshness lifetime](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness_lifetime). Cache-Control: max-age=604800 Age: 100 #### `s-maxage` The `s-maxage` response directive also indicates how long the response is [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) for (similar to `max-age`) — but it is specific to shared caches, and they will ignore `max-age` when it is present. Cache-Control: s-maxage=604800 #### `no-cache` The `no-cache` response directive indicates that the response can be stored in caches, but the response must be validated with the origin server before each reuse, even when the cache is disconnected from the origin server. Cache-Control: no-cache If you want caches to always check for content updates while reusing stored content, `no-cache` is the directive to use. It does this by requiring caches to revalidate each request with the origin server. Note that `no-cache` does not mean "don't cache". `no-cache` allows caches to store a response but requires them to revalidate it before reuse. If the sense of "don't cache" that you want is actually "don't store", then `no-store` is the directive to use. #### `must-revalidate` The `must-revalidate` response directive indicates that the response can be stored in caches and can be reused while [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness). If the response becomes [stale](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness), it must be validated with the origin server before reuse. Typically, `must-revalidate` is used with `max-age`. Cache-Control: max-age=604800, must-revalidate HTTP allows caches to reuse [stale responses](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) when they are disconnected from the origin server. `must-revalidate` is a way to prevent this from happening - either the stored response is revalidated with the origin server or a 504 (Gateway Timeout) response is generated. #### `proxy-revalidate` The `proxy-revalidate` response directive is the equivalent of `must-revalidate`, but specifically for shared caches only. #### `no-store` The `no-store` response directive indicates that any caches of any kind (private or shared) should not store this response. Cache-Control: no-store #### `private` The `private` response directive indicates that the response can be stored only in a private cache (e.g. local caches in browsers). Cache-Control: private You should add the `private` directive for user-personalized content, especially for responses received after login and for sessions managed via cookies. If you forget to add `private` to a response with personalized content, then that response can be stored in a shared cache and end up being reused for multiple users, which can cause personal information to leak. #### `public` The `public` response directive indicates that the response can be stored in a shared cache. Responses for requests with `Authorization` header fields must not be stored in a shared cache; however, the `public` directive will cause such responses to be stored in a shared cache. Cache-Control: public In general, when pages are under Basic Auth or Digest Auth, the browser sends requests with the `Authorization` header. This means that the response is access-controlled for restricted users (who have accounts), and it's fundamentally not shared-cacheable, even if it has `max-age`. You can use the `public` directive to unlock that restriction. Cache-Control: public, max-age=604800 Note that `s-maxage` or `must-revalidate` also unlock that restriction. If a request doesn't have an `Authorization` header, or you are already using `s-maxage` or `must-revalidate` in the response, then you don't need to use `public`. #### `must-understand` The `must-understand` response directive indicates that a cache should store the response only if it understands the requirements for caching based on status code. `must-understand` should be coupled with `no-store` for fallback behavior. Cache-Control: must-understand, no-store If a cache doesn't support `must-understand`, it will be ignored. If `no-store` is also present, the response isn't stored. If a cache supports `must-understand`, it stores the response with an understanding of cache requirements based on its status code. #### `no-transform` Some intermediaries transform content for various reasons. For example, some convert images to reduce transfer size. In some cases, this is undesirable for the content provider. `no-transform` indicates that any intermediary (regardless of whether it implements a cache) shouldn't transform the response contents. Note: [Google's Web Light](https://developers.google.com/search/docs/advanced/mobile/web-light?visit_id=637855965115455923-776951611&rd=1) is one kind of such an intermediary. It converts images to minimize data for a cache store or slow connection and supports `no-transform` as an opt-out option. #### `immutable` The `immutable` response directive indicates that the response will not be updated while it's [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness). Cache-Control: public, max-age=604800, immutable A modern best practice for static resources is to include version/hashes in their URLs, while never modifying the resources — but instead, when necessary, *updating* the resources with newer versions that have new version-numbers/hashes, so that their URLs are different. That's called the **cache-busting** pattern. <script src=https://example.com/react.0.0.0.js></script> When a user reloads the browser, the browser will send conditional requests for validating to the origin server. But it's not necessary to revalidate those kinds of static resources even when a user reloads the browser, because they're never modified. `immutable` tells a cache that the response is immutable while it's [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) and avoids those kinds of unnecessary conditional requests to the server. When you use a cache-busting pattern for resources and apply them to a long `max-age`, you can also add `immutable` to avoid revalidation. #### `stale-while-revalidate` The `stale-while-revalidate` response directive indicates that the cache could reuse a stale response while it revalidates it to a cache. Cache-Control: max-age=604800, stale-while-revalidate=86400 In the example above, the response is [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) for 7 days (604800s). After 7 days it becomes [stale](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness), but the cache is allowed to reuse it for any requests that are made in the following day (86400s), provided that they revalidate the response in the background. Revalidation will make the cache be [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) again, so it appears to clients that it was always [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) during that period — effectively hiding the latency penalty of revalidation from them. If no request happened during that period, the cache became [stale](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) and the next request will revalidate normally. #### `stale-if-error` The `stale-if-error` response directive indicates that the cache can reuse a [stale response](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) when an origin server responds with an error (500, 502, 503, or 504). Cache-Control: max-age=604800, stale-if-error=86400 In the example above, the response is [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) for 7 days (604800s). After 7 days it becomes [stale](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness), but it can be used for an extra 1 day (86400s) if the server responds with an error. After a period of time, the stored response became [stale](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) normally. This means that the client will receive an error response as-is if the origin server sends it. ### [Request Directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#request_directives "Permalink to Request Directives") #### [`no-cache`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#no-cache_2 "Permalink to no-cache") The `no-cache` request directive asks caches to validate the response with the origin server before reuse. Cache-Control: no-cache `no-cache` allows clients to request the most up-to-date response even if the cache has a [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) response. Browsers usually add `no-cache` to requests when users are **force reloading** a page. #### [`no-store`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#no-store_2 "Permalink to no-store") The `no-store` request directive allows a client to request that caches refrain from storing the request and corresponding response — even if the origin server's response could be stored. Cache-Control: no-store Note that the major browsers do not support requests with `no-store`. #### [`max-age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#max-age_2 "Permalink to max-age") The `max-age=N` request directive indicates that the client allows a stored response that is generated on the origin server within *N* seconds — where *N* may be any non-negative integer (including `0`). Cache-Control: max-age=3600 In the case above, if the response with `Cache-Control: max-age=604800` was generated more than 3 hours ago (calculated from `max-age` and the `Age` header), the cache couldn't reuse that response. Many browsers use this directive for **reloading**, as explained below. Cache-Control: max-age=0 `max-age=0` is a workaround for `no-cache`, because many old (HTTP/1.0) cache implementations don't support `no-cache`. Recently browsers are still using `max-age=0` in "reloading" — for backward compatibility — and alternatively using `no-cache` to cause a "force reloading". If the `max-age` value isn't non-negative (for example, `-1`) or isn't an integer (for example, `3599.99`), then the caching behavior is undefined. However, the [Calculating Freshness Lifetime](https://httpwg.org/specs/rfc7234.html#calculating.freshness.lifetime) section of the HTTP specification states: > Caches are encouraged to consider responses that have invalid freshness information to be stale. In other words, for any `max-age` value that isn't an integer or isn't non-negative, the caching behavior that's encouraged is to treat the value as if it were `0`. #### [`max-stale`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#max-stale "Permalink to max-stale") The `max-stale=N` request directive indicates that the client allows a stored response that is [stale](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) within *N* seconds. Cache-Control: max-stale=3600 In the case above, if the response with `Cache-Control: max-age=604800` was generated more than 3 hours ago (calculated from `max-age` and the `Age` header), the cache couldn't reuse that response. Clients can use this header when the origin server is down or too slow and can accept cached responses from caches even if they are a bit old. Note that the major browsers do not support requests with `max-stale`. #### [`min-fresh`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#min-fresh "Permalink to min-fresh") The `min-fresh=N` request directive indicates that the client allows a stored response that is [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) for at least *N* seconds. Cache-Control: min-fresh=600 In the case above, if the response with `Cache-Control: max-age=3600` was stored in caches 51 minutes ago, the cache couldn't reuse that response. Clients can use this header when the user requires the response to not only be [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness), but also requires that it won't be updated for a period of time. Note that the major browsers do not support requests with `min-fresh`. #### [`no-transform`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#no-transform_2 "Permalink to no-transform") Same meaning that `no-transform` has for a response, but for a request instead. #### [`only-if-cached`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#only-if-cached "Permalink to only-if-cached") The client indicates that cache should obtain an already-cached response. If a cache has stored a response, it's reused. ## [Use Cases](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#use_cases "Permalink to Use Cases") ### [Preventing storing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#preventing_storing "Permalink to Preventing storing") If you don't want a response stored in caches, use the `no-store` directive. ```yaml Cache-Control: no-store ``` Note that `no-cache` means "it can be stored but don't reuse before validating" — so it's not for preventing a response from being stored. ```yaml Cache-Control: no-cache ``` In theory, if directives are conflicted, the most restrictive directive should be honored. So the example below is basically meaningless because `private`, `no-cache`, `max-age=0` and `must-revalidate` conflict with `no-store`. ```yaml # conflicted Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate # equivalent to Cache-Control: no-store ``` ### [Up-to-date contents always](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#up-to-date_contents_always "Permalink to Up-to-date contents always") For content that's generated dynamically, or that's static but updated often, you want a user to always receive the most up-to-date version. If you don't add a `Cache-Control` header because the response is not intended to be cached, that could cause an unexpected result. Cache storage is allowed to cache it heuristically — so if you have any requirements on caching, you should always indicate them explicitly, in the `Cache-Control` header. Adding `no-cache` to the response causes revalidation to the server, so you can serve a [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) response every time — or if the client already has a new one, just respond `304 Not Modified`. Cache-Control: no-cache Most HTTP/1.0 caches don't support `no-cache` directives, so historically `max-age=0` was used as a workaround. But only `max-age=0` could cause a [stale response](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) to be reused when caches disconnected from the origin server. `must-revalidate` addresses that. That's why the example below is equivalent to `no-cache`. Cache-Control: max-age=0, must-revalidate But for now, you can simply use `no-cache` instead. ### [Clearing an already-stored cache](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#clearing_an_already-stored_cache "Permalink to Clearing an already-stored cache") Unfortunately, there are no cache directives for clearing already-stored responses from caches. Imagine that clients/caches store a [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#freshness) response for a path, with no request flight to the server. There is nothing a server could do to that path. Alternatively, `Clear-Site-Data` can clear a browser cache for a site. But be careful: that clears every stored response for a site — and only in browsers, not for a shared cache. ## 启发式缓存 > https://stackoverflow.com/a/27972908 如果一个请求 response header 没有设置 Expires 和 Cache-Control,但是有设置 Last-Modified 信息 (**ps: 有 Etag 的也会走启发式缓存(强缓存),但是在 *普通刷新* 动作下:至少在较新版的 chrome 里,浏览器会在当前 url 的 request header 带上 chache-control: max-age=0,所以普通刷新动作下 index.html 不会走强缓存,能走协商缓存的情况会走协商缓存,由于这个动作干扰导致问题变成偶现不易排查**),这种情况下浏览器会有一个默认的缓存策略:(当前时间 - Last-Modified) * 0.1,这就是启发式缓存。 启发式缓存是强缓存,不过期就不会走 HTTP 请求。 No explicit HTTP Cache Lifetime information was provided. Heuristic expiration policies suggest defaulting to: 10% of the delta between Last-Modified and Date. **解决办法: Cache-Control: no-cache** The `no-cache` response directive indicates that the response can be stored in caches, but the response must be validated with the origin server before each reuse, even when the cache is disconnected from the origin server. Cache-Control: no-cache If you want caches to always check for content updates while reusing stored content, `no-cache` is the directive to use. It does this by requiring caches to revalidate each request with the origin server. Note that `no-cache` does not mean "don't cache". `no-cache` allows caches to store a response but requires them to revalidate it before reuse. If the sense of "don't cache" that you want is actually "don't store", then `no-store` is the directive to use. **~~当然这不是一个好的办法,因为 memory cache, 即页面打开很久没有关闭的情况,这个时候如果文件名没有改变,会直接从内存中读取。导致浏览的依然是历史版本。~~ 但是在 chrome v103 版本 no-cache 是可以解决 memory cache 缓存的问题了**
powerfulyangThu, May 26, 2022 3:40 PM

DOM 相关

--- title: DOM 相关 date: 2022-05-21 posterId: 46085 tags: - DOM - Click --- ## UIEvent.detail The **`UIEvent.detail`** read-only property, when non-zero, provides the current (or next, depending on the event) click count. For [`click`](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event "click") or [`dblclick`](https://developer.mozilla.org/en-US/docs/Web/API/Element/dblclick_event "dblclick") events, `UIEvent.detail` is the current click count. For [`mousedown`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mousedown_event "mousedown") or [`mouseup`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseup_event "mouseup") events, `UIEvent.detail` is *1 plus* the current click count. For all other [`UIEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent) objects, `UIEvent.detail` is always zero. ## event.button `event.button` 的所有可能值如下: | 鼠标按键状态 | `event.button` | | --- | --- | | 左键 (主要按键) | 0 | | 中键 (辅助按键) | 1 | | 右键 (次要按键) | 2 | | X1 键 (后退按键) | 3 | | X2 键 (前进按键) | 4 | ## 非替换元素和替换元素 ### 替换元素 + 替换元素是浏览器根据其`标签`与`属性`来判断显示具体的内容。 + 比如:<input />, type="text" 时,这是一个文本输入框,type 换一个值,浏览器显示就不一样。 + 常见的可替换元素,例如 `<iframe>`、`<video>`、`<embed>`、`<img>` 等,有些元素在特定情况下会被当作可替换元素处理,例如 `<option>`、`<audio>`、`<canvas>`、`<object>`、`<applet>`、`<input>` 等。 ### 非替换元素 * 非替换元素的内容不会超出 `CSS` 的模型范围,`CSS` 在渲染时会考虑非替换元素的内容。 * `HTML` 的大多数元素是不可替换元素,即其内容直接表现给浏览器,例如 `<div>`、`<p>`、`<h1>~<h6>`、`<table>` 等。 ## innerHTML、innerText 和 outerHTML、outerText 的区别 * innerHTML 设置或获取位于对象起始和结束标签内的 HTML * outerHTML 设置或获取对象及其内容的 HTML 形式 * innerText 设置或获取位于对象起始和结束标签内的文本 * outerText 设置(包括标签)或获取(不包括标签)对象的文本 innerText 和 outerText 在获取时是相同效果,但在设置时,innerText 仅设置标签内的文本,而outerText 设置包括标签在内的文本。 ```codepen <iframe height="300" style="width: 100%;" scrolling="no" title="innerHTML、innerText 和 outerHTML、outerText 的区别" src="https://codepen.io/powerfulyang/embed/xxjMwKV?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/powerfulyang/pen/xxjMwKV"> innerHTML、innerText 和 outerHTML、outerText 的区别</a> by powerfulyang (<a href="https://codepen.io/powerfulyang">@powerfulyang</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> <br/> ``` ## 自动重复 如果按下一个键足够长的时间,它就会开始“自动重复”:`keydown` 会被一次又一次地触发,然后当按键被释放时,我们最终会得到 `keyup`。因此,有很多 `keydown` 却只有一个 `keyup` 是很正常的。 对于由自动重复触发的事件,`event` 对象的 `event.repeat` 属性被设置为 `true`。 ## 防止滚动 我们如何使某些东西变成不可滚动? 我们不能通过在 `onscroll` 监听器中使用 `event.preventDefault()` 来阻止滚动,因为它会在滚动发生 **之后** 才触发。 但是我们可以在导致滚动的事件上,例如在 pageUp 和 pageDown 的 `keydown` 事件上,使用 `event.preventDefault()` 来阻止滚动。 如果我们向这些事件中添加事件处理程序,并向其中添加 `event.preventDefault()`,那么滚动就不会开始。 启动滚动的方式有很多,使用 CSS 的 `overflow` 属性更加可靠。 ## 移动端点击,触发click事件存在300ms延迟 在十几年前,当时的网页基本上都是为PC设备所设计的,没有什么移动端适配的概念,导致字体看起来非常小,阅读困难。 为了处理这种情况,苹果的工程师们想了各种应对方案,其中最为出名的,当属**双击缩放**(double tap to zoom)。通过双击,在放大比例和原始比例之间进行切换。 如果判断用户是点击还是双击呢?苹果的逻辑如下: **在用户点击完此处第一次后,如果300ms内没有在此处进行第二次点击,就认为是一个纯点击操作。** 这就是300ms延迟的来源,浏览器通过300ms的时间间隔猜测你的行为意图,试图分辨你是想单击还是双击。 **解决办法: 现在已经不会有这种事啦,无需 fastclick 用原生 click 就好** Chrome 32+ on Android with `width=device-width` in the [viewport meta tag](https://developer.mozilla.org/en-US/docs/Mobile/Viewport_meta_tag) doesn't have a 300ms delay. ```html <meta name="viewport" content="width=device-width, initial-scale=1"/> ``` Same goes for Chrome on Android (all versions) with `user-scalable=no` in the viewport meta tag. But be aware that `user-scalable=no` also disables pinch zooming, which may be an accessibility concern. For IE11+, you can use `touch-action: manipulation;` to disable double-tap-to-zoom on certain elements (like links and buttons). For IE10 use `-ms-touch-action: manipulation`. ## DOMContentLoaded ### DOMContentLoaded 和 脚本 当浏览器处理一个 HTML 文档,并在文档中遇到 `<script>` 标签时,就会在继续构建 DOM 之前运行它。这是一种防范措施,因为脚本可能想要修改 DOM,甚至对其执行 `document.write` 操作,所以 `DOMContentLoaded` 必须等待脚本执行结束。 因此,`DOMContentLoaded` 肯定在下面的这些脚本执行结束之后发生: ```js <script> document.addEventListener("DOMContentLoaded", () => { alert("DOM ready!"); }); </script> <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script> <script> alert("Library loaded, inline script executed"); </script> ``` 在上面这个例子中,我们首先会看到 “Library loaded…”,然后才会看到 “DOM ready!”(所有脚本都已经执行结束)。 此规则有两个例外: 1. 具有 `async` 特性(attribute)的脚本不会阻塞 `DOMContentLoaded`。 2. 使用 `document.createElement('script')` 动态生成并添加到网页的脚本也不会阻塞 `DOMContentLoaded`。 ### DOMContentLoaded 和样式 外部样式表不会影响 DOM,因此 DOMContentLoaded 不会等待它们。 但这里有一个陷阱。如果在样式后面有一个脚本,那么该脚本必须等待样式表加载完成: ```js <link type="text/css" rel="stylesheet" href="style.css"> <script> // 在样式表加载完成之前,脚本都不会执行 alert(getComputedStyle(document.body).marginTop); </script> ``` 原因是,脚本可能想要获取元素的坐标和其他与样式相关的属性,如上例所示。因此,它必须等待样式加载完成。 当 DOMContentLoaded 等待脚本时,它现在也在等待脚本前面的样式。 ### 浏览器内建的自动填充 Firefox,Chrome 和 Opera 都会在 `DOMContentLoaded` 中自动填充表单。 例如,如果页面有一个带有登录名和密码的表单,并且浏览器记住了这些值,那么在 `DOMContentLoaded` 上,浏览器会尝试自动填充它们(如果得到了用户允许)。 因此,如果 `DOMContentLoaded` 被需要加载很长时间的脚本延迟触发,那么自动填充也会等待。你可能在某些网站上看到过(如果你使用浏览器自动填充)—— 登录名/密码字段不会立即自动填充,而是在页面被完全加载前会延迟填充。这实际上是 `DOMContentLoaded` 事件之前的延迟。 ### readyState `document.readyState` 是文档的当前状态,可以在 `readystatechange` 事件中跟踪状态更改: ```js addEventListener('readystatechange', (event) => { // document.readyState }); ``` * `loading` —— 文档正在被加载。 * `interactive` —— 文档已被解析完成,与 `DOMContentLoaded` 几乎同时发生,但是在 `DOMContentLoaded` 之前发生。 * `complete` —— 文档和资源均已加载完成,与 `window.onload` 几乎同时发生,但是在 `window.onload` 之前发生。 ## 脚本:async,defer ### defer `defer` 特性告诉浏览器不要等待脚本。相反,浏览器将继续处理 HTML,构建 DOM。脚本会“在后台”下载,然后等 DOM 构建完成后,脚本才会执行。 这是与上面那个相同的示例,但是带有 `defer` 特性: ```html <p>...content before script...</p> <script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script> <!-- 立即可见 --> <p>...content after script...</p> ``` 换句话说: * 具有 `defer` 特性的脚本不会阻塞页面。 * 具有 `defer` 特性的脚本总是要等到 DOM 解析完毕,但在 `DOMContentLoaded` 事件之前执行 `DOMContentLoaded` 事件处理程序等待具有 `defer` 特性的脚本执行完成。它仅在脚本下载且执行结束后才会被触发。 **具有 `defer` 特性的脚本保持其相对顺序,就像常规脚本一样。** 假设,我们有两个具有 `defer` 特性的脚本:`long.js` 在前,`small.js` 在后。 ```html <script defer src="https://javascript.info/article/script-async-defer/long.js"></script> <script defer src="https://javascript.info/article/script-async-defer/small.js"></script> ``` 浏览器扫描页面寻找脚本,然后并行下载它们,以提高性能。因此,在上面的示例中,两个脚本是并行下载的。`small.js` 可能会先下载完成。 ……但是,`defer` 特性除了告诉浏览器“不要阻塞页面”之外,还可以确保脚本执行的相对顺序。因此,即使 `small.js` 先加载完成,它也需要等到 `long.js` 执行结束才会被执行。 **`defer` 特性仅适用于外部脚本** 如果 `<script>` 脚本没有 `src`,则会忽略 `defer` 特性。 ### async `async` 特性意味着脚本是完全独立的: * 浏览器不会因 `async` 脚本而阻塞(与 `defer` 类似)。 * 其他脚本不会等待 `async` 脚本加载完成,同样,`async` 脚本也不会等待其他脚本。 * `DOMContentLoaded` 和异步脚本不会彼此等待: * `DOMContentLoaded` 可能会发生在异步脚本之前(如果异步脚本在页面完成后才加载完成) * `DOMContentLoaded` 也可能发生在异步脚本之后(如果异步脚本很短,或者是从 HTTP 缓存中加载的) **`async` 特性仅适用于外部脚本** ### 动态脚本 **默认情况下,动态脚本的行为是“异步”的。** 如果我们显式地设置了 `script.async=false`,则可以改变这个规则。然后脚本将按照脚本在文档中的顺序执行,就像 `defer` 那样。 ### 跨源策略 如果我们使用的是来自其他域的脚本,并且该脚本中存在 error,那么我们无法获取 error 的详细信息。 **要允许跨源访问,`<script>` 标签需要具有 `crossorigin` 特性(attribute),并且远程服务器必须提供特殊的 header。** 这里有三个级别的跨源访问: 1. **无 `crossorigin` 特性** —— 禁止访问。 2. **`crossorigin="anonymous"`** —— 如果服务器的响应带有包含 `*` 或我们的源(origin)的 header `Access-Control-Allow-Origin`,则允许访问。浏览器不会将授权信息和 cookie 发送到远程服务器。 3. **`crossorigin="use-credentials"`** —— 如果服务器发送回带有我们的源的 header `Access-Control-Allow-Origin` 和 `Access-Control-Allow-Credentials: true`,则允许访问。浏览器会将授权信息和 cookie 发送到远程服务器 ## 选择(Selection)和范围(Range) 这玩意知道就好,**选中和光标位置设置**
powerfulyangSat, May 21, 2022 10:37 AM

Express 相关

--- title: Express 相关 author: powerfulyang date: 2022-04-08 posterId: 46073 tags: - Express - Response Header --- > Relate to https://stackoverflow.com/a/7086621 Comprehensive list of Node.js/Express response methods and when they must be called: Response must be in **Head** and remains in **Head**: 1. [`res.writeContinue()`](https://nodejs.org/docs/latest/api/http.html#http_response_writecontinue) 2. [`res.statusCode = 404`](https://nodejs.org/docs/latest/api/http.html#http_response_statuscode) 3. [`res.setHeader(name, value)`](https://nodejs.org/docs/latest/api/http.html#http_request_setheader_name_value) 4. [`res.getHeader(name)`](https://nodejs.org/docs/latest/api/http.html#http_request_getheader_name) 5. [`res.removeHeader(name)`](https://nodejs.org/docs/latest/api/http.html#http_request_removeheader_name) 6. [`res.header(key[, val])`](https://expressjs.com/en/api.html#setHeaders) (Express only) 7. `res.charset = 'utf-8'` (Express only; only affects Express-specific methods) 8. [`res.contentType(type)`](https://expressjs.com/en/api.html#req.is) (Express only) --- Response must be in **Head** and becomes **Body**: 1. [`res.writeHead(statusCode, [reasonPhrase], [headers])`](https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers) --- Response can be in either **Head/Body** and remains in **Body**: 1. [`res.write(chunk, encoding='utf8')`](https://nodejs.org/docs/latest/api/http.html#http_request_write_chunk_encoding_callback) --- Response can be in either **Head/Body** and becomes **Finished**: 1. [`res.end([data], [encoding])`](https://nodejs.org/docs/latest/api/http.html#http_response_end_data_encoding_callback) --- Response can be in either **Head/Body** and remains in its current state: 1. [`res.addTrailers(headers)`](https://nodejs.org/docs/latest/api/http.html#http_response_addtrailers_headers) --- Response must be in **Head** and becomes **Finished**: 1. `return next([err])` (Connect/Express only) 2. Any exceptions within middleware `function(req, res, next)` (Connect/Express only) 3. [`res.send(body|status[, headers|status[, status]])`](https://expressjs.com/en/api.html#res.attachment) (Express only) 4. [`res.attachment(filename)`](https://expressjs.com/en/api.html#res.attachment) (Express only) 5. [`res.sendfile(path[, options[, callback]])`](https://expressjs.com/en/api.html#res.attachment) (Express only) 6. [`res.json(obj[, headers|status[, status]])`](https://expressjs.com/en/api.html#res.attachment) (Express only) 7. [`res.redirect(url[, status])`](https://expressjs.com/en/api.html#res.redirect) (Express only) 8. [`res.cookie(name, val[, options])`](https://expressjs.com/en/api.html#res.cookie) (Express only) 9. [`res.clearCookie(name[, options])`](https://expressjs.com/en/api.html#res.clearCookie) (Express only) 10. [`res.render(view[, options[, fn]])`](https://expressjs.com/en/api.html#res.clearCookie) (Express only) 11. [`res.partial(view[, options])`](http://expressjs.com/2x/guide.html#res.partial()) (Express only)
powerfulyangMon, Apr 11, 2022 4:54 PM

Cookies

--- title: Cookies author: powerfulyang date: 2022-04-08 tags: - Cookie - SameSite --- ## Cookie Cookie 是由 Set-Cookie: NAME=VALUE; Expires=DATE; Path=PATH; Domain=DOMAIN_NAME;SECURE 组成 ### SameSite **Secure context:** This feature is available only in [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) (HTTPS), in some or all [supporting browsers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#browser_compatibility). The **`SameSite`** attribute of the [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) HTTP response header allows you to declare if your cookie should be restricted to a [first-party](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#third-party_cookies) or same-site context. **Note:** Standards related to the Cookie `SameSite` attribute recently changed such that: * The cookie-sending behavior if `SameSite` is not specified is `SameSite=Lax`. Previously the default was that cookies were sent for all requests. * Cookies with `SameSite=None` must now also specify the `Secure` attribute (they require a secure context/HTTPS). * Cookies from the same domain are no longer considered to be from the same site if sent using a different scheme (`http:` or `https:`). #### [Values](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#values "Permalink to Values") The `SameSite` attribute accepts three values: + [`Lax`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax "Permalink to Lax") Cookies are not sent on normal cross-site subrequests (for example to load images or frames into a third party site), but are sent when a user is *navigating to* the origin site (i.e., when following a link). This is the default cookie value if `SameSite` has not been explicitly specified in recent browser versions (see the "SameSite: Defaults to Lax" feature in the Browser Compatibility). **Note:** `Lax` replaced `None` as the default value in order to ensure that users have reasonably robust defense against some classes of cross-site request forgery ([CSRF](https://developer.mozilla.org/en-US/docs/Glossary/CSRF)) attacks. + [`Strict`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#strict "Permalink to Strict") Cookies will only be sent in a first-party context and not be sent along with requests initiated by third party websites. + [`None`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#none "Permalink to None") Cookies will be sent in all contexts, i.e. in responses to both first-party and cross-origin requests. If `SameSite=None` is set, the cookie [`Secure`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure) attribute must also be set (or the cookie will be blocked). ### [Fixing common warnings](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#fixing_common_warnings "Permalink to Fixing common warnings") #### [`SameSite=None` requires `Secure`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#samesitenone_requires_secure "Permalink to SameSite=None requires Secure") Warnings like the ones below might appear in your console: Cookie "myCookie" rejected because it has the "SameSite=None" attribute but is missing the "secure" attribute. This Set-Cookie was blocked because it had the "SameSite=None" attribute but did not have the "Secure" attribute, which is required in order to use "SameSite=None". The warning appears because any cookie that requests `SameSite=None` but is not marked `Secure` will be rejected. Set-Cookie: flavor=choco; SameSite=None Copy to Clipboard To fix this, you will have to add the `Secure` attribute to your `SameSite=None` cookies. Set-Cookie: flavor=choco; SameSite=None; Secure Copy to Clipboard A [`Secure`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#secure) cookie is only sent to the server with an encrypted request over the HTTPS protocol. Note that insecure sites (`http:`) can't set cookies with the `Secure` directive. **Note:** On older browser versions you might get a warning that the cookie will be blocked in future. For example: Cookie `myCookie` will be soon rejected because it has the `SameSite` attribute set to `None` or an invalid value, without the `secure` attribute. #### [Cookies without `SameSite` default to `SameSite=Lax`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#cookies_without_samesite_default_to_samesitelax "Permalink to Cookies without SameSite default to SameSite=Lax") Recent versions of modern browsers provide a more secure default for `SameSite` to your cookies and so the following message might appear in your console: Cookie "myCookie" has "SameSite" policy set to "Lax" because it is missing a "SameSite" attribute, and "SameSite=Lax" is the default value for this attribute. The warning appears because the `SameSite` policy for a cookie was not explicitly specified: Set-Cookie: flavor=choco Copy to Clipboard You should explicitly communicate the intended `SameSite` policy for your cookie (rather than relying on browsers to apply `SameSite=Lax` automatically). This will also improve the experience across browsers as not all of them default to `Lax` yet. Set-Cookie: flavor=choco; SameSite=Lax
powerfulyangFri, Apr 8, 2022 5:42 PM

OSI [Open System Interconnection Model]

--- title: OSI [Open System Interconnection Model] author: powerfulyang date: 2022-04-06 posterId: 46074 tags: - OSI 七层模型 --- For IT professionals, the seven layers refer to the Open Systems Interconnection (OSI) model, a conceptual framework that describes the functions of a networking or telecommunication system. ## Layer 7 - Application To further our bean dip analogy, the Application Layer is the one at the top - it’s what most users see. In the OSI model, this is the layer that is the “closest to the end user”. Applications that work at Layer 7 are the ones that users interact with directly. A web browser (Google Chrome, Firefox, Safari, etc.) or other app - Skype, Outlook, Office - are examples of Layer 7 applications. + 它是计算机用户,以及各种应用程序和网络之间的接口,其功能是直接向用户提供服务,完成用户希望在网络上完成的各种工作。 + 应用层为用户提供的服务和协议有:文件服务、目录服务、文件传输服务(FTP)、远程登录服务(Telnet)、电子邮件服务(E-mail)、打印服务、安全服务、网络管理服务、数据库服务等 + 应用层向最终用户应用程序提供网络服务。网络服务通常是处理用户数据的协议。例如,在Web浏览器应用程序中,应用程序层协议HTTP打包了发送和接收网页内容所需的数据。该层7将数据提供给表示层(并从中获得数据)。 ## Layer 6 - Presentation The Presentation Layer represents the area that is independent of data representation at the application layer. In general, it represents the preparation or translation of application format to network format, or from network formatting to application format. In other words, the layer “presents” data for the application or the network. A good example of this is encryption and decryption of data for secure transmission - this happens at Layer 6. + 它对来自应用层的命令和数据进行解释,对各种语法赋予相应的含义,并按照一定的格式传送给会话层。其主要功能是“处理用户信息的表示问题,如:数据格式处理、数据的编码、压缩和解压缩、加密和解密。 + 表示层是所有OSI模型中最简单的功能。 ## Layer 5 - Session When two devices, computers or servers need to “speak” with one another, a session needs to be created, and this is done at the Session Layer. Functions at this layer involve setup, coordination (how long should a system wait for a response, for example) and termination between the applications at each end of the session. + 负责连接,建立和断开连接的时机,数据的发送顺序 ## Layer 4 – Transport (TCP,UDP) The Transport Layer deals with the coordination of the data transfer between end systems and hosts. How much data to send, at what rate, where it goes, etc. The best known example of the Transport Layer is the Transmission Control Protocol (TCP), which is built on top of the Internet Protocol (IP), commonly known as TCP/IP. TCP and UDP port numbers work at Layer 4, while IP addresses work at Layer 3, the Network Layer. + 负责连接的建立和断开和数据的传输,并保证数据的可靠性(数据不丢失)和完整性(数据不缺失)和正确性(顺序不混乱) + 传输的数据单位是段segment + 传输层通过网络连接传递数据。TCP是传输第4层网络协议的最常见示例。 不同的传输协议可能支持一系列可选功能,包括错误恢复,流控制以及对重传的支持。 ## Layer 3 - Network (IP,ICMP,RIP,OSPF,BGP,IGMP) Here at the Network Layer is where you’ll find most of the router functionality that most networking professionals care about and love. In its most basic sense, this layer is responsible for packet forwarding, including routing through different routers. You might know that your Boston computer wants to connect to a server in California, but there are millions of different paths to take. Routers at this layer help do this efficiently. + 选择合适的路径将数据发送到目标地址(ip地址) + 传输的是数据包 + 网络层在数据链路层之上添加了路由的概念。当数据到达网络层时,将检查每个帧内包含的源地址和目标地址,以确定数据是否已到达其最终目标。如果数据已到达最终目的地,则此第3层将数据格式化为传递到传输层的数据包。否则,网络层将更新目标地址,并将帧向下推至较低层。 为了支持路由,网络层维护逻辑地址,例如 网络上设备的IP地址。网络层还管理这些逻辑地址和物理地址之间的映射。在IP网络中,此映射是通过地址解析协议(ARP)完成的。 > 数据链路层和网络层的共同点和区别: > > 1、都是基于目标地址将数据发给接收端,但网络层是ip地址,数据链路层是mac地址。网络层发送的整个数据,数据链路层发送的是数据的一个分段 ## Layer 2 – Data Link (SLIP,CSLIP,PPP,ARP,RARP,MTU) The Data Link Layer provides node-to-node data transfer (between two directly connected nodes), and also handles error correction from the physical layer. Two sublayers exist here as well - the Media Access Control (MAC) layer and the Logical Link Control (LLC) layer. In the networking world, most switches operate at Layer 2. + 设备: 网卡 + 负责建立和管理节点间的链路(mac地址) + 传输的是frame数据帧 + 从物理层获取数据时,数据链路层会检查物理传输错误,并将位打包到数据“帧”中。数据链路层还管理物理寻址方案,例如用于以太网的MAC地址,控制任何各种网络设备对物理介质的访问。因为数据链路层是OSI模型中最复杂的单个层,所以它通常分为两部分:媒体访问控制子层和逻辑链路控制子层 ## Layer 1 - Physical (ISO2110, IEEE802, IEEE802.2) At the bottom of our OSI bean dip we have the Physical Layer, which represents the electrical and physical representation of the system. This can include everything from the cable type, radio frequency link (as in an 802.11 wireless systems), as well as the layout of pins, voltages and other physical requirements. When a networking problem occurs, many networking pros go right to the physical layer to check that all of the cables are properly connected and that the power plug hasn’t been pulled from the router, switch or computer, for example. + 传输介质: 网线电缆、集线器、光纤、还有无线信道。 + 传输的是比特流,单位是bit比特位(本质上是0、1高低电平)。 + 在物理层,使用物理介质支持的信号类型来传输数据:电压,射频或红外或普通光脉冲。
powerfulyangWed, Apr 6, 2022 9:58 PM

nohup and &

--- title: nohup and & author: powerfulyang date: 2022-04-06 posterId: 46068 tags: - nohup --- ## & & 的意思是在后台运行,什么意思呢?意思是说,当你在执行 ./a.out & 的时候,即使你用 ctrl C, 那么 a.out 照样运行(因为对 SIGINT 信号免疫)。但是要注意, 如果你直接关掉 shell 后, 那么,a.out 进程同样消失。可见, & 的后台并不硬(因为对 SIGHUP 信号不免疫)。 ## nohup nohup 的意思是忽略 SIGHUP 信号,所以当运行 nohup ./a.out 的时候,关闭 shell, 那么 a.out 进程还是存在的(对 SIGHUP 信号免疫)。但是,要注意,如果你直接在 shell 中用 Ctrl C,那么,a.out 进程也是会消失的(因为对 SIGINT 信号不免疫) ## nohup , & 所以,& 和 nohup 没有半毛钱的关系,要让进程真正不受 shell 中 Ctrl C 和 shell 关闭的影响, 那该怎么办呢?那就用 nohup ./a.out & 吧, 两全其美。 ## /dev/null and > override ### override and append + `>` 代表override + `>>` 代表append ### /dev/null + 可以将 /dev/null 看作"黑洞"。 它非常等价于一个只写文件,所有写入它的内容都会永远丢失。而尝试从它那儿读取内容则什么也读不到。然而,/dev/null 对命令行和脚本都非常的有用。 **用处:** + _禁止标准输出 command 1>/dev/null stdout 被遗弃_ + _禁止标准错误 command 2>/dev/null stderr 被遗弃_ ### `command 1>/dev/null 2>&1` **解读 `1>/dev/null` `2>&1` 分为两部分** + 1 表示stdout标准输出,系统默认值是1,所以 + `command>/dev/null` 等同于 `command 1>/dev/null` + `command 1>/dev/null 2>&1` 等同于 `command>/dev/null 2>&1` + 2 表示stderr标准错误 + & 表示等同于的意思,2>&1,表示2的输出重定向等同于1
powerfulyangWed, Apr 6, 2022 9:53 PM

Cross-Origin Resource Sharing

--- title: Cross-Origin Resource Sharing author: powerfulyang date: 2022-03-27 tags: - CORS --- > Relate to https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS 首先不谈 JSONP 这种注入执行动态js的方案。 ## Simple requests A simple request is one that meets all the following conditions: + One of the allowed methods: + GET + HEAD + POST + Apart from the headers automatically set by the user agent (for example, [`Connection`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection), [`User-Agent`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent), or [the other headers defined in the Fetch spec as a *forbidden header name*](https://fetch.spec.whatwg.org/#forbidden-header-name)), the only headers which are allowed to be manually set are [those which the Fetch spec defines as a CORS-safelisted request-header](https://fetch.spec.whatwg.org/#cors-safelisted-request-header), which are: + Accept + Accept-Language + Content-Language + Content-Type + The only type/subtype combinations allowed for the [media type](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type) specified in the [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) header are: + `application/x-www-form-urlencoded` + `multipart/form-data` + `text/plain` + If the request is made using an [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object, no event listeners are registered on the object returned by the [`XMLHttpRequest.upload`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/upload) property used in the request; that is, given an [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) instance `xhr`, no code has called `xhr.upload.addEventListener()` to add an event listener to monitor the upload. + No [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) object is used in the request. ## 复杂请求 不符合简单请求的情况为复杂请求,请求流程如下: 1、**PUT** / **DELETE** 或者 `Content-Type:application/json` 类型的请求 2、发出 CORS 请求时,会在正式通信之前增加一次 **“预检”请求(OPTIONS 方法)**,来询问服务器,本次请求的域名是否在许可名单中,以及使用哪些头信息 3、当 **“预检”请求** 通过以后,才会正式发起 请求,否则报错 ### 头部信息概念 * `Access-Control-Allow-Origin`: `*` 设置可以访问的域名,\* 表示所有网站都可以访问资源。 * `Access-Control-Allow-Headers`: `Origin,Last-Modified, Cache-Control` 设置`resquest headers`里哪些字段有效 * `Access-Control-Allow-Methods`: `POST、GET` 允许请求的方式 * `Access-Control-Allow-Credentials` : `true` ,允许携带`Cookie`,同时 AJAX 请求中开启 `withCredentials` 属性,`xhr.withCredentials = true`,否则浏览器不会发送`Cookie` ### document.domain 只能用于二级域名相同的情况下,比如 `a.test.com` 和 `b.test.com` 适用。 只需要给页面添加 `document.domain = 'test.com'` 表示二级域名都相同就可以实现跨域
powerfulyangSun, Mar 27, 2022 10:15 PM

Tail Call Optimization

--- title: Tail Call Optimization author: powerfulyang date: 2022-03-22 tags: - JavasSript - ES6 - Tail Call Optimization - TCO --- [目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。](https://caniuse.com/?search=es6) [Node.js 也不支持,因为 V8 不支持](https://github.com/nodejs/node/issues/40502#issuecomment-1055289464) > Relate to https://exploringjs.com/es6/ch_tail-calls.html ## What is tail call optimization? Roughly, whenever the last thing a function does is to call another function then the latter does not need to return to its caller. As a consequence, no information needs to be stored on the call stack and the function call is more of a goto (a jump). This kind of call is named *tail call*; not growing the stack is named *tail call optimization* (TCO). Let’s look at an example to better understand TCO. I’ll first explain how it is executed without TCO and then with TCO. ### Checking whether a function call is in a tail position First, the way in which you call a function does not matter. The following calls can all be optimized if they appear in a tail position: * Function call: `func(···)` * Dispatched method call: `obj.method(···)` * Direct method call via `call()`: `func.call(···)` * Direct method call via `apply()`: `func.apply(···)` #### Tail calls in expressions Arrow functions can have expressions as bodies. For tail call optimization, we therefore have to figure out where function calls are in tail positions in expressions. Only the following expressions can contain tail calls: * The conditional operator (`? :`) ```js const a = x => x ? f() : g(); // Both f() and g() are in tail position. ``` * The logical Or operator (`||`) ```js const a = () => f() || g(); // f() is not in a tail position, but g() is in a tail position. // To see why, take a look at the following code, // which is equivalent to the previous code: const a = () => { const fResult = f(); // not a tail call if (fResult) { return fResult; } else { return g(); // tail call } }; // The result of the logical Or operator depends on the result of f(), // which is why that function call is not in a tail position (the caller does something with it other than returning it). // However, g() is in a tail position. ``` * The logical And operator (`&&`) ```js const a = () => f() && g(); // f() is not in a tail position, but g() is in a tail position. // To see why, take a look at the following code, // which is equivalent to the previous code: const a = () => { const fResult = f(); // not a tail call if (!fResult) { return fResult; } else { return g(); // tail call } }; // The result of the logical And operator depends on the result of f(), // which is why that function call is not in a tail position (the caller does something with it other than returning it). // However, g() is in a tail position. ``` * The comma operator (`,`) ```js const a = () => (f() , g()); // f() is not in a tail position, but g() is in a tail position. // To see why, take a look at the following code, // which is equivalent to the previous code: const a = () => { f(); return g(); } ``` #### Tail calls in statements For statements, the following rules apply. Only these compound statements can contain tail calls: * Blocks (as delimited by `{}`, with or without a label) * `if`: in either the “then” clause or the “else” clause. * `do-while`, `while`, `for`: in their bodies. * `switch`: in its body. * `try-catch`: only in the `catch` clause. The `try` clause has the `catch` clause as a context that can’t be optimized away. * `try-finally`, `try-catch-finally`: only in the `finally` clause, which is a context of the other clauses that can’t be optimized away. Of all the atomic (non-compound) statements, only `return` can contain a tail call. All other statements have context that can’t be optimized away. The following statement contains a tail call if `expr` contains a tail call. ```js return «expr»; ``` #### Tail call optimization can only be made in strict mode In non-strict mode, most engines have the following two properties that allow you to examine the call stack: * `func.arguments`: contains the arguments of the most recent invocation of `func`. * `func.caller`: refers to the function that most recently called `func`. With tail call optimization, these properties don’t work, because the information that they rely on may have been removed. Therefore, strict mode forbids these properties ([as described in the language specification](http://www.ecma-international.org/ecma-262/6.0/#sec-addrestrictedfunctionproperties)) and tail call optimization only works in strict mode. ### Tail-recursive functions A function is *tail-recursive* if the main recursive calls it makes are in tail positions. For example, the following function is not tail recursive, because the main recursive call in line A is not in a tail position: ```js function factorial(x) { if (x <= 0) { return 1; } else { return x * factorial(x-1); // (A) } } ``` `factorial()` can be implemented via a tail-recursive helper function `facRec()`. The main recursive call in line A is in a tail position. ```js function factorial(n) { return facRec(n, 1); } function facRec(x, acc) { if (x <= 1) { return acc; } else { return facRec(x-1, x*acc); // (A) } } ``` That is, some non-tail-recursive functions can be transformed into tail-recursive functions. #### Tail-recursive loops Tail call optimization makes it possible to implement loops via recursion without growing the stack. The following are two examples. + `forEach()` ```js function forEach(arr, callback, start = 0) { if (0 <= start && start < arr.length) { callback(arr[start], start, arr); return forEach(arr, callback, start+1); // tail call } } forEach(['a', 'b'], (elem, i) => console.log(`${i}. ${elem}`)); ``` + `findIndex()` ```js function findIndex(arr, predicate, start = 0) { if (0 <= start && start < arr.length) { if (predicate(arr[start])) { return start; } return findIndex(arr, predicate, start+1); // tail call } } findIndex(['a', 'b'], x => x === 'b'); // 1 ```
powerfulyangTue, Mar 22, 2022 5:16 PM

SSH 使用指南

--- title: SSH 使用指南 author: powerfulyang date: 2022-03-20 tags: - SSH --- ## SSH config file location `~/.ssh/config` file template is below ```bash Host github.com HostName github.com User git Port 22 IdentityFile ~/.ssh/private_key ``` ## generate public key and private key use command `ssh-keygen` ## Copy public key to remote server 1. Append the content of xxx.pub into **~/.ssh/authorized_keys** file on the server-side. 2. Change the permission of the **~/.ssh/authorized_keys** file. ```bash cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys ``` ## Disable password login on the server 1. Edit `/etc/ssh/sshd_config` in server, **PasswordAuthentication no**. ```bash PasswordAuthentication no ``` 2. Add this configuration **PubkeyAuthentication yes** in `/etc/ssh/sshd_config`. ```bash PubkeyAuthentication yes ``` 3. To enable the change, restart SSH daemon with this command `systemctl restart sshd`. ## How to prevent SSH from disconnecting if it's been idle for a while + 修改ssh设置 + vim ~/.ssh/config ```bash ServerAliveInterval 60 ``` ## Local Port Forwarding ```bash ssh -L [LOCAL_IP:]LOCAL_PORT:DESTINATION:DESTINATION_PORT [USER@]SSH_SERVER ``` 参数说明: * \[LOCAL\_IP:\]LOCAL\_PORT - 本地 IP 和端口号,LOCAL\_IP 默认是 localhost。 * DESTINATION:DESTINATION\_PORT - 目标机器的 IP 地址和端口号。 * \[USER@\]SERVER\_IP - 远程 SSH 地址和登录用户。 案例: 使用本地地址 `127.0.0.1:58211` 连接远程的数据库 `1.1.1.1:58211` ```bash ssh -L 58211:1.1.1.1:58211 user@remote-server ```
powerfulyangSun, Mar 20, 2022 8:22 PM

Windows 下 Hyper-V 虚拟机的一些记录

--- title: Windows 下 Hyper-V 虚拟机的一些记录 author: powerfulyang date: 2022-03-18 tags: - Hyper-v - Windows - GPU --- ## Install Windows 11 in Hyper-V 1. Check if the Hyper-V Windows feature is installed 2. Create a new VM for Windows 11 + Specify Generation choose `Generation 2` + After the VM is created, start VM. Will get boot fail error! ![source protected](45746) + Turn off VM. + Then right click on it and go to **Settings**, Open **Security** tab and make sure **Enable Secure Boot** are not selected and **Enable Trusted Platform Module** are selected. + Connect to VM, then you can install Win11. ## 中文渲染问题 英文版的 win11 默认渲染中文会使用日文,导致文字变形,需要一些额外的设置来让文字显示正常。 打开 **Settings** -> **Time & language** -> **Related settings** -> **Administrative language settings** -> **Administrative** Tab -> Change system locale -> Current system locale **Chinese (Simplified, China)** ## Hyper-V 下显卡虚拟化 vGPU 设置 + Run belong script with administrator privilege. ```powershell $vm = "virtual machine name" Add-VMGpuPartitionAdapter -VMName $vm Set-VMGpuPartitionAdapter -VMName $vm -MinPartitionVRAM 80000000 -MaxPartitionVRAM 100000000 -OptimalPartitionVRAM 100000000 -MinPartitionEncode 80000000 -MaxPartitionEncode 100000000 -OptimalPartitionEncode 100000000 -MinPartitionDecode 80000000 -MaxPartitionDecode 100000000 -OptimalPartitionDecode 100000000 -MinPartitionCompute 80000000 -MaxPartitionCompute 100000000 -OptimalPartitionCompute 100000000 Set-VM -GuestControlledCacheTypes $true -VMName $vm Set-VM -LowMemoryMappedIoSpace 1Gb -VMName $vm Set-VM -HighMemoryMappedIoSpace 32GB -VMName $vm ``` + Copy Host machine `C:\Windows\System32\DriverStore\FileRepository\` to Virtual machine `C:\Windows\System32\HostDriverStore\FileRepository\`. + Copy Host machine `C:\Windows\System32\nvapi64.dll` to Virtual machine same path. + Press `Win + R` and type `dxdiag` to check. ## RDP 开启 GPU 加速 1. Press `Win + R` and type `gpedit.msc`. 2. **Computer Configuration** -> **Administrative Templates** -> **Windows Components** -> **Remote Desktop Services** -> **Remote Desktop Session Host** -> **Remote Session Environment** + Select it, then double click `Use hardware graphics adapters for all Remote Desktop Services sessions` and edit value to **Enabled**. ## 解决几个快捷键冲突 繁体/简体切换快捷键和 jetbrains 格式化冲突 英文/中文切换快捷键和 jetbrains 提示冲突 Settings Location: **Settings** -> **Time & language** -> **Language & region** -> **Language** tab -> Chinese (Simplified, China) -> Language options -> **Keyboards** -> Microsoft Pinyin -> Keyboard options -> Keys -> **Mode Switch** -> ... 1. `Ctrl + Space` Chinese/English mode switch 2. `Ctrl + Shift + F` Simplified/Traditional Chinese input switch
powerfulyangFri, Mar 18, 2022 7:34 PM

CSS 相关

--- title: CSS 相关 author: powerfulyang date: 2022-03-16 tags: - CSS --- 关于 css 小细节的集合: ## white-space | | New lines | Spaces and tabs | Text wrapping | End-of-line spaces | End-of-line other space separators | | :--- | --- | --- | --- | --- | --- | | `normal` | Collapse | Collapse | Wrap | Remove | Hang | | `nowrap` | Collapse | Collapse | No wrap | Remove | Hang | | `pre` | Preserve | Preserve | No wrap | Preserve | No wrap | | `pre-wrap` | Preserve | Preserve | Wrap | Hang | Hang | | `pre-line` | Preserve | Collapse | Wrap | Remove | Hang | | `break-spaces` | Preserve | Preserve | Wrap | Wrap | Wrap | ## break-word & overflow-wrap **word-break**: normal | break-all | keep-all | break-word --- **overflow-wrap**: normal | break-word | anywhere The property was originally a nonstandard and unprefixed Microsoft extension called `word-wrap`, and was implemented by most browsers with the same name. It has since been renamed to overflow-wrap, with word-wrap being an alias. In contrast to `word-break`, `overflow-wrap` will only create a break if an entire word cannot be placed on its own line without overflowing. ## css 设置数字等宽 **`font-variant-numeric`** CSS 属性控制数字,分数和序号标记的替代字形的使用。 Formal syntax: ```css font-variant-numeric = normal | [ <numeric-figure-values> || <numeric-spacing-values> || <numeric-fraction-values> || ordinal || slashed-zero ] <numeric-figure-values> = lining-nums | oldstyle-nums <numeric-spacing-values> = proportional-nums | tabular-nums <numeric-fraction-values> = diagonal-fractions | stacked-fractions ``` 可以使用 `font-variant-numeric: tabular-nums` 来使数字等宽。 ## mix-blend-mode 最近遇到的一个问题,首页的文章列表使用了未知颜色的图片做背景,想在上面显示标题和日期,遇到的问题就是颜色重合导致看不清文字。 其实使用 mix-blend-mode: difference 可以尝试去解决,但是我当时并没有意识到。 ps: 估计效果不太好因为颜色太斑驳。 当时的解决办法是在角落加了一个:半透明的的黑色背景,然后再显示文字,当然效果也不差。 ## BFC BFC 全称为 block formatting context,中文为“块级格式化上下文”。它是一个只有块级盒子参与的独立块级渲染区域,它规定了内部的块级盒子如何布局,且与区域外部无关。 **BFC 有什么用** * 修复浮动元素造成的高度塌陷问题。 * 避免非期望的外边距折叠。 * 实现灵活健壮的自适应布局。 **触发 BFC 的常见条件** * <html> 根元素。 * float 的值不为 none。 * position 的值不为 relative 或 static。 * overflow 的值不为 visible 或 clip(除了根元素)。 * display 的值为 table-cell,table-caption,或 inline-block 中的任意一个。 * display 的值为 flow-root,或 display 值为 flow-root list-item。 * flex items,即 display 的值为 flex 或 inline-flex 的元素的直接子元素(该子元素 display 不为 flex,grid,或 table)。 * grid items,即 display 的值为 grid 或 inline-grid 的元素的直接子元素(该子元素 display 不为 flex,grid,或 table)。 * contain 的值为 layout,content,paint,或 strict 中的任意一个。 * column-span 设置为 all 的元素。 **提示**:`display: flow-root`,`contain: layout` 等是无副作用的,可在不影响已有布局的情况下触发 BFC。 ```codepen <iframe height="300" style="width: 100%;" scrolling="no" title="Block Formatting Context" src="https://codepen.io/powerfulyang/embed/QWrzYKm?default-tab=" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/powerfulyang/pen/QWrzYKm"> Block Formatting Context</a> by powerfulyang (<a href="https://codepen.io/powerfulyang">@powerfulyang</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> ``` ### Mastering margin collapsing 属于同一个 BFC 的两个相邻块级元素的上下 margin 会发生重叠(设置 writing-mode: tb-rl 时,水平 margin 会发生重叠)。当两个相邻块级子元素属于不同的 BFC 时可以阻止外边距重叠 The [top](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top) and [bottom](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-bottom) margins of blocks are sometimes combined (collapsed) into a single margin whose size is the largest of the individual margins (or just one of them, if they are equal), a behavior known as **margin collapsing**. Note that the margins of [floating](https://developer.mozilla.org/en-US/docs/Web/CSS/float) and [absolutely positioned](https://developer.mozilla.org/en-US/docs/Web/CSS/position#types_of_positioning) elements never collapse. ## IFC 内联格式化上下文(IFC,inline formatting context)。 ## FFC 弹性格式化上下文(FFC,flex formatting context),在 CSS3 中定义。 ## GFC 栅格格式化上下文(GFC,grid formatting context),在 CSS3 中定义。
powerfulyangWed, Mar 16, 2022 4:48 PM

N1 使用 OpenWrt 经验

--- title: N1 使用 OpenWrt 经验 author: powerfulyang date: 2022-03-12 tags: - op --- ## 下载 OpenWrt 可以下载 [69版本](https://pan.baidu.com/s/1kbvtyxpcmniLKN_ziH-kqQ?pwd=jla9) 的。 ![source protected](45715) 2022-03-12的时候按时间排序下载最上面的文件,当然这个是给 `斐讯N1` 使用的。 **解压之后里面的内容** ![source protected](45716) ## 下载 Etcher [Etcher](https://www.balena.io/etcher/) 是一款帮助用户快速将镜像文件刻录到 USB 驱动器或 SD 卡的工具软件, 并且可以在 Windows、macOS 和主流 Linux 版本等多种操作系统中使用。 1. 准备一个空 U 盘,下载 [Etcher](https://www.balena.io/etcher/) 并打开。 2. 打开上一步骤解压后的文件,选中 `.img` 后缀的文件。![source protected](45717) 3. 选择要刻录的 U 盘,点击 Flash! 按钮。 到此一个带 OpenWrt 的 U 盘就制作好了。 ## 使用 U 盘系统启动 N1 1. 下载 [N1 傻瓜包](https://www.right.com.cn/forum/thread-431683-1-1.html) 2. 把 U 盘插入 N1 中,然后开机 **很重要的一点 我买的是 yyf 系统的 N1 好像原版 N1 需要降级,但是好像没有原版系统卖基本上** 3. 点开傻瓜包里面的 `U盘启动.BAT` 4. 然后等重启了,插上网线。 OpenWrt 的管理界面是 [192.168.1.1](http://192.168.1.1), 可能和路由器冲突哦,可以拔掉网线连 N1 的Wifi。 #### 算了不讲了。。。。
powerfulyangSat, Mar 12, 2022 9:12 PM

Question And Answer

--- title: Question And Answer author: powerfulyang date: 2022-03-08 posterId: 45520 tags: - q&a --- ## 问答系列 ### Chrome in iOS, `history.pushState` will casue bottom toolbar to disappear. > https://github.com/powerfulyang/powerfulyang.com/issues/23 So don't use **history.pushState**. ### How to disable overflow-x scrollbar in `monaco-editor`? > https://github.com/powerfulyang/powerfulyang.com/issues/28 > **options** => wordWrap: 'on' ### Safari don't send browser cookies during a 302 redirect > https://github.com/powerfulyang/api.powerfulyang.com/issues/46 > https://developer.apple.com/forums/thread/113892 > Safari executes redirect before reading all headers, set-cookie did after redirect. > **In iOS 15.3.1**, set-cookie did behind redirect. ### Nest.js, Error: Can't set headers after they are sent to the client > https://github.com/powerfulyang/api.powerfulyang.com/issues/50 > https://powerfulyang.com/post/40 ### Click to edit should ensure it exist. Before go to edit page, ensure item exist to prevent crash. ### OpenWrt can't resovle domain DNS to **Local Area Network,LAN**. Uncheck **Network** => **DCHP and DNS** => **General Settings** => **Rebind protection** ![source protected](45782) ### NestJS Request Lifecycle. > https://docs.nestjs.com/faq/request-lifecycle In general, the request lifecycle looks like the following: 1. Incoming request 2. Globally bound middleware 3. Module bound middleware 4. Global guards 5. Controller guards 6. Route guards 7. Global interceptors (pre-controller) 8. Controller interceptors (pre-controller) 9. Route interceptors (pre-controller) 10. Global pipes 11. Controller pipes 12. Route pipes 13. Route parameter pipes 14. Controller (method handler) 15. Service (if exists) 16. Route interceptor (post-request) 17. Controller interceptor (post-request) 18. Global interceptor (post-request) 19. Exception filters (route, then controller, then global) 20. Server response ### Why have typescript property decorators stopped working after an upgrade? > https://stackoverflow.com/questions/68941453/why-have-typescript-property-decorators-stopped-working-after-an-upgrade The fix for TypeScript property decorators breaking when you upgrade TypeScript is to add `"useDefineForClassFields": false` to your `tsconfig.json`. ### iPhone 快速投屏到 Mac ![source protected](47753) ### No colorized output Docker 日志突然就木有颜色了。 ANSI codes has been removed. > Relate to [https://github.com/docker/compose/issues/2231#issuecomment-165137408](https://github.com/docker/compose/issues/2231#issuecomment-165137408) **Try adding `tty: true` to the service config.** ```yaml 1 qa.powerfulyang.com: 2 image: powerfulyang/qa.powerfulyang.com 3 container_name: qa.powerfulyang.com 4 restart: always 5 command: npm run start 6 env_file: 7 - ./qa.powerfulyang.com/.env 8 tty: true 9 logging: 10 driver: gelf 11 options: 12 gelf-address: udp://localhost:12201 13 networks: 14 - default 15 - public ``` ### Windows 键盘按 Win 键无效 `Fn + Win` 会使 Win 失灵
powerfulyangTue, Mar 8, 2022 7:42 PM

Webstorm 快捷键大全

--- title: Webstorm 快捷键大全 author: powerfulyang date: 2022-03-07 posterId: 46063 tags: - 快捷键 --- ## 查找类 + `command + shift + f`
powerfulyangMon, Mar 7, 2022 5:51 PM

RxJS 使用指南

--- title: RxJS 使用指南 author: powerfulyang date: 2022-02-27 tags: - RxJS - switchMap --- ## switchMap `switchMap` 和其他打平操作符的主要区别是它具有取消效果。在每次发出时,会取消前一个内部 observable (你所提供函数的结果) 的订阅,然后订阅一个新的 observable 。你可以通过短语**切换成一个新的 observable**来记忆它。 它能在像 typeaheads 这样的场景下完美使用,当有新的输入时便不再关心之前请求的响应结果。在内部 observable 长期存活可能会导致内存泄露的情况下,这也是一种安全的选择,例如,如果你使用 mergeMap 和 interval,并忘记正确处理内部订阅。记住,`switchMap` 同一时间只维护一个内部订阅。 不过要小心,在每个请求都需要完成的情况下,考虑写数据库,你可能要避免使用 `switchMap` 。如果源 observable 发出速度足够快的话,`switchMap` 可以取消请求。在这些场景中,mergeMap 是正确的选择。 > [一个 v2ex 上的帖子](https://v2ex.com/t/873784#reply36) ```js id$ = new Subject(); price$ = id$.pipe( switchMap(id => getPriceById(id)) ) // merge + map 很干净,省一个 subject isLoading$ = merge( id$.pipe(mapTo(true)), price$.pipe(mapTo(false)) ); getPriceById 需要返回一个 observable, unsubscribe 时 abort 请求即可。 切商品直接 id$.next(newId), price$ 和 isLoading$ 会自动更新。 上一次没完成的请求 switchMap 会自动 unsubscribe ,简直毫无负担。 ``` ## mergeMap + flatMap 是 mergeMap 的别名! + 如果同一时间应该只有一个内部 subscription 是有效的,请尝试 `switchMap`。 + 如果内部 observables 发送和订阅的顺序很重要,请尝试 `concatMap`。 ## exhaustMap 返回的 Observable 基于应用一个函数来发送项,该函数提供给源 Observable 发出的每个项, 并返回一个(所谓的“内部”) Observable 。当它将源值投射成 Observable 时,输出 Observable 开始发出由投射的 Observable 发出的项。然而,如果前一个投射的 Observable 还未完成的话, 那么 exhaustMap 会忽略每个新投射的 Observable 。一旦完成,它将接受并打平下一个 内部 Observable ,然后重复此过程。 ```js 只要没有当前活动的计时器,那么每次点击就会运行一个有限的计时器。 var clicks = Rx.Observable.fromEvent(document, 'click'); var result = clicks.exhaustMap((ev) => Rx.Observable.interval(1000).take(5)); result.subscribe(x => console.log(x)); ```
powerfulyangSun, Feb 27, 2022 1:23 PM

Web Worker

--- title: Web Worker author: powerfulyang date: 2022-02-27 tags: - service worker --- ## 什么是 web worker
powerfulyangSun, Feb 27, 2022 1:22 PM

JavaScript 模块化

--- title: JavaScript 模块化 author: powerfulyang date: 2022-02-27 tags: - CommonJS - AMD - UMD --- ## 为什么要使用模块 模块化可以使你的代码低耦合,功能模块直接不相互影响。我个人认为模块化主要有以下几点好处: 1. **可维护性**:根据定义,每个模块都是独立的。良好设计的模块会尽量与外部的代码撇清关系,以便于独立对其进行改进和维护。维护一个独立的模块比起一团凌乱的代码来说要轻松很多。 2. **命名空间**:在 JavaScript 中,最高级别的函数外定义的变量都是全局变量(这意味着所有人都可以访问到它们)。也正因如此,当一些无关的代码碰巧使用到同名变量的时候,我们就会遇到“命名空间污染”的问题。 3. **可复用性**:现实来讲,在日常工作中我们经常会复制自己之前写过的代码到新项目中。 ## CommonJS ## AMD Asynchronous Module Definition(异步模块定义规范),简称 AMD。 ## UMD 在一些同时需要 AMD 和 CommonJS 功能的项目中,你需要使用另一种规范:Universal Module Definition(通用模块定义规范)。
powerfulyangSun, Feb 27, 2022 10:37 AM

XSS and CSRF

--- title: XSS and CSRF author: powerfulyang date: 2022-02-27 posterId: 45512 tags: - Content-Security-Policy --- ## Content-Security-Policy 一个网站管理者允许网页应用的用户在他们自己的内容中包含来自任何源的图片,但是限制音频或视频需从信任的资源提供者 (获得),所有脚本必须从特定主机服务器获取可信的代码。 ```yaml Content-Security-Policy: default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com ``` 在这里,各种内容默认仅允许从文档所在的源获取,但存在如下例外: * 图片可以从任何地方加载 (注意 "*" 通配符)。 * 多媒体文件仅允许从 media1.com 和 media2.com 加载 (不允许从这些站点的子域名)。 * 可运行脚本仅允许来自于 userscripts.example.com。 ## CSRF 防范: * 使用 CSRF Token 验证用户身份 * 原理:服务端生成 CSRF Token (通常存储在 Session 中),用户提交请求时携带上 Token,服务端验证 Token 是否有效。 * 优点:能比较有效的防御 CSRF (前提是没有 XSS 漏洞泄露 Token)。 * 缺点:大型网站中 Session 存储会增加服务器压力,且若使用分布式集群还需要一个公共存储空间存储 Token,否则可能用户请求到不同服务器上导致用户凭证失效;有一定的工作量。 * 双重 Cookie 验证 * 原理:利用攻击者不能获取到 Cookie 的特点,在 URL 参数或者自定义请求头上带上 Cookie 数据,服务器再验证该数据是否与 Cookie 一致。 * 优点:无需使用 Session,不会给服务器压力。 * 设置 Cookie 的 SameSite 属性可以用来限制第三方 Cookie 的使用,可选值有 Strict、Lax、None。 * Strict:完全禁止第三方 Cookie。 * Lax:只允许链接、预加载请求和 GET 表单的场景下发送第三方 Cookie。 * None:关闭 SameSite 属性。 * 设置白名单,仅允许安全域名请求 * 增加验证码验证 ## 中间人攻击(MITM) 对于开发者来说: * 支持 HTTPS。 * 开启 HSTS 策略。 对于用户来说: * 尽可能使用 HTTPS 链接。 * 避免连接不知名的 WiFi 热点。 * 不忽略不安全的浏览器通知。 * 公共网络不进行涉及敏感信息的交互。 * 用可信的第三方 CA 厂商,不下载来源不明的证书。
powerfulyangSun, Feb 27, 2022 10:35 AM

一些常用的命令

--- title: 一些常用的命令 date: 2022-02-27 author: powerfulyang tags: - command --- ## DNS ### 刷新 DNS 缓存 + Windows `ipconfig /flushdns` + 其余的暂时还没用到 macOS, CentOS ## Docker + 清空无用的 images `docker image prune -a` ## 端口 ### netstat 下面的命令仅测试过 linux `netstat -tunlp` * -t (tcp) 仅显示tcp相关选项 * -u (udp) 仅显示udp相关选项 * -n 拒绝显示别名,能显示数字的全部转化为数字 * -l 仅列出在Listen(监听)的服务状态 * -p 显示建立相关链接的程序名 ### traceroute 简单用法 也是只在 linux 使用 `traceroute [-n] -T -p [$Port] [$Host]` * -n:直接使用IP地址而非主机名称(禁用DNS反查)。 * -T:通过TCP探测。 * -p:设置探测的端口号。 * [$Port]:需要探测的端口号,比如80。 * [$Host]:需要探测的目标服务器地址,比如 `10.10.1.1`。 **相关** Windows 下使用 `tracecert` ## 文件查看搜索 ### 查看日志 + 可以使用head(查看前几行)、tail(查看末尾几行)两个命令。 查看/etc/profile的前10行内容,应该是: `head -n 10 /etc/profile` 查看/etc/profile的最后5行内容,应该是: `tail -n 5 /etc/profile` **注意:** + `tail -n 1000` 显示最后1000行 + `tail -n +1000` 从1000行开始显示,显示1000行以后的 + `head -n 1000` 显示前面1000行 + sed 命令 `sed -n '5,10p' filename` 这样你就可以只查看文件的第5行到第10行。 ## Disk ### 显示磁盘空间信息 (df) + 使用 `df -k` 命令以千字节为单位显示磁盘空间信息。 + `-k` 表示 kb + `-m` 表示 mb + `-g` 表示 gb ```bash df -k Filesystem kbytes used avail capacity Mounted on /dev/dsk/c0t3d0s0 192807 40231 133296 24% / ``` + 其中每列的含义 |字段名| 说明| |---| --- | |kbytes| 文件系统中可用空间的总大小 | |used|已用空间| |avail|可用空间| |capacity|已用空间百分比| |mounted on|挂载点| ### Understanding Linux File Permissions example: **drwxrwxrwx** 第一位代表文件类型,有两个数值:“d”和“-”,“d”代表目录,“-”代表非目录。 后面9位可以拆分为3组来看,分别对应不同用户,2-4位代表所有者 user 的权限说明,5-7位代表组群 group 的权限说明,8-10位代表其他人 other 的权限说明。 r 代表可读权限,w 代表可写权限,x 代表可执行权限。 **drwxrwxrwx** 表示所有用户都对这个目录有可读可写可执行权限。 #### Permission Groups + u 代表所有者 (user)-The Owner permissions apply only the owner of the file or directory, they will not impact the actions of other users. + g 代表所有者所在的组和群 (group)-The Group permissions apply only to the group that has been assigned to the file or directory, they will not effect the actions of other users. + o 代表其他人但不是 u 和 g(other) + a 代表全部人 - The All Users permissions apply to all other users on the system, this is the permission group that you want to watch the most. #### Permission Types + read – The Read permission refers to a user’s capability to read the contents of the file. + write – The Write permissions refer to a user’s capability to write or modify a file or directory. + execute – The Execute permission affects a user’s capability to execute a file or view the contents of a directory. #### Advanced Permissions + _ – no special permissions + d – directory + l – The file or directory is a symbolic link + s – This indicated the setuid/setgid permissions. This is not set displayed in the special permission part of the permissions display, but is represented as a s in the read portion of the owner or group permissions. + t – This indicates the sticky bit permissions. This is not set displayed in the special permission part of the permissions display, but is represented as a t in the executable portion of the all users permissions *r,w,x 可以用数字表示 r=0x100 w=0x10 x=0x1* + rw------- (600) 只有所有者才有读和写的权限 + rw-r–r-- (644) 只有所有者才有读和写的权限,组群和其他人只有读的权限 + rwx------ (700) 只有所有者才有读,写,执行的权限 + rwxr-xr-x (755) 只有所有者才有读,写,执行的权限,组群和其他人只有读和执行的权限 + rwx–x--x (711) 只有所有者才有读,写,执行的权限,组群和其他人只有执行的权限 + rw-rw-rw- (666) 每个人都有读写的权限 ### Change Permissions The potential Assignment Operators are + (plus) and – (minus); these are used to tell the system whether to add or remove the specific permissions. use *command* `chmod` to modify permissions; + To make this modification you would invoke the command: **_chmod a-rw file1_** + To add the permissions above you would invoke the command: **_chmod a+rw file1_** + You use the chown command to change owner and group assignments, the syntax is simple **`chown owner:group filename`**, so to change the owner of file1 to user1 and the group to family you would enter **_chown user1:family file1_**.
powerfulyangSat, Feb 26, 2022 8:59 PM

阅读 React 官方文档 Part-1

--- title: 阅读 React 官方文档 Part-1 date: 2022-01-23 author: powerfulyang tags: - React - startTransition --- ## 开启Concurrent模式 开启 `Concurrent` 模式,使用 `ReactDOM.createRoot(rootElement).render(<App />)` 替换 `ReactDOM.render()`。 ### [startTransition](https://github.com/facebook/react/blob/main/packages/react/src/ReactStartTransition.js) React state updates are classified into two categories: * Urgent updates — They reflect direct interaction, such as typing, clicking, pressing, dragging, etc. * Transition updates — They transition the UI from one view to another. source code: ```js export function startTransition( scope: () => void, options?: StartTransitionOptions, ) { const prevTransition = ReactCurrentBatchConfig.transition; ReactCurrentBatchConfig.transition = {}; const currentTransition = ReactCurrentBatchConfig.transition; try { scope(); } finally { ReactCurrentBatchConfig.transition = prevTransition; } } ``` `scope()` 里的代码被标记为 **Transition updates** #### Where can I use it?** You can use `startTransition` to wrap any update that you want to move to the background. Typically, these type of updates fall into two categories: * Slow rendering: These updates take time because React needs to perform a lot of work in order to transition the UI to show the results. [Here is a real-world demo of adding `startTransition` to keep the app responsive in the middle of an expensive re-render.](https://github.com/reactwg/react-18/discussions/65) * Slow network: These updates take time because React is waiting for some data from the network. This use case is tightly integrated with Suspense. ### [dispatchAction]() ### [ReactFiberLane](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberLane.new.js) ```js // Lane values below should be kept in sync with getLabelForLane(), used by react-devtools-timeline. // If those values are changed that package should be rebuilt and redeployed. export const TotalLanes = 31; export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000; export const NoLane: Lane = /* */ 0b0000000000000000000000000000000; export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001; export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010; export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100; export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000001000; export const DefaultLane: Lane = /* */ 0b0000000000000000000000000010000; const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000000100000; const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000; const TransitionLane1: Lane = /* */ 0b0000000000000000000000001000000; const TransitionLane2: Lane = /* */ 0b0000000000000000000000010000000; const TransitionLane3: Lane = /* */ 0b0000000000000000000000100000000; const TransitionLane4: Lane = /* */ 0b0000000000000000000001000000000; const TransitionLane5: Lane = /* */ 0b0000000000000000000010000000000; const TransitionLane6: Lane = /* */ 0b0000000000000000000100000000000; const TransitionLane7: Lane = /* */ 0b0000000000000000001000000000000; const TransitionLane8: Lane = /* */ 0b0000000000000000010000000000000; const TransitionLane9: Lane = /* */ 0b0000000000000000100000000000000; const TransitionLane10: Lane = /* */ 0b0000000000000001000000000000000; const TransitionLane11: Lane = /* */ 0b0000000000000010000000000000000; const TransitionLane12: Lane = /* */ 0b0000000000000100000000000000000; const TransitionLane13: Lane = /* */ 0b0000000000001000000000000000000; const TransitionLane14: Lane = /* */ 0b0000000000010000000000000000000; const TransitionLane15: Lane = /* */ 0b0000000000100000000000000000000; const TransitionLane16: Lane = /* */ 0b0000000001000000000000000000000; const RetryLanes: Lanes = /* */ 0b0000111110000000000000000000000; const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000; const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000; const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000; const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000; const RetryLane5: Lane = /* */ 0b0000100000000000000000000000000; export const SomeRetryLane: Lane = RetryLane1; export const SelectiveHydrationLane: Lane = /* */ 0b0001000000000000000000000000000; const NonIdleLanes: Lanes = /* */ 0b0001111111111111111111111111111; export const IdleHydrationLane: Lane = /* */ 0b0010000000000000000000000000000; export const IdleLane: Lane = /* */ 0b0100000000000000000000000000000; export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000; ``` ## Why is isMounted() an anti-pattern and what is the proper solution? The primary use case for `isMounted()` is to avoid calling `setState()` after a component has been unmounted, because it will emit a warning. ```js if (this.isMounted()) { this.setState({...}) } ``` Checking `isMounted()` before calling `setState()` does eliminate the warning, but it also defeats the purpose of the warning. Using `isMounted()` is a code smell because the only reason you would check is because you think you might be holding a reference after the component has unmounted. An optimal solution would be to find places where `setState()` might be called after a component has unmounted, and fix them. Such situations most commonly occur due to callbacks, when a component is waiting for some data and gets unmounted before the data arrives. Ideally, any callbacks should be canceled in `componentWillUnmount()`, prior to unmounting. ## React keys ### [对子节点进行递归](https://zh-hans.reactjs.org/docs/reconciliation.html#recursing-on-children) 默认情况下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个 mutation。 在子元素列表末尾新增元素时,更新开销比较小。比如: ```jsx <ul> <li>first</li> <li>second</li> </ul> <ul> <li>first</li> <li>second</li> <li>third</li> </ul> ``` React 会先匹配两个 `<li>first</li>` 对应的树,然后匹配第二个元素 `<li>second</li>` 对应的树,最后插入第三个元素的 `<li>third</li>` 树。 如果只是简单的将新增元素插入到表头,那么更新开销会比较大。比如: ```jsx <ul> <li>Duke</li> <li>Villanova</li> </ul> <ul> <li>Connecticut</li> <li>Duke</li> <li>Villanova</li> </ul> ``` React 并不会意识到应该保留 `<li>Duke</li>` 和 `<li>Villanova</li>`,而是会重建每一个子元素。这种情况会带来性能问题。 ### [Keys](https://zh-hans.reactjs.org/docs/reconciliation.html#keys) 为了解决上述问题,React 引入了 `key` 属性。当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。以下示例在新增 `key` 之后,使得树的转换效率得以提高: ```jsx <ul> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul> <ul> <li key="2014">Connecticut</li> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul> ``` 现在 React 知道只有带着 `'2014'` key 的元素是新元素,带着 `'2015'` 以及 `'2016'` key 的元素仅仅移动了。 实际开发中,编写一个 key 并不困难。你要展现的元素可能已经有了一个唯一 ID,于是 key 可以直接从你的数据中提取: ```jsx <li key={item.id}>{item.name}</li> ``` 当以上情况不成立时,你可以新增一个 ID 字段到你的模型中,或者利用一部分内容作为哈希值来生成一个 key。这个 key 不需要全局唯一,但在列表中需要保持唯一。 最后,你也可以使用元素在数组中的下标作为 key。这个策略在元素不进行重新排序时比较合适,如果有顺序修改,diff 就会变慢。 当基于下标的组件进行重新排序时,组件 state 可能会遇到一些问题。由于组件实例是基于它们的 key 来决定是否更新以及复用,如果 key 是一个下标,那么修改顺序时会修改当前的 key,导致非受控组件的 state(比如输入框)可能相互篡改,会出现无法预期的变动。 在 Codepen 有两个例子,分别为 [展示使用下标作为 key 时导致的问题](https://zh-hans.reactjs.org/redirect-to-codepen/reconciliation/index-used-as-key),以及[不使用下标作为 key 的例子的版本,修复了重新排列,排序,以及在列表头插入的问题](https://zh-hans.reactjs.org/redirect-to-codepen/reconciliation/no-index-used-as-key)。 **如果你选择不指定显式的 key 值,那么 React 将默认使用索引用作为列表项目的 key 值。** ## [深入 JSX](https://zh-hans.reactjs.org/docs/jsx-in-depth.html) 实际上,JSX 仅仅只是 `React.createElement(component, props, ...children)` 函数的语法糖。如下 JSX 代码: ```jsx <MyButton color="blue" shadowSize={2}> Click Me </MyButton> ``` 会编译为: ```jsx React.createElement( MyButton, {color: 'blue', shadowSize: 2}, 'Click Me' ) ``` 如果没有子节点,你还可以使用自闭合的标签形式,如: ```jsx <div className="sidebar" /> ``` 会编译为: ```jsx React.createElement( 'div', {className: 'sidebar'} ) ``` 如果你想测试一些特定的 JSX 会转换成什么样的 JavaScript,你可以尝试使用 [在线的 Babel 编译器](https://babeljs.io/repl/#?presets=react&code_lz=GYVwdgxgLglg9mABACwKYBt1wBQEpEDeAUIogE6pQhlIA8AJjAG4B8AEhlogO5xnr0AhLQD0jVgG4iAXyJA)。 ### [指定 React 元素类型](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#specifying-the-react-element-type) JSX 标签的第一部分指定了 React 元素的类型。 大写字母开头的 JSX 标签意味着它们是 React 组件。这些标签会被编译为对命名变量的直接引用,所以,当你使用 JSX `<Foo />` 表达式时,`Foo` 必须包含在作用域内。 #### [React 必须在作用域内](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#react-must-be-in-scope) 由于 JSX 会编译为 `React.createElement` 调用形式,所以 `React` 库也必须包含在 JSX 代码作用域内。 例如,在如下代码中,虽然 `React` 和 `CustomButton` 并没有被直接使用,但还是需要导入: ```jsx import React from 'react'; import CustomButton from './CustomButton'; function WarningButton() { // return React.createElement(CustomButton, {color: 'red'}, null); return <CustomButton color="red" />; } ``` 如果你不使用 JavaScript 打包工具而是直接通过 `<script>` 标签加载 React,则必须将 `React` 挂载到全局变量中。 #### [在 JSX 类型中使用点语法](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#using-dot-notation-for-jsx-type) 在 JSX 中,你也可以使用点语法来引用一个 React 组件。当你在一个模块中导出许多 React 组件时,这会非常方便。例如,如果 `MyComponents.DatePicker` 是一个组件,你可以在 JSX 中直接使用: ```jsx import React from 'react'; const MyComponents = { DatePicker: function DatePicker(props) { return <div>Imagine a {props.color} datepicker here.</div>; } } function BlueDatePicker() { return <MyComponents.DatePicker color="blue" />;} ``` #### [用户定义的组件必须以大写字母开头](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized) 以小写字母开头的元素代表一个 HTML 内置组件,比如 `<div>` 或者 `<span>` 会生成相应的字符串 `'div'` 或者 `'span'` 传递给 `React.createElement`(作为参数)。大写字母开头的元素则对应着在 JavaScript 引入或自定义的组件,如 `<Foo />` 会编译为 `React.createElement(Foo)`。 我们建议使用大写字母开头命名自定义组件。如果你确实需要一个以小写字母开头的组件,则在 JSX 中使用它之前,必须将它赋值给一个大写字母开头的变量。 例如,以下的代码将无法按照预期运行: ```jsx import React from 'react'; // 错误!组件应该以大写字母开头: function hello(props) { // 正确!这种 <div> 的使用是合法的,因为 div 是一个有效的 HTML 标签 return <div>Hello {props.toWhat}</div>; } function HelloWorld() { // 错误!React 会认为 <hello /> 是一个 HTML 标签,因为它没有以大写字母开头: return <hello toWhat="World" />; } ``` 要解决这个问题,我们需要重命名 `hello` 为 `Hello`,同时在 JSX 中使用 `<Hello />` : ```jsx import React from 'react'; // 正确!组件需要以大写字母开头: function Hello(props) { // 正确! 这种 <div> 的使用是合法的,因为 div 是一个有效的 HTML 标签: return <div>Hello {props.toWhat}</div>; } function HelloWorld() { // 正确!React 知道 <Hello /> 是一个组件,因为它是大写字母开头的: return <Hello toWhat="World" />; } ``` #### [在运行时选择类型](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#choosing-the-type-at-runtime) 你不能将通用表达式作为 React 元素类型。如果你想通过通用表达式来(动态)决定元素类型,你需要首先将它赋值给大写字母开头的变量。这通常用于根据 prop 来渲染不同组件的情况下: ```jsx import React from 'react'; import { PhotoStory, VideoStory } from './stories'; const components = { photo: PhotoStory, video: VideoStory }; function Story(props) { // 错误!JSX 类型不能是一个表达式。 return <components[props.storyType] story={props.story} />; } ``` 要解决这个问题, 需要首先将类型赋值给一个大写字母开头的变量: ```jsx import React from 'react'; import { PhotoStory, VideoStory } from './stories'; const components = { photo: PhotoStory, video: VideoStory }; function Story(props) { // 正确!JSX 类型可以是大写字母开头的变量。 const SpecificStory = components[props.storyType]; return <SpecificStory story={props.story} />; } ``` ### [JSX 中的 Props](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#props-in-jsx) 有多种方式可以在 JSX 中指定 props。 #### [JavaScript 表达式作为 Props](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#javascript-expressions-as-props) 你可以把包裹在 `{}` 中的 JavaScript 表达式作为一个 prop 传递给 JSX 元素。例如,如下的 JSX: ```jsx <MyComponent foo={1 + 2 + 3 + 4} /> ``` 在 `MyComponent` 中,`props.foo` 的值等于 `1 + 2 + 3 + 4` 的执行结果 `10`。 `if` 语句以及 `for` 循环不是 JavaScript 表达式,所以不能在 JSX 中直接使用。但是,你可以用在 JSX 以外的代码中。比如: ```jsx function NumberDescriber(props) { let description; if (props.number % 2 == 0) { description = <strong>even</strong>; } else { description = <i>odd</i>; } return <div>{props.number} is an {description} number</div>; } ``` 你可以在对应的章节中学习更多关于[条件渲染](https://zh-hans.reactjs.org/docs/conditional-rendering.html)和[循环](https://zh-hans.reactjs.org/docs/lists-and-keys.html)的内容。 #### [字符串字面量](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#string-literals) 你可以将字符串字面量赋值给 prop。如下两个 JSX 表达式是等价的: ```jsx <MyComponent message="hello world" /> <MyComponent message={'hello world'} /> ``` 当你将字符串字面量赋值给 prop 时,它的值是未转义的。所以,以下两个 JSX 表达式是等价的: ```jsx <MyComponent message="&lt;3" /> <MyComponent message={'<3'} /> ``` 这种行为通常是不重要的,这里只是提醒有这个用法。 #### [Props 默认值为 “True”](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#props-default-to-true) 如果你没给 prop 赋值,它的默认值是 `true`。以下两个 JSX 表达式是等价的: ```jsx <MyTextBox autocomplete /> <MyTextBox autocomplete={true} /> ``` 通常,我们不建议不传递 value 给 prop,因为这可能与 [ES6 对象简写](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015)混淆,`{foo}` 是 `{foo: foo}` 的简写,而不是 `{foo: true}`。这样实现只是为了保持和 HTML 中标签属性的行为一致。 #### [属性展开](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#spread-attributes) 如果你已经有了一个 props 对象,你可以使用展开运算符 `...` 来在 JSX 中传递整个 props 对象。以下两个组件是等价的: ```jsx function App1() { return <Greeting firstName="Ben" lastName="Hector" />; } function App2() { const props = {firstName: 'Ben', lastName: 'Hector'}; return <Greeting {...props} />;} ``` 你还可以选择只保留当前组件需要接收的 props,并使用展开运算符将其他 props 传递下去。 ```jsx const Button = props => { const { kind, ...other } = props; const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton"; return <button className={className} {...other} />; }; const App = () => { return ( <div> <Button kind="primary" onClick={() => console.log("clicked!")}> Hello World! </Button> </div> ); }; ``` 在上述例子中,`kind` 的 prop 会被安全的保留,它将*不会*被传递给 DOM 中的 `<button>` 元素。 所有其他的 props 会通过 `...other` 对象传递,使得这个组件的应用可以非常灵活。你可以看到它传递了一个 `onClick` 和 `children` 属性。 属性展开在某些情况下很有用,但是也很容易将不必要的 props 传递给不相关的组件,或者将无效的 HTML 属性传递给 DOM。我们建议谨慎的使用该语法。 ### [JSX 中的子元素](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#children-in-jsx) 包含在开始和结束标签之间的 JSX 表达式内容将作为特定属性 `props.children` 传递给外层组件。有几种不同的方法来传递子元素: #### [字符串字面量](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#string-literals-1) 你可以将字符串放在开始和结束标签之间,此时 `props.children` 就只是该字符串。这对于很多内置的 HTML 元素很有用。例如: ```jsx <MyComponent>Hello world!</MyComponent> ``` 这是一个合法的 JSX,`MyComponent` 中的 `props.children` 是一个简单的未转义字符串 `"Hello world!"`。因此你可以采用编写 HTML 的方式来编写 JSX。如下所示: ```jsx <div>This is valid HTML &amp; JSX at the same time.</div> ``` JSX 会移除行首尾的空格以及空行。与标签相邻的空行均会被删除,文本字符串之间的新行会被压缩为一个空格。因此以下的几种方式都是等价的: ```jsx <div>Hello World</div> <div> Hello World </div> <div> Hello World </div> <div> Hello World </div> ``` #### [JSX 子元素](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#jsx-children) 子元素允许由多个 JSX 元素组成。这对于嵌套组件非常有用: ```jsx <MyContainer> <MyFirstComponent /> <MySecondComponent /> </MyContainer> ``` 你可以将不同类型的子元素混合在一起,因此你可以将字符串字面量与 JSX 子元素一起使用。这也是 JSX 类似 HTML 的一种表现,所以如下代码是合法的 JSX 并且也是合法的 HTML: ```jsx <div> Here is a list: <ul> <li>Item 1</li> <li>Item 2</li> </ul> </div> ``` React 组件也能够返回存储在数组中的一组元素: ```jsx render() { // 不需要用额外的元素包裹列表元素! return [ // 不要忘记设置 key :) <li key="A">First item</li>, <li key="B">Second item</li>, <li key="C">Third item</li>, ]; } ``` #### [JavaScript 表达式作为子元素](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#javascript-expressions-as-children) JavaScript 表达式可以被包裹在 `{}` 中作为子元素。例如,以下表达式是等价的: ``` <MyComponent>foo</MyComponent> <MyComponent>{'foo'}</MyComponent> ``` 这对于展示任意长度的列表非常有用。例如,渲染 HTML 列表: ```jsx function Item(props) { return <li>{props.message}</li>; } function TodoList() { const todos = ['finish doc', 'submit pr', 'nag dan to review']; return ( <ul> {todos.map((message) => <Item key={message} message={message} />)} </ul> ); } ``` JavaScript 表达式也可以和其他类型的子元素组合。这种做法可以方便地替代模板字符串: ```jsx function Hello(props) { return <div>Hello {props.addressee}!</div>; } ``` #### [函数作为子元素](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#functions-as-children) 通常,JSX 中的 JavaScript 表达式将会被计算为字符串、React 元素或者是列表。不过,`props.children` 和其他 prop 一样,它可以传递任意类型的数据,而不仅仅是 React 已知的可渲染类型。例如,如果你有一个自定义组件,你可以把回调函数作为 `props.children` 进行传递: ```jsx // 调用子元素回调 numTimes 次,来重复生成组件 function Repeat(props) { let items = []; for (let i = 0; i < props.numTimes; i++) { items.push(props.children(i)); } return <div>{items}</div>; } function ListOfTenThings() { return ( <Repeat numTimes={10}> {(index) => <div key={index}>This is item {index} in the list</div>} </Repeat> ); } ``` 你可以将任何东西作为子元素传递给自定义组件,只要确保在该组件渲染之前能够被转换成 React 理解的对象。这种用法并不常见,但可以用于扩展 JSX。 #### [布尔类型、Null 以及 Undefined 将会忽略](https://zh-hans.reactjs.org/docs/jsx-in-depth.html#booleans-null-and-undefined-are-ignored) `false`, `null`, `undefined`, and `true` 是合法的子元素。但它们并不会被渲染。以下的 JSX 表达式渲染结果相同: ```jsx <div /> <div></div> <div>{false}</div> <div>{null}</div> <div>{undefined}</div> <div>{true}</div> ``` 这有助于依据特定条件来渲染其他的 React 元素。例如,在以下 JSX 中,仅当 `showHeader` 为 `true` 时,才会渲染 `<Header />` 组件: ```jsx <div> {showHeader && <Header />} <Content /> </div> ``` 值得注意的是有一些 [“falsy” 值](https://developer.mozilla.org/en-US/docs/Glossary/Falsy),如数字 `0`,仍然会被 React 渲染。例如,以下代码并不会像你预期那样工作,因为当 `props.messages` 是空数组时,将会渲染为数字 `0`: ```jsx <div> {props.messages.length && <MessageList messages={props.messages} /> } </div> ``` 要解决这个问题,确保 `&&` 之前的表达式总是布尔值: ```jsx <div> {props.messages.length > 0 && <MessageList messages={props.messages} /> } </div> ``` 反之,如果你想渲染 `false`、`true`、`null`、`undefined` 等值,你需要先将它们[转换为字符串](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#String_conversion): ```jsx <div> My JavaScript variable is {String(myVariable)}. </div> ```
powerfulyangSun, Jan 23, 2022 6:11 PM

CSS values and units

--- title: CSS values and units author: powerfulyang date: 2022-01-06 posterId: 46054 tags: - css --- ## units css units ### [Lengths](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units#lengths "Permalink to Lengths") The numeric type you will come across most frequently is `<length>`. For example `10px` (pixels) or `30em`. There are two types of lengths used in CSS — relative and absolute. It's important to know the difference in order to understand how big things will become. #### Absolute length units The following are all **absolute** length units — they are not relative to anything else, and are generally considered to always be the same size. | Unit | Name | Equivalent to | | --- | --- | --- | | `cm` | Centimeters | 1cm = 37.8px = 25.2/64in | | `mm` | Millimeters | 1mm = 1/10th of 1cm | | `Q` | Quarter-millimeters | 1Q = 1/40th of 1cm | | `in` | Inches | 1in = 2.54cm = 96px | | `pc` | Picas | 1pc = 1/6th of 1in | | `pt` | Points | 1pt = 1/72th of 1in | | `px` | Pixels | 1px = 1/96th of 1in | Most of these units are more useful when used for print, rather than screen output. For example, we don't typically use `cm` (centimeters) on screen. The only value that you will commonly use is `px` (pixels). #### Relative length units Relative length units are relative to something else, perhaps the size of the parent element's font, or the size of the viewport. The benefit of using relative units is that with some careful planning you can make it so the size of text or other elements scales relative to everything else on the page. Some of the most useful units for web development are listed in the table below. | Unit | Relative to | | --- | :--- | | `em` | Font size of the parent, in the case of typographical properties like [font-size](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size), and font size of the element itself, in the case of other properties like [width](https://developer.mozilla.org/en-US/docs/Web/CSS/width). | | `ex` | x-height of the element's font. | | `ch` | The advance measure (width) of the glyph "0" of the element's font. | | `rem` | Font size of the root element. | | `lh` | Line height of the element. | | `vw` | 1% of the viewport's width. | | `vh` | 1% of the viewport's height. | | `vmin` | 1% of the viewport's smaller dimension. | | `vmax` | 1% of the viewport's larger dimension. |
powerfulyangThu, Jan 6, 2022 5:26 PM