WebAssembly(Wasm):前端性能提升的关键技术

想象一下你正在构建一个高性能的 Web 应用程序,比如一个 3D 游戏、一个图像编辑器或者一个数据处理仪表盘。你需要应用程序快速流畅,在不减速的情况下执行各种复杂操作。但是仅靠 JavaScript 能做到的有限:无论你如何优化,总会有 JavaScript 无法快速运行的代码。 WebAssembly(Wasm)—— 有了这项酷炫的技术,现在我们可以在浏览器中运行高性能代码,几乎就像在原生应用程序中一样。 WebAssembly 究竟是什么 WebAssembly 或 Wasm 到底是什么呢?它基本上是 JavaScript 的超级伙伴。WebAssembly 是一种低级二进制格式,可以在浏览器中以接近原生的速度运行。它是为计算密集型任务而构建的,而这些任务 JavaScript 自己处理起来效果不太好。 最棒的是,WebAssembly 与特定编程语言无关。它是一个与语言无关的平台,像 C、C++ 或 Rust 等语言的代码可以直接在浏览器中运行。开发者终于可以将其他语言的高性能代码编译成 WebAssembly,在 Web 上与 JavaScript 一起使用。 WebAssembly 特性 极速性能 Wasm 代码的运行速度几乎与原生应用程序一样快,所以你可以将它用于性能密集型任务。如果你正在构建一个图像编辑器,Wasm 可以轻松处理实时图像处理,如调整大小、颜色调整或应用滤镜,而让 JavaScript 处理用户界面。 跨浏览器一致 所有主流浏览器都支持 WebAssembly,即 Chrome、Firefox、Safari 和 Edge。这意味着无论你的用户在哪里,Wasm 代码的运行方式都相似。所以,我们可以保证应用程序的性能始终一致且快速。 更多语言选择 有了 WebAssembly,你不限于使用 JavaScript。你可以引入其他语言,如 C++ 或 Rust,它们以性能和内存效率著称。这对于速度至关重要的项目,或者当你想重用现有代码库时非常有用。 优化资源使用 WebAssembly 被开发为低内存占用。这使得它适合资源有限的设备,如移动设备。这一点非常重要,因为现代应用程序需要在各种设备上运行。 何时应该使用 WebAssembly 并非每个 Web 项目都需要 WebAssembly。对于许多事情,JavaScript 仍然完全够用:表单验证、基本交互、DOM 操作…… 但如果你需要更快的速度,或者你正在处理特别大量的数据,以下是 Wasm 可能派上用场的时候: 图形密集型应用程序:需要 3D 渲染的应用程序,如基于 Web 的游戏或模拟等 实时数据处理:需要快速计算的应用程序,如金融 / 科学分析工具等 Web 上的遗留代码:如果你有现有的用 C++ 或 Rust 编写的代码,WebAssembly 允许你将其带到 Web 上而无需完全重写。 举个栗子 我所在的团队就开发了一款类似于 figma 的图形编辑软件,其底层的渲染引擎则是由 c++ 实现,然后通过编译成 WebAssembly 后提供给 js 层调用,从而保障丝滑的操作体验。 ...

November 10, 2023 · 2 min · 214 words · Link

Relay/Figma 插件开发快速上手

