CMS

博客

  • 关于音符一拍的时值计算

    音符一拍的时值计算是音乐理论中的基础概念,涉及音符的时值和拍号:

    1. 音符时值

    音符时值表示音符的持续时间,常见音符及其时值如下:

    • 全音符:4拍
    • 二分音符:2拍
    • 四分音符:1拍
    • 八分音符:0.5拍
    • 十六分音符:0.25拍

    2. 拍号

    拍号决定每小节的拍数和每拍的时值,通常表示为分数,如4/4、3/4等。

    • 分子:每小节的拍数
    • 分母:每拍的音符类型(4代表四分音符,8代表八分音符)

    3. 计算一拍的时值

    以4/4拍为例:

    • 拍号:4/4
    • 每拍时值:四分音符(分母为4)
    • 每拍时长:1拍

    4. 不同拍号的时值计算

    • 3/4拍:每拍为四分音符,每小节3拍。
    • 6/8拍:每拍为八分音符,每小节6拍。

    5. 示例

    • 4/4拍:四分音符=1拍,二分音符=2拍,全音符=4拍。
    • 6/8拍:八分音符=1拍,四分音符=2拍,附点四分音符=3拍。

    6. 实际应用

    • 4/4拍:四分音符=1拍,八分音符=0.5拍。
    • 3/4拍:四分音符=1拍,二分音符=2拍。

    总结

    音符一拍的时值由拍号决定,分母指定每拍的音符类型,分子决定每小节的拍数。

  • 【CSS】动画性能优化

    60fps 与设备刷新率

    目前大多数设备的屏幕刷新率为60fps(Frame per Second),即每秒60帧。因此,如果在页面中有一个动画或渐变效果,或者用户正在滚动页面,那么浏览器渲染动画或页面的每一帧的速率也需要跟设备屏幕的刷新率保持一致,即每一帧要在16毫秒(1S/60 = 16.66ms)之内完成。如果无法完成,由于帧率的下降会导致内容在屏幕上抖动。此现象通常称为卡顿,会对用户体验产生负面影响。

    浏览器渲染

    在讲性能之前,我们需要先对浏览器渲染页面有一个基础的理解。

    css 图层

    浏览器在渲染一个页面时,会将页面分为很多个图层,图层有大有小,每个图层上有一个或多个节点。需要注意的是,如果图层中某个元素需要重绘,那么整个图层都需要重绘(关于重绘下面会讲到)。

    渲染过程

    简单来说,浏览器的渲染过程其实就是将页面转换成像素显示到屏幕上,大致有如下几个步骤: 渲染过程流程图

    • JavaScript操作: 一般来说,我们会使用 JavaScript 来实现一些交互操作。比如用往页面里添加一些元素,切换显示隐藏等。
    • style 样式计算: 该过程根据 css 选择器,获取每个元素匹配的 css 样式并计算其最终应用样式。
    • Layout 布局:该过程计算元素要占据的空间大小及其在屏幕的位置。网页的布局模式意味着一个元素可能影响其他元素,例如 元素的宽度一般会影响其子元素的宽度以及树中各处的节点,因此对于浏览器来说,布局是经常发生的。
    • Paint 绘制:本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等。也就是绘制元素所有的可视效果。
    • Composite 渲染层合并:在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上如果我们需要提高动画的性能,需要做的就是减少浏览器在动画运行时所需要做的工作。当 css 在进行动画时,其不同属性值引起的改变,重新渲染可能会有三种执行路径:
      1. layout -> paint -> composite
      2. paint -> composite
      3. composite 很明显,最短路径的 C 动画性能是最高的,所以我们在使用动画的时候就得考虑使用什么属性,以尽量减少执行路径。

    动画属性

    css 的属性大致分为三类:布局类(layout),绘制类(paint),合成类(composite)。

    重排(reflow)

    由元素的布局类属性改变所触发的行为过程,我们称为 reflow,也叫做 relayout(重新布局)。当某个节点 reflow 时会重新计算节点的尺寸和位置,还可能会引起其它节点的 reflow。

    该系列属性的改变,会执行路径 A 进行重新渲染,所以性能是最差的。(这充分说明,重排会引起重绘)

    触发重排的属性

    • 盒子模型相关属性会触发重布局:

      • width
      • height
      • padding
      • margin
      • display
      • border-width
      • border
      • min-height
    • 定位属性及浮动也会触发重布局:

      • top
      • bottom
      • left
      • right
      • position
      • float
      • clear
    • 改变节点内部文字结构也会触发重布局:

      • text-align
      • overflow-y
      • font-weight
      • overflow
      • font-family
      • line-height
      • vertival-align
      • white-space
      • font-size

    重绘(repaint)

    由绘制类属性改变触发节点重新绘制其可视效果的过程,我们称为 repaint。 该系列属性的改变,会执行路径 B,所以性能一般。

    修改时只触发重绘的属性有:

    • color
    • border-style
    • border-radius
    • visibility
    • text-decoration
    • background
    • background-image
    • background-position
    • background-repeat
    • background-size
    • outline-color
    • outline
    • outline-style
    • outline-width
    • box-shadow

    上面的属性由于不会修改节点的大小和位置,因此不会触发重排,其只是改变了节点内部的渲染效果,所以只会进行重绘以下的步骤。

    composite

    目前只有两个属性属于 composite 类:

    • transform
    • opactiy

    优化技巧

    减少动画元素

    减少动画元素是动画性能优化中首先需要完成的。通过审查页面动画 DOM 元素结构,去除不必要的动画元素,减少元素的数量,相应地会减少布页面局和绘制的时间。

    尽量使用 fixed、absolute 定位

    对于动画元素,尽量使用用 fixed、absolute 定位方式,避免影响到其他节点重排。

    尽量只改变 transform 和 opacity

    能用 transform、opacity 优先使用,其属性的改变不会发生重排和重绘。如位移操作的,可以使用translate 来实现,渐隐渐现效果可以使用 opacity 属性来实现。

    恰当开启硬件加速效果

    对动画元素应用

    transform: translate3d(0, 0, 0);
    /* transform: translateZ(0); */
    will-change: transform;

    等来开启硬件加速。通常开启硬件加速可以让动画变得更加流畅。但这里需注意,在不需要的时候需去掉避免过多的内存消耗。

  • 【Bus World】广州一汽-28路

    制造商
    广州客车制造厂
    车型
    GZK6120FS
    比例
    1/76
    材质
    金属
    城市
    广州
    线路
    28
    运营区间
    天平架 ↔ 龙洞
    票价
    ¥1.00

    小时候和好友拿着月卡特意去天平架坐双层巴士“游车河”的岁月就保存在模型里吧~

    介绍

    广州公交28路(Guangzhou Public Transport Line 28)运营于中国广东省广州市天河区、海珠区,为市区线路,隶属于广州市一汽巴士有限公司二分公司。

    历史

    • 1993年,广州公交28路投入使用由广州客车厂生产的双层巴士。
    • 2008年1月5日,广州公交28路新增停靠天河客运站(南行)二车道。
    • 2013年12月28日,因地铁长湴站出入口工程施工结束,广州公交28路恢复停靠射击场站(北行)。
    • 2014年11月16日,广州公交28路新增停靠地铁长湴站(南行)。
    • 2020年11月28日,广州公交28路线路走向由天平架总站至龙洞(龙逸山庄)总站调整为珠江嘉苑总站循环线。
    • 2021年10月10日,广州公交28路线路走向由珠江嘉苑总站循环线调整为珠江嘉苑总站至琶洲塔公交总站。
    • 2023年4月8日,广州公交28路双向调整取道车陂南隧道,取消行驶琶洲大桥。
  • 【Bus World】广州一汽-5路

    制造商
    广州客车制造厂
    车型
    GZK6100
    比例
    1/76
    材质
    树脂胶
    城市
    广州
    线路
    5
    运营区间
    省汽车站 ↔ 黄沙
    票价
    ¥1.00

    一条非常熟悉但又少坐的线路,无额头的GZK6100让我想起14路。

    介绍

    广州公交5路(Guangzhou Public Transport Line 5)是运营于中国广东省广州市越秀区、荔湾区、海珠区,为广州早期线路,市区线路,由广州市一汽巴士有限公司三分公司管理。

    历史

    • 2008年2月显示,广州公交5路起止站点为站前路至海珠客运站。
    • 2009年1月、2011年2月显示,广州公交5路起止站点为市客运站(广州火车站)至海珠客运站(南洲路)。
    • 2022年10月17日,广州公交5路停靠的市客运站总站更名为环市西路(壹马大厦)总站。
    • 2024年9月17日,广州公交5路起止站点由环市西路(壹马大厦)总站至海珠客运站总站调整为广州火车站(草暖公园)总站至海珠客运站总站。
    • 2024年11月16日,广州公交5路起止站点由广州火车站(草暖公园)总站至海珠客运站总站调整为广州火车站(草暖公园)总站至沥滘总站。
  • 【Bus World】广州三汽-239路

    制造商
    广州客车制造厂
    车型
    GZK644
    比例
    1/76
    材质
    树脂胶
    城市
    广州
    线路
    239
    运营区间
    瑞宝乡 ↔ 火车站
    票价
    ¥1.00

    印象中我经常看见但没坐过这条线路,但它同款车型、涂装的208、287却是载满我儿时的记忆。

    介绍

    广州公交239路是广州市的一条公交线路,隶属于广州巴士集团有限公司第三分公司。

  • 【Bus World】广州一汽-3路

    制造商
    广州客车制造厂
    车型
    GZK644
    比例
    1/76
    材质
    树脂胶
    城市
    广州
    线路
    3
    运营区间
    如意坊 ↔ 东山
    票价
    ¥1.00

    3路车属于熟悉的路人甲级别,因为印象中就是最“残破”的车型都由它拥有,想想大热天司机在前引擎驾驶享受桑拿,实在太‘人性化’。(找不到3路,先放个38路顶替)

    介绍

    广州公交3路(Guangzhou Public Transport Line 3)运营于中国广东省广州市越秀区、荔湾区,为市区线路,隶属于广州市一汽巴士有限公司。

    历史

    • 1952年,广州公交3路开通运营,为新中国成立后广州的第一批线路。
    • 直到现在,3路车路线基本一直没变,直到上下九步行街启动建设,才稍微调整了站点。
  • 【Bus World】珍宝巴士-506

    制造商
    广州客车制造厂
    车型
    GZK6100
    比例
    1/76
    材质
    树脂胶
    城市
    广州
    线路
    506
    运营区间
    广州火车东站 ↔ 广州永和开发区
    票价
    ¥1.00

    又淘到一台绝版的东晓GZK6100模型。好像在科学城上班的时候坐过。

  • 【Typescript】接口、类

    接口

    TypeScript的核心原则之一是对值所具有的结构进行类型检查。

    interface LabelledValue {
      label: string;
    }
    
    function printLabel(labelledObj: LabelledValue) {
      console.log(labelledObj.label);
    }
    
    let myObj = {size: 10, label: "Size 10 Object"};
    printLabel(myObj);

    可选属性

    interface SquareConfig {
        width: number
        height: number
        color?: string // 可选属性
    }

    只读属性

    只能在刚创建时修改其值

    interface Point {
        readonly x: number;
        readonly y: number;
    }

    额外的属性检查

    如果 SquareConfig带有上面定义的类型的color和width属性,并且还会带有任意数量的其它属性,可以这样定义:

    interface SquareConfig {
        width: number
        color?: string
        [propName: string]: any
    }

    函数类型

    interface SearchFunc {
      (source: string, subString: string): boolean
    }
    
    // 对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。 
    let mySearch: SearchFunc;
    mySearch = function(src: string, sub: string): boolean {
      let result = src.search(sub);
      return result > -1;
    }

    可索引的类型

    interface StringArray {
      [index: number]: string;
    }
    
    let myArray: StringArray;
    myArray = ["Bob", "Fred"];
    
    let myStr: string = myArray[0];

    类类型

    interface ClockInterface {
        currentTime: Date;
        setTime(d: Date);
    }
    
    // 通过 implements 描述
    class Clock implements ClockInterface {
        currentTime: Date;
        setTime(d: Date) {
            this.currentTime = d;
        }
        constructor(h: number, m: number) { }
    }

    继承接口

    interface Shape {
        color: string;
    }
    
    interface PenStroke {
        penWidth: number;
    }
    
    interface Square extends Shape, PenStroke {
        sideLength: number;
    }
    
    let square = <Square>{};
    square.color = "blue"
    square.sideLength = 10
    square.penWidth = 5

    混合类型

    一个对象可以同时做为函数和对象使用,并带有额外的属性

    interface Counter {
        (start: number): string;
        interval: number;
        reset(): void;
    }
    
    function getCounter(): Counter {
        let counter = <Counter>function (start: number) { };
        counter.interval = 123;
        counter.reset = function () { };
        return counter;
    }
    
    let c = getCounter();
    c(10);
    c.reset();
    c.interval = 5.0;

    class Animal {
        move(distanceInMeters: number = 0) {
            console.log(`Animal moved ${distanceInMeters}m.`);
        }
    }
    
    class Dog extends Animal {
        bark() {
            console.log('Woof! Woof!');
        }
    }
    
    const dog = new Dog();
    dog.bark();
    dog.move(10);
    dog.bark();

    继承

    通过 extends 关键字

    class Animal {
        name: string;
        constructor(theName: string) { this.name = theName; }
        move(distanceInMeters: number = 0) {
            console.log(`${this.name} moved ${distanceInMeters}m.`);
        }
    }
    
    class Snake extends Animal {
        constructor(name: string) { super(name); }
        move(distanceInMeters = 5) {
            console.log("Slithering...");
            super.move(distanceInMeters);
        }
    }

    公共,私有与受保护的修饰符

    public(默认)

    在TypeScript里,成员都默认为 public。

    class Animal {
        public name: string;
    }
    private

    当成员被标记成private时,它就不能在声明它的类的外部访问。

    class Animal {
        private name: string
        constructor(theName: string) { this.name = theName; }
    }
    
    new Animal('Cat').name // 错误: 'name' 是私有的.
    protected

    protected与private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问。

    class Person {
        protected name: string
        constructor(name: string) { this.name = name }
    }
    
    class Employee extends Person {
        private department: string
        public getElevatorPitch() {
            return `my name is ${this.name} and I work in ${this.department}.`
        }
    }
    
    let dick = new Employee("Howard", "Sales")
    console.log(dick.getElevatorPitch());
    console.log(dick.name); // 错误

    readonly修饰符

    readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。

    class Octopus {
        readonly name: string;
        constructor (theName: string) {
            this.name = theName;
        }
    }
    let dad = new Octopus("Man with the 8 strong legs");
    dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.

    存取器

    TypeScript支持通过getters/setters来截取对对象成员的访问。

    let passcode = "secret passcode";
    
    class Employee {
        private _fullName: string;
    
        get fullName(): string {
            return this._fullName;
        }
    
        set fullName(newName: string) {
            if (passcode && passcode == "secret passcode") {
                this._fullName = newName;
            }
            else {
                console.log("Error: Unauthorized update of employee!");
            }
        }
    }
    
    let employee = new Employee();
    employee.fullName = "Bob Smith";
    if (employee.fullName) {
        alert(employee.fullName);
    }

    抽象类

    抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。

    abstract class Department { ... }
    class AccountingDepartment extends Department { ... }
    
    let department: Department // 允许创建一个对抽象类型的引用
    department = new Department() // 错误: 不能创建一个抽象类的实例
    department = new AccountingDepartment() // 允许对一个抽象子类进行实例化和赋值
  • 【JS】小数精度处理技巧

    JavaScript 使用 IEEE 754 双精度浮点数表示所有数字,这会导致一些常见的小数精度问题,例如 0.1 + 0.2 !== 0.3。以下是处理 JavaScript 小数精度的常用技巧:

    1. 四舍五入方法

    // toFixed方法(返回字符串)
    let num = 0.1 + 0.2; // 0.30000000000000004
    let fixed = num.toFixed(2); // "0.30"
    
    // 使用Math.round四舍五入
    function round(value, decimals) {
      return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
    }
    round(1.005, 2); // 1.01

    2. 精度运算库

    使用专门的数学库处理精确计算:

    • decimal.js —— 功能相对简单,适合基本的高精度计算需求
    • big.js —— 支持三角函数、指数、对数等高级数学运算
    // 使用decimal.js示例
    import Decimal from 'decimal.js';
    let sum = new Decimal(0.1).add(new Decimal(0.2)); // 0.3

    3. 整数运算技巧

    将小数转换为整数进行计算,再转换回去:

    function add(num1, num2) {
      const multiplier = Math.pow(10, Math.max(getDecimalLength(num1), getDecimalLength(num2)));
      return (num1 * multiplier + num2 * multiplier) / multiplier;
    }
    
    function getDecimalLength(num) {
      const decimalStr = num.toString().split('.')[1];
      return decimalStr ? decimalStr.length : 0;
    }

    4. 比较数字时的容差方法

    function numbersEqual(a, b, tolerance = 1e-10) {
      return Math.abs(a - b) < tolerance;
    }

    5. 格式化显示

    // 使用Intl.NumberFormat
    const formatter = new Intl.NumberFormat('en-US', {
      minimumFractionDigits: 2,
      maximumFractionDigits: 2
    });
    formatter.format(0.1 + 0.2); // "0.30"

    6. 避免常见陷阱

    • 不要直接比较浮点数
    • 对于货币计算,通常使用整数表示分(如用100表示1.00元)
    • 大整数计算考虑使用BigInt类型

    对于简单的显示问题,toFixed可能足够;对于复杂的财务计算,建议使用专门的库。

  • 【NPM】package.json 参数详解

    package.json 必须是纯 JSON 格式,而不仅仅是一个 JavaScript 对象字面量。

    name 和 version

    Name 和 version 字段是 package.json 文件中最重要的字段。这是必须的字段,如果你的 npm 包没有指定这两个字段,将无法被安装。name 和 version 字段被假定组合成一个唯一的标识符。包内容的更改和包版本的更改是同步的。

    description

    npm 包的描述,description 是一个字符串。它可以帮助人们在使用 npm search 时找到这个包。

    keywords

    npm 包的关键字,keywords 是一个字符串的数组。它可以帮助人们在使用 npm search 时找到这个包。

    homepage

    项目主页的 url

    bugs

    改项目的 issue 跟踪页面或这报告 issue 的 email 地址。这对使用这个包遇到问题的用户会有帮助。

    license

    证书许可。SPDX表达式:

    { "license": "ISC" }
    { "license": "(MIT OR Apache-2.0)" }

    通常我们不希望授权别人以任何形式使用的私有包或未发布包,可以这样写:

    { "license": "UNLICENSED”}

    或者设置

    { "private": true }

    author, contributors

    关于人的字段

    files

    files 字段是一个被项目包含的文件名数组,如果你在里面放一个文件夹名,那么这个文件夹中的所有文件都会被包含进项目中(除非是那些在其他规则中被忽略的文件)。

    main

    Main 字段指定了模块的入口程序文件。就是说,如果你的模块名叫 “foo”,用户安装了它,并且调用了 require(“foo”),则这个main字段指定的模块的导出对象会被返回。 例如 node_modules 中引入的模块指定主入口文件。

    bin

    许多包有一个或多个可执行文件希望被安装到系统路径。提供一个bin字段,它是一个命令名和本地文件名的映射。在安装时,如果是全局安装,npm将会使用符号链接把这些文件链接到prefix/bin,如果是本地安装,会链接到 ./node_modules/.bin/。

    比如,要使用myapp作为命令时可以这么做:

    { "bin" : { "myapp" : "./cli.js" } }

    当安装完毕myapp,npm会从cli.js文件创建一个到/usr/local/bin/myapp的符号链接(这使你可以直接在命令行执行myapp)。

    man

    指定一个单一的文件名或一个文件名数组来让man程序使用。如果只给man字段提供一个文件,则安装完毕后,它就是 man 的结果,这和此文件名无关。

    directories

    CommonJS Packages 规范说明了几种你可以用 directories 对象来标示你的包结构的方法(lib、bin、man、doc、example)。

    repository

    指明你的代码被托管在何处,这对那些想要参与到这个项目中的人来说很有帮助。如果 git 仓库在 github 上,用 npm docs 命令将会找到你。

    scripts

    scripts 字段是一个由脚本命令组成的字典,这些命令运行在包的各个生命周期中。这里的键是生命周期事件名,值是要运行的命令。

    config

    config 字段是一个对象,可以用来配置包脚本中的跨版本参数。

    dependencies

    dependencies 字段是一个对象,它指定了依赖的包名和其版本范围的映射。版本范围是个有一个或多个空白分隔描述符的字符串。dependencies 字段还可以用 tarball 或者 git URL。

    (请不要将测试或过渡性的依赖放到 dependencies 中)

    devDependencies

    如果有人计划在他们的项目中下载和使用你的模块,但他们可能并不想或并不需要你开发所使用的外部测试和文档框架。 在这种情况下,最好将这些附加的项放在 devDependencies 中。即开发模式下的依赖。

    peerDependencies

    在某些情况下,当一个主机无法 require 依赖包时,你会想要告诉它还有哪些工具或库与这个依赖包兼容。这通常被成为一个插件。尤其是在 host 文档中声明的模块会暴露一个特定的接口。

    bundledDependencies

    在发布包时,包名的数组会被打包进去。

    optionalDependencies

    如果一个依赖项可用,但希望在这个依赖项无法被找到或者安装时失败npm还能继续处理(不中断),那么你可以把它放在 optionalDependencies中。和 dependencies 一样,optionalDependencies 是一个包名和版本号或 url 的映射。区别在于 optionalDependencies 中的依赖构建失败时不会导致 npm 整体安装失败。

    engines

    你可以指定node的工作版本:

    { "engines": { "node": ">=0.10.3 <0.12" } }

    os

    可以指定模块运行的操作系统:”os” : [ “darwin”, “linux” ] 也可以使用操作系统黑名单来替代白名单,只要在前面加个’!’:

    { "os": [ "!win32" ] }

    cpu

    指明只能运行在特定的 cpu 架构上

    preferGlobal

    如果你的包是一个需要进行全局安装的命令行应用,需要设置 preferGlobal为true,如果这个包被本地安装会报出一个警告。 这个选项并不会阻止用户本地安装这个包,但这么做确实能在包未按照预期被安装造成诸多麻烦时提供一些提示。

    private

    如果你在包的 package.json 中设置” private”: true “,则 npm 会拒绝发布它。这是防止私有包被以外发布的一种方法。

    publishConfig

    这是一个在 publish-time 时会用到的配置集合。当你想设置 tag、registry 或] access 时特别有用,所以你可以确保一个给定的包无法在没有被打上”latest”标记时就被发布到全局公共的registry。 任何配置都可以被覆盖,当然可能只有”tag”, “registry” 和 “access” 和发布意图有关。