1. 程式人生 > >關於領域驅動設計(DDD)中聚合設計的一些思考

關於領域驅動設計(DDD)中聚合設計的一些思考

關於DDD的理論知識總結,可參考這篇文章。

DDD社群官網上一篇關於聚合設計的幾個原則的簡單討論:

  1. 聚合是用來封裝真正的不變性,而不是簡單的將物件組合在一起;
  2. 聚合應儘量設計的小;
  3. 聚合之間的關聯通過ID,而不是物件引用;
  4. 聚合內強一致性,聚合之間最終一致性;

上面這幾條原則,作者通過一個例子來逐步闡述。下面我按照我的理解對每個原則做一個簡單的描述。

聚合是用來封裝真正的不變性,而不是簡單的將物件組合在一起

這個原則,就是強調聚合的真正用途除了封裝我們本身所關心的資訊外,最主要的目的是為了封裝業務規則,保證資料的一致性。在我看來,這一點是設計聚合時最重要和最需要考慮的點;當我們在設計聚合時,要多想想當前聚合封裝了哪些業務規則,實現了哪些資料一致性。所謂的業務規則是指,比如一個銀行賬號的餘額不能小於0,訂單中的訂單明細的個數不能為0,訂單中不能出現兩個明細對應的商品ID相同,訂單明細中的商品資訊必須合法,商品的名稱不能為空,回覆被建立時必須要傳入被回覆的帖子(因為沒有帖子的回覆不是一個合法的回覆),等;

聚合應儘量設計的小

這個原則,更多的是從技術的角度去考慮的。作者通過一個例子來說明,該例子中,一開始聚合設計的很大,包含了很多實體,但是後來發現因為該聚合包含的東西過多,導致多人操作時併發衝突嚴重,導致系統可用性變差;後來開發團隊將原來的大聚合拆分為多個小聚合,當然,拆分為小聚合後,原來大聚合內維護的業務規則同樣在多個小聚合上有所體現。所以實現了既能解決併發衝突的問題,也能保證讓聚合來封裝業務規則,實現模型級別的資料一致性;另外,回覆中的一位道友“”提到,聚合設計的小還有一個好處,就是:業務決定聚合,業務改變聚合。聚合設計的小除了可以降低併發衝突的可能性之外,同樣減少了業務改變的時候,聚合的拆分個數,降低了聚合大幅重構(拆分)的可能性,從而能讓我們的領域模型更能適應業務的變化。

聚合之間通過ID關聯

這個原則,是考慮到,其實聚合之間無需通過物件引用的方式來關聯;

  1. 首先通過引用關聯,會導致聚合的邊界不夠清晰,如果通過ID關聯,由於ID是值物件,且值物件正好是用來表達狀態的;所以,可以讓聚合內只包含只屬於自己的實體或值物件,那這樣每個聚合的邊界就很清晰;每個聚合,關心的是自己有什麼資訊,自己封裝了什麼業務規則,自己實現了哪些資料一致性;
  2. 如果通過引用關聯,那需要實現LazyLoad的效果,否則當我們載入一個聚合的時候,就會把其關聯的其他聚合也一起載入,而實際上我們有時在載入一個聚合時,不需要用到關聯的那些聚合,所以在這種時候,就給效能帶來一定影響,不過幸好我們現在的ORM都支援LazyLoad,所以這點問題相對不是很大;
  3. 你可能會問,聚合之間如果通過物件引用來關聯,那聚合之間的互動就比較方便,因為我可以方便的直接拿到關聯的聚合的引用;是的,這點是沒錯,但是如果聚合之間要互動,在經典DDD的架構下,一般可以通過兩種方式解決:1)如果A聚合的某個方法需要依賴於B聚合物件,則我們可以將B聚合物件以引數的方式傳遞給A聚合,這樣A對B沒有屬性上的關聯,而只是引數上的依賴;一般當一個聚合需要直接訪問另一個聚合的情況往往是在職責上表明A聚合需要通知B聚合做什麼事情或者想從B聚合獲取什麼資訊以便A聚合自己可以實現某種業務邏輯;2)如果兩個聚合之間需要互動,但是這兩個聚合本身只需要關注自己的那部分邏輯即可,典型的例子就是銀行轉賬,在經典DDD下,我們一般會設計一個轉賬的領域服務,來協調源賬號和目標賬號之間的轉入和轉出,但源賬號和目標賬號本身只需要關注自己的轉入或轉出邏輯即可。這種情況下,源賬號和目標賬號兩個聚合例項不需要相互關聯引用,只需要引入領域服務來協調跨聚合的邏輯即可;
  4. 如果一個聚合單單儲存另外的聚合的ID還不夠,那是否就需要引用另外的聚合了呢?也不必,此時我們可以將當前聚合所需要的外部聚合的資訊封裝為值物件,然後自己聚合該值物件即可。比如經典的訂單的例子就是,訂單聚合了一些訂單明細,每個訂單明細包含了商品ID、商品名稱、商品價格這三個來自商品聚合的資訊;此時我們可以設計一個ProductInfo的值物件來包含這些資訊,然後訂單明細持有該ProductInfo值物件即可;實際上,這裡的ProductInfo所包含的商品資訊是在訂單生成時對商品資訊的狀態的冗餘,訂單生成後,即便商品的價格變了,那訂單明細中包含的ProductInfo資訊也不會變,因為這個資訊已經完全是訂單聚合內部的東西了,也就是說和商品聚合無關了。
  5. 實際上通過ID關聯,也是達到設計小聚合的目標的一種方式;

