【D3.js in Action 3 精译_037】4.1 DIY 实战:D3 源码分析之——d3.timeFormat() 函数

当前内容所在位置(可进入专栏查看其他译好的章节内容)

  • 第一部分 D3.js 基础知识
    • 第一章 D3.js 简介(已完结)
      • 1.1 何为 D3.js?
      • 1.2 D3 生态系统——入门须知
      • 1.3 数据可视化最佳实践(上)
      • 1.3 数据可视化最佳实践(下)
      • 1.4 本章小结
    • 第二章 DOM 的操作方法(已完结)
      • 2.1 第一个 D3 可视化图表
      • 2.2 环境准备
      • 2.3 用 D3 选中页面元素
      • 2.4 向选择集添加元素
      • 2.5 用 D3 设置与修改元素属性
      • 2.6 用 D3 设置与修改元素样式
      • 2.7 本章小结
    • 第三章 数据的处理(已完结)
      • 3.1 理解数据
      • 3.2 准备数据
      • 3.3 将数据绑定到 DOM 元素
        • 3.3.1 利用数据给 DOM 属性动态赋值
      • 3.4 让数据适应屏幕
        • 3.4.1 比例尺简介(上篇)
        • 3.4.2 线性比例尺(中篇)
          • 3.4.2.1 基于 Mocha 测试 D3 线性比例尺(DIY 实战)
        • 3.4.3 分段比例尺(下篇)
          • 3.4.3.1 使用 Observable 在线绘制 D3 条形图(DIY 实战)
      • 3.5 加注图表标签(上篇)
        • 3.5.1 人物专访:Krisztina Szűcs(下篇)
      • 3.6 本章小结
    • 第四章 直线、曲线与弧线的绘制 ✔️
      • 4.1 坐标轴的创建(上篇)
        • 4.1.1 D3 中的边距约定(中篇)
        • 4.1.2 坐标轴的生成(中篇)
          • 4.1.2.1 比例尺的声明(中篇)
          • 4.1.2.2 坐标轴的添加(下篇)
          • 4.1.2.3 轴标签的添加(下篇)
          • 4.1.2.4 DIY 实战:在 Observable 平台实现折线图坐标轴的绘制
          • 4.1.2.5 DIY 实战:D3 源码分析之 d3.timeFormat() 函数 ✔️
      • 4.2 D3 折线图的绘制(精译中 ⏳)

文章目录

  • DIY 实战:D3 源码分析之:d3.timeFormat() 函数
    • 1 起因
    • 2 官方文档探秘
    • 3 源码分析
      • 3.1 验证一:local() 函数和 new Date() 是否一样
      • 3.2 验证二:timeFormat() 函数是否为 d3.timeFormat() 函数
      • 3.3 newFormat() 函数详解
    • 4 小结

《D3.js in Action》全新第三版封面

《D3.js in Action》全新第三版封面

DIY 实战:D3 源码分析之:d3.timeFormat() 函数


1 起因

