p5.js 文档风格指南:为创意编程库编写高质量参考文档的完整规范
【免费下载链接】p5.jsp5.js is a client-side JS platform that empowers artists, designers, students, and anyone to learn to code and express themselves creatively on the web. It is based on the core principles of Processing. Looking for p5.js 2.0? http://beta.p5js.org项目地址: https://gitcode.com/GitHub_Trending/p5/p5.js
导读
本文基于 p5.js 仓库中的官方文档风格指南,系统讲解如何为 p5.js 编写有效、包容、可访问且符合项目规范的文档与代码示例。p5.js 是一个面向艺术家、设计师、学生与所有人的客户端 JavaScript 创意编程平台,其 API 参考文档直接由源码中的注释生成,因此注释质量直接决定文档体验。阅读本文后,你将掌握 p5.js 文档写作的措辞规范、无障碍与无偏见要求、YUIDoc 注释语法,以及一套从变量声明到类定义的完整代码示例风格约定,可直接应用于为 p5.js 贡献参考文档的实际工作中。
YUIDoc:p5.js 文档的生成机制
p5.js 使用 YUIDoc 从源码内联注释生成 API 参考文档。要生成本地文档,请切换到 p5.js 仓库根目录,先运行npm install安装依赖,再执行:
$ npm run grunt yui:dev输出将出现在 docs/reference 目录中。更多信息请参考内联文档指南(中文版见 contributor_docs/zh-Hans/contributing_to_the_p5js_reference.md)。
从源码结构看,p5.js 源码中的每个公开函数都带有 YUIDoc 风格的注释块,例如 src/math/trigonometry.js 中sin()的注释使用@method sin、@param {Number} angle、@return {Number}等标签;src/color/creating_reading.js 中color()的注释则展示了同一函数多个签名(重载)的写法。这些注释块始终以/**开头、以*/结束,每个 p5 函数/变量的参考页面内容均由源码中的这些注释构建而成。
写作规范
使用美式英语
请使用美式英语拼写(color、center、modularize 等),而非英式拼写(colour、centre、modularise 等)。
使用牛津逗号
请使用牛津逗号(序列逗号):写作 "red, white, and blue",而不是 "red, white and blue"。
措辞原则
写简单、陈述性的句子。简洁是加分项:直奔主题。使用现在时态:"Returns an object that...",而不是 "Returned an object that..." 或 "Will return an object that..."。
注释以大写字母开头,并遵循常规标点规则:
// Draws a fractal from a Julia set. function drawFractal(c, radius, maxIter) { // ... }明确和隐式地传达当前的做事方式,使用指南中推荐的惯用语,必要时重新排序部分以强调首选方法。文档应该是最佳实践的典范,并且对初学者友好。文档必须简洁但全面:探索并记录边缘情况——每种参数组合会发生什么?初学者的代码中最可能出现哪些错误?
正确拼写专有名称:p5.js、CSS、HTML、JavaScript、WebGL。如有疑问,请参考官方文档等权威来源。
无偏见的文档
编写文档时不要对任何类型的人有偏见。在记录特别要求高/敏感的主题时,花时间自己学习。确保你的写作不会无意中伤害或冒犯他人。具体要求包括:
- 包容所有性别认同和表达、性取向、种族、民族、语言、神经类型、体型、残障、阶级、宗教、文化、亚文化、政治观点、年龄、技能水平、职业和背景,使示例像社区一样多样化;
- 避免政治化内容,如果政治内容是必要的,保持中立;
- 遵循可访问性指南;
- 避免会侮辱或伤害人们的内容;
- 不要对人们、国家和文化做任何概括(包括正面或中立的概括);
- 不要编写针对少数群体的偏见和歧视性内容;
- 避免与历史事件相关的术语。
优先使用避免 "you" 和 "your" 的措辞。例如,不要写 "If you need to declare a variable, it is recommended that you uselet.",而是采用 "Always useletto declare variables." 这种风格。
代词选择:推荐使用中性单数代词,避免性别二元假设:
| 推荐 | 不推荐 |
|---|---|
| they | he or she |
| them | him or her |
| their | his or her |
| theirs | his or hers |
| themselves | himself or herself |
可访问性和残障
- 注重读者而不是强调他们的不便;
- 不要将残障人士称为残疾人,使用批准术语指代特定残障的人;
- 在整个 p5.js 文档中保持统一的结构,在风格和视觉上强调重要点;
- 使用屏幕阅读器测试文档;
- 考虑所有类型设备和操作系统的多平台可访问性;
- 创建使用所有类型输入设备的示例,如基于语音和手势的设备、控制器、鼠标和键盘;
- 不要使用能力歧视语言;
- 对 HTML 语义采取实用方法,不要纯粹为了语义而添加语义;如果有明显匹配内容的 HTML 结构,请使用该元素(例如一组链接很可能应该使用列表元素);
- 使用简单的表格和表格格式,避免使用
rowspan、colspan等 span 标签,因为表格对屏幕阅读器来说很困难。
可访问性术语对照(以人为主的语言):
| 推荐 | 不推荐 |
|---|---|
| 残障人士 | 残疾人、残障、能力不同、有挑战、不正常 |
| 非残障人士 | 正常人、健康人、健全人 |
| 有[残障] | 受害者、遭受、受...影响、被...折磨 |
| 无法说话,使用合成语音 | 哑巴、失语 |
| 聋人,听力低下 | 听力障碍 |
| 盲人,视力低下 | 视力障碍,视觉挑战 |
| 认知或发育障碍 | 智力挑战,学习缓慢 |
| 行动不便的人,身体残障的人 | 瘸子,残障 |
代码示例
选择有意义的代码示例,涵盖基础知识以及容易出错的地方。只有在解释功能工作原理时才使用高级语法。当一个圆就能传达想法时,不要画五个圆来解释。代码示例本身应遵循以下全部指南——事实上,p5.js 仓库通过 lint 工具对文档示例代码执行与主代码库相同的编码约定检查,确保示例风格与库本身一致(参见 eslint.config.mjs)。
注释
- 使用
//进行单行注释。将单行注释放在注释主题上方的新行上。除非是块的第一行,否则在注释前放置一个空行:
// 不好。 let magicWord = 'Please'; // 记住这个。 // 好。 // 记住这个。 let magicWord = 'Please'; // 不好。 if (keyIsPressed === true) { thing1(); // 这是一个重要的注释。 thing2(); } // 好。 if (keyIsPressed === true) { thing1(); // 这是一个重要的注释。 thing2(); }- 所有注释都以空格开头,使其更易于阅读:
// 不好。 //记住这个。 let magicWord = 'Please'; // 好。 // 记住这个。 let magicWord = 'Please';- 使用
//进行多行注释,而非块注释:
// 不好。 /** * 我将使用 // 进行多行注释。 * 我将使用 // 进行多行注释。 * 我将使用 // 进行多行注释。 * 我将使用 // 进行多行注释。 * 我将使用 // 进行多行注释。 */ // 不好。 /* 我将使用 // 进行多行注释。 我将使用 // 进行多行注释。 我将使用 // 进行多行注释。 我将使用 // 进行多行注释。 我将使用 // 进行多行注释。 */ // 好。 // 我将使用 // 进行多行注释。 // 我将使用 // 进行多行注释。 // 我将使用 // 进行多行注释。 // 我将使用 // 进行多行注释。 // 我将使用 // 进行多行注释。注意:普通代码注释与 YUIDoc 参考注释(/** ... */)用途不同,后者专用于生成 API 参考页面,语法细节见为 p5.js 参考文献做贡献。
空白
- 缩进块 2 个空格:
// 不好。 function setup() { createCanvas(400, 400); } // 不好。 function setup() { createCanvas(400, 400); } // 好。 function setup() { createCanvas(400, 400); }- 在左大括号前放置 1 个空格。
- 在控制语句(如
if和for)中的左括号前放置 1 个空格;在参数列表和函数名之间不要放置空格。 - 在运算符之间放置空格:
let y = x + 5;。
分号
使用分号。原因是 JavaScript 的自动分号插入(ASI)可能导致细微的错误:
// 不好。 let x = 0 // 好。 let x = 0;命名约定
- 避免使用单字母名称,要有描述性:
// 不好。 function f(x, y) { // ... } // 好。 function vectorField(x, y) { // ... }- 使用驼峰命名法(camelCase)命名对象、函数和实例:
// 不好。 let OBJEcttsssss = {}; let this_is_my_object = {}; // 好。 let thisIsMyObject = {};- 使用帕斯卡命名法(PascalCase)命名类:
// 不好。 class player { constructor(name) { this.name = name; } } // 好。 class Player { constructor(name) { this.name = name; } }- 不要使用尾随或前导下划线。因为 JavaScript 没有私有属性或方法:
// 不好。 class Spy { constructor(secret) { this._secret = secret; } } // 好。 class Spy { constructor(secret) { this.secret = secret; } }变量
- 避免使用
var声明变量。使用var声明的变量具有令人困惑的作用域规则,会导致细微的错误:
// 不好,因为它看起来合理。 circle(x, y, 50); var x = 200; var y = 200; // 好,因为它会抛出 ReferenceError。 circle(x, y, 50); let x = 200; let y = 200;- 始终使用
let声明变量,避免使用const。变量在草图中经常被重新赋值,默认使用let更有帮助:
// 不好。 flower = '🌸'; var flower = '🌸'; const flower = '🌸'; // 好。 let flower = '🌸';- 每个变量或赋值使用一个
let声明,更易阅读和添加新变量:
// 不好。 let positions = getPositions(), startSearch = true, dragonball = 'z'; // 好。 let positions = getPositions(); let startSearch = true; let dragonball = 'z';- 在需要的地方分配变量,并将它们放在合理的位置。因为
let是块作用域而不是函数作用域:
// 不好 - 不必要的搜索。 function getCharacter(name = 'default') { let character = characters.find(c => c.name === name); if (name === 'default') { return false; } if (character) { return character; } return false; } // 好。 function getCharacter(name = 'default') { if (name === 'default') { return false; } let character = characters.find(c => c.name === name); if (character) { return character; } return false; }- 避免使用一元递增和递减(
++、--)。它们受自动分号插入影响,可能导致静默错误;使用num += 1也更具表现力:
// 不好。 let num = 1; num++; --num; // 好。 let num = 1; num += 1; num -= 1;字符串
- 使用单引号
''表示字符串:
// 不好。 let name = "Hilma af Klint"; // 不好 - 模板字面量应包含插值或换行。 let name = `Hilma af Klint`; // 好。 let name = 'Hilma af Klint';- 不要连接导致行超过 80 个字符的字符串,断开的字符串难以阅读且不易搜索:
// 不好。 let essay = 'You see us as you want to see us: \ in the simplest terms, in the most convenient definitions.'; // 不好。 let essay = 'You see us as you want to see us: ' + 'in the simplest terms, in the most convenient definitions.'; // 好。 let essay = 'You see us as you want to see us: in the simplest terms, in the most convenient definitions.';- 需要时使用模板字符串而不是连接,因为模板字符串语法简洁,且提供适当的换行和字符串插值:
let name = 'Dave'; // 不好。 text( name + ', this conversation can serve no purpose anymore. Goodbye.' + name, 0, 0 ); // 好。 text(`${name}, this conversation can serve no purpose anymore. Goodbye.`, 0, 0);- 不要在字符串中不必要地转义字符,反斜杠会损害可读性:
// 不好。 let bad = '\'this\' \i\s \"quoted\"'; // 好。 let good = 'Air quotes make you look "cool".';布尔运算符
- 使用
===和!==而不是==和!=。 - 不要使用布尔值的快捷方式,对初学者来说显式比较更容易理解:
// 不好。 if (mouseIsPressed) { // ... } // 好。 if (mouseIsPressed === true) { // ... } // 不好。 if (name) { // ... } // 好。 if (name !== '') { // ... } // 不好。 if (collection.length) { // ... } // 好。 if (collection.length > 0) { // ... }- 除非必要,否则不要使用
switch语句。 - 混合运算符时使用括号,唯一的例外是算术运算符
+、-和**,这样更易读并避免细微错误。
条件语句
- 对所有多行块使用大括号:
// 不好。 if (mouseIsPressed === true) circle(mouseX, mouseY, 50); // 最好。 if (mouseIsPressed === true) { circle(mouseX, mouseY, 50); }- 将
else放在前一个if块的右大括号的同一行。 - 在总是执行
return语句的if块后不要使用else块:
// 不好。 function mouseIsOnLeft() { if (mouseX < width * 0.5) { return true; } else { return false; } } // 好。 function mouseIsOnLeft() { if (mouseX < width * 0.5) { return true; } return false; }- 如果条件太长,将每个(分组的)条件放在新行上,逻辑运算符应该开始行,这样更易读。
- 不要使用选择运算符代替条件语句:
// 不好。 refrigeratorIsRunning && goCatchIt(); // 好。 if (refrigeratorIsRunning === true) { goCatchIt(); }迭代
- 除非必要,否则不要使用
while或do-while循环;使用for循环来迭代固定次数:
let numPetals = 7; // 不好。 let i = 0; while (i < numPetals) { ellipse(0, 0, 20, 80); rotate(PI / numPetals); i += 1; } // 好。 for (let i = 0; i < numPetals; i += 1) { ellipse(0, 0, 20, 80); rotate(PI / numPetals); }- 不要使用
for循环来迭代数组。纯函数比副作用更容易推理。使用forEach()/map()/every()/filter()/find()/findIndex()/reduce()/some()等来迭代数组;使用Object.keys()/Object.values()/Object.entries()来生成用于迭代对象的数组:
let diameters = [50, 40, 30, 20, 10]; // 不好。 for (let i = 0; i < diameters.length; i += 1) { circle(0, 0, diameters[i]); } // 不好。 for (let d of diameters) { circle(0, 0, d); } // 好。 diameters.forEach(d => circle(0, 0, d));对象
- 使用字面量语法创建对象:
// 不好。 let ball = new Object(); // 好。 let ball = {};- 只对无效标识符的属性使用引号,这样更易读、语法高亮更好,JavaScript 引擎也更容易优化性能:
// 好。 let secretObject = { x: 3, y: 4, 'top-secret': 'classified' };- 使用点表示法访问属性;使用方括号表示法
[]访问带有变量的属性:
let turtle = { name: 'Leonardo', color: 'dodgerblue', weapon: '🗡️', food: '🍕' }; // 不好。 let turtleName = turtle['name']; // 好。 let turtleName = turtle.name; function getProp(prop) { return turtle[prop]; } let turtleName2 = getProp('name');- 不要使用前导逗号,添加尾随逗号:
// 不好。 let artist = { firstName: 'Lauren' , lastName: 'McCarthy' }; // 好。 let artist = { firstName: 'Lauren', lastName: 'McCarthy', };数组
- 使用字面量语法创建数组:
// 不好。 let images = new Array(); // 好。 let images = [];- 使用
Array#push而不是直接赋值来向数组添加项目:
let lyrics = []; // 不好。 lyrics[lyrics.length] = 'Little rough around the edges, but I keep it smooth'; // 好。 lyrics.push('Little rough around the edges, but I keep it smooth');- 使用
slice()方法复制数组:
// 不好。 let numbersCopy = []; for (let i = 0; i < numbers.length; i += 1) { numbersCopy[i] = numbers[i]; } // 好。 let numbersCopy = numbers.slice();- 当提高可读性时,在多行上编写数组:在左括号后和右括号前使用换行,并添加尾随逗号。
函数
- 使用函数声明而不是命名函数表达式,函数声明虽有一些陷阱,但对初学者来说更容易理解:
// 不好。 let foo = function () { // ... }; // 不好。 let foo = () => { // ... }; // 好。 function foo() { // ... }- 使用默认参数语法,不要改变函数参数:
// 不好。 function createBall(diameter) { diameter = diameter || 50; // ... } // 好。 function createBall(diameter = 50) { // ... }- 始终将默认参数放在最后:
// 不好。 function drawSpiral(angle = 90, length) { // ... } // 好。 function drawSpiral(length, angle = 90) { // ... }箭头函数
- 对匿名函数使用箭头函数表示法,回调是这种语法的常见用例。语法更简洁,且创建了在
this上下文中执行的函数版本。如果匿名函数很复杂,将其重写为声明的函数:
// 不好。 function setup() { loadImage('assets/moonwalk.jpg', function (img) { image(img, 0, 0); }); } // 好。 function setup() { loadImage('assets/moonwalk.jpg', img => { image(img, 0, 0); }); } // 不好。 function preload() { loadImage('assets/moonwalk.jpg', img => { // 复杂的预处理... }); } // 好。 function preload() { loadImage('assets/moonwalk.jpg', processImage); } function processImage(img) { // 复杂的预处理... }- 尽可能使用隐式返回:如果函数体返回单个语句且没有副作用,则省略大括号;否则保留大括号并使用
return语句:
// 好。 [1, 2, 3].map(number => `${number} squared is ${number ** 2}.`);- 始终在参数周围包含括号,减少更改参数时的错误:
// 好。 [1, 2, 3].map(number => number * number);链式调用
使用单独的函数调用而不是函数链式调用,以适应可能不熟悉函数链式调用概念的用户:
// 不好。 fill(0).strokeWeight(6).textSize(20); // 好。 fill(0); strokeWeight(6); textSize(20);类
- 始终使用
class,避免直接操作prototype。唯一的例外是解释如何创建库。class语法更简洁、更容易理解:
// 不好。 function Mover(x, y, radius) { this.x = x; this.y = y; this.radius = radius; } Mover.prototype.update = function () { this.x += 1; this.y += 1; }; Mover.prototype.render = function () { circle(this.x, this.y, 2 * this.radius); }; // 好。 class Mover { constructor(x, y, radius) { this.x = x; this.y = y; this.radius = radius; } update() { this.x += 1; this.y += 1; } render() { circle(this.x, this.y, 2 * this.radius); } }- 使用
extends进行继承:
class RandomMover extends Mover { update() { this.x += random(-1, 1); this.y += random(-1, 1); } }- 确保自定义
toString()方法不会引起副作用:
// 不好。 class Mover { // ... toString() { this.x += 1; return `Mover at (${this.x}, ${this.y})`; } } // 好。 class Mover { // ... toString() { return `Mover at (${this.x}, ${this.y})`; } }- 不要编写空的构造函数或仅委托给父类的构造函数,因为类有默认构造函数:
// 不好。 class Dot { constructor() {} render() { circle(mouseX, mouseY, 50); } } // 好。 class Dot { render() { circle(mouseX, mouseY, 50); } } // 不好。 class DragonBall extends Ball { constructor(x, y, d) { super(x, y, d); } } // 好。 class DragonBall extends Ball { constructor(x, y, d, numStars) { super(x, y, d); this.numStars = numStars; } }- 避免重复的类成员。重复的类成员声明优先选择最后一个,有重复通常意味着有错误:
// 不好。 class Mover { // ... update() { this.x += this.xspeed; this.y += this.yspeed; } update() { this.x = 0; this.y = 0; } } // 好。 class Mover { // ... update() { this.x += this.xspeed; this.y += this.yspeed; } reset() { this.x = 0; this.y = 0; } }资源:从 assets 文件夹加载
始终从名为 "assets" 的文件夹加载资源。这模拟了良好的项目组织,也是资源被收录进 p5.js 网站在线文档的必要条件(对应 p5.js 网站仓库中的示例、参考页面与学习页面目录)。
let img; // 不好。 function preload() { img = loadImage('moonwalk.jpg'); } // 好。 function preload() { img = loadImage('assets/moonwalk.jpg'); }深入阅读
- 为 p5.js 参考文献做贡献:YUIDoc 标签语法、函数/变量文档写法、示例添加与参考生成预览的完整教程;
- 为 p5.js 参考文献做贡献(英文原版):包含标签速查表与 v1.x/v2.x 差异附录;
- 创建库:
prototype直接操作(类规范中唯一例外)的适用场景; - 贡献者指南:从提交到评审的完整贡献流程;
- 文档风格指南的配套工程实践:仓库中的 eslint.config.mjs 配置了 lint 规则,用于约束源码与文档示例的代码风格一致性。
本文所涉规范并非孤立的文档写作建议——它们与 p5.js 的参考生成管线(YUIDoc 注释 → 参考页面)、可访问性系统(如describe()画布描述,见 src/accessibility/describe.js)以及示例代码质量保障机制(lint 检查)紧密咬合。遵循本指南,就是在为 p5.js 生态贡献可持续维护、对初学者友好且人人可用的文档。
【免费下载链接】p5.jsp5.js is a client-side JS platform that empowers artists, designers, students, and anyone to learn to code and express themselves creatively on the web. It is based on the core principles of Processing. Looking for p5.js 2.0? http://beta.p5js.org项目地址: https://gitcode.com/GitHub_Trending/p5/p5.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考