聚合內強一致性,聚合之間最終一致性

這個原則主要的背景是:如果用CQRS+Event Sourcing的架構來實現DDD,那聚合之間因為通過Domain Event(領域事件)來實現互動了,所以同樣也不需要聚合與聚合之間的物件引用,同時也不需要領域服務了,因為領域服務已經被Process(流程聚合根)和Process Manager(流程管理器,無狀態)所替代。流程聚合根,負責封裝流程的當前狀態以及流程下一步該怎麼走的邏輯,包括流程遇到異常時的回滾處理邏輯;流程管理器,無狀態。負責協調流程中各個參與者聚合根之間的訊息互動,它會接受聚合根產生的domain event,然後傳送command另外一方面,由於CQRS的引入,使得我們的domain只需要處理業務邏輯,而不需要應付查詢相關的需求了,各種查詢需求專門由各種查詢服務實現;所以我們的domain就可以非常瘦身,僅僅只需要通過聚合根來封裝必要的業務規則(保證聚合內資料的強一致性)即可,然後每個聚合根做了任何的狀態變更後,會產生相應的領域事件,然後事件會被持久化到EventStore,EventStore用來持久化所有的事件,整個domain的狀態要恢復,只需要通過Event Sourcing的方式還原即可;另外,當事件持久化完成後,框架會通過事件匯流排將事件釋出出去,然後Process Manager就可以響應事件,然後傳送新的command去通知相應的聚合根去做必要的處理;

上面這個過程可以在任何一個CQRS的架構圖(包括enode的架構圖)中找到,我這裡就不貼圖了。enode中對經典的轉賬場景用這種思路實現了一下,有興趣可以去下載enode原始碼,然後看一下其中的BankTransferSample這個例子就清楚了。另外,因為事件的響應和Command的傳送是非同步的,所以,這種架構下,聚合根的互動是非同步的;

需要再次強調的一點是,聚合如果只需要關注如何實現業務規則而不需要考慮查詢需求所帶來的好處,那就是我們不需要在domain裡維護各種統計資訊了,而只要維護各種業務規則所潛在的必須依賴的狀態資訊即可;舉個例子,假如一個論壇,有版塊和帖子,以前,我們可能會在版塊物件上有一個帖子總數的屬性,當新增一個帖子時,會對這個屬性加1;而在CQRS架構下,domain內的版塊聚合根無需維護總帖子數這個統計資訊了,總帖子數會在查詢端的資料庫獨立維護;

從聚合和哲學的角度思考,為什麼需要狀態?

聚合的角度

首先,什麼是狀態?很簡單,比如一個商品的庫存資訊,那麼該庫存資訊有一個商品的數量這個屬性,表示當前商品在庫存中還有多少件;那麼我們為什麼需要記錄該屬性呢?也就是為什麼需要記錄這個狀態呢?因為有業務規則的存在。以這個例子為例,因為存在“商品的庫存不能為負數”這樣的一個業務規則,那這個規則如果要能保證,首先必須先記錄商品的庫存數量;因為商品的庫存數量是會隨著商品的賣出而減少的,而減少就是通過:Product.Count = Product.Count - 1這樣的邏輯運算來實現;這個邏輯運算要能執行的前提就是商品要有庫存資訊。從這個例子我們不難理解,一個聚合根的很多狀態,不是平白無辜設計上去的,而是某些業務規則潛在的要求,必須要設計這些狀態才能實現相應的業務規則;這樣的例子還有很多,比如銀行賬號的餘額不能小於0,導致我們的銀行賬號必須要設計一個當前餘額的屬性;