前几天完成了 4.1 节剩余内容的翻译,主要介绍了 D3 折线图坐标轴的绘制方法(详见本专栏 第 035 篇译文)。讲解过程中,作者通过 d3.timeFormat('%b') 函数拿到了月份的英文简写字符串(即 "Jan""Feb" 等),但对于该函数的用法及参数的含义却一笔带过,让大家感兴趣的话自行参考 D3 官方文档(更奇怪的是,当时也没有提供具体的文档链接)。这一做法似乎和本书一贯的“手把手”教学风格相悖。怀着这份好奇,我自行补上了这个链接(https://d3js.org/d3-time-format),想看看作者不展开讲解的原因;结果在 D3 官网越看越上头,就有了分享出来的冲动。

2 官方文档探秘

原来,这个 d3-time-format 模块是仿照 C 语言的标准库函数 strptime 和 strftime 实现的。要在 D3 语境下格式化某个日期,需要用指定的标识符(specifier,格式为 %格式指令,如 %b%d 等等)声明一个格式化工具函数 formatter,然后再将日期传入,就能得到最终的结果。换句话说,d3.timeFormat() 其实是一个高阶函数,示例代码中传入 tickFormat() 的其实就是一个 formatter 函数:

const bottomAxis = d3.axisBottom(xScale)
  .tickFormat(d3.timeFormat("%b"));

我就纳闷了:实现这么简单的一个格式化逻辑,竟然也需要用高阶函数这把牛刀?不就是两行代码的事么:

const months = 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(',');
const formatter = date => months[date.getMonth()];

原谅我的强迫症——

图 1 根据需求自行实现的月份格式化逻辑

【图 1 根据需求自行实现的月份格式化逻辑】

难道说 D3 另有深意?带着这个疑问,我又一次愉快地打开了潘多拉女神的魔盒:

图 2 将 d3.timeFormat("%b") 打印到控制台得到的结果(貌似玩笑开大了点)

【图 2 将 d3.timeFormat(“%b”) 打印到控制台得到的结果(貌似玩笑开大了点)】

点进去一看,发现还不如不点:

图 3 点开 d3.timeFormat("%b") 看到的格式化处理后的函数源码

【图 3 点开 d3.timeFormat(“%b”) 看到的格式化处理后的函数源码】

这是要逼我看源码的节奏啊……别慌,先把那页官方文档看完。所谓的标识符 specifier,可用的格式指令(directives)如下:

  • %a:缩写的星期名称。*
  • %A:完整的工作日名称。*
  • %b:缩写的月份名称。*
  • %B:完整月份名称。*
  • %c:本地的日期和时间,例如 %x, %X .*
  • %d:用十进制数字表示的零填充的月份中的天数 [01,31]。
  • %e:用空格填充的月份日期,作为十进制数字 [1,31];等同于 %_d
  • %f:微秒作为十进制数字 [000000, 999999]。
  • %g:ISO 8601 基于周的年份(不含世纪),以十进制数字表示 [00,99]。
  • %G:ISO 8601 基于周的年份,世纪作为十进制数字。
  • %H:小时(24 小时制)作为十进制数字 [00,23]。
  • %I:小时(12 小时制)作为十进制数字 [01,12]。
  • %j:一年中的天数,作为十进制数字 [001,366]。
  • %m:作为十进制数字的月份 [01,12]。
  • %M:以十进制数字表示的分钟 [00,59]。
  • %L:毫秒,作为一个十进制数字 [000, 999]。
  • %p:早上或下午。*
  • %q:年的四分之一,作为小数表示 [1,4].
  • %Q:自 UNIX 纪元以来的毫秒数。
  • %s:自 UNIX 纪元以来的秒数。
  • %S:作为小数的秒数 [00,61].
  • %u:以星期一为基础的(ISO 8601)工作日,作为十进制数字 [1,7]。
  • %U:以星期日为基础的年份周数,作为十进制数字 [00,53]。
  • %V:ISO 8601 年中的周数,作为十进制数字 [01, 53]。
  • %w:以星期日为基础的工作日,作为十进制数字 [0,6]。
  • %W:以星期一为基础的年份周数,作为十进制数字 [00,53]。
  • %x:本地的日期,例如 %-m/%-d/%Y .*
  • %X:本地时间,例如 %-I:%M:%S %p .*
  • %y:不带世纪的年份,作为十进制数字 [00,99]。
  • %Y:以十进制数字表示的世纪年份,例如 1999
  • %Z:时区偏移,例如 -0700-07:00-07Z
  • %%:一个字面上的百分号 ( % )。

其中,末尾带星号标记(*)的指令可能会受到当地区域设置的影响。

另外,% 符号用来标识一个指令,后面还可以紧跟一个填充修饰符:

  • 0:用 0 来填充;
  • _:用空格来填充;
  • -:禁用填充。

介绍完 specifier,文档还提到了 D3 的默认区域设置(美国-英文):

const enUs = d3.timeFormatDefaultLocale({
  dateTime: "%x, %X",
  date: "%-m/%-d/%Y",
  time: "%-I:%M:%S %p",
  periods: ["AM", "PM"],
  days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
  shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
  months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
  shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
});

言下之意……D3 还支持其他地区和语言的设置吗?于是果断进入 d3-time-format 模块的 GitHub 仓库。果然,在 d3-time-format/locale/ 文件夹看到了 8 年前最后提交的中文配置(zh-CN.json):

{
  "dateTime": "%x %A %X",
  "date": "%Y年%-m月%-d日",
  "time": "%H:%M:%S",
  "periods": ["上午", "下午"],
  "days": ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
  "shortDays": ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
  "months": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
  "shortMonths": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
}

要配置成中文对应的地区,D3 只提供了一个 d3.timeFormatDefaultLocale(definition) 接口,参数 definition 就是上面的 JSON 配置。只可惜,D3 没能提供查询地区配置文件的接口,如果要让 d3.timeFormat('%b') 显示 十月,只能像这样手动操作:

图 4 手动切换 D3 默认地区的相关接口测试情况(切换为中文)

【图 4 手动切换 D3 默认地区的相关接口测试情况(切换为中文)】

有了上述的准备工作,就可以正式开始 d3.timeFormat() 的源码解读了。

3 源码分析

可能很多朋友看源码都是直接从 src 目录开始的,但我更习惯从项目的测试用例入手。找到 test 文件夹下的 format-test.js,很快就定位到了 %b 标识符对应的单元测试模块:

it("timeFormat(\"%b\")(date) formats abbreviated months", () => {
  const f = timeFormat("%b");
  assert.strictEqual(f(local(1990,  0, 1)), "Jan");
  assert.strictEqual(f(local(1990,  1, 1)), "Feb");
  assert.strictEqual(f(local(1990,  2, 1)), "Mar");
  assert.strictEqual(f(local(1990,  3, 1)), "Apr");
  assert.strictEqual(f(local(1990,  4, 1)), "May");
  assert.strictEqual(f(local(1990,  5, 1)), "Jun");
  assert.strictEqual(f(local(1990,  6, 1)), "Jul");
  assert.strictEqual(f(local(1990,  7, 1)), "Aug");
  assert.strictEqual(f(local(1990,  8, 1)), "Sep");
  assert.strictEqual(f(local(1990,  9, 1)), "Oct");
  assert.strictEqual(f(local(1990, 10, 1)), "Nov");
  assert.strictEqual(f(local(1990, 11, 1)), "Dec");
});

可能为了大幅降低单元测试的编写难度,这里只用了 Mocha.jsBDD 风格,断言方法也是直接来自 node 的内置断言模块。这里有两点需要明确:

  1. 第 3 ~ 14 行中的 local(...) 函数为什么不使用 new Date(...)
  2. 第 2 行的 timeFormat 是否是我要考察的目标函数?

由于网页不支持方法的快速定位,只能转到本地操作了:

git clone https://github.com/d3/d3-time-format.git d3-time-format
cd d3-time-format
yarn
yarn test

不出意外的话,马上就出意外了:

图 5 本地运行单元测试报错(不支持 Windows 环境)

【图 5 本地运行单元测试报错(不支持 Windows 环境)】

好在这个坑已经踩过了,加个 cross-env 依赖就行了:

# 修复 Windows 不兼容 TZ 设置问题
$ yarn add -D cross-env
# 修改 test 命令脚本
$ (gc package.json) -replace '"test": "(.*?)"', '"test": "cross-env $1"' | Set-Content package.json
# 验证 test 命令脚本是否修改成功
$ cat package.json | sls TZ
    "test": "cross-env TZ=America/Los_Angeles mocha 'test/**/*-test.js' && eslint src test",
# 再次运行测试
$ yarn test

运行结果:

图 6 修复单元测试不兼容 Windows 系统的问题后,重新运行测试,全部通过。

【图 6 修复单元测试不兼容 Windows 系统的问题后,重新运行测试,全部通过。】

然后就可以用 VSCode 打开该模块了:

$ code .

3.1 验证一:local() 函数和 new Date() 是否一样

先从简单的问题入手:单元测试为什么要用自定义的 local() 函数,而不是使用原生的 new Date()?直接跳转到 local() 的定义:

export function local(year, month, day, hours, minutes, seconds, milliseconds) {
  if (year == null) year = 0;
  if (month == null) month = 0;
  if (day == null) day = 1;
  if (hours == null) hours = 0;
  if (minutes == null) minutes = 0;
  if (seconds == null) seconds = 0;
  if (milliseconds == null) milliseconds = 0;
  if (0 <= year && year < 100) {
    const date = new Date(-1, month, day, hours, minutes, seconds, milliseconds);
    date.setFullYear(year);
    return date;
  }
  return new Date(year, month, day, hours, minutes, seconds, milliseconds);
}

原来如此!第 9 行对年份介于 0 ~ 99 的日期做了单独处理,不让原生 JavaScriptDate 构造函数中的默认转换生效(new Date(99, 0, 1) 的结果为 1999 年 1 月 1 日)。第 10 行的 -1 也很巧妙,刚好绕开了 Date 的默认转换,写起来也方便。

结论:local() 函数得到的就是一个 Date 实例,只不过考虑得更全面。

3.2 验证二:timeFormat() 函数是否为 d3.timeFormat() 函数

再来看此次源码解读的核心 —— timeFormat() 函数。虽然种种迹象表明,答案必定是肯定的,但还是有必要跟着源码过一遍。这样就跟踪到了 src/index.js,进而定位到 defaultLocale.js 模块:

// d3-time-format/test/format-test.js
import {timeFormat} from "../src/index.js";
// index.js
export {default as timeFormatDefaultLocale, timeFormat, timeParse, utcFormat, utcParse} from "./defaultLocale.js";
// defaultLocale.js
export var timeFormat;
// ...
export default function defaultLocale(definition) {
  locale = formatLocale(definition);
  timeFormat = locale.format;
  // ...
  return locale;
}

从第 2 行可以断定,单元测试中的 timeFormat() 函数就是 d3.timeFormat() 函数。继续追踪可以看到,它的赋值是在 defaultLocale.js 中完成的(第 10 行)。那么赋给它的值 locale.format 究竟是什么呢?这得看上一行中的 formatLocale(definition) 究竟在干什么。还是分两步走:

  1. 搞懂 definition 是什么;
  2. 搞懂 formatLocale 函数的定义。

第一个问题很简单,definition 就是前面提过的 D3 默认地区设置,来看 defaultLocale.js 的完整截图就明白了:

图 7 搞懂 definition 是什么:D3 默认的地区语言设置

【图 7 搞懂 definition 是什么:D3 默认的地区语言设置】

接着跳转到 formatLocale() 函数的定义,就来到了 src/locale.js 模块:

图 8 找到 src/locale.js 模块下的 formatLocale() 函数定义

【图 8 找到 src/locale.js 模块下的 formatLocale() 函数定义】

这里我们只关心函数返回值中的 format 属性,因此直接定位到该函数的 return 语句:

图 9 定位到 formatLocale 函数的 return 语句,并锁定返回值中的 format 属性

【图 9 定位到 formatLocale 函数的 return 语句,并锁定返回值中的 format 属性】

从图 9 不难看出,最终赋值给 d3.timeFormat 函数的,正是第 366 行中的 newFormat(specifier += "", formats),也就是文章最开始的图 2 所看到的那一堆压缩版的函数定义。注意第 366 行还传入了第二个参数 formats,这是一个典型的闭包结构,formats 是一个内置的 JS 对象。对于我们要考察的 %b 而言,只需要用到其中的两个键值对,可简化为:

var specifier = "%b";
var formats = {
  "b": formatShortMonth,
  "%": formatLiteralPercent
};
var f = newFormat("%b", {
  "b": formatShortMonth,
  "%": formatLiteralPercent
})

这样一来,问题的关键就变为对函数 newFormat() 的解读了。

3.3 newFormat() 函数详解

定位到 newFormat 函数,将看到这一段终极源码:

function newFormat(specifier, formats) {
  return function(date) {
    var string = [],
        i = -1,
        j = 0,
        n = specifier.length,
        c,
        pad,
        format;

    if (!(date instanceof Date)) date = new Date(+date);

    while (++i < n) {
      if (specifier.charCodeAt(i) === 37) {
        string.push(specifier.slice(j, i));
        if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i);
        else pad = c === "e" ? " " : "0";
        if (format = formats[c]) c = format(date, pad);
        string.push(c);
        j = i + 1;
      }
    }

    string.push(specifier.slice(j, i));
    return string.join("");
  };
}

