Web开发中的元素扩展、测试与应用实践
1. 元素扩展方法
在Web开发中,有许多实用的元素扩展方法,这些方法大多定义在
effects.js
文件中,除特别说明外,都会添加到Prototype的
Element.Methods
对象中,通过
$()
或
$$()
函数访问的所有DOM元素会自动混入这些方法。以下是一些常用方法:
| 方法名 | 功能 | 示例 |
| ---- | ---- | ---- |
|
collectTextNodes(element)
| 返回元素所有子文本节点连接成的字符串 |
Element.collectTextNodes('target')
|
|
collectTextNodesIgnoreClass(element, className)
| 返回元素所有子文本节点连接成的字符串,排除指定类名的节点 |
Element.collectTextNodesIgnoreClass('target', 'a')
|
|
setContentZoom(element, percent)
| 通过改变字体大小样式设置元素的缩放级别 |
Element.setContentZoom('target', 200)
|
|
getOpacity(element)
| 返回元素的不透明度,值为0 - 1之间的浮点数 |
Element.getOpacity('target')
|
|
setOpacity(element, value)
| 设置元素的不透明度,值应在0 - 1之间 |
Element.setOpacity('target', 0.5)
|
|
childrenWithClassName(element, className[, findFirst])
| 返回元素所有匹配指定类名的子元素,若
findFirst
为
true
,则只返回第一个元素 |
Element.childrenWithClassName('container', 'green', true)
|
|
forceRerendering(element)
| 通过添加并移除一个空格字符的文本节点,强制元素重新渲染 |
Element.forceRerendering('target')
|
|
visualEffect(element, effect[, options])
| 为指定元素创建一个新的效果对象,返回元素ID,支持链式调用 |
$('target').visualEffect('blind_up').visualEffect('fade')
|
此外,还有一些定义在
dragdrop.js
中的方法:
-
isParent(child, element)
:判断
child
是否包含在
element
中。
-
findChildren(element, only, recursive, tagName)
:返回元素所有指定标签名的子元素,可选择限制类名,若
recursive
为
true
,则搜索所有后代元素。
-
offsetSize(element, type)
:若
type
为
vertical
或
height
,返回元素的偏移高度,否则返回偏移宽度。
2. DOM构建器
DOM
构建器定义在
builder.js
中,默认的Rails骨架不包含该文件。
Builder.node()
方法可用于创建DOM元素:
element = Builder.node('p', {className:'green'},
'Here is a green paragraph.');
document.body.appendChild(element);
上述代码会创建一个带有指定类名和文本内容的段落元素。
Builder.node()
方法还支持嵌套调用,可用于创建复杂的DOM结构,如表格:
$('my_div').appendChild(
Builder.node('table', { width:'100%', border:'1'}, [
Builder.node('tbody', [
Builder.node('tr', {className:'header'}, [
Builder.node('td', [ Builder.node('strong', 'Table Cell')])
])
])
])
);
需要注意的是,动态创建的表格中
TBODY
元素是必需的。
3. JavaScript单元测试
JavaScript
单元测试工具定义在
unittest.js
中,默认的Rails骨架不包含该文件。
Test.Unit.Runner
是编写单元测试用例的实用类,测试用JavaScript编写并在浏览器中运行。以下是创建测试运行器实例的示例:
new Test.Unit.Runner({
// 可选的设置函数,在每个测试用例前运行
setup: function( ) { with(this) {
// code
}},
// 可选的清理函数,在每个测试用例后运行
teardown: function( ) { with(this) {
// code
}},
// 测试用例,每个以 "test" 开头的方法被视为一个测试用例
testATest: function( ) { with(this) {
// code
}},
testAnotherTest: function( ) { with(this) {
// code
}}
});
构造函数的第二个可选参数是一个选项对象,其键包括:
-
testLog
:指定接收测试输出的元素ID,默认为
testlog
。
-
test
:若指定,则只运行给定的测试用例。
测试用例在以下页面模板中创建:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>JavaScript Unit Test</title>
<script src="prototype.js" type="text/javascript"></script>
<script src="unittest.js" type="text/javascript"></script>
<!-- 测试所需的其他JavaScript文件 -->
<link href="test.css" type="text/css" />
</head>
<body>
<h1>JavaScript Unit Test</h1>
<!-- 日志输出 -->
<div id="testlog"> </div>
<!-- 沙箱 -->
<div id="sandbox"> </div>
<!-- 测试 -->
<script type="text/javascript">
new Test.Unit.Runner({
// 测试用例
});
</script>
</body>
</html>
沙箱元素可包含测试用例所需的任何HTML标记。测试运行结果可通过在测试模板URL中添加
resultsURL
查询参数报告回服务器。
4. 断言方法
在
Test.Unit.Runner
的测试方法中,基本的断言调用如下:
testExample: function( ) { with(this) {
var myElement = $('mydiv');
assertEqual("DIV", myElement.tagName);
assertEqual("DIV", myElement.tagName, "Hmm, not a DIV?");
}};
所有断言方法的最后一个参数是可选的消息,用于断言失败时的额外日志备注。常见的断言方法有:
| 方法名 | 功能 |
| ---- | ---- |
|
assert(expression [, message ])
| 断言表达式求值为
true
|
|
assertEqual(expected, actual[, message ])
| 断言
expected
和
actual
相等 |
|
assertNotEqual(expected, actual[, message ])
| 断言
expected
和
actual
不相等 |
|
assertNull(object[, message ])
| 断言对象为
null
|
|
assertNotNull(object[, message ])
| 断言对象不为
null
|
|
assertHidden(element[, message ])
| 断言元素的
display
属性为
none
|
|
assertVisible(element[, message ])
| 断言元素可见(其自身及其所有祖先元素的
display
属性都不为
none
) |
|
assertNotVisible(element[, message ])
| 断言元素(或其某个祖先元素)的
display
属性为
none
|
|
assertInstanceOf(object, expected[, message ])
| 断言对象是指定类型的实例 |
|
assertNotInstanceOf(object, expected[, message ])
| 断言对象不是指定类型的实例 |
|
assertEnumEqual(expected, actual[, message ])
| 断言实际集合的所有成员与预期集合的成员匹配 |
5. 实用方法
script.aculo.us
还定义了一些不属于主要类别的实用方法:
-
Scriptaculous.Version
:返回
script.aculo.us
的当前版本号。
-
Scriptaculous.require(libraryName)
:加载指定URL的JavaScript文件。
-
Scriptaculous.load()
:加载
script.aculo.us
发行版中的标准文件,包括
builder.js
、
effects.js
、
dragdrop.js
、
controls.js
和
slider.js
。
-
String.prototype.parseColor
:将
rgb(x, x, x)
或
#xxx
格式的字符串转换为
#xxxxxx
格式。
"rgb(255,255,255)".parseColor( ); // '#ffffff'
"#123".parseColor( ); // '#112233'
-
Array.prototype.call(arg1[, arg2 ...]):数组中的每个元素应为函数,依次调用每个函数并传递参数。
var functions = [ function(v){ alert('hello, ' + v);},
function(v){ alert('hi, ' + v);} ];
functions.call('scott');
6. 复习测验应用
复习测验应用是一个用于提供共享测验的应用,类似于抽认卡,用户可以创建和使用测验进行自我学习和复习。该应用没有用户账户和登录机制,但采用了基于会话的身份验证。当用户创建新测验时,会存储其会话ID,只有使用相同会话ID才能进行更改。
6.1 数据库和模型
应用的数据库和模型非常简单,只有两个表和两个模型:
quizzes
和
questions
。以下是数据库结构的定义:
ActiveRecord::Schema.define(:version => 1) do
create_table "questions", :force => true do |t|
t.column "quiz_id", :integer
t.column "position", :integer
t.column "question", :text, :default => "", :null => false
t.column "answer", :text, :default => "", :null => false
end
add_index "questions", ["quiz_id"],
:name => "questions_quiz_id_index"
add_index "questions", ["position"],
:name => "position"
create_table "quizzes", :force => true do |t|
t.column "name", :string,
:default => "New Quiz", :null => false
t.column "session_id", :string,
:limit => 50, :default => "", :null => false
t.column "created_at", :datetime,
:null => false
end
add_index "quizzes", ["created_at"], :name => "created_at"
end
Question
模型除了基本的
ActiveRecord
功能外,还定义了一个返回下一个问题的方法:
class Question < ActiveRecord::Base
belongs_to :quiz
acts_as_list :scope => :quiz
# 返回当前问题之后的下一个问题,排除指定的正确答案ID
def next right_keys
quiz.questions.find :first,
:conditions => "position > #{position}" +
(right_keys.blank? ? "" : " and id not in (#{right_keys})")
end
end
Quiz
模型添加了一个关联方法,用于轻松查找未错过的问题:
class Quiz < ActiveRecord::Base
# 关联方法,如 quiz.questions.unmissed 用于检索未错过的问题
module AssociationExtension
def unmissed right_keys
cond = "id not in (#{right_keys})" unless right_keys.blank?
find :all, :conditions => (cond || nil), :limit => 5
end
end
has_many :questions,
:order =>'position',
:dependent => :destroy,
:extend => AssociationExtension
# 查找最后创建的20个测验
def self.recent
find :all, :limit => 20, :order => "created_at desc"
end
end
6.2 控制器
应用通过
QuizzesController
实现,路由映射包括Rails默认路由、主页路由和一个为测验控制器定义的命名路由集合:
ActionController::Routing::Routes.draw do |map|
map.resources :quizzes,
:member => { :create_q => :post,
:destroy_q => :post,
:reorder => :post,
:answer => :post,
:reset => :post }
map.home '', :controller =>'quizzes'
map.connect ':controller/:action/:id'
end
QuizzesController
使用
before_filter
确保有当前测验(如果需要),并在需要权限的操作中确保用户有权编辑测验。以下是部分主要方法:
class QuizzesController < ApplicationController
before_filter :find_quiz, :except => [ :index, :create ]
before_filter :check_permissions,
:only => [ :edit, :reorder, :questions, :destroy_question ]
# 列出最近的测验
def index
@quizzes = Quiz.recent
end
# 创建新测验并保存用户的会话ID
def create
quiz = Quiz.new params[:new_quiz]
quiz.session_id = session.session_id
quiz.save
redirect_to edit_quiz_url(:id => quiz)
end
# 显示编辑测验的视图
def edit
end
# 创建新问题,支持Ajax和传统表单提交
def create_q
@question = @quiz.questions.create params[:question]
respond_to do |format|
format.html { redirect_to edit_quiz_url }
format.js
end
end
# 处理拖放重新排序问题
def reorder
params[:quiz].each_with_index do |id, position|
q = @quiz.questions.find id
q.position = position + 1
q.save
end
render :nothing => true
end
# 处理删除问题
def destroy_q
question = @quiz.questions.find params[:question_id]
question.destroy
render :nothing => true
end
# 显示前五个未错过的问题
def show
@questions = @quiz.questions.unmissed right_keys
end
# 返回问题的答案响应
def answer
score @quiz.id, params[:question_id], params[:right]=='true'
last = @quiz.questions.find params[:last]
@next = last.next right_keys
end
# 重置用户的测验计分板
def reset
reset_scoreboard params[:id]
redirect_to quiz_url
end
private
# 查找测验的前置过滤器
def find_quiz( ) @quiz = Quiz.find params[:id] end
# 检查权限的前置过滤器
def check_permissions
redirect_to home_url and return false unless mine?
end
# 判断当前用户是否是测验的创建者
def mine?
@quiz.session_id == session.session_id
end
helper_method :mine?
# 包装会话以跟踪用户的测验结果
def scoreboard id=nil
return (session[:quizzes] ||= {}) unless id
return (scoreboard[id.to_i] ||= {})
end
# 清除指定测验的用户计分板
def reset_scoreboard id
scoreboard[id.to_i] = {}
end
# 记录问题的答案结果
def score id, q, right
scoreboard(id)[q.to_i] = right
end
# 获取指定测验的正确答案数组
def right(id) scoreboard(id).reject{ |q, v| !v } end
helper_method :right
# 获取指定测验的错误答案数组
def wrong(id) scoreboard(id).reject{ |q, v| v } end
helper_method :wrong
# 获取当前测验正确答案的ID组成的逗号分隔字符串
def right_keys
questions = right(@quiz.id)
questions.keys.join ','
end
end
6.3 视图和模板
应用的视图和模板包括布局视图
application.rhtml
、编辑模板
edit.rhtml
、显示模板
show.rhtml
等。
-
application.rhtml
:测验的顶级布局,包含一个添加新测验的简单Ajax表单和一个用于显示问题的
DIV
:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Review Quiz</title>
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag :defaults %>
</head>
<body>
<h1><%= link_to "Review Quiz", home_url %></h1>
<% form_for :new_quiz, Quiz.new, :url => quizzes_url,
:html => { :id => "new_quiz" } do |f| %>
<label for="new_quiz_name">Create a quiz:</label>
<%= f.text_field :name %> <%= submit_tag "Create" %>
<% end %>
<div class="clear"></div>
<%= yield %>
</body>
</html>
-
edit.rhtml:允许用户添加新问题、删除现有问题并通过拖放重新排序问题:
<h2>Edit: <%= @quiz.name %></h2>
<ul id="links">
<li id="done" <% unless @quiz.questions.any? %>
style="display: none"<% end %>>
<%= link_to "Take the quiz", quiz_url %>
</li>
</ul>
<ul id="quiz">
<% @quiz.questions.each do |question| %>
<%= render :partial => "edit_question",
:locals => { :question => question } %>
<% end %>
</ul>
<%= sortable_element :quiz, :url => reorder_quiz_url %>
<% remote_form_for :question, Question.new,
:url => create_q_quiz_url,
:html => { :id => "new_question",
:onKeyPress => "return Quiz.captureKeypress(event);" } do |f| %>
<div id="starting" <% if @quiz.questions.any? %>
style="display: none"<% end %>>
<strong>Add the first question</strong> to your new quiz.
</div>
<h3>Add a Question</h3>
<label for="question_question">Question</label>
<%= f.text_area :question %>
<label for="question_answer">Answer</label>
<%= f.text_area :answer %>
<%= submit_tag "Save" %>
<%= javascript_tag "$('question_question').focus( )" %>
<% end %>
-
show.rhtml:负责渲染给定的测验,包括计分板和供用户回答的问题列表:
<%= render :partial =>'scoreboard' %>
<h2><%= h(@quiz.name) %></h2>
<ul id="links">
<% if mine? %>
<li><%= link_to "Edit this quiz", edit_quiz_url %></li>
<% end %>
<li style="display: none" id="startover">
<%= link_to "Start Over", reset_quiz_url, :method => :post %>
</li>
</ul>
<div id="questions">
<%= render :partial => "question", :collection => @questions %>
</div>
<div id="finished" style="display: none">
<strong>You're done!</strong> Now you can
<%= link_to "start over", reset_quiz_url, :method => :post %>,
or just <%= link_to "review what you missed.", quiz_url %>
</div>
6.4 JavaScript代码
应用特定的JavaScript代码定义在
application.js
中,包含处理表单按键、显示提示信息、显示答案、提交答案等功能:
var Quiz = {
/* 处理创建问题表单中的回车事件 */
captureKeypress: function(evt) {
var keyCode = evt.keyCode ? evt.keyCode :
evt.charCode ? evt.charCode : evt.which;
if (keyCode == Event.KEY_RETURN) {
if(Event.element(evt).id=='question_question')
$('question_answer').focus( );
if(Event.element(evt).id=='question_answer')
$('new_question').onsubmit( );
return false;
}
return true;
},
/* 编辑测验时隐藏和显示帮助信息 */
updateHints: function( ) {
$('quiz').cleanWhitespace( );
if($A($('quiz').childNodes).any( )) {
$('done').show( );
$('starting').hide( );
}
},
/* 显示问题的答案节点 */
reveal: function(questionId) {
$(questionId+'_a').visualEffect('blind_down', {duration:0.25})
},
/* 处理提交答案 */
answer: function(quizId, questionId, right) {
var url = '/quizzes/' + quizId + ';answer';
var params ='question_id=' + questionId +
'&right=' + (right ?'true' : false) +
'&last=' + this.questions().last( ).id;
new Ajax.Request(url, {parameters:params});
$(questionId.toString( )).visualEffect('fade_up', {duration:0.5});
if(this.showingQuestions() && !$('finished').visible( ))
$('finished').visualEffect('appear_down');
$('startover').show( );
},
/* 返回所有问题的DOM节点 */
questions: function( ) {
var questions = $('questions');
questions.cleanWhitespace( );
return $A(questions.childNodes);
},
/* 判断是否有显示的问题节点 */
showingQuestions: function( ) {
return this.questions( ).select(function(e){
return e.visible( );
}).length==1;
}
}
// 自定义效果:结合BlindUp和Fade
Effect.FadeUp = function(element) {
element = $(element);
element.makeClipping( );
return new Effect.Parallel(
[ new Effect.Opacity(element, {from:1,to:0}),
new Effect.Scale(element, 0,
{scaleX:false,scaleContent:false,restoreAfterFinish: true}) ],
Object.extend({
to: 1.0,
from: 0.0,
transition: Effect.Transitions.linear,
afterFinishInternal: function(effect) {
effect.effects[0].element.hide( );
effect.effects[0].element.undoClipping( );
}}, arguments[1] || {})
);
}
// 自定义效果:结合BlindDown和Appear
Effect.AppearDown = function(element) {
element = $(element);
var elementDimensions = element.getDimensions( );
return new Effect.Parallel(
[ new Effect.Opacity(element, {from:0,to:1}),
new Effect.Scale(element, 100,
{from:0,to:1,scaleX:false,
scaleContent:false,restoreAfterFinish:true,
scaleMode:{originalHeight:elementDimensions.height,
originalWidth:elementDimensions.width}}) ],
Object.extend({
transition: Effect.Transitions.linear,
afterSetup: function(effect) {
effect.effects[0].element.makeClipping( );
effect.effects[0].element.setStyle({height:'0px'});
effect.effects[0].element.show( );
},
afterFinishInternal: function(effect) {
effect.effects[0].element.undoClipping( );
}}, arguments[1] || {})
);
}
通过以上介绍,我们了解了Web开发中的元素扩展方法、DOM构建器、JavaScript单元测试、实用方法以及一个完整的复习测验应用的实现。这些知识和技术可以帮助我们更好地开发和优化Web应用。
Web开发中的元素扩展、测试与应用实践
7. 样式表设计
应用的样式表对页面的布局和外观起着关键作用,以下是样式表的详细内容:
/* Basics */
/* ------------------------- */
html {
background-color: #ddd;
padding: 20px;
border-top: 8px solid #494;
height: 100%;
}
body {
width: 80%;
margin: 0 auto 0 auto;
padding: 0 20px 0 20px;
border-top: 1px solid #bbb;
border-right: 1px solid #999;
border-bottom: 1px solid #999;
border-left: 1px solid #bbb;
background-color: #fff;
font-family: helvetica, arial, sans-serif;
min-height: 100%;
}
h1 {
float: left;
}
h2 a {
font-size: 0.5em;
}
.clear {
clear: both;
}
#links {
margin-top: -1.7em;
padding-left: 15px;
list-style-type: square;
font-size: 0.7em;
}
/* Links */
/* ------------------------- */
a {
color: #a44;
text-decoration: none;
}
a:hover {
text-decoration: underline;
color: #464;
}
a.green, a.red, a.yellow {
text-transform: uppercase;
font-size: 0.7em;
padding: 1px 2px;
}
a.green {
color: #363;
background-color: #cfc;
border: 1px solid #696;
}
a.red {
color: #633;
background-color: #fcc;
border: 1px solid #966;
}
a.yellow {
color: #663;
background-color: #ffc;
border: 1px solid #996;
}
/* Create Quiz */
/* ------------------------- */
#new_quiz {
font-size: .7em;
text-transform: uppercase;
float: right;
margin-top: 20px;
background-color: #bdb;
padding: 5px 10px;
border: 1px solid #9b9;
}
#new_quiz input[type='text'] {
width: 100px;
font-weight: bold;
background-color: #cfc;
}
/* Edit Quiz */
/* ------------------------- */
#new_question {
clear: right;
background-color: #bdb;
padding: 5px 10px;
border: 1px solid #9b9;
width: 55%;
padding-right: 80px;
padding-top: 10px;
margin-top: 50px;
}
#new_question h3 {
margin-top: 0;
margin-bottom: 8px;
font-size: 0.7em;
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: bold;
}
#new_question label {
font-size: 0.7em;
text-transform: uppercase;
font-weight: normal;
float: left;
width: 65px;
margin-top: 5px;
}
#new_question textarea {
width: 100%;
display: block;
height: 40px;
vertical-align: top;
margin-bottom: 10px;
}
#new_question input {
margin-left: 65px;
}
#starting {
color: #331;
background-color: #ffc;
border: 1px solid #cca;
padding: 5px;
margin-bottom: 10px;
}
#finished {
color: #331;
background-color: #ffc;
border: 1px solid #cca;
padding: 10px;
width: 270px;
}
/* Take Quiz */
/* ------------------------- */
#questions {
padding-top: 20px;
}
.question .q {
margin-bottom: 10px;
}
.question .a {
margin-bottom: 30px;
margin-left: 30px;
}
/* Scoreboard */
/* ------------------------- */
#scoreboard {
padding: 6px;
float: right;
width: 150px;
color: #331;
background-color: #ffc;
border: 1px solid #cca;
text-align: center;
margin-left: 20px;
margin-bottom: 10px;
}
#scoreboard #total, #scoreboard #remaining {
text-transform: uppercase;
font-size: 0.7em;
color: #888;
letter-spacing: 0.1em;
}
#scoreboard #score {
font-weight: bold;
margin: 2px 0 3px 0;
}
#scoreboard #right {
color: #090;
}
#scoreboard #wrong {
color: #900;
}
样式表涵盖了多个方面的样式设置,以下是一些关键部分的总结:
| 部分 | 描述 |
| ---- | ---- |
| Basics | 设置HTML和body的背景、边框、字体等基本样式 |
| Links | 定义链接的颜色、悬停效果以及不同颜色链接的样式 |
| Create Quiz | 设置创建测验表单的样式 |
| Edit Quiz | 设置编辑测验表单和相关提示信息的样式 |
| Take Quiz | 设置测验问题显示的样式 |
| Scoreboard | 设置计分板的样式 |
8. 整体流程分析
下面通过一个mermaid流程图来展示复习测验应用的整体流程:
graph LR
A[用户访问首页] --> B{选择操作}
B -->|创建新测验| C[输入测验名称,提交表单]
C --> D[保存测验,记录会话ID]
D --> E[进入编辑测验页面]
E --> F{编辑操作}
F -->|添加问题| G[输入问题和答案,提交表单]
G --> H[保存问题,更新页面]
F -->|删除问题| I[删除问题,更新页面]
F -->|重新排序问题| J[拖放问题,更新位置,保存排序]
B -->|选择已有测验| K[进入测验页面]
K --> L[显示问题列表和计分板]
L --> M{回答问题}
M -->|回答正确| N[记录正确答案,更新计分板]
M -->|回答错误| O[记录错误答案,更新计分板]
N --> P{是否还有问题}
P -->|是| L
P -->|否| Q[显示完成信息,提供重新开始或复习选项]
从流程图可以看出,用户可以在首页选择创建新测验或选择已有测验。创建新测验后可以进行编辑操作,包括添加、删除和重新排序问题。选择已有测验后进入测验页面,回答问题并根据回答结果更新计分板,直到完成所有问题。
9. 应用特点总结
- 低门槛使用 :应用没有用户账户和登录机制,采用会话ID进行简单的身份验证,降低了新用户的使用门槛。
- Ajax交互 :大部分操作如添加问题、删除问题、重新排序问题、提交答案等都通过Ajax实现,提高了用户体验,减少了页面刷新。
- 代码结构清晰 :控制器负责数据管理,视图负责页面展示,JavaScript代码负责处理用户交互和页面效果,各部分职责明确。
- 可扩展性 :可以通过添加更多的功能,如用户账户系统、更多的测验类型、统计分析等,进一步扩展应用的功能。
10. 技术要点回顾
- 元素扩展方法 :提供了一系列操作DOM元素的方法,方便对元素进行文本收集、透明度设置、子元素查找等操作。
-
DOM构建器
:使用
Builder.node()方法可以动态创建复杂的DOM结构,如表格。 -
JavaScript单元测试
:通过
Test.Unit.Runner和各种断言方法,可以对JavaScript代码进行单元测试,确保代码的正确性。 -
实用方法
:
script.aculo.us提供的实用方法,如版本获取、文件加载、颜色转换等,提高了开发效率。
通过对这些技术要点的掌握和应用,我们可以更好地开发和优化Web应用,实现丰富的交互效果和功能。
综上所述,Web开发涉及到多个方面的知识和技术,从元素扩展到应用实践,每个环节都有其重要性。通过不断学习和实践,我们可以提升自己的开发能力,打造出更加优秀的Web应用。
超级会员免费看
1487

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