另外一個原因是,看起來像是廢話,呵呵。就是:因為我們關心這些資訊,所以需要設計在當前聚合上;比如,以一個論壇的帖子為例,作為一個帖子,我們通常都會關心帖子的標題、描述、發帖人、發帖時間、所屬版塊(如果論壇有版塊這個概念的話);所以,我們就會在帖子聚合根上設計出這些屬性,以表達我們所關心的這些資訊的狀態;

哲學的角度

下面在從偏哲學的角度表達一下物件的概念吧:

人類永遠無法認識完整的事物,因為我們認識到的總是事物的某一方面。我們所說的物件實際上是客觀事物在人頭腦裡的反應,而事物則是不因人的認識發生改變的客觀存在。同樣一根鐵棒,在鋼材生產廠家看來,它是成品;在機械加工廠家看來,它是原料;在廢品站看來,他是商品。成品、原料、商品,這三者擁有不同的屬性,有本質的不同。為什麼同一事物在不同人的眼裡就截然不同了呢?這是因為我們總是取對我們有用的方面來認識事物。當這根鐵棒作為商品時,它的原料屬性依然存在,只是我們不關心了。 所以,總結出來就是,因為我們關心一個物件的某些方面,所以我們才會為他設計某些狀態屬性;

關於聚合的設計的一些思考

上面只是簡單提到,聚合的設計應該多考慮它封裝了哪些業務規則這個問題。下面我想再多講一點我的一些想法:

關於GRASP九大模式中的最重要模式:資訊專家模式

還是以論壇的帖子為例,建立一個帖子時,有一個業務規則,那就是帖子的發帖人、標題、描述、所屬板塊(如果論壇有板塊這個概念的話)都不能為空或無效的值,因為這些資訊只要有任何一個無效,那就意味著被創建出來的帖子是無效的,那就是沒有保證業務規則,也就沒辦法談領域模型的資料一致性了;如果像以往的三層貧血架構,那帖子只是一個數據的載體,不包含任何業務規則,帖子會先被構造一個空的帖子物件出來,然後我們給這個空帖子物件的某些屬性賦值,然後儲存該帖子物件到資料庫;這種設計,帖子物件只是一個數據的容器,它完全控制不了自己的狀態,因為它的狀態都是被別人(如service)去修改的;這樣的設計,相當於是沒有把業務規則封裝在業務物件內部,而是轉移到了外部service中,雖然這樣通常也沒問題,事實上我們大部分人都一直在這麼幹,因為這樣幹寫程式碼很隨意,也很高效,呵呵。

GRASP九大模式中有一個面向物件的模式叫資訊專家模式,不知道大家有了解過沒有,該模式的描述是:將職責分配給擁有執行該職責所需資訊的物件;這個模式告訴我們,如果一個物件負責維護一些資訊,那它就有職責維護好這些資訊。體現到物件的屬性上,那就是這個物件的屬性不能被外部隨便更改,物件自己的屬性必須自己負責維護修改。建構函式和普通的方法都會改變物件的狀態,所以,我們對建構函式和物件普通的公共方法,都要秉持這個原則;這點非常重要,否則,如果像貧血模型那樣,那物件就不叫物件了,而只是一個普通的容納資料的容器而已,和資料庫裡的一條記錄也無本質差別了。實際上,在我看來,這也是DDD中的聚合區別於貧血模型中的實體的最大的地方。聚合不僅有狀態,還有嚴格維護好自己狀態的各種方法,包括建構函式在內;而貧血模型,則只有狀態,沒有行為;

關於DDD中一個領域物件是否是聚合根的考慮

這個問題,沒有非常清晰的放之四海而皆準的確定方法,我的想法是:

  1. 首先從我們對領域的最基本的常識方面的理解去思考,該物件是否有獨立的生命週期,如果有,那基本上是聚合根了;
  2. 如果領域內的一個物件,我們會在後臺有一個獨立的模組去管理它,那它基本上也是聚合根了;
  3. 是否有獨立的業務場景會去建立或修改一個物件;
  4. 如果物件有全域性唯一的標識,那它也是聚合根了;
  5. 如果你不能確定一個物件是否是聚合根的的時候,就先放一下,就先假定它是聚合根也無妨,然後可以先分析一下你已經確定的那些聚合根應該具體聚合哪些資訊;也許等你分析清楚其他的那些聚合的範圍後,也推匯出了你之前不確定是否是聚合根的那個物件是否應該是聚合根了呢。