虽然也比较复杂,但对比图 3 那样的简化版已经很不错了。注意第 16 行新引入的闭包结构 pads,这是格式化结果中负责拼接填充符号的键值对,比较简单:

var pads = {
    "-": "", 
    "_": " ", 
    "0": "0"
};

再次明确我们的分析目标:考察以下代码的底层逻辑:

const formatter = d3.timeFormat('%b'); 
console.log(formatter(new Date())); // 'Oct'

因此,将 '%b'new Date() 即刚才分析的简化 formats 代入,就可以得到简化版的 formatter 定义:

var formats = {
  "b": formatShortMonth,
  "%": () => '%'
};
var pads = {"-": "", "_": " ", "0": "0"};
const formatter = function(date) {
  var string = [],
      i = -1,
      j = 0,
      n = 2,  // '%b'.length => 2
      c,
      pad,
      format;

  while (++i < 2) {
    if ('%b'.charCodeAt(i) === 37) {
      string.push('%b'.slice(j, i));
      if ((pad = pads[c = '%b'.charAt(++i)]) != null) c = '%b'.charAt(++i);
      else pad = c === "e" ? " " : "0";
      if (format = formats[c]) c = format(date, pad);
      string.push(c);
      j = i + 1;
    }
  }

  string.push('%b'.slice(j, i));
  return string.join("");
}

