学习笔记本
type: channel
link: http://www.bingxinlu.cn/
description: 我的学习笔记本
items: 2025年货单
items: js的事件循环机制下，记录一个意外错误
items: Monorepo模式
items: 前端权限开发——设计到实践
items: 【基础】js表达式和运算符
items: 前端优化-渐进式图片
items: js逆向
items: 1 行命令引发的Go应用崩溃
items: 【整理】开发注意事项-1
items: 【CSS】font-weight设置为500显示不出加粗效果

2025年货单
type: item
date: 2025-01-25T06:14:00+00:00
description: 周四剪发；周六上午染发购物买年货，周日中午聚餐，收拾屋子。周一收拾屋子。购物清单：[ ] 清洗： 强力去污剂；衣架；[ ] 肉类：牛肉可以多买点，可以买个羊腿，猪肝大肠要买就买一点最多买两盘菜的量；鱼尽量买少刺的或者无刺的；可以买点鸡胸肉，海鲜【少买少吃】；清蒸鲈鱼[ ] 坚果零食：再买点瓜子就行[ ] 水果：[ ] 饮料：够了[ ] 调料：醋，老抽，生抽，耗油，料酒，盐，白糖，食用油，大料，火锅底料，酸菜鱼料包，（炖肉料）[ ] 干货：干木耳，干香菇，黄花菜，粉条，海米干，虾皮；[ ] 蔬菜沙拉：【冰草】、黄瓜、樱桃萝卜、圣女果、苦菊、生菜，有时候还会放一些紫衣甘蓝和西兰花；青瓜，圣女果，生菜，水果胡萝卜，黄椒，青椒，苦菊，包菜，紫甘蓝，菜花，还可以加些鲜柠檬，橘子，火龙果更美味[ ] 火锅，微波炉等食材清单，搜索就可以五花肉；各种肉卷【牛肉，羊肉，肥牛，培根】；排骨；鸡胸肉；午餐肉、火腿肠；鸭肠；黄喉；毛肚；鸭血巴沙鱼；鱿鱼；虾；丸子类菌类（属于高嘌呤食物）：金针菇；茶树菇；杏鲍菇；香菇；海鲜菇；银耳；木耳；口蘑菇蔬菜类：藕；笋；土豆；胡萝卜；秋葵；海带；茼蒿；菠菜；生菜；小白菜；大白菜；油麦菜豆制品：豆面筋；豆泡；豆皮；腐竹；冻豆腐；鱼豆腐；宽粉；粉丝；年糕1、蔬菜类的火锅配菜香蒜、大葱、白菜、菠菜、莴笋、卷心菜、土豆、莲藕、茭白、冬笋、春笋、白萝卜、胡萝卜、冬瓜、菜花、金针菜、蘑菇、香菇、平菇 、丝瓜、鲜豌豆、四季豆、金针菇、黄豆芽、绿豆芽、豆腐、豆腐干、香菜、空心菜、生菜、茼蒿、油麦菜、上海青以及荸荠等。2、肉类的火锅配菜(1)畜肉：羊肉、牛肉、猪肉、腊肉、香肠、猪灌肠、猪牛羊的肾、 肝心、肝蹄筋、肝心、肝蹄筋、牛鞭、猪肠、猪脑花以及猪脊髓等。(2)禽肉：鸡肉、鸭肉、鹅肉、鸡翅、鸡血、鸭血、鸡鸭额肝、肠、肫、肝、心、胗、爪等。(3)水产：鲫鱼、草鱼、鲤鱼、鳝鱼、泥鳅、河蟹、河虾、海蟹、海虾、鲜贝、带鱼等鱼虾类，水发海参、水发墨鱼、水发鱿鱼、水发鱼肚、鱼唇、鱼翅、鲜贝、带鱼以及水发海带等。(4)丸子：糖醋丸子、虾仁丸子、鱼香丸子、鸡肉丸子、猪肉丸子等。(5)野味类(人工饲养)：野鸡、野鸭、鹌鹑、狗肉、狗鞭、蛇肉、田鸡、蜗牛、牛蛙以及龟肉等。3、干货类的火锅配菜干金针菜、笋干、粉丝、米线、面条、饺子、红枣、魔芋、木耳、银耳、香菇、口蘑以及竹荪等。锅底辣过瘾选它：重庆牛油辣、青椒辣。不辣养生选它：野山菌汤、花胶鸡汤、猪肚鸡汤、番茄汤底。蘸料调料：海鲜汁、 XO酱、醋、 香油 、菌菇酱、腐乳、辣椒酱、芝麻酱。辅料：香菜、香葱、芝麻粒、花生碎、蒜泥、小米辣。附录1、维生素：三高加尿酸高是指高血压、高血脂、高血糖、尿酸高，饮食上可以多摄入维生素含量较丰富的食物，比如青椒、西红柿、猕猴桃、橙子、西兰花等，有利于补充机体所需要的微量元素。2、膳食纤维：三高加尿酸高的患者还要多摄入富含膳食纤维的食物，例如燕麦片、荞麦、红豆、绿豆、玉米、小米等。3、蛋白质：三高加尿酸高的患者饮食上还应摄入蛋白质含量较丰富的食物，例如牛奶、鱼肉、虾肉、瘦肉、豆制食品等。4、低嘌呤食物：常见的有冬瓜、茄子、青菜、白菜、苦瓜、萝卜、西葫芦、马铃薯等。