關於一個聚合內應該聚合哪些資訊的思考

  1. 把我們所需要關心的屬性設計進去;
  2. 分析該聚合要封裝和實現哪些業務規則,從而像上面的例子(商品庫存)那樣推匯出需要設計哪些屬性狀態到該聚合內;
  3. 如果我們在建立或修改一個物件時,總是會級聯建立或修改一些級聯資訊,比如在一個任務系統,當我們建立一個任務時,可能會上傳一些附件,那這些附件的描述資訊(如附件ID,附件名稱,附件下載地址)就應該被聚合在任務聚合根上;
  4. 聚合內只需要值物件和內部的實體即可,不需要引用其他的聚合根,引用其他的聚合根只會讓當前聚合的邊界模糊;

關於如何更合理的設計聚合來封裝各種業務規則的思考

這一點在最上面的幾個原則中,實際上已經提到過一點,那就是儘量設計小聚合,這裡的出發點主要是從技術的角度去思考,為了降低對公共物件(大聚合)的併發修改,從而減小併發衝突的可能性,從而提高系統的可用性(因為系統使用者不會經常因為併發衝突而導致它的操作失敗);關於這一點,我還想再舉幾個例子,來說明,其實要實現各種業務規則,可以有多種聚合的設計方式,大聚合只是其中一種;

比如,帖子和回覆,大家都知道一個帖子有多個回覆,沒有帖子,回覆就沒有意義;所以很多人就會認為帖子應該聚合回覆;但實際上不需要這樣,如果你這樣做了,那對於一個論壇來說,同一個帖子被多個人同時回覆的可能性是非常高的,那這樣的話,多個人同時回覆一個帖子,就會導致多個人同時修改同一個帖子物件,那就導致大家都回復不了,因為會有併發衝突或者資料庫事務的等待超時,因為大家都在修改同一個帖子聚合根;實際上如果我們從業務規則的角度去思考一下,那可以發現,其實帖子和回覆之間,只有一個簡單的規則,那就是回覆一旦被建立,那他所對應的帖子不能被修改即可;這樣的話,要實現這個規則其實很簡單,把回覆作為聚合根,然後把帖子傳入回覆聚合根的建構函式,然後回覆儲存帖子ID,然後回覆將帖子ID設定為不允許外部修改(private set;即可),這樣我們就實現了這個業務規則,同時還做到了多人同時推一個帖子回覆時,不會對同一個帖子物件就併發修改,而是每個回覆都是並行的往資料庫插入一條回覆記錄即可;

所以,通過這個例子,我們發現,要實現領域模型內的各種業務規則,方法不止一種,我們除了要從業務角度考慮物件的內聚關係外,還要從技術角度考慮,但是不管從什麼角度考慮,都是以實現所要求的業務規則為前提;

從這個例子,我們其實還發現了另外一件有意義的事情,那就是一個論壇中,發表帖子和發表回覆是兩個獨立的業務場景;一個人發表了帖子,然後可能過了一段時間,另一個人對該帖子發表了回覆;所以將帖子和回覆都設計為獨立的很容易理解;這裡雖然帖子和回覆是一對多,回覆離開帖子確實也沒意義,但是將回復設計在帖子內沒任何好處,反而讓系統的可用性降低;相反,像上面提到的關於建立任務時同時上傳一些附件的例子,雖然一個任務也是對應多個附件資訊,但是我們發現,人物的附件資訊總是隨著任務被建立或修改時,一起被修改的。也就是說,我們沒有獨立的業務場景需要獨立修改任務的某個附件資訊;所以,沒有必要將任務的附件資訊設計為獨立聚合根;

ENode框架對聚合設計和聚合之間互動的支援