注意,第 11 行就是判定字符串的首字符是否为 %,这显然是满足的,因此重点关注第 16 ~ 22 行。

第一轮:i = 0, j = 0——

  • 执行第 17 行,结果为 string = ['']
  • 执行第 18 行,c = '%b'.charAt(1) = 'b'pad = pads[c] = undefined,显然 if 条件 undefined != null 为假,pad 转到第 19 行被重新赋值:pad = c === "e" ? " " : "0",因此 pad = " "
  • 执行第 20 行,此时 c = 'b',故 format = formats['b'] = formatShortMonth,满足 if 条件,c 被重新赋值为 formatShortMonth(date, " ")
  • 执行第 21 行,得到新的 string 数组:['', c]
  • 执行第 22 行,此时 i = 1, j = 1

第二轮:i = 2, j = 1——

  • 由于 i 值已不满足 while 循环条件,因此跳出循环,直接前往第 26 行;此时 i = 2, j = 1

  • 执行第 26 行,string 数组更新为 ['', c, '']

  • 执行第 27 行,可得到 formatter 的进一步简化版定义:

    const formatter = date => "" + formatShortMonth(date, " ") + "";
    

这里的 formatShortMonth() 又是什么呢,跳转过去看到的源码是这样的:

function formatShortMonth(d) {
  return locale_shortMonths[d.getMonth()];
}