背景 最近在负责 D2C 生成组件代码的需求,首先我们在设计工具上实现了一个组件绑定功能。大概就是研发需要选择设计组件然后逐个绑定设计属性,生成代码就能包含组件代码,提高代码可用率。 由于手动绑定研发组件需要一个个点击表单,效率较低。大家的时间都很宝贵,因此批量操作通过自动化的方式就尤其有必要,插件是解决方案之一。 插件介绍 Figma 插件是扩展 Figma 设计工具功能的小程序。这些插件可以自动化设计流程,提供额外的设计资源,或者增强协作功能。设计师和开发者可以使用插件来提高工作效率,减少重复性工作。插件市场包含各种类型的插件,例如图标生成器、颜色工具、原型插件等。用户可以在 Figma 的插件商店中找到并安装这些插件,以满足特定的设计需求。 而 Relay 插件跟 Figma 插件一摸一样,所以开发 relay 插件直接先在 figma 环境开发即可。 搭建初始项目 具体可以参考这篇文章 https://www.figma.com/plugin-docs/plugin-quickstart-guide/ 初始化项目之后,我们就得到这样一个初始化工程: 插件运行环境 为了保持第三方代码的安全性,不会因为恶意代码影响 figma 平台运行,figma 提供了一套沙箱环境来执行 js 代码,并在沙箱环境中提供相应的 API 操作图层数据,从而达到插件与 figma 的交互。 在figma的沙箱环境是可以执行所有的 es6 语法,但不提供浏览器 DOM API。如果需要使用浏览器 API 或自定义 UI 则需要使用 figma.showUI() 方法实现,figma.showUI 的实质则是创建一个 iframe 来运行你的 UI 代码。 在沙箱与iframe之间则通过 postMessage 进行通信,这套架构在vscode的插件实现上也是类似方案。 逻辑与 UI 分离 首先,项目拆分成两个目录: native 用来存放逻辑相关的代码。执行在 sandbox 里的 js 代码,也是调用 figma API 的逻辑,如获取节点、创建节点、修改节点等; web 用来存放前端 UI 代码,用来渲染插件的界面。 ...

March 8, 2023 · 2 min · 397 words · Link

useLayoutEffect与useEffect:React Hooks 理解