enode提供了一個基於DDD+CQRS+Event Sourcing+In Memory+EDA這些技術的應用開發架構;

  1. enode在框架層面就限制了一個command只能修改一個聚合根,這就杜絕了我們使用Unit of Work的模式來以事務的方式來一次性修改多個聚合根;
  2. enode提供了可靠的原子操作和併發衝突檢測機制,來保證對單個聚合的操作的強一致性;
  3. enode提供了可靠的事件機制,來保證我們的domain中的聚合之間資料互動可以通過事件非同步通訊的方式來實現聚合之間的最終一致性;如果有些複雜業務場景是一個流程,那我們可以通過Process+Process Manager的思想來實現流程狀態的跟蹤和流程的流轉;
  4. enode因為基於domain event,所以,我們的聚合根不需要引用,每個聚合根只需要負責自己的狀態更新,然後更新完後產生相應的domain event即可,這本質就是就是實現了:Don’t Ask,Tell這個設計原則;
  5. enode提供了可靠的事件釋出機制,可以確保command side和query side的資料最終一定是一致的;
  6. enode提供了in memory的設計,使得我們的domain可以非常高效的執行,持久化事件不需要事務,獲取聚合根直接從in memory獲取;
  7. enode提供了很多設計,可以讓我們最大化的對不同的聚合根例項做並行操作,從而提高整個系統的吞吐量;

使用enode,將會迫使你思考如何設計聚合,如何通過流程實現聚合之間的非同步互動;迫使你思考如何定義domain event,將領域內的狀態更改顯式化;迫使你將外部對領域的各種操作顯式化,即定義出各種command;迫使你將command side和query side的資料分離和架構分離,技術分離。減少的是,我們不必再設計unit of work,不必設計domain service,不必讓聚合設計各種非第一手的冗餘的統計資訊;

相關推薦

關於領域驅動設計DDD聚合設計一些思考

關於DDD的理論知識總結,可參考這篇文章。 DDD社群官網上一篇關於聚合設計的幾個原則的簡單討論: 聚合是用來封裝真正的不變性,而不是簡單的將物件組合在一起; 聚合應儘量設計的小; 聚合之間的關聯通過ID,而不是物件引用; 聚合內強一致性,聚合之間最終一致性; 上面這幾條原則,作者通過

領域驅動設計DDD- 請先搞清楚一些概念

責任 可能 升級 是你 ora ext 計數 方法 避免 開發一個新系統   一般我們開始開發一個商業系統都需要做什麽?讀需求文檔去查找功能點,拆解任務。多數情況下,拆解項目是為了評估工作,做評估、分配任務到個人、設計數據庫結構,然後就開始了Coding。 所以,這種方

領域驅動設計DDD在美團點評業務系統的實踐

點選上方藍字訂閱,不錯過下一篇好文章本文轉自美團點評技術公眾號:meituantech前言至少3

領域驅動設計DDD部分核心概念的個人理解

領域驅動設計(DDD)是一種基於模型驅動的軟體設計方式。它以領域為核心,分析領域中的問題,通過建立一個領域模型來有效的解決領域中的核心的複雜問題。Eric Ivans為領域驅動設計提出了大量的最佳實踐和經驗技巧。只有對領域的不斷深入認識,才能得到一個解決領域核心問題的領域模型。如果一個應用的複雜性不是在技

從零開始使用CodeArt實踐最佳領域驅動開發

using emp 程序集 mman his return main 更新 物理 本章內容還在整理上傳中,你可以等全部更新完畢後再查閱也可以先預覽已上傳的內容。。。。。。 7. 應用層的命令模式   在上個章節裏我們設計並編碼了領域對象Permission,但是目前Perm

bartender鏈接數據庫提示:無法鏈接到數據庫 外部數據驅動程序1的意外錯誤#6670

bartender 6670 外部數據驅動程序bartender鏈接數據庫提示:無法鏈接到數據庫 外部數據驅動程序(1)中的意外錯誤#6670是 微軟 10月份更新的補丁導致。解決:1.關閉自動更新2.卸載補丁:win7 KB4041678 KB404168

外部數據庫驅動程序1的意外錯誤

器) 服務器 html 初步 作用 選擇 不能 class 微軟補丁 原文:用友u8各版本在輸出的時候報錯提示:外部數據庫驅動程序(1)中的意外錯誤 ,報錯內容“外部數據庫驅動程序(1)中的意外錯誤”,經初步分析有以下解決方案:1、卸載微軟的補丁:

Drools 規則引擎----向領域驅動進步

PS:文章還在寫,目前都是一些概念性質的,想要做拓展的程式猿請過幾天再看,Drools會一致做完的~~~ 1. 工欲善其事,必先利其器 Drools提供基於eclipse的IDE(這是可選的),但其核心只需要Java 1.5(Java SE)。