js的事件循环机制下，记录一个意外错误
type: item
date: 2024-06-05T06:59:00+00:00
description: js的事件循环机制是前端的基础概念。下面我们先看两段代码：setTimeout(()=>{ console.log("children2") Promise.resolve().then(()=>{ console.log("children3") }) },0) new Promise(function(resolve,reject){ console.log("children4") setTimeout(function(){ console.log("children5") resolve("children6") },0) }).then(res=>{ console.log("children7") setTimeout(function(){ console.log(res) },0) }) console.log("start") 上面的代码输出：children4、start、children2、children3、children5、children7、children6async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { console.log("async2"); } console.log("script start"); setTimeout(function () { console.log("setTimeout"); }, 0); async1(); new Promise(function (resolve) { console.log("promise1"); resolve(); }).then(function () { console.log("promise2"); }); console.log("script end");上面的代码输出：script start、async1 start、async2、promise1、script end、async1 end、promise2、setTimeout第二道题时，我出现一个疑惑，当await 后面跟的不是一个 Promise 对象（而是一个同步值或已经解决的 Promise），那么await 后面的代码会被放入微任务队列吗?验证代码：async function example() { console.log('Start'); await Promise.resolve(); // 立即解决的 Promise console.log('After await'); } console.log('Before async call'); example(); console.log('After async call start'); // 输出顺序： // Before async call // After async call start // Start // After await上面的代码表明：如果 await 后面跟的不是一个异步操作，虽然await 不会真正“等待”任何东西，但它仍然会将后续代码放入微任务队列中。结论：无论 await 后面跟的是否是一个真正需要等待的异步操作，它都会导致后续代码被放入微任务队列中执行。

Monorepo模式
type: item
date: 2024-02-08T07:19:00+00:00
description: 什么是 Monorepo？一个 Monorepo 是一个单一的存储库，包含 多个不同的项目 ，和 明确的关系 。和 Monorepo 对立的是传统的 Polyrepo 模式，每个项目对应一个单独的仓库来分散管理现代的前端工程越来越多的都会选择 Monorepo 的方式进行开发，比如 Vue、React 等知名开源项目都采用的 Monorepo 的方式进行管理。Monorepo 的组织方式如下├── packages | ├── pkg1 | | ├── package.json | ├── pkg2 | | ├── package.json ├── package.json在 packages 目录下存放多个子项目，当然也可以叫其他名字，每个子项目都有自己的 package.json 文件，用来管理子包。在更换新的方式之前，一定要知道原先的方式有什么缺点。接下来就先聊聊 Polyrepo 的方式在生产上都存在着哪些弊端。Polyrepo 的弊端1. 代码重复Polyrepo 容易导致代码重复，主要是因为不同的项目都有自己的独立代码仓库。在维护一些基础组件或者工具函数时，会想着直接从 A 仓库将代码复制到 B 仓库里使用，这样很方便，但是却给后续维护成本带来了很大的负担。一旦出现需要修改的地方，需要在 多个仓库中进行修改 ，这可能会导致修改过程出现错误，并可能导致代码的不一致。那么当然我们可以不进行复制，将公共逻辑抽离出来，单独发包，作用一个 npm 模块引入到项目中，需要修改时，也只用修改一份代码，然后重新发布即可。但是这整个流程还是太复杂了，并不是完美的解决方式，仅仅是为了修改一点点东西，就需要去发包、更新，这太麻烦了。小结：不同仓库之间的割裂感、导致复用代码的成本很高，开发体验差。或者需要在多个仓库中重复编写相同的代码，从而导致代码的不一致性和冗余性。2. 版本管理因为多个仓库分别管理自己的代码，这样会缺乏一个 全局视角去看待所有仓库 ，无法清晰的了解到整个项目的结构和状态。当不同仓库中的有依赖关系时，会有依赖管理的问题，时时刻刻都需要注意依赖的版本和代码是否一致例如：主项目依赖了子项目提供的组件，如果此时子项目在某次升级时产生了 breaking change 版本，和原来的组件使用方式完全不一致，这样主项目就直接寄了。因此我们需要在子项目更新时，及时的更新主项目的依赖，这样就形成了负担。当组件出现异常时，就会开始怀疑包版本是否不正确...因此，Polyrepo 需要有合适的版本管理方法，保证版本和代码的一致性3. 工具混乱每个项目都使用自己的一组命令来运行测试、构建、服务、检查、部署等。不一致会导致记住在不同项目中使用哪些命令的心理负担。单独部署、独立工作流在多仓库情况下，由于在不同的仓库里，继承测试会变得更加的困难，需要很多步骤才能组合在一起测试。同时每个项目都使用自己的一组命令来运行测试、构建、服务、检查、部署等。不一致会导致记住在不同项目中使用哪些命令的心理负担，维护起来也是问题例如：每个项目都会配置自己的 webpack.config.js 文件，但是这些配置很多都是一致的。例如：CI、CD 流程很难将多个仓库组合到一起去，测试也比较难处理4. 代码共享要跨存储库共享代码，可能会为共享代码创建一个存储库。那么必须设置工具和 CI 环境，将提交者添加到存储库，并设置包发布以便其他存储库可以依赖它。让我们不要开始跨存储库协调第三方库的不兼容版本....5. 代码管理基于 Polyrepo 管理，每个开发人员的 commit 都会提交到各个仓库，不利于代码的 CR，也不利于管理代码合入机制，导致容易出现逃逸现象。Monorepo 能解决什么问题？Monorepo 都能解决以上的问题不仅仅在于减少了维护和管理多个代码库的成本，同时还有以下优点：代码复用 ：因为多个项目共享一个代码库，所以避免了在不同项目中重复编写相同功能代码的问题，提高了开发效率。提升协作效率 ：多个项目在同一个代码库中进行开发，可以方便地共享代码和文档，避免不同项目之间的沟通和协调成本。集中管理 ：Monorepo 架构中，不同的应用程序都在同一个代码库中，方便管理和监控。这一点非常重要，特别是在需要同时对多个版本进行修改和维护的情况下。统一构建 ：Monorepo 的一个重要特点是可以共用一套构建系统和 工具链进行构建和部署 ，提升了构建的效率。可以快速定位问题 ：由于所有的代码都在同一个代码库中进行开发，debugger 可以很快找出问题所在的代码文件和行数，便于开发人员调试问题。一个版本 ：无需担心因为项目依赖于第三方库的冲突版本而导致的不兼容问题。Monorepo 和 Polyrepo 的不同？前面说完之后，相信这里已经有了答案，这里再用对比…

前端权限开发——设计到实践
type: item
date: 2024-01-25T07:25:00+00:00
description: 做后台项目区别于做其它的项目，权限验证与安全性是非常重要的，可以说是一个后台项目一开始就必须考虑和搭建的基础核心功能。在后台管理系统中，实现权限控制可以采用多种方案：权限方案类型描述基于角色的访问控制（Role-Based Access Control，RBAC）是一种广泛采用的权限控制方案。系统中定义了不同的角色，每个角色具有一组权限，而用户被分配到一个或多个角色。通过控制用户角色的分配，可以实现对用户访问系统中不同功能和资源的权限控制。基于权限的访问控制（Permission-Based Access Control）这种方案将权限直接分配给用户，而不是通过角色来管理。每个用户都有自己的权限列表，控制用户对系统中各项功能和资源的访问。基于资源的访问控制（Resource-Based Access Control，RBAC）这种方案将权限控制与资源本身关联起来。系统中的每个资源都有自己的访问权限，用户通过被授予资源的访问权限来控制其对资源的操作。层次结构权限控制（Hierarchical Access Control）这种方案基于资源和操作的层次结构来进行权限控制。系统中的资源和操作被组织成层次结构，用户被授予访问某个层次及其子层次的权限。基于规则的访问控制（Rule-Based Access Control）这种方案使用预定义的规则来确定用户对系统中功能和资源的访问权限。规则可以基于用户属性、环境条件或其他因素进行定义。这里我选择了基于角色的访问控制（Role-Based Access Control，RBAC） 这是因为RBAC提供了一种灵活且易于管理的方式来控制用户对系统功能和资源的访问，也是目前最主流的前端权限方案选择。在RBAC中，系统中的功能和资源被组织成角色，而用户则被分配到不同的角色。每个角色都有一组权限，定义了该角色可以执行的操作和访问的资源。通过给用户分配适当的角色，可以实现对用户的权限控制。RBAC的好处之一是它简化了权限管理的复杂性。管理员只需管理角色和分配角色给用户，而不需要为每个用户单独定义权限。当需要对用户的权限进行修改时，只需调整其角色的权限即可。此外，RBAC还支持灵活的权限组合，允许创建具有不同权限组合的角色，以适应不同用户的需求。它也便于扩展，可以随着系统的发展和需求的变化而调整和添加角色。2.RBAC下的权限字段设计与管理模型1.用户权限授权是对用户身份认证的细化。可简单理解为访问控制，在用户身份认证通过后，系统对用户访问菜单或按钮进行控制。也就是说，该用户有身份进入系统了，但他不一定能访问系统里的所有菜单或按钮，而他只能访问管理员给他分配的权限菜单或按钮。主要包括：Permission（权限标识、权限字符串）：针对系统访问资源的权限标识，如：用户添加、用户修改、用户删除。Role （角色）：可以理解为权限组，也就是说角色下可以访问和点击哪些菜单、访问哪些权限标识。权限标识或权限字符串校验规则：权限字符串：指定权限串必须和菜单中的权限标识匹配才可访问权限字符串命名规范为：模块:功能:操作，例如：sys:user:edit使用冒号分隔，对授权资源进行分类，如 sys:user:edit 代表 系统模块:用户功能:编辑操作设定的功能指定的 权限字符串与当前用户的 权限字符串进行匹配，若匹配成功说明当前用户有该功能权限还可以使用简单的通配符，如 sys:user:*，建议省略为 sys:user（分离前端不能使用星号写法）举例1 sys:user 将于 sys:user 或 sys:user: 开头的所有权限字符串匹配成功举例2 sys 将于 sys 或 sys: 开头的所有权限字符串匹配成功 这种命名格式的好处有：可读性和可理解性 ：使用模块、功能和操作的格式可以直观地表达权限的含义。每个部分都有明确的作用，模块表示特定的模块或子系统，功能表示模块内的某个功能或页面，操作表示对功能进行的具体操作。通过这种格式，权限名称可以更容易地被开发人员、管理员和其他人员理解和解释。可扩展性和灵活性： 通过使用模块、功能和操作的格式，可以轻松地扩展和管理权限。每个模块、功能和操作都可以被单独定义和控制。当系统需要增加新的功能或操作时，可以根据需要添加新的权限字符串，而不需要修改现有的权限规则和代码。细粒度的权限控制： 这种格式支持细粒度的权限控制，可以针对特定的功能和操作进行权限管理。通过将权限名称拆分为模块、功能和操作，可以精确地定义哪些用户或角色具有访问或操作特定功能的权限。避免权限冲突： 使用模块、功能和操作的格式可以避免权限之间的冲突。不同模块、功能和操作的权限名称是唯一的，这样可以避免同名权限之间的混淆和冲突。2.权限管理模型关键数据模型如下：用户：登录账号、密码、角色角色：角色名称、角色权限…

【基础】js表达式和运算符
type: item
date: 2024-01-23T06:39:00+00:00
description: 迭代器和生成器JavaScript中的迭代器和生成器都是ES6中引入的重要特性，迭代器和生成器都实现了迭代器协议，它们具有相似的使用方法和不同的用途和使用场景。前提、迭代协议迭代协议规定了一个对象必须实现 next()方法，该方法返回一个对象，该对象包含两个属性：value：表示下一个要返回的元素的值。done：一个布尔值，如果迭代器已经遍历完集合中的所有元素，则为 true；否则为 false。迭代器创建迭代器在JavaScript中，你可以通过以下几种方式创建迭代器：默认迭代器：某些内置对象（如数组、字符串、Map和Set等）具有默认的迭代器，可以通过调用它们的Symbol.iterator方法获取。 const arr = [1, 2, 3]; const iterator = arrSymbol.iterator; console.log(iterator.next()); // { value: 1, done: false } console.log(iterator.next()); // { value: 2, done: false } console.log(iterator.next()); // { value: 3, done: false }console.log(iterator.next()); // { value: undefined, done: true }可迭代对象：任何实现了Symbol.iterator方法的对象都是可迭代对象。你可以通过定义一个带有Symbol.iterator方法的对象来创建自定义的可迭代对象。 const myIterable = { data: [1, 2, 3], [Symbol.iterator]() { let index = 0; return { next: () => { if (index < this.data.length) { return { value: this.data[index++], done: false }; } else { return { value: undefined, done: true }; } } }; } }; const iterator = myIterable[Symbol.iterator](); console.log(iterator.next()); // { value: 1, done: false } // ...生成器函数：生成器函数（使用function*定义的函数）返回的是一个迭代器对象，该对象也实现了迭代器协议。 function* myGenerator() { yield 1; yield 2; yield 3; } const iterator = myGenerator(); console.log(iterator.next()); // { value: 1, done: false } // ...使用迭代器迭代器通常与for...of循环一起使用，因为for...of循环会自动调用迭代器的next()方法，直到done属性为true。 const arr = [1, 2, 3]; for (const value of arr) { console.log(value); // 1, 2, 3 }在这个例子中，arr是一个数组，它有一个默认的迭代器。for...of循环会调用这个迭代器，并逐个处理数组中的元素。总之，迭代器是JavaScript中一个强大的工具，它允许你以灵活和高效的方式处理集合中的元素。通过实现迭代器协议，你可以创建自己的可迭代对象，并使用for...of循环或其他迭代方法来遍历它们。生成器在JavaScript中，生成器（Generators）是一种可以暂停和恢复执行的函数。它们允许你使用function*语法定义一个生成器函数，并使用yield关键字在函数执行过程中暂停和恢复。生成器特别适用于需要逐步生成值或处理异步操作的场景。下面是一个简单的例子，展示了如何定义和使用生成器：定义生成器函数 function* numberGenerator() { console.log('Start'); yield 1; console.log('Resume at 2'); yield 2; console.log('Resume at 3'); yield 3; console.log('End'); }使用生成器函数生成器函数返回一个生成器对象，这个对象有一个next()方法，用于控制生成器的执行。每次调用next()方法，生成器函数会执行到下一个yield语句，并返回该语句的值。 const gen = numberGenerator()…

前端优化-渐进式图片
type: item
date: 2024-01-23T02:39:21+00:00
description: 前言关于前端的性能及体验问题，图片的加载绝对是重中之重，尤其是在一些有着许多大图的页面，在网络不稳定或稍慢的时候，我们经常能够看到页面中图片的位置是一片空白，等图片加载完成才开始慢慢渲染，在这段时间对用户的体验其实是非常不友好的。那么我们应该如何来进行优化呢？大家想说的是不是：把图片压缩一下不就好了？是的，图片压缩是我们前端开发过程必备的一步，但是光靠图片压缩并不能解决所有的图片体验问题，有些图片本身就比较大，内容质量都比较高，此时压缩图片带来的加载性能提升似乎就没有那么明显了，这时候我们就可能需要使用一些技巧来进行优化了。background-image之前在需求中有使用过 background-image来进行优化，先来看看效果：优化前：优化后：从上述对比中可以清晰看出，优化后的图片加载体验相较于优化前实现了显著提升。优化后，用户所经历的白屏时间显著缩短，取而代之的是图片从模糊逐渐变得清晰的过程。这意味着用户能够更早地预览到图片的内容，而无需再焦急地等待白屏消失，从而大大提升了用户的体验感受！原理原理实际上很简单，从上图我们可以看到优化后的效果实际上是加载了两张图片，一张小图（1.9k），一张原图（2.8M），所有最开始看到的模糊图片其实是这张小图，最后变清晰看到的是原图。这里使用的技术也很简单，只需要CSS的 background-image❝background-image 属性用于为一个元素设置一个或者多个背景图像。.pic_container { width: 300px; height: 375px; background-image: url("../assets/origin.jpg"), url("../assets/small.jpg"); background-repeat: no-repeat; background-size: 100% 100%; }在绘制时，图像以 z 方向堆叠的方式进行。先指定的图像会在之后指定的图像上面绘制。因此指定的第一个图像“最接近用户”。也就是说，原图与缩略图是叠在一起的，并且原图在上缩略图在下，在原图还没加载完成时，可以先看到下面的缩略图，也就是那张模糊的图片，等原图加载完成之后，浏览器才会开始渲染原图，由于两张图是在同一个位置，所以我们可以看到一个从模糊变清晰的过程。img + background-image虽然 background-image天生就支持设置多个背景图，但更多时候我们还是使用 img来承载图片，但是 img的 src属性可没有这一特点。是的，如果不使用JS的话光靠 img也是做不到上面的效果的。如果想要使用 img的话，可以使用 img + background-image组合来实现。<div class="pic_container"> <img src="../assets/origin.jpg" alt="origin" /> <style lang="scss" scoped> .pic_container { background-image: url("../assets/small.jpg"); background-repeat: no-repeat; background-size: 100% 100%; img { width: 300px; height: 375px; } } 效果跟上面那种类似，实际上原理也差不多。需要注意的是：img元素占位在图片还没开始渲染时，它在页面上实际上是透明的，所以我们能够在看到下面的背景图片，等 img资源加载完成开始渲染时，才会慢慢遮盖下面的背景图。为了更方便的复用，我们可以把它封装成一个通用组件用来提升用户体验，但该组件需要提供两张图片，一张原图，一张缩略图。这样做有的同学可能觉得很麻烦，那么有没有更简便的方法呢？渐进式图片除了以上这两种方案，我还见过另外一种方案实现的效果，但它并不依赖于 background-image，只需要 img元素就可以，比如：这种效果应该图片格式的功劳，通常网站使用的 JPEG 的内容显示通常有两种类型：基线 JPEG渐进式 JPEG一般来说我们见的比较多的应该是基线JPEG基线JPEG（Baseline JPEG）基线 JPEG 最常见的应用之一是在 Web 浏览器中呈现的图像。基线 JPEG 算法在从网络下载处理数据时逐行渲染图像。当数据从网络到达计算机的缓冲区时，数据以流的形式进行处理。基线式的编码方式是图片从上到下，从左到右地进行处理和编码。这就会形成我们查看大图时比较常见的从上至下逐行显示，即首先显示图像的顶部。然后它会逐行加载到底部，直到显示出完整的图像。这种格式的图片在面对越大的图片时，往往需要我们等待越长的时间才能看到完整图片，比较容易让网站…

js逆向
type: item
date: 2024-01-22T06:45:00+00:00
description: JavaScript逆向工程（ReverseEngineering）是指分析和理解已编译或已部署的JavaScript代码的过程，以便了解其工作原理、找到漏洞、进行修改或复制其功能。这个过程通常用于安全研究、调试、性能优化或破解某些软件。注意： 逆向工程在某些情况下可能涉及法律问题，尤其是当用于破解版权保护的软件时。确保你的行为符合当地法律和行业准则。以下是一些JavaScript逆向工程的基本步骤和技巧：1. 收集信息确定目标 ：确定你要逆向的JavaScript代码的位置。这可以是网页中的 03、事件监听操作事件监听也是JS逆向油猴脚本中常用的一种Hook方式。通过监听网页上的事件，我们可以触发自定义的操作和行为。 04、AJAX拦截操作AJAX拦截也是JS逆向油猴脚本中常用的一种Hook方式。通过拦截网页上的AJAX请求，我们可以实现对数据的控制和修改。 05、函数替换操作函数替换也是JS逆向油猴脚本中常用的一种Hook方式。通过替换网页上的函数，我们可以实现对函数的控制和修改。 06、Header操作Header Hook 用于定位 Header 中关键参数生成位置，以下代码演示了当 Header 中包含 Authorization 关键字时，则插入断点： 07、URL操作URL Hook 用于定位请求 URL 中关键参数生成位置，以下代码演示了当请求的 URL 里包含 login 关键字时，则插入断点： 08、JSON.stringify操作JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串，在某些站点的加密过程中可能会遇到，以下代码演示了遇到 JSON.stringify() 时，则插入断点： 09、JSON.parse操作JSON.parse() 方法用于将一个 JSON 字符串转换为对象，在某些站点的加密过程中可能会遇到，以下代码演示了遇到 JSON.parse() 时，则插入断点：(function() { var parse = JSON.parse; JSON.parse = function(params) { console.log("Hook JSON.parse ——> ", params); debugger; return parse(params); } })();10、eval操作JavaScript eval() 函数的作用是计算 JavaScript 字符串，并把它作为 脚本代码来执行。如果参数是一个表达式，eval() 函数将执行表达式。如果参数是 Javascript 语句，eval() 将执行 Javascript 语句，经常被用来动态执行 JS。以下代码执行后，之后所有的 eval() 操作都会在控制台打印输出将要执行的 JS 源码：(function() { // 保存原始方法 window.__cr_eval = window.eval; // 重写 eval var myeval = function(src) { console.log(src); console.log("=============== eval end ==============="); debugger; return window.__cr_eval(src); } // 屏蔽 JS 中对原生函数 native 属性的检测 var _myeval = myeval.bind(null); _myeval.toString = window.__cr_eval.toString; Object.defineProperty(window, 'eval', { value: _myeval }); })();11、Function操作以下代码执行后，所有的函数操作都会在控制台打印输出将要执行的 JS 源码：(function() { // 保存原始方法 window.__cr_fun = window.Function; // 重写 function var myfun = function() { var args = Array.prototype.slice.call(arguments, 0, -1).join(","), src = arguments[arguments.length - 1]; console.log(src); console.log("=============== Function end ==============="); debugger; return window.__cr_fun.apply(this, arguments); } // 屏蔽js中对原生函数native属性的检测 myfun.toStrin…

1 行命令引发的Go应用崩溃
type: item
date: 2024-01-17T01:18:00+00:00
description: 青风 阿里云开发者;) 2025年01月15日 08:31浙江这篇文章分析了Go编译时插桩工具导致go build -race竞态检测产生崩溃的原因。不久前， 阿里云 ARMS 团队、编译器团队、MSE 团队携手合作，共同发布并开源了 Go 语言的编译时自动插桩技术。 该技术以其零侵入的特性，为 Go 应用提供了与 Java 监控能力相媲美的解决方案。开发者只需将 go build 替换为新编译命令 otel go build，就能实现对 Go 应用的全面监控和治理。问题描述近期，我们收到用户反馈，使用otel go build -race替代正常的go build -race命令后，编译生成的程序会导致崩溃。-race[3]是Go编译器的一个参数，用于检测数据竞争（data race）问题。通过为每个变量的访问添加额外检查，确保多个 goroutine 不会以不安全方式同时访问这些变量。理论上，我们的工具不应影响-race竞态检查的代码，因此出现崩溃的现象是非预期的，所以我们花了一些时间排查这个崩溃问题，崩溃的堆栈信息如下：(gdb) bt #0 0x000000000041e1c0 in __tsan_func_enter () #1 0x00000000004ad05a in racecall () #2 0x0000000000000001 in ?? () #3 0x00000000004acf99 in racefuncenter () #4 0x00000000004ae7f1 in runtime.racefuncenter (callpc=4317632) #5 0x0000000000a247d8 in ../sdk/trace.(*traceContext).TakeSnapShot (tc=<optimized out>, ~r0=...) #6 0x00000000004a2c25 in runtime.contextPropagate #7 0x0000000000480185 in runtime.newproc1.func1 () #8 0x00000000004800e2 in runtime.newproc1 (fn=0xc00030a1f0, callergp=0xc0000061e0, callerpc=12379404, retVal0=0xc0002c8f00) #9 0x000000000047fc3f in runtime.newproc.func1 () #10 0x00000000004a992a in runtime.systemstack () ....可以看到崩溃源于 __tsan_func_enter，而引发该问题的关键点是 runtime.contextPropagate。我们的工具在 runtime.newproc1 函数的开头插入了以下代码： func newproc1(fn * funcval, callergp * g, callerpc uintptr)(retVal0 * g) { // 我们插入的代码 retVal0.otel_trace_context = contextPropagate(callergp.otel_trace_context) ... } // 我们插入的代码 func contextPropagate(tls interface{}) interface{} { if tls == nil { return nil } if taker, ok := tls.(ContextSnapshoter); ok { return taker.TakeSnapShot() } return tls } // 我们插入的代码 func(tc * traceContext) TakeSnapShot() interface{ } { ... }TakeSnapShot 被 Go 编译器在函数入口和出口分别注入了 racefuncenter() 和 racefuncexit()，最终调用 __tsan_func_enter导致崩溃。由此确定崩溃问题确实是我们的注入代码导致的，继续深入排查。排查过程使用 objdump 查看 __tsan_func_enter 的源码，看到它接收两个函数参数，出错的地方是第一行 mov 0x10(%rdi),%rdx，它约等于 rdx = *(rdi + 0x10)。打印寄存器后发现 rdi = 0，根据调用约定，rdi 存放的是第一个函数参数，因此这里的问题就是函数第一个参数 thr 为 0。// void __tsan_func_enter(ThreadState *thr, void *pc); 0000…

【整理】开发注意事项-1
type: item
date: 2023-12-24T05:11:00+00:00
description: 开发注意事项考虑 边界值 ：如果要展示一个列表，就要考虑列表为空、列表长度超过一页的情况；如果展示的是文字，则要考虑文字为空、文字超长的情况；访问a.b.c时，a或b是否可能为undefined。考虑 特殊场景 ：如交互状态（hover、disabled、文字提示）、浮点数计算精度（使用utils方法）、防重复提交、分辨率兼容、移动设备兼容、事件冒泡、防抖和节流；考虑 需求变更和功能拓展 ：需求变更是不可避免的，那就要在开发的时候考虑到哪些地方容易变(数值、变量)，哪些不容易变（框架、模式），提前做好设计规划，减少因需求变更造成的大规模重构。考虑代码 可读性 ：复杂方法标注用途、复杂逻辑解释清楚、修改他人代码先理解上下文并做好自测。保持优化代码的好习惯：所有不合理的问题，都是可以改的，代价大，就细致谋划，不要搁置，避免埋雷。代码越短就越好吗？实际的业务书写中，如果减少10%的代码换来的是可读性变差，那并不推荐这么做。我们推荐的是，在保证可读性的前提下，尽可能让代码变少。 // bad 三元嵌套三元不利于可读性 let message = (age < 3) ? 'Hi, baby!' : (age < 80) ? 'Hello!' : 'What an unusual age!'; // good if (age < 3) { message = 'Hi, baby!'; } else if (age < 80) { message = 'Hello!'; } else { message = 'What an unusual age!'; } // bad if (company == 'Netscape') { return true; } else { return false; } // good return company === 'Netscape';魔法值的问题魔法值，也叫做魔法数值、魔法数字，通常是指在代码编写时莫名出现的数字， 无法直接判断数值代表的含义，必须通过联系代码上下文分析才可以明白， 严重降低了代码的可读性。除数字之外，代码中作为key值的常量字符串也被认为是魔法值， 尽管其表示含义比数值较为清晰，但是仍然会产生不规范问题。 // 魔法值举例 if(flag === '5'){ ....... } if (businessType === 101){ ....... }通常会采用枚举类型来解决魔法值问题，由于JS中没有枚举类型，可以使用对象字面量的方式来模拟枚举，如下： const BusinessTypeEnum = { SYSTEM: 0, // 系统 CRM: 1, // CRM JXC: 2, // JXC UNKNOWN: 404, // 未知对象类型 CUSTOMER_MANAGEMENT: 100, // 客户管理 CUSTOMER: 101, // 客户 CUSTOMER_FOCUS: 102, // 重点客户 CUSTOMER_DEAL: 103, // 成交客户 CUSTOMER_FOLLOW: 104, // 跟进客户 CUSTOMER_PUBLIC: 105 // 客户公海池 } if (businessType === BusinessTypeEnum.CUSTOMER){ ....... }采用枚举的另外一个好处，当某个值因为需求迭代需求变更时，我们只需要在枚举中将该值替换，并不需要在全局搜素替换。程序中的电车难题如图，当一辆电车快速驶来，无论图上的人采取哪种选择，似乎都是错误的。但是，在程序中出现预期之外的错误必须要导致一个负面结果时，一定要选择代价最小的。举个例子： 某同学开发了一个新手引导弹窗功能，该功能有个透明的全屏遮罩，只有当新手引导结束时，遮罩才会消失。由于程序中存在一处逻辑判断不严谨，导致在特定情况下全屏遮罩不能被关闭，然后整个系统都不能被点击了。一个简单的新手引导功能却引发了系统不可用的严重故障，这是非常大的代价。通过上述例子去分析，系统不可用的负面影响远大于新手引导不可用。因此，我们应该修改逻辑判断方案，当程序脱离预期执行时，直接去关闭新手引导。 // before if (step === 0) { dialog.close() } else { dialog.show() } // after if ([1, 2, 3].includes(step)) { dialog.show() } else { dialog.close() }我们无法保证自己写的代码没有任何差错，但是我们可以提前考虑，万一发生预期之外的错误，我们要如何处理来让损失最低。不要让用户等待由用户触发的交互操作，应立即给予响应。立即响应不等于立即呈现数据，有些异步操作，无法立即呈现…

【CSS】font-weight设置为500显示不出加粗效果
type: item
date: 2023-12-12T09:14:44+00:00
description: 【CSS】font-weight设置为500显示不出加粗效果问题出在操作系统上：macOS 系统默认的华文黑体(STHeiti) 有七个矢量级别：Heavy/Bold/MediumP4/Regular/Thin/Light/UltraLightP2 ，它包含上面 CSS 中设定的 500 这个精度。Windows 系统默认的宋体(simsun) 没有那么多级别。在缺少级别支持的前提下，CSS 会根据回退机制寻找一个类似的精度，结果就变成了400 (Normal)。回退机制：如果指定的权重值不可用，则使用以下规则来确定实际呈现的权重：如果指定的权重值在 400和 500之间（包括 400和 500）：按升序查找指定值与 500之间的可用权重；如果未找到匹配项，按降序查找小于指定值的可用权重；如果未找到匹配项，按升序查找大于 500的可用权重。如果指定值小于 400，按降序查找小于指定值的可用权重。如果未找到匹配项，按升序查找大于指定值的可用权重（先尽可能的小，再尽可能的大）。如果指定值大于 500，按升序查找大于指定值的可用权重。如果未找到匹配项，按降序查找小于指定值的可用权重（先尽可能的大，再尽可能的小）。以上策略意味着，如果一个字体只有 normal 和 bold 两种粗细值选择，指定粗细值为 100-500 时，实际渲染时将使用 normal，指定粗细值为 501-900 时，实际渲染时将使用 bold 。