在開篇以前,先附上日曆的代碼地址和演示地址,代碼是本文要分析的代碼,演示效果是本文要實現的效果
代碼地址:https://github.com/aspwebchh/javascript-control/tree/master/calendar
演示地址: https://www.chhblog.com/html/demo/calendar.htmljavascript
本文的目的除了詳細說明開發一款具有基本功能的網頁日曆的方法與細節之外,還附加說明了如何合理的組織日曆特效的代碼和所以帶來的好處。css
按照本文的教程開發出來的效果以下html
他具備選擇年月日、選擇今天、清空文本框這些日曆的基本功能,能知足平常項目中出現的普通日期選擇需求, 算的上是五臟俱全的小麻雀。java
本文主要描述JavaScript實現的細節,日曆的CSS佈局細節將被省略,有興趣的同窗可閱讀calendar.css中的css代碼獲知實現方法。git
此日曆特效由原生JavaScript代碼寫成,並不依賴jQuery等第三方框架。它的JavaScript代碼由三個文件構成github
公用函數庫文件, 裏面的函數都是通用型的,並不只僅和特效相關,在任何網頁特效中均可以使用它們web
一個純粹的、通用的日曆特效的全部代碼,更任何其它頁面元素沒有關係,好比說用來放置日期的文本框ajax
合理調用calendar_core.js中的代碼來構建一個真正可使用的日曆特效。編程
關於calendar_core.js和calendar.js的說明,彷佛有點使人犯迷糊,不過這沒關係,經過下面的詳細講解,會使讀者瞭解到這兩個文件中代碼做用與區別。設計模式
由於JavaScript在一些常規的編程概念上沒有統一的實現方法的緣故,在介紹日曆的核心實現邏輯之情,先介紹下代碼中全部使用的容易分散讀者注意力或者形成讀者出現理解誤差的語法細節。
此案列的大部分代碼會被這樣一段代碼包圍起來
(function(){ //功能代碼 })();
其實這麼作的主要目的是爲了讓變量名稱和函數的名稱全局名稱空間, 換句話說就是讓用不到它的地方看不到它。
那爲何這個function要被一個括號括起來,並且在這個括號後面再加上一個括號。 括號的做用很簡單,跟小學數學中所學的括號做用同樣,是用來提高運算優先級的,好比說(1+2)*3,其中(1+2)會被優先與乘法運算,返回的結果就是3。可JavaScript沒有規定,括號中必須放置四則運算表達式,括號中也能夠放別的東西,好比說函數。這麼一說就好理解了
(function(){ //功能代碼 })();
這段代碼可一被分解爲兩步, 第一個括號的做用是返回括號中的函數,第二個括號的做用是調用第一個括號返回的函數,這跟下面這段代碼是一個意思,只是合在一塊兒能夠省略函數名。
var func = (function(){ //功能代碼 }); func();
非ES6的JavaScript語法不支持類,可是類是不可缺乏的編程元素,所幸JavaScript能夠經過function關鍵字模擬類的實現。
常規的模擬方法是使用function和function的它的prototype屬性,可這麼作沒法實現面向對象中private關鍵字的效果,因此我在這個案列中並無採用這種方法,而是使用了
function Klass(){ this.publicFunc1 = function(){} this.publicFunc2= funciton(){} var privateFunc1 = funciton(){} var privateFunc2 = function(){} } var klass = new Klass(); Klass.publicFunc1(); Klass.publicFunc2();
這種方式模擬類的實現。
此文件中有4個函數
爲DOM對象綁定事件。由於要兼容低版本IE,因此特意封裝成函數
移除DOM對象綁定的事件
得到html元素在頁面中的位置。使用場景如點擊輸入框彈出日曆時將日曆定位到文本框下方就要用到這個函數。
檢查日期字符串格式是否合法
這4個函數的代碼在文中略去,有須要的讀者可直接查看源代碼
此文件包含日曆特效的核心功能,其中有一個函數和一個類。
函數代碼以下
var instanceCount = 0; function newCalendarID(){ return 'calender_' + ( ++instanceCount ); }
這個函數的做用是生成表明日曆DOM元素的ID。 不少時候, 一個頁面上不會只有一個日曆,以下圖
因此必需要一個不重複的值做爲不一樣日曆HTML元素的ID,以防止JavaScript操做日曆html元素時形成衝突。 newCalendarID經過自增一個數值變量並結合一個字符串來生成日曆的ID,生成的ID格式以下
calender_1
calender_2
calender_3
calender_4
這個函數在日曆的構造函數中被調用,每次實例化一個日曆時爲日曆html元素賦予一個ID。
類 Calender 封裝了實現日曆功能的代碼,包括生成日曆、年份月份切換、 選擇清空日期等等。
Calender 類的公共接口以下
function Calender() { //事件 this.onClear = function() {}; this.onSetToday = function() {}; this.onSelected = function( y, m, d ) {}; //方法 this.render = function( placeholder ) {} this.setDate = function( y, m, d ) {} this.position = function( left, top ) {} this.hide = function() {} this.contains = function( target ) {} }
咱們經過從外到內的模式講解日曆類的實現,先講解日曆的接口,再講解代碼的細節。
首先,有有點讀者要明白,Calender類表示的就是日曆,是那個在網頁上實現日期選擇功能的日曆特效。
當實例化Calender對象並調用對象的render方法,一個日曆就被顯示在網頁上了。 代碼以下
var c = new Calender(); c.render();
render方法就是用來生成日曆特效的html元素,並將元素添加到頁面上,執行後的效果以下圖
setDate 方法用來設置日曆的日期 ,接受年月日三個參數。日曆初始化時持有的日期是當前的日期,所以日曆界面上當前日期的位置被設爲選中狀態。然而,有時候咱們但願被選中的日期是任意的而非只能是今天,這個時候setDate方法就能派上用場。
position 方法用來設置日曆在頁面上的位置,接受left、top兩個參數。 好比說,當調用render方法初始化日曆後,咱們想讓日曆顯示在頁面中間,能夠這樣作
var c = new Calender(); c.render(); c.position(window.innerWidth / 2, window.innerHeight / 2)
代碼執行效果以下圖
hide方法用來隱藏日曆,contains方法用來檢查html元素是否包含在日曆元素之中,這兩個方法在接下來的功能實現剖析中有使用的場景。
this.onSelected = function( y, m, d ) {}; this.onClear = function() {}; this.onSetToday = function() {};
這三個方法其實並非方法,而是事件,就像html元素的onclick事件同樣,會在特定的時候被觸發。
onSelected事件在選中日期時被觸發
onClear事件在點擊日曆右下角的「清空」按鈕時被觸發
onSetToday事件在點擊日曆右下角的「今天」按鈕時被觸發
以一個最基本的最多見的日期選着並填充文本框爲例,咱們能夠經過結合這三個事件和上面講解的部分方法來實現, 代碼以下
//得到文本框元素 var dateInput = document.getElementById("date"); //實例化日曆對象 var calender = new Calender(); //綁定onSelected事件,當選中日期後被執行 calender.onSelected = function(y,m,d) { //填充選中的日期至文本框 dateInput.value = [y,m,d].join("-"); //填充後隱藏日曆 this.hide(); } //綁定onSetToday事件,當點擊今天按鈕後被執行 calender.onSetToday = function() { var now = new Date(); //填充當前日期至文本框 dateInput.value = now.getFullYear() + '-' + ( now.getMonth() + 1 ) + '-' + now.getDate(); //填充後隱藏日曆 this.hide(); } //綁定onClear事件,當點擊清空按鈕後被執行 calender.onClear = function() { //清空文本框 dateInput.value = ""; //填充後隱藏日曆 this.hide(); } //初始化日曆 calender.render(); //由於初始化後的日曆會顯示在頁面上,因此須要事先隱藏 calender.hide(); //但文本框得到焦點時顯示日曆 dateInput.onfocus = function() { //得到文本框在頁面中的位置,getOffset方法在以前講解過 let offet = getOffset(this); //讓日曆現實在文本框的下方 calender.position(offet.left, offet.top + 20); }
效果如圖
不知道讀者們有沒有從這段代碼中發現,日曆特效自己和輸入框之間是沒有任何關聯,它們之間的交互是經過那三個事件間接進行的,這正是軟件工程中「低耦合」設計原則的體現。在日曆和輸入框之間有一個銜接層,這個銜接層就是那三個事件, 這三個事件是能夠動態設置的, 假如需求改變,咱們點擊日曆時不想將值填充到文本框,而是想直接將日期發送至服務器,那麼咱們只須要將onSelected事件中的代碼改成發送數據的ajax請求代碼便可, 日曆類自己的代碼徹底不用改動, 這極大的下降的代碼的維護成本。
其實這中經過事件去解耦的代碼設計方式隨處可見,好比咱們點擊一個按鈕彈出一個提示消息這樣的效果
var btn = document.getElementById("btn"); btn.onclick = function() { alert("hello world"); }
如上面的代碼,用的也是一樣的思路,html按鈕元素和其它JavaScript效果是沒有聯繫的,然而它必然要和外部交互,好比點擊的時候執行某個動做,否者就沒有存在的意義了。如何作到既不與外部元素綁死又能與外部元素交互?答案就是增長一個銜接層,這個銜接層就是「事件」。咱們的日曆特效不正也是採用這種作法嗎。
若是瞭解設計模式的讀者應該能看的出來,這實際上是策略模式的應用,若是更貼切一點也能夠說是觀察者模式的應用。
接下來咱們再講講Calender類內部的構建。
Calender類有6個私有的成員變量
var calendarID = newCalendarID(); var self = this; var calendarEl; var selectedYear; var selectedMonth; var selectedDate;
calendarID ,日曆html元素的ID, 調用newCalendarID方法生成, 關於此函數的細節在前文有過介紹。
self,保存Calender的this指針,供程序上下文中有須要的地方使用,由於JavaScript中this指針不肯定的緣由,要在類中正確的使用this指針,必須在某個this值還指向類自身的地方將它保存下來,以供應後面的代碼使用。
calendarEl,日曆html元素的根元素。日曆是動態生成的html元素,此變量指向的就是日曆html元素的DOM對象。
selectedYear,日曆選中日期的年份
selectedMonth,日曆選中日期的月份
selectedDate,日曆選中日期的天
Calender類中除了有這六個私有變量之外,還有一系列私有方法
getStartDate
getEndDate
getContentItemHtml
getContentHtml
getCalendarHtml
getElement
genCalanderElementID
monthChangeAction
yearChangeAction
initCalendar
refreshCalender
這些方法不是Calender類對外公佈接口的一部分,可是他們參與了實現日曆的功能。 在這裏咱們不一個一個的介紹方法的做用,咱們根據日曆初始化代碼的執行順序來介紹他們,輪到誰就介紹誰。
日曆類被實例化後render方法首先被調用。
var calender = new Calender(); calender .render();
newCalender()實例化的過程很簡單,無非就是聲明和初始化部分紅員變量的值,真正的大戲是render方法被調用。
this.render = function( placeholder ) { var now = new Date(); selectedYear = now.getFullYear(); selectedMonth = now.getMonth(); selectedDate = now.getDate(); initCalendar( selectedYear, selectedMonth, selectedDate, placeholder ); }
rander方法接受一個placeholder參數,這個參數是一個html元素的ID,表示日曆初始化後所在的位置,也就是說當表示日曆的html元素生成後,會成爲ID爲這個參數的值的元素的子元素,假如調用render方法時不指定這個參數, 那麼日曆html成爲body子元素。
接着,render方法會將類的三個表示選中的年月日的成員變量設置爲當前的年月日,而後在調用私有方法initCalendar初始化日曆, initCalendar承載着生成日曆的主要工做。
var initCalendar = function( placeholder ) { calendarEl = document.createElement( 'div' ); calendarEl.id = calendarID; calendarEl.className = 'aspwebchh'; calendarEl.innerHTML = getCalendarHtml(); placeholder = placeholder ? document.getElementById( placeholder ) : document.body; placeholder.appendChild( calendarEl ); refreshCalender(selectedYear, selectedMonth, selectedDate); monthChangeAction(); yearChangeAction(); dateSelectedChangeAction(); }
咱們知道 calendarEl 成員變量表示日曆的html元素,在initCalendar方法中,它被初始化了。 從代碼中可一看出,它是一個div元素,被設置一個惟一id,被設置一個class, 日曆的html結構由 getCalendarHtml 方法生成, 並被設爲 id 爲placeholder的值的子元素,若是id爲placeholder的元素不存在,那麼由body元素代替它。
如今,咱們來重點看看 getCalendarHtml 這個方法,日曆的html結構是由它動態生成的。
var getCalendarHtml = function() { var html = ' <div class="calendar_tool" id="'+ genCalanderElementID("tool") +'">'+ ' <div class="calendar_month">'+ ' <select id="'+ genCalanderElementID("month_select") +'"><option value="0">1月</option>'+ ' <option value="1">2月</option>'+ ' <option value="2">3月</option>'+ ' <option value="3">4月</option>'+ ' <option value="4">5月</option>'+ ' <option value="5">6月</option>'+ ' <option value="6">7月</option>'+ ' <option value="7">8月</option>'+ ' <option value="8">9月</option>'+ ' <option value="9">10月</option>'+ ' <option value="10">11月</option>'+ ' <option value="11">12月</option></select>'+ ' </div>'+ ' <div class="calendar_year">'+ ' <input type="button" value="<" class="calendar_year_left" id="'+ genCalanderElementID("year_prev") +'"><input'+ ' type="text" class="calendar_year_input" id="'+ genCalanderElementID("year_input") +'"><input type="button"'+ ' value=">" class="calendar_year_right" id="'+ genCalanderElementID("year_next") +'">'+ ' </div>'+ ' </div>'; html += '<div class="calendar_content" id="'+ genCalanderElementID("date_list") +'"></div>'; html += '<div class="calendar_action">' + '<input type="button" value="清空" id="'+ genCalanderElementID("clear") +'">' + '<input type="button" value="今天" id="'+ genCalanderElementID("today") +'">'+ '</div>'; return '<div class="calendar_body">' + html + '</div>'; }
由代碼能夠看出,getCalendarHtml 方法就是經過動態拼接JavaScript字符串生成日曆的html的。在此方法中, 仍是一個 genCalanderElementID 方法被頻繁的調用,這個方法的代碼以下
var genCalanderElementID = function( id ) { return calendarID + "_" + id; }
他的做用就是用來生成日曆的一些子元素的ID, 固然,生成的ID全局惟一的, 由於它的前綴就是標識日曆惟一性的calendarID。
標紅的就是用這個方法生成的ID
這個時候生成的日曆html元素還並不完整,日期部分處於缺失狀態, 以下圖
咱們再回處處於調用棧上一層的initCalendar方法中來,當日歷的外圍html結構生成完畢之後,接着會調用 refreshCalender 方法。
refreshCalender方法的做用是刷新日曆的界面, 它接受年月日三個參數, 根據這三個參數來更新日曆的界面。
上面兩長圖片是分別給refreshCalender傳遞2018,5,16和2018,6,13兩組參數的執行結果,能夠看出,此方法是整個日曆特效的核心方法,日曆界面的更新變化都要靠它。
refreshCalender方法作三件事請。
getContentHtml 方法接受年和月兩個參數,生成整一個月份的html
上圖就是 getContentHtml 方法生成的內容。 方法的開頭有這樣兩行代碼用來得到日期範圍。
var startDate = getStartDate( y, m ); var endDate = getEndDate( y, m );
這個日期範圍是必須的。日曆效果的一個特色是要作到星期和日期對應,望一眼日期就能知道是星期幾。此外,日曆界面還要保持工整, 所以, 咱們必需要知道日曆的第一週的開始時間是幾號,日曆的最後一週結束日期是幾號, 要知道爲保持日曆界面的工整,每一頁日曆展現的日期都是須要跨月的,上面的兩行代碼就是得到每一頁日曆的開始日期和結束日期的。
以上圖爲例,一個5月份的日曆,那麼這一頁的開始日期是4月29日,週日;結束日期是6月5日,週二。
以後的代碼就是根據這個時間範圍生成html,並經過判斷日期給每一個日期元素加上對應的css class屬性, 由於咱們要讓非本月份的日期顯示成灰色, 本月份的日期顯示成藍色,當前日期擁有藍色背景。具體的實現細節能夠經過閱讀下面的代碼清單獲知,在這裏就不贅述了。
var getStartDate = function( y, m ) { var dt = new Date( y, m, 1 ); var week = dt.getDay(); dt.setDate( dt.getDate() - week ); return dt; } var getEndDate = function( y, m ) { var dt = new Date( y, m ,1 ); dt.setMonth( dt.getMonth() + 1 ); dt.setDate( 0 ); return dt; } var getContentItemHtml = function( date, currMonth ) { var content = ''; if( date.getDate() == selectedDate && date.getMonth() == selectedMonth && date.getFullYear() == selectedYear ) { content += '<li class="selected">'; } else if( currMonth == date.getMonth() ) { content += '<li class="c">' } else { content += '<li>'; } content += '<a href="javascript:;">' + date.getDate() +'</a>'; content += '</li>'; return content; } var getContentHtml = function( y, m ) { var startDate = getStartDate( y, m ); var endDate = getEndDate( y, m ); var title = '<dl class="calendar_title"><dd>日</dd><dd>一</dd><dd>二</dd><dd>三</dd><dd>四</dd><dd>五</dd><dd>六</dd></dl>'; var content = '<ul>'; for( var i = 0; i < 38; i++ ) { content += getContentItemHtml(startDate, m); if( ( i + 1 ) % 7 == 0 ) { content += '</ul><ul>'; } startDate.setDate( startDate.getDate() + 1 ); } content += '</ul>'; return title + content; }
讓咱們再回到 initCalendar 方法中來,調用 refreshCalender 方法後, 接下是
monthChangeAction(); yearChangeAction(); dateSelectedChangeAction();
這三個方法的調用。
這三個方法的做用是給日曆中的元素綁定操做效果事件的。
monthChangeAction方法用於當日歷的月份選擇的值改變時刷新日曆的日期部份內容
var monthChangeAction = function() { var monthSelect = getElement( 'month_select' ); var yearInput = getElement( 'year_input' ); addEventHandler( monthSelect, 'change', function() { var month = this.value; var year = yearInput.value; getElement( 'date_list' ).innerHTML = getContentHtml( year, month ); } ); }
yearChangeAction方法用於當日歷的年份改變時刷新日曆的日期面板
var yearChangeAction = function() { var monthSelect = getElement( 'month_select' ); var yearInput = getElement( 'year_input' ); var yearPrev = getElement( 'year_prev' ); var yearNext = getElement( 'year_next' ); addEventHandler( yearInput, 'blur', function() { if( /[^\d]+/.test( this.value ) ) { this.value = this.value.replace( /[^\d]+/g, '' ); } getElement( 'date_list' ).innerHTML = getContentHtml( yearInput.value, monthSelect.value ); } ); addEventHandler( yearPrev, 'click', function() { var year = yearInput.value; var month = monthSelect.value; getElement( 'date_list' ).innerHTML = getContentHtml( --year, month ); yearInput.value = year; } ); addEventHandler( yearNext, 'click', function() { var year = yearInput.value; var month = monthSelect.value; getElement( 'date_list' ).innerHTML = getContentHtml( ++year, month ); yearInput.value = year; } ); }
yearChangeAction相對monthChangeAction較爲複雜,由於它不但要處理年份輸入框的事件, 還要處理「上一年」和 「下一年」兩個按鈕的的事件處理。
dateSelectedChangeAction用於處理日期選擇事件、「清空」按鈕事件、「今天」按鈕事件, 從方法的代碼結構中就能夠看出方法的功能由這三部分構成。
var dateSelectedChangeAction = function() { //日期選中處理 addEventHandler( getElement( 'date_list' ), 'click', function( e ) { e = e || window.event; var t = e.target || e.srcElement; if( t.tagName != 'A' ) { return; } var year = getElement( 'year_input' ).value; var month = getElement( 'month_select' ).value; var date = t.innerHTML; if( typeof( self.onSelected ) == 'function' ) { self.onSelected( parseInt( year ), parseInt( month ), parseInt( date ) ); } } ); //「清空」按鈕點擊處理 addEventHandler( getElement( 'clear' ), 'click', function() { if( typeof( self.onClear ) == 'function' ) { self.onClear(); } } ); //「今天」按鈕點擊處理 addEventHandler( getElement( 'today' ), 'click', function() { if( typeof( self.onSetToday ) == 'function' ) { self.onSetToday(); } } ); }
這三部分事件處理代碼其實自己不執行具體的功能, 它們的真正做用是觸發另外一個事件。具體一點說,這個方法作了這麼三件事情
這三個事件咱們在前面講過是用於解除日曆自己與使用日曆的頁面的耦合的,如此能使日曆更加通用化。
至此calendar_core.js中的Calender類的內部結構已經解析完畢,一款功能完善的日曆特效呈如今了咱們面前
window.Calender = Calender;
經過這行代碼導出日曆類,咱們就能夠在外部使用它了。
接下來咱們說說如何去使用它。
一般,日曆特效的使用都會伴隨着輸入框,以下圖所示
當日歷上的日期被選中時,着個日期會別填充到輸入框裏。 同時,這個日曆是但實例的,無論頁面上有多少個輸入框須要輸入日期,同一時刻,頁面上只能有一個日曆, 一個日曆服務與多個不一樣的文本框。
calendar.js文件中的代碼示例就是以此模式實現。
(function(){ var single; var element; function closeHandler( e ) { e = e || window.event; var t = e.target || e.srcElement; if( single.contains( t ) ) { return; } if( t == element ) { return; } single.hide(); } function checkElementValue() { if( !element.checkDateAction ) { addEventHandler( element, 'blur', function() { if( this.value != '' && this.value != undefined && !checkDate( this.value ) ) { alert( '日期格式不正確' ); this.value = ''; } } ); element.checkDateAction = true; } } function renderCalendar() { if( !single ) { single = new Calender(); single.onSelected = function( y, m, d ) { var datestr = y + '-' + ( m + 1 ) + '-' + d; element.value = datestr; this.hide(); } single.onSetToday = function() { var now = new Date(); element.value = now.getFullYear() + '-' + ( now.getMonth() + 1 ) + '-' + now.getDate(); this.hide(); } single.onClear = function() { element.value = ''; this.hide(); } single.render(); } var offset = getOffset( element ); single.position( offset.left, offset.top + element.offsetHeight ); } function initCalendarSelectedValue() { var date = element.value; if( checkDate( date ) ) { var date = date.replace(/\-|\/|\./g,"/"); var ymd = date.split("/"); var y = parseInt(ymd[0]); var m = parseInt(ymd[1]) - 1; var d = parseInt(ymd[2]); single.setDate( y, m, d ); } } function calendar() { var e = calendar.caller.arguments[0] || window.event; element = e.target || e.srcElement; removeEventHandler( document.documentElement, 'click', closeHandler ); checkElementValue(); renderCalendar(); initCalendarSelectedValue(); addEventHandler( document.documentElement, 'click', closeHandler ); } window.calendar = calendar; })();
calendar.js中有2個全局變量和5個函數
var single; var element;
closeHandler() checkElementValue() renderCalendar(element) initCalendarSelectedValue() calendar()
變量single是日曆的實例,它只被初始化一次,能夠把它當作一個單列。
變量element是調用日曆的輸入框,它會在calendar函數調用時被重複賦值,引用當前input輸入框的DOM對象。
calendar是主函數,惟一一個被導出到頁面使用的函數,其它的函數是calendar函數功能的部分,爲了使代碼易於維護纔將他們提煉成爲函數。
咱們看到,其它4個函數都會在calendar函數中的某個位置出現
closeHandler 是一個工具函數, 用於實現點擊頁面上除日曆自己之外的任何位置便隱藏日曆的效果的。
checkElementValue 用於檢查文本框中默認有值的狀況下值的格式是否正確,假如不正確則給予提示。
renderCalendar用於實例化日曆,並設置相應的事件,被初始化的實例是惟一的, 與此同時, 日曆將被顯示在輸入框的下方。
initCalendarSelectedValue用於將輸入框中的默認值設置爲日曆的當前日期。
最後, calendar函數被導出
window.calendar = calendar;
在頁面中使用便可,使用方式很簡潔
<input type="text" onfocus="calendar()" id="begin_time" />
觸發輸入框的focus事件即能使用日曆。
至此,一款完整的日曆的全部細節展示在了咱們面前。這款日曆功能很簡單, 可它有一個優勢,它的代碼結構清晰,類和函數之間,方法與方法之間,職責異常清晰。 日曆自己與頁面之間是解耦的,互相之間經過事件進行通訊, 這使得日曆的代碼複用能力變強了,若是咱們哪天想把這個日曆挪做他用, 只須要提取出calendar_core.js中的代碼,稍微改動便可,至於calender.js中的代碼徹底能夠忽視。這是高內聚低耦合軟件設計思想的體現,以被業界證實是有效的提高代碼可維護性的思想,除了日曆,也適合在任何程序設計環境中使用。因此, 這篇文章與其說是在講解日曆特效的編寫,還不如說是在講解如何設計出結構優良的代碼的方法,從某種角度來說,這比寫出炫麗的JavaScript特效更加有用。