Drools 規則引擎----向領域驅動進步

1.複雜事件處理 到目前為止,我們已經看到如何使用規則,以基於資料(我們稱呼它為fact)來做出決定。這個資訊幾乎是任何一組Java物件,它們描述了我們正在做決策的域的狀態,但是它總是在一個特定的時間點上代表這個世界的狀態。本章我們將會去看一些列的概念,配置和

[ODBC]讀Excel時報錯:外部資料庫驅動程式1的意外錯誤解決方案

在MFC下使用ODBC讀取Excel資料庫,之前用著還好好的,突然就用不了了,幾經查詢發現是Windows安全更新的補丁搞的鬼。 出現錯誤: 解決方案: 1. 在控制面板——解除安裝——已

資料庫設計—— 使用者許可權設計模式

方法一:從職位(角色)的角度出發,指定職位擁有那些操作許可權。給定使用者一個職位,當用戶登入的時候通過Session儲存該職位所具有的操作許可權。當用戶執行操作時,邏輯層通過遍歷判斷使用者是否具有這個操

【格式、工具】寫作論文遇到的一些問題及其解決方案持續更新......

論文中一些常見的規則本文更新時間日誌:2018.04.29:以 office-word 為主,整理一些關於公式的問題;2018.05.24:參考文獻(會議論文)的格式及其搜尋方法;===========================================【offi

C#進階系列——DDD領域驅動設計初探聚合

前言:又有差不多半個月沒寫點什麼了,感覺這樣很對不起自己似的。今天看到一篇博文裡面寫道:越是忙人越有時間寫部落格。呵呵,似乎有點道理,博主為了證明自己也是忙人,這不就來學習下DDD這麼一個聽上去高大上的東西。前面介紹了下MEF和AOP的相關知識,後面打算分享Automapper、倉儲模式、WCF等東西的,可是

解構領域驅動設計:為什麼領域驅動設計能夠解決軟體複雜性

1 為什麼我要研究領域驅動設計 1.1 設計方法各樣且程式碼無法反映設計 我大概從2017年10月份開始研究DDD,當時在一家物流資訊化的公司任職架構師,研究DDD的初衷在於為團隊尋找一種軟體設計的方法論。作為架構師,經常參與設計評審,包括:需求評審、設計評審、程式碼評審。在評審過程中,有一點感受非常深,

解構領域驅動設計:為什麽領域驅動設計能夠解決軟件復雜性

unp 問題 困難 技術 工作 質量管理 exce urn 如果 1 為什麽我要研究領域驅動設計 1.1 設計方法各樣且代碼無法反映設計 我大概從2017年10月份開始研究DDD,當時在一家物流信息化的公司任職架構師,研究DDD的初衷在於為團隊尋找一種軟件設計的方法論。

解構領域驅動設計領域驅動設計的核心之分層架構

() shc created win cif nec upd 方法 bool 反映業務規則的代碼是整個軟件的核心,但是它一般只占很小的一部分,在傳統的基於貧血模型的分層軟件架構中,業務規則可能分散到各個層、各個代碼段,從而使得通過代碼來還原業務規則或者保證代碼與業務規則一致

領域驅動設計

領域驅動 問題 圖片 info wid http 空間 ima alt 問題空間 領域驅動設計(一)

解構領域驅動設計領域驅動設計

ddd 引擎 .get states 成員變量 float 類的屬性 table custom 在上一部分,分層架構的目的是為了將業務規則剝離出來在單獨的領域層中進行實現。再回顧一下領域驅動設計的分層中應用層代碼的實現。 @Override public void

JavaScript高級程序設計2在HTML使用JavaScript

有效 頁面 itl 延遲腳本 文件包含 其他 amp 體驗 url 本章內容:使用<script>元素、嵌入腳本與外部腳本、文檔模式對JavaScript的影響、考慮禁用JavaScript的場景。 1.<script>元素 向HTML頁面中

JavaScript高級程序設計3基本概念

算數 variable 多個 產生 加法 關系操作符 style 語句 移動 操作符 ECMA-262描述了一組用於操作數據值的操作符,包括算數操作符、位操作符、關系操作符和相等操作符。他們能夠適應很多值,例如字符串、數字值、布爾值甚至對象。在應用對象時,相應的操作符