Prototype 作为早期前端框架的代表,以 "扩展原生对象、简化开发" 为核心设计理念,深刻影响了后续 jQuery 等工具库的发展。本节课将深入剖析 Prototype 的设计思想、核心 API 及实战应用,理解它如何用简洁的方式解决了 2000 年代 JavaScript 开发的诸多痛点。

一、框架设计思想
Prototype 诞生的背景是:原生 JavaScript API 粗糙、DOM 操作繁琐、缺乏现代语言特性。其设计哲学可概括为两点:
1. 原生对象扩展
Prototype 不创造全新的 API 体系,而是直接扩展 JavaScript 原生对象 (Object、Array、String、Element等),在保留原生语法习惯的基础上增加实用功能。
例如,原生Array没有each()方法,Prototype 通过扩展Array.prototype实现:
javascript
// Prototype的核心实现逻辑(简化版)
Array.prototype.each = function(callback) {
for (var i = 0; i < this.length; i++) {
callback(this[i], i, this); // 传递当前元素、索引、原数组
}
return this; // 支持链式调用
};
这种设计的优势是学习成本低------ 开发者无需记忆全新 API,只需在原有知识基础上扩展认知。
2. 无侵入式 API
Prototype 的 API 设计遵循 "无侵入" 原则:
- 不强制要求特定的代码组织形式(如模块定义);
- 扩展方法尽可能保持与原生方法的风格一致;
- 核心功能通过全局函数(如
$())提供,无需实例化框架对象。
这种设计让开发者可以按需使用,即使只引入框架的某一个功能(如 DOM 选择器),也能正常工作。
二、核心工具方法
Prototype 的核心价值之一是简化 DOM 操作,通过$()、$$()等工具方法,让开发者摆脱冗长的原生 API。
1. 元素获取:$()与$$()
① $(id):快速获取单个元素
替代原生document.getElementById(),支持直接调用元素方法:
javascript
// 原生写法
var box = document.getElementById('box');
box.style.color = 'red';
// Prototype写法
$('box').setStyle({ color: 'red' }); // 直接链式调用
进阶特性:
-
若传入 DOM 元素,直接返回该元素(兼容处理);
-
若传入多个 ID,返回包含对应元素的数组:
javascriptvar elements = $('box1', 'box2', 'box3'); // 返回 [box1, box2, box3]
② $$(selector):CSS 选择器批量获取元素
支持 CSS 选择器语法(如类选择器、后代选择器),返回元素数组:
html
<ul id="list">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li>Item 3</li>
</ul>
javascript
// 获取所有class为item的元素
var items = $$('.item');
items.each(function(item) {
item.addClassName('highlight'); // 给每个元素添加类名
});
// 复杂选择器:ul#list下的li元素
var listItems = $$('ul#list li');
这比原生getElementsByClassName()或querySelectorAll()(IE8 及以下不支持)更易用,是早期前端开发的 "刚需" 功能。
2. 元素扩展
Prototype 通过Element.extend()方法,为 DOM 元素注入大量实用方法,如样式操作、类名管理、事件绑定等。
常用元素方法:
| 方法名 | 功能描述 | 示例 |
|---|---|---|
addClassName(class) |
给元素添加类名 | $('box').addClassName('active') |
removeClassName(class) |
移除元素类名 | $('box').removeClassName('active') |
hasClassName(class) |
判断是否包含类名 | if ($('box').hasClassName('active')) |
setStyle(styles) |
设置 CSS 样式(对象形式) | $('box').setStyle({ color: 'red', fontSize: '16px' }) |
getStyle(prop) |
获取计算后的样式值 | var color = $('box').getStyle('color') |
hide()/show() |
隐藏 / 显示元素(设置 display) | $('box').hide() |
update(content) |
更新元素内容(类似 innerHTML) | $('box').update('<p>新内容</p>') |
observe(event, handler) |
绑定事件处理器 | $('btn').observe('click', function() { ... }) |
实战:元素样式批量操作
需求:将所有class="product"的元素设置为灰色背景,点击时切换为白色背景并添加边框。
html
<div class="product">商品1</div>
<div class="product">商品2</div>
<div class="product">商品3</div>
java
// 页面加载完成后执行(类似DOMContentLoaded)
document.observe('dom:loaded', function() {
// 获取所有商品元素
var products = $$('.product');
// 批量设置初始样式
products.each(function(product) {
product.setStyle({
backgroundColor: '#f5f5f5',
padding: '10px',
margin: '5px'
});
// 绑定点击事件
product.observe('click', function() {
// 切换背景色
var isGray = product.getStyle('backgroundColor') === '#f5f5f5';
product.setStyle({
backgroundColor: isGray ? '#fff' : '#f5f5f5',
border: isGray ? '1px solid #ccc' : 'none'
});
});
});
});
这段代码体现了 Prototype 的核心优势:用简洁的 API 实现批量 DOM 操作,避免了大量 for 循环和原生方法调用。
三、数据类型增强
Prototype 对 JavaScript 原生数据类型(Object、Array、String)进行了扩展,补充了大量实用方法,解决了早期 JavaScript 的功能短板。
1. Object 扩展:对象操作工具
① Object.clone(obj):深拷贝对象
java
var user = { name: '张三', age: 20 };
var userCopy = Object.clone(user);
userCopy.name = '李四';
console.log(user.name); // 仍为'张三'(深拷贝)
② Object.extend(dest, src):合并对象
将 src 对象的属性合并到 dest 对象(类似 jQuery.extend):
java
var defaults = { color: 'red', size: '16px' };
var options = { size: '18px', fontWeight: 'bold' };
Object.extend(defaults, options);
// 结果:{ color: 'red', size: '18px', fontWeight: 'bold' }
2. Array 扩展:数组方法的 "先驱"
Prototype 为Array添加的方法,很多后来被 ES5/ES6 采纳为标准(如forEach、map)。
① each(iterator)
java
var numbers = [1, 2, 3];
numbers.each(function(num, index) {
console.log('索引' + index + '的值:' + num);
});
② map(iterator)
java
var numbers = [1, 2, 3];
var squares = numbers.map(function(num) {
return num * num; // 返回平方值
});
// squares: [1, 4, 9]
③ find(iterator)
java
var users = [
{ name: '张三', age: 18 },
{ name: '李四', age: 20 },
{ name: '王五', age: 20 }
];
// 查找年龄为20的第一个用户
var user = users.find(function(u) {
return u.age === 20;
});
// 结果:{ name: '李四', age: 20 }
④ without(value...)
java
var numbers = [1, 2, 3, 2, 4];
var unique = numbers.without(2); // 排除所有2
// unique: [1, 3, 4]
3. String 扩展
① stripScripts()
防止 XSS 攻击的实用方法,移除字符串中的<script>标签及内容:
java
var html = '<p>安全内容</p><script>恶意代码</script>';
var safeHtml = html.stripScripts();
// 结果:'<p>安全内容</p>'
② truncate(length, suffix)
java
var longStr = '这是一段很长的文本,需要截断显示...';
var shortStr = longStr.truncate(10, '...');
// 结果:'这是一段很长的...'(长度为10)
③ camelize()/underscore()
java
var cssProp = 'font-size';
var jsProp = cssProp.camelize(); // 转换为驼峰式:'fontSize'
var jsName = 'userName';
var dbName = jsName.underscore(); // 转换为下划线式:'user_name'
四、批量处理商品列表
需求说明
实现一个商品列表交互功能,包含:
- 页面加载后,为所有商品添加默认样式;
- 点击 "高亮" 按钮,批量为库存 > 10 的商品添加高亮样式;
- 点击 "重置" 按钮,恢复所有商品样式;
- 点击单个商品,显示其详情(名称、价格、库存)。
HTML 结构
html
<button id="highlightBtn">高亮库存充足商品</button>
<button id="resetBtn">重置样式</button>
<div id="productList">
<div class="product" data-id="1" data-price="99" data-stock="20">
<h3>商品A</h3>
<p>库存:20</p>
</div>
<div class="product" data-id="2" data-price="199" data-stock="5">
<h3>商品B</h3>
<p>库存:5</p>
</div>
<div class="product" data-id="3" data-price="299" data-stock="15">
<h3>商品C</h3>
<p>库存:15</p>
</div>
</div>
<div id="detail"></div>
实现代码
java
// 页面加载完成后初始化
document.observe('dom:loaded', function() {
// 获取元素
var products = $$('.product');
var highlightBtn = $('highlightBtn');
var resetBtn = $('resetBtn');
var detail = $('detail');
// 1. 设置默认样式
products.each(function(product) {
product.setStyle({
border: '1px solid #ddd',
padding: '10px',
margin: '5px',
cursor: 'pointer'
});
});
// 2. 高亮按钮点击事件
highlightBtn.observe('click', function() {
products.each(function(product) {
// 获取库存数据(从data-stock属性)
var stock = parseInt(product.readAttribute('data-stock'));
// 库存>10的商品添加高亮
if (stock > 10) {
product.addClassName('highlight');
product.setStyle({ backgroundColor: '#e6f7ff' });
} else {
product.removeClassName('highlight');
product.setStyle({ backgroundColor: '' });
}
});
});
// 3. 重置按钮点击事件
resetBtn.observe('click', function() {
products.each(function(product) {
product.removeClassName('highlight');
product.setStyle({ backgroundColor: '' }); // 恢复默认
});
detail.update(''); // 清空详情
});
// 4. 商品点击事件(显示详情)
products.each(function(product) {
product.observe('click', function() {
var name = product.down('h3').innerHTML; // 获取子元素h3的内容
var price = product.readAttribute('data-price');
var stock = product.readAttribute('data-stock');
// 更新详情区域
detail.update(`
<h4>商品详情</h4>
<p>名称:${name}</p>
<p>价格:${price}元</p>
<p>库存:${stock}件</p>
`);
});
});
});
代码解析
- 用
document.observe('dom:loaded', ...)确保 DOM 加载完成后执行初始化,避免元素未加载导致的错误; - 通过
$$('.product')批量获取商品元素,结合each()实现批量样式设置; - 用
readAttribute()读取自定义数据属性(data-*),实现数据与 DOM 的关联; - 事件绑定采用
observe()方法,语法简洁且兼容各浏览器。
五、Prototype 的局限与历史意义
尽管 Prototype 在当时解决了很多问题,但随着 Web 应用复杂度提升,其局限性逐渐显现:
- 原生对象污染 :扩展
Array.prototype、Object.prototype等全局对象,可能与其他库冲突(如两个库都定义了Array.prototype.each); - 性能问题:批量 DOM 操作仍基于原生 DOM API,在复杂页面中性能不足;
- 缺乏模块化:没有内置模块系统,大型项目代码组织困难。
这些局限间接推动了 jQuery(解决冲突问题)和现代框架(数据驱动、组件化)的发展。
但 Prototype 的历史意义不可忽视:
- 首次普及了 "简洁 DOM 操作" 和 "链式调用" 的理念;
- 其数组方法设计为 ES5 标准提供了参考;
- 证明了前端框架可以显著提升开发效率,为后续框架爆发奠定了基础。
最后小结
Prototype 框架以 "增强原生 JavaScript" 为核心,通过扩展原生对象和提供简洁 API,极大简化了 2000 年代的前端开发。本节课学习的$()/$$()选择器、元素扩展方法、数据类型增强等功能,是理解早期前端开发范式的关键。虽然现代框架已采用不同的设计思路,但 Prototype"让开发者更高效" 的核心理念一直延续至今。下节课我们将学习 YUI 框架,对比其与 Prototype 在设计理念上的差异。