可见,formatShortMonth(date, " ") 的第二个参数根本没用到!因此 formatter 还可以精简为:

const formatter = date => "" + locale_shortMonths[date.getMonth()] + "";

这样,就和我自定义的逻辑很像了,我之前是这样写的:

const months = 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(',');
const formatter = date => months[date.getMonth()];

现在问题就变成了:locale_shortMonthsmonths 是不是同一个数组?别急,来看 locale_shortMonths 的定义:

图 10 变量 locale_shortMonths 的声明情况

【图 10 变量 locale_shortMonths 的声明情况】

显然,locale_shortMonths 是从参数中直接赋的值。那这个参数 locale 是什么值呢?这就得再回到此前调用 formatLocale() 函数的地方了,也就是前面提过的图 7:

图 7 搞懂 definition 是什么:D3 默认的地区语言设置

注意第 17 行,shortMonths 就是我要找的那个数组。终于衔接上了!!!formatter 的终极定义如下:

var locale_shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const formatter = date => "" + locale_shortMonths[date.getMonth()] + "";

大功告成。

4 小结

通过对 d3.timeFormat() 源码的全面解读,可以归纳出以下几点:

  • 从单元测试用例入手,既可以快速锁定目标函数,又可以了解目标函数的具体用法,一举多得;
  • 遇到需要分步走的情况时,先做好记录,从简单的分支入手,再逐步逼近复杂分支;
  • 作为工具库函数,需要考虑各种格式化指令的解析和其他辅助配置,因此不得不经过一系列筛选、赋值、高阶函数处理,以满足工具函数的一致性;对于一些简单的格式化逻辑,手写应该比调用库函数更方便。
  • 源码最复杂的部分,其实就是那个 while 循环,用于解析不同的 specifier 标识符,并在内置的 formats 对象里找到对应的格式化方法,然后返回最终结果。
  • 遇到复杂的问题,要时刻明确自己的目标,并围绕目标将问题一步步简化,做到心中有数,稳扎稳打。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/900217.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Shopee虾皮登录不了的常见原因及解决方式

