Prototype 扩展与贡献指南
1. Prototype 对象系统基础
JavaScript 并非基于类的语言,而是基于原型的语言。在 JavaScript 里,不存在真正意义上的类,只有对象。每个对象都基于构造函数创建,构造函数本身也是对象。对象具有原型,原型定义了该对象所有实例共享的属性和方法。
Prototype 对原生 JavaScript 对象的构造函数进行扩展,以此增强其功能。在 Prototype 1.6 版本中,其进行了深度重新设计,拥有更便捷的语法,能更好地模拟继承和混入。
1.1 定义新类
Prototype 创建类依旧依赖
Class.create()
,不过语法更为丰富。完整语法如下:
Class.create( [ superclass ] [ , Module... ] [ , { instanceMethod... } ] ) → Class
所有参数均为可选,你可以像在 Prototype 1.5 中那样不传递任何参数调用它,之后再添加实例方法。但现在可以直接在调用时定义实例方法,示例如下:
var Animal = Class.create({
initialize: function(name) {
this.name = name;
},
eat: function(food) {
return this.say('Yum!');
},
say: function(msg) {
return this.name + ': ' + msg;
}
});
使用示例:
var fido = new Animal('Fido');
fido.name; // => 'Fido'
fido.say('Hi'); // => 'Fido: Hi'
fido.eat('bone'); // => 'Fido: Yum!'
1.2 模块混入
模块也是实例方法的集合,你可以指定模块进行混入,而非仅为类提供自定义方法的字面量集合:
var Dequeue = Class.create(Enumerable, {
// dequeue 方法
});
当然,你可以同时结合父类和模块:
var ChildClass = Class.create(ParentClass, Enumerable, MyCoolModule, {
// 子类的实例方法
});
1.3 继承
在 JavaScript 中,“传统”继承通过重新分配子类构造函数的原型来实现,这可能会让非专业人士头疼。而 Prototype 为你隐藏了这些复杂性。当你想定义一个类为另一个类的子类时,只需将父类作为第一个参数传递给
Class.create()
,示例如下:
var Cat = Class.create(Animal, {
eat: function($super, food) {
if (food instanceof Mouse) return $super(food);
return this.say('Yuck! I only eat mice.');
}
});
var Mouse = Class.create(Animal); // 简单子类
使用示例:
var fido = new Animal('Fido');
var tom = new Cat('Tom');
var jerry = new Mouse('Jerry');
tom.say('Hi'); // => 'Tom: Hi'
jerry.eat('cheese'); // => 'Jerry: Yum!'
tom.eat(fido); // => 'Tom: Yuck! I only eat mice.'
tom.eat('bone'); // => 'Tom: Yuck! I only eat mice.'
tom.eat(jerry); // => 'Tom: Yum!'
1.4 方法重写与调用父类方法
在方法签名前添加
$super
参数,Prototype 会将该方法的父类版本作为参数传递给你,需要时即可调用。示例如下:
var Cat = Class.create(Animal, {
eat: function($super, food) {
if (food instanceof Mouse) return $super(food);
return this.say('Yuck! I only eat mice.');
}
});
1.5 添加更多实例属性和方法
使用
klass.addMethods()
方法可以为类添加更多实例属性和方法,示例如下:
Cat.addMethods({
purr: function() { return this.say('Rrrrrr...'); }
});
tom.purr(); // => 'Tom: Rrrrrr...'
1.6 添加静态属性和方法
使用
Object.extend()
可以为类添加静态属性和方法,示例如下:
var Animal = Class.create({
initialize: function(name) {
Animal.newInstance();
this.name = name;
},
eat: function(food) {
return this.say('Yum!');
},
say: function(msg) {
return this.name + ': ' + msg;
}
});
Object.extend(Animal, {
instanceCount: 0,
newInstance: function() { ++this.instanceCount; }
});
1.7 自动类属性
Prototype 维护了三个属性,方便进行强大的内省:
-
obj.constructor
:返回对象的构造函数,即其类。
-
class.superclass
:返回类的父类,如果没有父类则为
null
。
-
class.subclasses
:返回扩展当前类的所有子类的数组。
示例:
tom.constructor == Cat; // => true
tom.constructor.superclass == Animal; // => true
Cat.superclass == Animal; // => true
Animal.superclass; // => null
Animal.subclasses.length; // => 2
Animal.subclasses.first() == Cat; // => true
Animal.subclasses.last() == Mouse; // => true
Cat.subclasses; // => []
1.8 扩展 DOM 元素
使用
Element.addMethods()
可以为 DOM 元素添加更多方法,示例如下:
YourLib.Element.Methods = {
pulsate: function(element, options) {
return $(element).pulsate(options);
}
};
Element.addMethods(YourLib.Element.Methods);
为表单字段添加
markAsRequired()
方法:
YourLib.Field.Methods = {
markAsRequired: function(element) {
element = $(element);
element.addClassName('required');
var lbl = $$('label[for="' + element.id + '"]').reduce();
if (lbl) lbl.addClassName('required');
}
};
Element.addMethods($w('input textarea select'), YourLib.Field.Methods);
2. 贡献指南
2.1 贡献方式
有多种方式可以帮助并分享你掌握的 Prototype 技能:
1. 订阅支持列表,开始回答问题。回答时避免过度猜测,但也无需绝对权威。
2. 浏览 Trac,这里是大家提交 bug 报告和增强请求的地方。你可以从查找重复或无效的工单开始,若发现未报告且易复现的问题,也可提交 bug 报告。
3. 学习使用 Subversion 仓库,体验 “Prototype Edge”,即库的最新状态。这有助于了解开发动态,在工单修复后协助关闭工单。
4. 开始编写补丁!这需要掌握 Prototype 依赖的单元测试库,因为补丁需要附带完整的测试才能被接受。你还需在尽可能多的浏览器上进行测试。
2.2 Trac 使用指南
2.2.1 Trac 简介
Trac 是一个跟踪系统,用于提交 bug 报告和增强请求,具有文档共享、报告(可保存和重用的自定义查询)以及与版本控制系统(如 Subversion)的出色集成等功能。
2.2.2 浏览 Trac
任何人都可以浏览工单和进行搜索,但只有经过身份验证的用户才能修改工单(如添加评论、附件、关闭或重新打开工单)和创建新工单。获取用户账户很简单,在 Trac 主页右上角使用 “Register” 链接进行注册,之后访问 Trac 时记得先登录。
你可以通过时间线查看最新的工单活动,默认显示过去七天内的所有活动,你可以在右侧进行自定义设置。也可以通过 “View Tickets” 链接访问预定义的报告,例如报告 #22 包含 Prototype 和 script.aculo.us 的所有待处理补丁。
2.2.3 搜索 Trac
-
若要查找特定工单,可在右上角搜索框中输入工单编号(前面加
#)。 - 若要筛选报告,可通过 “View Tickets” 链接访问报告列表页面,或直接使用报告的 URL。
- 复杂条件的高级搜索可使用 “Custom query” 功能。
2.2.4 订阅 RSS 提要
Trac 的大多数报告页面都有相关的 RSS 提要,你可以将其添加到喜欢的聚合器中,确保不会错过任何信息。不过,时间线提要默认最多加载 50 项,时间跨度最多 90 天,你可以自定义其 URL。
2.2.5 工单处理
- 查找无效工单和重复工单 :常见的无效工单情况包括当前稳定版本未出现报告的问题、请求的增强过于特定用户化、工单明显是垃圾信息等。检测重复工单需要对现有工单有较好的了解。当你关闭一个重复工单时,务必添加评论说明它是哪个工单的重复项。
-
完成或创建工单
:一个好的工单应满足以下要求:
- 不是重复工单,且对当前主干版本有效。
- 摘要简洁明了,使用适当的前缀。
- 描述和评论使用正确的标记,特别是代码片段。
- 关键属性(类型、组件、严重性和优先级)设置正确。
- 有效使用规范化关键字。
- 附带针对当前主干(或相近版本)的干净补丁,并作为附件提供。
- 补丁包含所有相关的单元测试(可能还有功能测试),在未应用补丁时主干测试失败,应用补丁后在所有支持的浏览器上测试通过。
2.3 Subversion 仓库使用指南
2.3.1 获取最新源代码
Prototype 和 script.aculo.us 位于 Rails 的 Subversion 仓库分支中,地址为
http://svn.rubyonrails.org/rails/spinoffs/
。Prototype 使用经典的
trunk/tags/branches
结构:
-
trunk
:是下一个版本的开发地,即 Prototype Edge 的所在地。
-
branches
:为 Prototype Core 成员提供单独的区域进行调整。
-
tags
:是主干的冻结状态,代表特定版本。
要获取最新源代码,你需要一个 Subversion 客户端程序,首次使用时,在命令提示符中进入要检出的目录,执行以下命令:
$ svn co http://svn.rubyonrails.org/rails/spinoffs/prototype/trunk/
更新到最新版本:
$ svn up
2.3.2 创建补丁
编写补丁很简单,进入
trunk
的
src
目录,修改相应的
.js
文件,然后执行
rake dist
命令重新构建合并后的
prototype.js
文件。
2.3.3 测试
完成补丁后,确保运行适当的测试文件,在浏览器中打开测试文件,通过的测试显示为绿色,失败的显示为红色。你可以使用
rake test
命令自动运行所有测试,也可以使用
BROWSERS
和
TESTS
变量进行限制。
2.3.4 打包、提交和清理
将补丁打包为 Subversion diff 文件:
$ svn diff > good_patch_name.diff
将补丁文件放在
trunk
目录外,然后在 Trac 中编辑或创建工单,将补丁作为附件添加,并添加必要的解释说明。提交后,记得将
trunk
目录恢复到正常状态:
$ svn revert -R .
2.4 代码风格指南
- 使用两个空格进行缩进,而非制表符。
- 使用有表达力的变量名。
- 使用分号结束语句。
- 尽可能使用 Unix 换行符。
- 参考现有源代码的架构和布局,尽量避免污染全局命名空间。
- 发现易于修复的性能或整洁性问题时,及时处理。
2.5 常见陷阱
- 避免编写最终被认为无效的补丁,确保不重复现有的工作。
- 开始新补丁前,恢复本地仓库,避免与之前的补丁工作叠加。
- 更新本地仓库,确保针对最新主干进行补丁,更新前先恢复。
-
测试一直失败时,确保保存源文件后重新构建了库(
rake dist)。 - 至少在两个浏览器上进行测试,其中一个必须是 Firefox。
我们期待你的贡献!
以下是一个简单的 mermaid 流程图,展示贡献的大致流程:
graph LR
A[学习 Prototype] --> B[选择贡献方式]
B --> C{使用 Trac}
C -->|浏览工单| D[查找无效/重复工单]
C -->|搜索工单| E[提交工单]
B --> F{使用 Subversion}
F -->|获取代码| G[创建补丁]
G --> H[运行测试]
H -->|通过测试| I[打包提交补丁]
I --> J[清理本地仓库]
以下是一个表格总结关键操作命令:
| 操作 | 命令 |
| ---- | ---- |
| 检出代码 |
svn co http://svn.rubyonrails.org/rails/spinoffs/prototype/trunk/
|
| 更新代码 |
svn up
|
| 构建库文件 |
rake dist
|
| 运行测试 |
rake test
|
| 打包补丁 |
svn diff > good_patch_name.diff
|
| 恢复本地仓库 |
svn revert -R .
|
2.6 详细贡献流程梳理
为了更清晰地展示从开始学习 Prototype 到最终完成有效贡献的整个过程,我们可以将其拆分为以下几个关键步骤,并通过 mermaid 流程图直观呈现:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A([开始学习 Prototype]):::startend --> B(选择贡献途径):::process
B --> C{是否使用 Trac}:::process
C -->|是| D(浏览 Trac 工单):::process
D --> E(查找重复或无效工单):::process
E --> F(提交新工单或处理现有工单):::process
C -->|否| G{是否使用 Subversion}:::process
G -->|是| H(获取最新源代码):::process
H --> I(编写补丁):::process
I --> J(运行测试):::process
J -->|测试通过| K(打包并提交补丁):::process
J -->|测试失败| I
K --> L(清理本地仓库):::process
F --> M(等待审核与反馈):::process
K --> M
M --> N([完成贡献]):::startend
2.7 贡献实践案例
为了帮助大家更好地理解如何在实际中进行贡献,下面给出一个具体的实践案例。假设我们发现 Prototype 中
Array
对象的
first()
方法在某些特殊情况下返回结果不符合预期,我们可以按照以下步骤进行修复和贡献:
1.
问题发现与确认
:在使用 Prototype 过程中,发现
Array
对象的
first()
方法在数组为空时没有返回
null
,而是抛出了异常。通过在多个浏览器中进行测试,确认该问题确实存在。
2.
使用 Trac 提交工单
:
- 登录 Trac 系统,在搜索框中输入相关关键词,确认该问题未被报告过。
- 点击创建新工单,在摘要中使用
[PATCH]
前缀,填写 “[PATCH] Array.first() method throws exception when array is empty”。
- 在描述中详细说明问题,提供重现步骤,例如:
var emptyArray = [];
var firstElement = emptyArray.first(); // 期望返回 null,但实际抛出异常
- 设置工单的类型为 “defect”,组件为 “Prototype”,并根据问题的严重程度设置优先级和严重性。
-
使用 Subversion 获取代码并编写补丁
:
- 打开命令提示符,进入要检出代码的目录,执行以下命令获取最新源代码:
$ svn co http://svn.rubyonrails.org/rails/spinoffs/prototype/trunk/
- 进入 `trunk/src/array.js` 文件,找到 `first()` 方法的实现,修改代码如下:
Array.prototype.first = function() {
return this.length > 0 ? this[0] : null;
};
-
运行测试并确保补丁有效
:
-
在命令行中执行
rake dist重新构建库文件。 -
打开
test/unit/array.html文件,在浏览器中运行测试,确保修改后的first()方法在各种情况下都能正常工作。
-
在命令行中执行
-
打包并提交补丁
:
-
在
trunk目录下执行以下命令打包补丁:
-
在
$ svn diff > array_first_fix.diff
- 将生成的 `array_first_fix.diff` 文件作为附件添加到之前创建的 Trac 工单中,并在评论中说明补丁的具体修改内容和测试情况。
-
清理本地仓库
:
- 执行以下命令将本地仓库恢复到正常状态:
$ svn revert -R .
2.8 贡献的进阶建议
- 深入学习 JavaScript 原理 :虽然 Prototype 为我们隐藏了很多 JavaScript 底层的复杂性,但深入了解 JavaScript 的原型链、闭包、作用域等原理,有助于我们更好地理解 Prototype 的实现机制,从而写出更优质的补丁。
- 参与社区讨论 :加入 Prototype 的官方论坛或邮件列表,与其他开发者交流经验和想法。在讨论中,你可以了解到最新的开发动态、遇到的问题以及解决方案,还能结识志同道合的开发者,共同推动 Prototype 的发展。
- 关注行业趋势 :前端技术发展迅速,新的框架和库不断涌现。关注行业趋势,了解其他优秀框架的设计理念和实现方式,有助于我们从更宏观的角度审视 Prototype,为其提出更有前瞻性的改进建议。
2.9 总结
通过本文的介绍,我们详细了解了 Prototype 的对象系统,包括如何定义新类、实现继承、添加实例和静态属性等,同时也掌握了多种为 Prototype 社区贡献的方式和具体操作步骤。在贡献过程中,我们需要遵循代码风格指南,避免常见陷阱,确保提交的补丁质量高、测试完备。
希望大家能够积极参与到 Prototype 的开发和维护中来,通过自己的努力为开源社区做出贡献。每一次的贡献都是对技术的一次实践和提升,也将为整个前端开发领域带来更多的价值。
以下是一个表格,总结进阶建议的要点:
| 进阶建议 | 具体内容 |
| ---- | ---- |
| 深入学习 JavaScript 原理 | 了解原型链、闭包、作用域等原理,更好理解 Prototype 实现机制 |
| 参与社区讨论 | 加入官方论坛或邮件列表,交流经验、了解开发动态 |
| 关注行业趋势 | 了解其他优秀框架设计理念,提出前瞻性改进建议 |
最后,再次通过一个 mermaid 流程图强调贡献的重要性和积极意义:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A([个人学习 Prototype]):::startend --> B(掌握贡献技能):::process
B --> C(为社区贡献代码):::process
C --> D(推动 Prototype 发展):::process
D --> E(提升个人技术能力):::process
E --> F(促进前端技术进步):::process
F --> G([形成良性循环]):::startend
我们期待更多开发者加入到 Prototype 的贡献中来,共同创造更美好的前端开发环境!
超级会员免费看
42

被折叠的 条评论
为什么被折叠?