React Hooks 改变了我们在函数组件中管理状态和副作用的方式,为处理组件逻辑提供了更直观、灵活的途径。在众多可用的 Hooks 中,useEffect和useLayoutEffect在管理副作用(特别是涉及 DOM 更新或异步任务的副作用)方面起着关键作用。 选择正确的 Hook 对于保持最佳性能和流畅的用户体验至关重要。useEffect和useLayoutEffect都可用于类似任务,但基于执行时机和行为,它们各有特定优势。理解这些差异有助于避免不必要的重新渲染,并确保最佳的用户体验。 理解useEffect 用途 useEffect是 React 函数组件中处理副作用的首选 Hook,它取代了类组件中的生命周期方法,如componentDidMount、componentDidUpdate和componentWillUnmount,将它们整合为一个高效的 Hook。 工作原理 与类组件中同步运行的生命周期方法不同,useEffect在组件渲染之后执行。这种延迟执行允许浏览器在运行任何副作用之前更新屏幕,使useEffect不会阻塞渲染。因此,它非常适合那些不需要立即进行 DOM 更新的操作,如数据获取或事件监听器。 常见用例 useEffect用途广泛,常用于涉及非阻塞副作用的任务,以下是一些useEffect的理想场景: 1. 数据获取:使用useEffect从 API 获取数据并更新组件状态,且不影响初始渲染。 useEffect(() => { async function fetchData() { const response = await fetch('https://api.example.com/data'); const data = await response.json(); setData(data); } fetchData(); }, []); 2. 设置事件监听器:使用useEffect在组件挂载时设置事件监听器,并在卸载时清理 useEffect(() => { const handleResize = () => setWindowSize(window.innerWidth); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); 3. 管理异步任务:使用useEffect处理定时器或与本地存储交互,确保这些任务在组件挂载后运行 useEffect(() => { const timer = setTimeout(() => { setIsVisible(true); }, 1000); return () => clearTimeout(timer); }, []); 由于useEffect的非阻塞特性,它通常是默认选择,是处理大多数副作用且不干扰初始渲染的高效方式。 ...

November 7, 2019 · 1 min · 202 words · Link

编辑器-前端代码风格规范

最近在升级一个旧的项目,早前的项目由于没有使用 ts,改起来有点痛苦以及当时没有做好的规范约束,以至于不同人贡献的代码差别很大,看着着实痛苦。 规范的意义 1、保证代码的一致性,从根本上提高代码整体的可读性、可维护性、可复用性:保持一致的编码风格,能让其他人在维护代码时能快速上手,即使没有什么经验的开发同学也能保障其代码的交付质量,为后期维护提供更好的支持。 2、有效降低开发和沟通的成本,提升团队整体效率:大型项目最复杂的模块往往仅占整体的一小部分,更多的是相对简单的日常开发和维护。遵循统一的技术规范,能够有效的降低多人协作工程中的沟通成本。如果没有统一规范则出现混乱的局面,留下庞大的技术债。 技术栈统一 基础:Typescript + Tsx 框架:React + React Dom Css 处理器:Sass UI 框架:Ant Design / DongDesign 状态管理:React Redux + Rematch 构建工具:Vite ESLint 规范 基准规则 不重复造轮子,基于 eslint:recommend 配置并改进 能够帮助发现代码错误的规则,全部开启 ESLint 规则 允许非大写开头的构造函数名 ( new-cap: 0 ) 禁止使用 var ( no-var: 2 ) 限制 console 使用,仅允许 warn 和 error ( no-console: [1, { allow: [‘warn’, ’error’] }] ) 禁用内部声明 ( no-inner-declarations: 2 ) 强制使用 const 声明不会被修改的变量 ( prefer-const: [2, { ‘destructuring’: ‘all’ }] ) 禁止特定语法,如 debugger、label 和 with 语句 React 规则 允许组件没有 displayName 禁止 JSX 中的重复属性 禁止未定义的 JSX 元素 禁止直接修改 state 强制组件的 render 方法返回值 不要求 prop-types 验证 不要求导入 React (新版 React 不需要) 禁止在 componentDidUpdate 中使用 setState 鼓励使用无状态函数组件 强制执行 React Hooks 规则 Import 规则 禁止未使用的模块 import 语句后需要空行 禁止使用 CommonJS 模块语法 (require),但允许条件性 require 禁止重复导入 Import 排序 强制对 imports 和 exports 进行排序 TypeScript 规则 不强制函数返回类型注解 允许变量在定义前使用 对未使用的变量发出警告 不强制模块边界类型 允许特定情况下的空函数 允许 this 别名 允许 require 语句 允许 @ts-comment 注释 Git 规范 commit日志基本规范 <type>(<scope>): <subject> <BLANK LINE> <body> <BLANK LINE> <footer> feat: 新增 feature fix: 修复 bug docs: 仅仅修改了文档,比如 README, CHANGELOG, CONTRIBUTE等等 style: 仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑 refactor: 代码重构,没有加新功能或者修复 bug perf: 优化相关,比如提升性能、体验 test: 测试用例,包括单元测试、集成测试等 chore: 改变构建流程、或者增加依赖库、工具等 revert: 回滚到上一个版本 分支规范 test 测试分支 master 主分支 release release 分支 feat/xxx 特性分支 hotfix/xxx 修复分支 ESLint 配置示例 extends: - eslint:recommended - plugin:react/recommended - plugin:@typescript-eslint/eslint-recommended - plugin:@typescript-eslint/recommended - prettier - plugin:import/errors - plugin:import/warnings - plugin:import/typescript parser: '@typescript-eslint/parser' env: es6: true node: true plugins: - react - prettier - react-hooks - simple-import-sort - unused-imports - '@typescript-eslint' parserOptions: sourceType: module ecmaFeatures: jsx: true rules: prettier/prettier: [ 2, { singleQuote: true, trailingComma: 'es5', semi: false, 'endOfLine': 'auto', }, ] # eslint new-cap: 0 no-var: 2 no-console: [1, { allow: ['warn', 'error'] }] no-unused-vars: 0 no-inner-declarations: 2 camelcase: 0 no-useless-escape: 0 no-prototype-builtins: 0 no-restricted-syntax: - 2 - DebuggerStatement - LabeledStatement - WithStatement require-atomic-updates: 0 prefer-rest-params: 0 prefer-const: [2, { 'destructuring': 'all' }] prefer-spread: 0 # react react/display-name: 0 react/jsx-no-duplicate-props: 2 react/jsx-no-undef: 2 react/no-deprecated: 0 react/no-direct-mutation-state: 2 react/no-render-return-value: 2 react/require-render-return: 2 react/jsx-uses-react: 1 react/jsx-uses-vars: 1 react/prop-types: 0 react/react-in-jsx-scope: 0 react/no-did-update-set-state: 2 react/no-redundant-should-component-update: 2 react/no-typos: 2 react/no-unused-prop-types: 2 react/no-unused-state: 2 react/prefer-stateless-function: [1, { ignorePureComponents: true }] react/void-dom-elements-no-children: 2 react/jsx-boolean-value: 1 react/jsx-no-bind: [2, { ignoreRefs: true, allowArrowFunctions: true }] react-hooks/rules-of-hooks: 2 react-hooks/exhaustive-deps: 1 react-perf/jsx-no-new-object-as-prop: 1 react-perf/jsx-no-new-array-as-prop: 1 # import import/no-unused-modules: 2 import/newline-after-import: 2 import/no-commonjs: [2, { allowConditionalRequire: true }] import/no-duplicates: 2 # simple-import-sort simple-import-sort/imports: 2 simple-import-sort/exports: 2 # typescript '@typescript-eslint/camelcase': 0 '@typescript-eslint/explicit-function-return-type': 0 '@typescript-eslint/no-use-before-define': 0 '@typescript-eslint/no-unused-vars': [1, { ignoreRestSiblings: true }] '@typescript-eslint/explicit-module-boundary-types': 0 '@typescript-eslint/ban-types': 0 '@typescript-eslint/no-empty-function': [1, { 'allow': ['private-constructors', 'protected-constructors'] }] '@typescript-eslint/no-this-alias': 0 '@typescript-eslint/no-var-requires': 0 '@typescript-eslint/ban-ts-comment': 0 '@typescript-eslint/strict-boolean-expressions': [1, { allowNullableBoolean: true }] overrides: - files: - '*.js' - '*.jsx' rules: import/no-commonjs: 0 - files: - '*.ts' - '*.tsx' rules: import/no-unresolved: 0 lodash/prefer-lodash-method: 0 VSCode 配置 { "editor.tabSize": 2, "editor.insertSpaces": true, "search.exclude": { }, "editor.formatOnSave": false, "editor.renderControlCharacters": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, "[less]": { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[css]": { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[yaml]": { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[html]": { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[json]": { "editor.formatOnSave": true, "editor.defaultFormatter": "vscode.json-language-features", }, "[markdown]": { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", }, "[mdx]": { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", }, "files.associations": { "*.xml": "html", "*.svg": "html", }, "prettier.ignorePath": ".eslintignore", "liveServer.settings.port": 5501, "code-runner.executorMapByGlob": { "*.test.*": "cd $dir && yarn test --files $fullFileName", }, "typescript.tsdk": "node_modules/typescript/lib", "search.followSymlinks": false, "files.exclude": { "**/.git": true, "**/.svn": true, "**/.hg": true, "**/CVS": true, "**/.DS_Store": true, "**/tmp": true, "**/bower_components": true, }, "files.watcherExclude": { "**/.git/objects/**": true, "**/.git/subtree-cache/**": true, "**/node_modules/**": true, "**/tmp/**": true, "**/bower_components/**": true, "**/dist/**": true }, "makefile.configureOnOpen": false } tsconfig 配置 { "compilerOptions": { "typeRoots": ["./node_modules/@types", "./types"], "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "react", "outDir": "./dist", "plugins": [ { "name": "typescript-plugin-css-modules" } ], "paths": { "~/*": ["./src/*"], "@constant/*": ["./src/constant/*"] } }, "exclude": [ "build", "node_modules" ], "include": [ "src", "typing.d.ts" ] }

March 10, 2019 · 3 min · 633 words · Link

D2C - 生成 Tailwind Css 前端代码

背景 目前 Relay 生成代码功能研发过程中,我们发现 D2C 的难点之一:Class 类名不语义化的问题。D2C 算法生成的类名,如 view0、view1、view2、text0、text1 等缺乏有效含义,对研发不够友好。 方案 1、使用 LLM 大模型如 GPT4 进行语义优化 经过测试大模型可以优化代码,但存在几个问题: 大模型存在 token 限制问题,输入和输出都存在可能超出的情况,特别是输出限制了 8K 大小,很多稿子跑下来就超出了。 大模型耗时比较长,一个请求过去要好几秒,使用 SSE 体验会略好一点 大模型如果不结合图片做多模态预览效果也会略差一点,比如不同的稿子,在没有图片 input 时候,输出的 class 有一定概率重复,并可能跟设计稿件毫无关联:如 view 大概率会变成 product等。 2、生成 Tailwind css 风格的 class,不再需要自定义class Tailwind CSS 是目前世界上最流行的原子化 CSS 框架。它集成了诸如 flex, pt-4, text-center 和 rotate-90 这样语义化的类名。我们开发者能直接在各种脚本标记语言中编写它们,并把它们组合起来,构建出任何的设计。自从 3.x 大版本开始,Tailwind CSS 把引擎升级为 Just in Time(jit)。这使得我们能够编写代码的同时,实时生成各种 CSS,真正的做到了所写即所得。 Tailwind CSS 特点: 高度可定制化 响应式设计友好 提高开发速度 代码可读性和维护性 在 HTML 代码中直接使用工具类来描述样式,使得样式和结构紧密结合,代码的可读性更高。例如,看到 class="bg-gray-100 p-4 rounded-md" 这样的代码,就能够很直观地理解这个元素有一个浅灰色的背景、一定的内边距并且是中等圆角。 ...

March 3, 2019 · 2 min · 346 words · Link

Webpack 与 Vite 构建工具之差

在前端开发领域,Webpack 和 Vite 是两款备受瞩目的构建工具,它们在提升开发效率和优化项目构建流程方面发挥着关键作用,但在诸多方面存在显著差异。 一、构建原理 Webpack 以其强大的模块打包能力著称,它会在启动时对整个项目进行依赖分析,递归地构建模块依赖图。从项目入口文件开始,将所有相关的 JavaScript、CSS、图片等资源都纳入处理范围,将它们打包成一个或多个 bundle 文件。在这个过程中,Webpack 会对代码进行各种处理,如代码压缩、模块合并、loader 转换等,以适应不同的环境和需求。 而 Vite 则采用了一种截然不同的策略,利用浏览器原生(script type="module")的 ES 模块支持来实现快速的冷启动。在开发模式下,Vite 不会像 Webpack 那样预先打包所有模块,而是在浏览器请求某个模块时,才对其进行即时编译和传输。这使得 Vite 在启动项目时速度极快,大大缩短了开发过程中的等待时间。在生产环境下,Vite 会进行预构建来优化依赖,将一些常用的第三方库预先打包,进一步提升性能。 二、开发体验 在开发过程中,Webpack 的热更新(HMR)功能虽然能够在一定程度上实现代码修改后的实时更新,但由于其全量构建的特性,在大型项目中可能会出现更新速度较慢的情况。每次代码修改后,Webpack 需要重新构建相关的模块和依赖,这个过程可能会耗费数秒甚至更长时间,影响开发的流畅性。 相比之下,Vite 的热更新机制更为高效。由于其基于 ES 模块的按需加载特性,当代码发生变化时,Vite 能够精准地只更新受影响的模块,并且速度极快,几乎可以做到即时更新,让开发者能够迅速看到代码修改的效果,极大地提升了开发效率和体验。 三、配置复杂度 Webpack 的配置向来以复杂著称,为了实现各种功能,如代码分割、优化、loader 和 plugin 的配置等,开发者需要编写大量的配置代码。对于初学者来说,这无疑是一个较高的门槛,需要花费较多时间去学习和理解各种配置选项的作用和相互关系。 Vite 的配置则相对简洁明了,它默认提供了许多合理的配置,在大多数情况下,开发者只需要进行少量的配置调整即可满足项目需求。这使得 Vite 在项目初始化和配置方面更加便捷,能够让开发者更快地投入到实际开发中。 四、优缺点对比 Webpack 优点 拥有丰富的插件和 loader 生态系统,几乎可以处理任何前端资源和构建需求,能够对项目进行深度定制和优化。例如,可以通过特定的 loader 将不同类型的文件转换为 JavaScript 模块,或者使用插件实现复杂的功能,如代码压缩、混淆、提取公共代码等。 对大型项目的支持较为成熟,能够有效地管理复杂的依赖关系,确保项目的稳定性和可维护性。通过细致的配置,可以实现精确的代码分割,将不同页面或功能的代码拆分成独立的 chunk,提高页面加载速度和性能。 缺点 配置复杂,学习成本高,对于新手开发者来说可能会感到困惑和无从下手。过多的配置选项也容易导致配置文件变得冗长和难以维护。 开发时的热更新速度相对较慢,尤其是在大型项目中,每次代码修改后的重新构建过程可能会耗费较长时间,影响开发效率。 Vite 优点 开发启动速度极快,基于 ES 模块的按需加载和即时编译特性,大大缩短了冷启动时间和代码更新的反馈周期,提供了流畅的开发体验。 配置简单,默认配置已经能够满足大多数项目的基本需求,开发者可以快速上手并开始项目开发,减少了在配置上花费的时间和精力。 缺点 对一些老旧项目或不遵循 ES 模块规范的代码兼容性可能较差,需要进行一定的改造才能顺利使用 Vite 进行构建。 虽然在开发体验上表现出色,但在生产环境下的一些高级优化功能可能相对 Webpack 来说不够丰富,对于一些对性能极致追求的项目可能需要额外的配置和优化工作。 适用场景 对于大型项目,尤其是那些具有复杂依赖关系和大量代码的项目,Webpack 的强大功能和丰富的插件生态系统能够更好地应对。它可以通过细致的配置对项目进行深度优化,例如实现精确的代码分割,将不同页面或功能的代码拆分成独立的 chunk,提高页面加载速度和性能。 ...

January 10, 2019 · 1 min · 83 words · Link

CSS 单位到底怎么选

在 CSS 样式表中,合理地运用单位来定义元素的尺寸、间距、字体大小等属性是至关重要的,不同的单位有着各自的特性和适用场景。本文将深入探讨几种常见的 CSS 单位:px、rpx、vw 和 rem。 px(像素) 像素(Pixel)是最为基础和常见的单位,它直接对应屏幕上的一个物理像素点。在早期的网页设计中,px 几乎一统天下。例如,当我们设置一个 div 的宽度为 200px 时,它在屏幕上所占据的空间就是实实在在的 200 个像素点。这种确定性使得布局相对直观,设计师可以精确掌控元素的尺寸。然而,px 的缺点也逐渐显现,随着设备屏幕分辨率的多样化,固定像素值在不同分辨率下可能出现显示效果差异巨大的问题。比如,同样一个宽度为 300px 的按钮,在低分辨率屏幕上可能看起来大小适中,而在高分辨率的视网膜屏上,就会显得细小局促。 rpx(响应式像素) rpx 是微信小程序特有的一种单位,它是根据屏幕宽度进行自适应调整的。小程序规定屏幕宽度为 750rpx,无论在何种设备上打开小程序,都会将屏幕宽度等比例划分为 750 份。这意味着如果一个元素设置宽度为 375rpx,那么在宽度为 375px 的设备上,它将占据屏幕宽度的一半;而在宽度为 750px 的设备上,它依然占据屏幕宽度的一半,即 375px。这种自适应特性使得小程序的页面布局能够很好地适配各种手机屏幕,极大地减轻了开发者对不同屏幕适配的工作量,为用户带来一致的视觉体验。 vw(视口宽度百分比) vw 代表视口宽度的百分比单位,1vw 等于视口宽度的 1%。相较于 px,它具有一定的响应式特性。例如,设置一个容器的宽度为 50vw,那么它将始终占据当前视口宽度的一半。这在构建一些需要随浏览器窗口大小动态变化的布局时非常实用,如自适应的侧边栏或弹性的图片展示区域。不过,使用 vw 时要注意,它只与视口宽度相关,若元素的高度也需要按比例自适应,还需结合其他技术或单位来综合实现。 rem(根元素字体大小相对单位) rem 是以根元素(通常是 html 元素)的字体大小为基准的相对单位。默认情况下,大部分浏览器的根元素字体大小为 16px,此时 1rem 就等于 16px。但开发者可以通过修改根元素的字体大小来统一缩放整个页面的尺寸。比如,将根元素字体大小设置为 12px,那么后续所有以 rem 为单位的元素尺寸都会相应缩小。这使得页面在不同屏幕尺寸下,只要合理调整根元素字体大小,就能实现相对灵活且整体协调的布局。尤其在做移动端网页适配时,结合媒体查询动态修改根元素字体大小,rem 单位能发挥出强大的自适应优势,确保页面元素在各种手机和平板设备上都呈现出良好的视觉效果。 如何选择使用合适的单位? 怎样选择主要有以下两大纬度来决定: 项目类型 网页项目: px:对于一些需要精确控制尺寸的元素,如固定宽度的导航栏、按钮等,px 是一个不错的选择。它可以确保在不同浏览器和设备上都能呈现出一致的固定大小。 rem:在进行网页整体布局和字体大小设置时,rem 更为合适。通过设置根元素的字体大小,可以方便地实现整个页面的相对缩放,适应不同屏幕尺寸的设备,尤其在响应式设计中表现出色。 vw:当需要创建与视口宽度紧密相关的布局,如全屏的背景图片、自适应的轮播图等,vw 可以很好地实现根据视口宽度的动态调整。 微信小程序项目: rpx:由于微信小程序的跨设备适配需求较高,rpx 是首选单位。它能够自动根据屏幕宽度进行自适应调整,大大减少了开发者在不同设备上进行适配的工作量,确保页面在各种手机屏幕上都能保持良好的视觉效果。 设计需求 固定尺寸设计:如果设计稿中的元素尺寸是固定的,且不考虑设备屏幕的变化,那么 px 可以准确地还原设计稿中的尺寸。例如,一些品牌标识、特定尺寸的图标等,使用 px 可以确保其在任何情况下都保持原设计的大小和比例。 自适应布局设计:对于需要在不同设备上自适应显示的页面,rem 和 vw 是更好的选择。例如,一个新闻列表页面,要求在不同屏幕宽度的设备上都能自适应显示,使用 rem 或 vw 可以使列表项的宽度、间距等随着屏幕大小的变化而自动调整,提供更好的用户体验。 高分辨率屏幕适配:在处理高分辨率屏幕时,如视网膜屏,px 可能会导致元素在屏幕上显示得过于细小。此时,rem 可以通过调整根元素字体大小来进行整体缩放,使页面在高分辨率屏幕上也能保持清晰和易读。而 rpx 在微信小程序中已经自动处理了高分辨率屏幕的适配问题。 在实际项目中,通常会根据具体情况综合使用多种单位。例如,在网页项目中,可以以 rem 为主要单位进行整体布局,对于一些需要精确控制的元素再结合使用 px;在微信小程序项目中,以 rpx 为主,对于一些特殊情况可以适当使用 px 进行微调。关键是要根据项目的特点和需求,权衡各种单位的优缺点,选择最合适的方案。

December 30, 2018 · 1 min · 93 words · Link

JavaScript 的 SOLID 原则

SOLID 原则首先由著名的计算机科学家 Robert C·Martin (著名的Bob大叔)由 2000 年在他的论文中提出。但是 SOLID 缩略词是稍晚由 Michael Feathers 先使用的。 Bob大叔也是畅销书《代码整洁之道》和《架构整洁之道》的作者,也是 “Agile Alliance” 的成员。 SOLID 是一组原则的首字母缩写,包括: S 单一职责原则 O 开闭原则 L 里氏替换原则 I 接口隔离原则 D 依赖倒置原则 有助于软件工程师设计和编写可维护、可扩展和灵活的代码。其目的是什么呢?是为了提高遵循面向对象编程(OOP)范式开发的软件质量。 单一职责原则(SRP) SOLID 中的第一个字母代表单一职责原则。该原则建议一个类或模块应该只执行一个功能。如果一个类处理多个功能,那么在不影响其他功能的情况下更新一个功能就会变得棘手。随之而来的复杂性可能会导致软件性能出现故障。为了避免这些问题,我们应尽力编写关注点分离的模块化软件。 如果一个类有太多的职责或功能,修改起来就会很头疼。通过使用单一职责原则,我们可以编写模块化、更易于维护且不易出错的代码。例如,以一个人员模型为例: class Person { constructor(name, age, height, country) { this.name = name; this.age = age; this.height = height; this.country = country; } getPersonCountry() { console.log(this.country); } greetPerson() { console.log("Hi " + this.name); } static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { age--; } return age; } } 上面的代码看起来没问题,对吧?不完全是。示例代码违反了单一职责原则。Person类不仅仅是可以创建其他Person实例的唯一模型,它还有其他职责,如calculateAge、greetPerson和getPersonCountry。 ...