在近年来&#xff0c;随着电子商务的迅猛发展&#xff0c;Shopee虾皮作为东南亚地区的领先购物平台&#xff0c;吸引了大量用户。一些用户在使用过程中常常遇到登录问题&#xff0c;而登录不了的原因可能多种多样&#xff0c;包括网络连接不稳定、账号信息输入错误&#xff0c;…

【搭建个人图库】Docker部署Piwigo图库与公网环境远程传图全攻略

文章目录 前言1. 安装Docker2. 创建并启动Piwigo容器3. 本地访问测试与简单使用4. 公网远程访问本地Piwigo4.1 内网穿透工具安装4.2 创建远程连接公网地址4.3 使用固定公网地址远程访问 前言 本文和大家分享一下如何在Linux系统使用Docker部署一款开源的网络图片库管理系统Piw…

54页可编辑PPT | 大型集团企业数据治理解决方案

这份PPT是关于大型集团企业数据治理的全面解决方案&#xff0c;它详细介绍了数据治理的背景、需求、管理范围、框架、解决思路&#xff0c;以及数据治理在实际操作中的关键步骤。内容涵盖了数据架构、数据质量、数据应用等方面的问题&#xff0c;并提出了数据资产透视、智能搜索…

K8S如何基于Istio重新实现微服务

K8S如何基于Istio重新实现微服务 认识 Istio前言Istio 的理念Istio 的架构数据平面控制平面服务与 Istio 的关系 Istio 实践环境准备安装 Helm安装Istio 使用 Istio 代理运行应用情感分析应用的架构使用 Istio 代理运行应用Sidecar 注入Ingress 网关网关资源VirtualService 资源…

离线部署jdk8,jdk17, jdk21

目录 一、下载地址二、环境三、离线部署思路步骤 四、部署脚本 在开发过程中&#xff0c;有时我们需要在同一台机器上安装多个不同版本的 JDK&#xff0c;以满足不同项目的需求。本文将详细介绍如何在 Ubuntu 24.04 中离线安装 JDK8、JDK17 和 JDK21。 一、下载地址 建议下载O…

融合DevOps打造企业高效流程体系的实践与探索

一、引言 转眼间&#xff0c;我已毕业十多年&#xff0c;在IT领域深耕不辍&#xff0c;曾涉足全栈研发、大数据研发、架构设计与项目管理等多个岗位&#xff0c;更主导过公司从市场到交付再到运营的全链条流程建设。在这漫长的职业生涯中&#xff0c;一个问题始终萦绕在我心头&…

web前端练习

01 代码&#xff1a;HTML部分 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>search</title>…

华为配置BFD状态与接口状态联动实验

组网图形 图1 配置BFD状态与接口状态联动组网图 BFD简介配置注意事项组网需求配置思路操作步骤配置文件 BFD简介 为了减小设备故障对业务的影响&#xff0c;提高网络的可靠性&#xff0c;网络设备需要能够尽快检测到与相邻设备间的通信故障&#xff0c;以便及时采取措施&…

百科知识|选购指南

百科知识||选购指南 百科知识选购指南茶叶分类茶叶的味道来源茶叶制作步骤名茶其他一些茶叶的知识 百科知识 选购指南 茶叶 分类 茶叶种类: 六大茶类完美分析介绍&#xff01;茶友推荐收藏 (aboxtik.com) 1.绿茶&#xff08;发酵率0%&#xff09; 2.白茶&#xff08;发酵率…

对杨笠没有意见,但对京东有 | 生活周刊 #5

持续分享高效率工具&#xff0c;以及日常生活、个人成长、运动健康和法律案例等的内容&#xff0c;欢迎关注 &#x1f4f6; 关于杨笠 对于杨笠&#xff0c;我对她没什么偏见&#xff0c;也好奇为什么代言京东会触发这么大的反弹&#xff0c;直到我看到杨笠的冒犯的艺术&#x…

Axure垂直菜单展开与折叠

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;Axure垂直菜单展开与折叠 主要内容&#xff1a;垂直菜单单击实现展开/折叠&#xff0c;点击各菜单项显示选中效果 应用场景&#xff1a;后台菜单设…

【python实战】利用代理ip爬取Alibaba海外版数据

引言 在跨境电商的业务场景中&#xff0c;数据采集是分析市场、了解竞争对手以及优化经营策略的重要环节。然而&#xff0c;随着越来越多企业依赖数据驱动决策&#xff0c;许多跨境电商平台为了保护自身数据&#xff0c;采取了更严格的防护措施。这些平台通过屏蔽大陆IP地址或部…

专业135+总分400+西安交通大学815869(原909)信号与系统考研经验电子信息与通信工程,真题,大纲,参考书

经过将近一年的考研复习&#xff0c;终于梦圆西安交大&#xff0c;今年专业课815(和专硕869&#xff08;原909&#xff09;差不多)信号与系统135&#xff0c;总分400&#xff0c;回想这一年的复习还有很多经验和大家分享&#xff0c;希望可以对大家复习有所帮助&#xff0c;少走…

Docker 与 Yocto

Yocto项目为什么需要Docker Yocto 项目并不直接依赖 Docker&#xff0c;但在某些情况下使用 Docker 可以为 Yocto 项目提供以下具体且实际的好处&#xff1a; 1. 环境一致性&#xff1a; Yocto 构建需要一个稳定且一致的开发环境。不同的 Linux 发行版可能会有不同的库版本、…

如何实现智能图像擦除

我们在拍照时&#xff0c;往往会拍到一些路人或者杂物&#xff0c;但是这个照片又不想删掉&#xff0c;那么有没有啥方法可以把照片中的特定部分给删除&#xff0c;然后还原这部分的原始内容呢&#xff1f;有些人可能会用ps&#xff0c;但是ps操作比较复杂并且效果还不是很好&a…

Photoshop中的混合模式公式详解

图层混合简介 图层混合&#xff08;blend&#xff09;顾名思义&#xff0c;就是把两个图层混合成一个。 最基本的混合是alpha融合&#xff08;alpha compositing&#xff09;&#xff0c;这是一个遵循光的反射与透射等&#xff08;简化版&#xff09;物理学原理的混合方式。 各…

信号与系统学习:傅里叶级数

一、基本概念 1. 什么是傅里叶级数&#xff1f; 傅里叶级数是一种数学工具&#xff0c;可以将一个周期函数分解为一系列正弦和余弦函数&#xff08;即三角函数&#xff09;的和。这些正弦和余弦函数的频率是原函数的整数倍。 2. 为什么要使用傅里叶级数&#xff1f; 信号分…

J2学习打卡

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 ResNet50V2 import torch import torch.nn as nn import torch.nn.functional as Fclass Bottleneck(nn.Module):expansion 4def __init__(self, inplanes,…

软考(中级-软件设计师)计算机系统篇(1024)

#1024程序员节|正文# 六、树和二叉树 6.1 树的基本概念 描述结果结点的度子结点的个数树的度最大结点的度叶子结点没有子结点的结点内部结点除根结点和叶子结点外的结点父节点有子结点的结点子节点有父结点的结点兄弟节点有同一个父结点的结点层次4层 6.2 二叉树的基本概念…

AI时代LabVIEW程序员的未来出路

随着GPT等AI技术的迅速发展&#xff0c;AI已经能够自动完成大量的代码生成工作&#xff0c;这无疑给LabVIEW程序员带来了新的挑战和机遇。尽管AI能够替代部分编程工作&#xff0c;LabVIEW程序员依然可以通过以下几方面找到出路&#xff1a; 复杂系统集成&#xff1a; AI可以帮助…