December 13, 2018 · 4 min · 698 words · Link

Nginx empty-gif 模块

概述 Nginx 是一个高性能的 HTTP 和反向代理服务器,广泛用于负载均衡、缓存和静态文件服务。Empty-GIF 模块是 Nginx 的一个第三方模块,主要用于处理透明的 GIF 图像(1x1 像素),通常用于跟踪用户行为或在网页中占位。 主要功能 Empty-GIF 模块的主要功能包括: 生成透明 GIF: 该模块可以生成一个 1x1 像素的透明 GIF 图像,通常用于网页中的占位符或跟踪像素。 减少带宽消耗: 通过使用透明 GIF,网站可以减少不必要的图像请求,从而节省带宽。 简单的配置: 该模块的配置非常简单,用户只需在 Nginx 配置文件中添加几行代码即可启用。 配置 在 Nginx 的配置文件中(通常是 /etc/nginx/nginx.conf),可以通过以下方式启用 Empty-GIF 模块: http { ... server { listen 80; server_name example.com; location /tracking { empty_gif; # 使用 empty-gif 模块 } } } 使用场景 Empty-GIF 模块的使用场景包括: 用户行为跟踪: 在网页中嵌入透明 GIF,以便跟踪用户的访问行为。 广告监测: 广告网络可以使用透明 GIF 来监测广告的展示和点击情况。 占位符: 在某些情况下,开发者可能希望在页面中使用占位符图像,而不希望加载实际的图像文件。 🌰栗子 生成如下链接: ...

December 12, 2018 · 1 min · 71 words · Link

简单聊一聊单例设计模式

你是否曾经遇到过需要在应用程序的多个部分共享一个对象的情况,比如数据库连接、WebSocket 客户端或配置管理器或全局的 Logger 对象? 你如何管理这样一个对象,使其在整个应用程序或进程生命周期中保持一致且可访问?这就是单例设计模式发挥作用的地方。 概述 单例是一种创建型设计模式,属于设计模式的一类,用于解决使用new关键字或操作符创建对象的原生方式所带来的不同问题。 单例设计模式主要致力于解决两个主要问题: 我们如何为实例提供一个全局访问点? 我们如何确保一个类或特定类型的对象只有一个实例? 它可以简化并规范我们管理特定种类或类型的全局状态的方式,例如数据库连接、WebSocket 客户端、缓存服务,或者任何我们在整个应用程序生命周期中需要在内存中持久化和修改的内容。 如何实现单例设计模式 class Singleton { private static instance: Singleton private construct() { // 私有构造函数确保只能通过静态方法创建唯一实例 } public static getInstance () { if (!this.instance) { this.instance = new Singleton() } return this.instance } } 该类应该定义一个静态属性来存储唯一可共享的实例。static 关键字意味着实例对象与类的实例无关,而是与类定义本身相关联。类的构造函数应该标记为private 则无法通过 new 创建实例。获取类实例的唯一方法是调用 getInstance 静态方法。 const instance = Singleton.getInstance() 我们可以通过调用与单例类相关联的静态方法getInstance来使用上述类。getInstance 方法保证即使我们在代码库的不同位置多次实例化我们的类,我们始终得到相同的实例。 第一个实际场景 在 node 服务中,需要记录调用接口产生的 log,因此要设计一个全局的 Logger 集中管理日志行为。 class Logger { private static instance: Logger; private logs: string[] = []; private constructor() {} public static getInstance(): Logger { if (!Logger.instance) { Logger.instance = new Logger(); } return Logger.instance; } public log(message: string) { this.logs.push(message); } } // 使用 const logger = Logger.getInstance(); logger.log('Application started'); 在这个例子中,private constructor() 确保: ...

November 24, 2018 · 2 min · 279 words · Link