Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Learning JavaScript in Three Web Apps(中文)

5 252 vues

Publié le

iOS卡牌游戏、TodoMVC、真实的豆瓣产品,通过3个从小到大,从原始到专业的web应用项目,来学习JS语言和基于JS的前端开发

Publié dans : Technologie
  • Soyez le premier à commenter

Learning JavaScript in Three Web Apps(中文)

  1. 1. Learning JavaScript in Three Web Apps Dexter.Yy @ ⾖豆瓣
  2. 2. Overview • JSMatchismo • TodoMVC • GalEditor
  3. 3. • 斯坦福iOS应⽤用开发课程 (CS193p,Winter 2013)⾥里 的卡牌游戏 • 对⽐比:Cocoa等传统客户端开发环境 • 从零搭建,不引⼊入任何依赖,不使⽤用任何库、框 架、编译⼯工具 • 模块化和MVC分层 App 1 - JSMatchismo
  4. 4. 把HTML看作配置⽂文件, ⽽而不是数据和内容 ⽤用button.card的个数来配置卡牌数 量,⽤用classname和属性来配置状态 先创建⼀一个./index.html
  5. 5. 创建./css/main.css 把 CSS 看作描述状态的 配置⽂文件 随便找⼀一张图⽚片做 卡牌背⾯面
  6. 6. DOM 是平台(浏览器) ⾃自带的、 特定状态下的、 彼此之间 存在关系的, 能被 JS 访问 和调⽤用的 『UI/视图组件』 实例对象 编辑 HTML 就是在 编辑 runtime 中 对象的状态和关系 HTML 和 CSS 是前端开发者的画板和 Interface Builder
  7. 7. 创建 app.js
  8. 8. 创建 app.js ECMAScript 5 (ECMA-262-5) Strict Mode TC39 ECMA-262 (ECMAScript, ES, JavaScript, JS) ECMA-357 (E4X) ECMAScript 3 (ECMA-262-3, JavaScript 1.5) JavaScript 1.6 JavaScript 1.8 ECMAScript 4 (ActionScript 3) ECMAScript 5 (ECMAScript 3.1) ECMAScript 5.1 (JavaScript 1.8.5) ECMAScript 6 (ECMAScript Harmony, ES.next, JavaScript 2.0)
  9. 9. Function Object Function Object -- * JS最最核⼼心的数据类型和特殊概念 * A collection of named values (‘value’: any primitive datatypes, or reference to any objects) * ~= hash table * ~= dictionary * != class instance * Pass by reference (~= pointer) * 除 primitive type之外,万物皆为object * 两种创建⽅方法:Literal(字⾯面量/直接 量)、‘new’操作符 Primitive datatype
  10. 10. Function Object Function Function -- * 包含 executable code 的 object * Named function 或 Anonymous function * ⽤用字⾯面量创建时,有 Function Declaration 和 Function Expression 两种⽅方式 * ⽤用法:structured programming (like C), late binding 的 object method、constructor、创建 lexical scope(词法作⽤用域)、Currying、传递 block (like Ruby)、元编程、…… * 某些 built-in / host 对象的 executable code 是 native code Primitive datatype
  11. 11. Function Object Function Primitive datatype -- * 3 + 1:number、string、boolean + undefined * null 是 object,Infinity、NaN 等都是 number * Pass by value * 前三种有 “Wrapper Class” (like Java),但 100% 情 况下都只使⽤用字⾯面量,Wrapper的价值是众多原⽣生 ⼯工具函数(静态⽅方法) Primitive datatype
  12. 12. Global context Function context Function context Execution context (执⾏行上下⽂文) Stack
  13. 13. Activation object == Variable object in function context { app: {...}, arguments: [arguments object], this: {Global object} } Global object == Variable object in global context { app: {...}, window: {Global object}, this: {Global object}, Math: {...}, Array: {...}, Object: {...}, ... }
  14. 14. export public API data hiding、private member (Traditional) Module Pattern global namespace
  15. 15. 相同的 Global context
  16. 16. 创建 model/ 和 card.js
  17. 17. 进⼊入 execution context 解析形式参数和所有declaration(函数声明和变 量声明),填充当前上下⽂文的Variable object { Card: function(){...} exports: function(){...} } 从上⾄至下执⾏行代码 退出函数上下⽂文,返回上⼀一级的上下⽂文继续执 ⾏行代码,刚才的Variable object 作为 Scope 被 Card、exports 等还能继续访问的函数继续保存
  18. 18. 假如代码是这样… 函数表达式和普通的变量声明 对未声明变量赋值,其实是 window.b = 10 的省略写法 函数表达式 函数声明 形式参数
  19. 19. 进⼊入 execution context 解析形式参数和所有declaration(函数声明和变 量声明),填充当前上下⽂文的Variable object { Card: function(){...} exports: undefined, a: undefined, c: undefined, d: 1 } 从上⾄至下执⾏行代码 { Card: function(){...} exports: function(){...}, a: 10, c: 30, d: 1 } Global Object: { window: {Global}, card: function(){...}, b: 10, ... }
  20. 20. Contructor(构造函数) 是 普通函数,不是Class prototype 是普通对象 Prototype based model of OOP new 操作符⽤用函数的 prototype 属性作 为模板,复制出新的对象,Card 本⾝身的 return 只要不是对象就被忽略 以⼯工⼚厂函数 / Wrapper / 模块对象作为 public API,避免紧耦合等问题
  21. 21. this 是 late binding 的 (new Card().contents)() 时, this 指向新对象, (false || new Card().contents)() 时, this 指向 Global Object ⽤用 new 调⽤用 Card 时,在进⼊入 Card 函数上下⽂文的阶段,this 被指向复制 出来的新对象 Card 和 contents 被作为属性(property)形式的 引⽤用值被调⽤用时,this 指向属性所属的对象,当 作为 Identifier (⽐比如变量名)形式的引⽤用值或实 际值被调⽤用时,this 被默认填充为 Global Object
  22. 22. jQuery style 的存取器 (Ad-Hoc Polymorphism) ⽤用下划线前缀显式声明private member,但不存在真正的约束 this._contents 使⽤用前不需要声明,因为 JS 的 object 都是 dynamic mutable object, 不存在的属性、没有实参的形参、未赋值的 变量,访问得到的都是 undefined prototype 只应该⽤用来定义⽅方法,属性必 须在构造函数内定义才能保证每个实例 都持有⾃自⼰己的属性(引⽤用类型)
  23. 23. 此处类似 Duck Typing (Parametric Polymorphism) 因为从后⾯面的实现可以看到, 函数的⾏行为总是⼀一致 ⾃自省/类型判断:typeof, Array.isArray, toString, constructor, instanceof, ...
  24. 24. block ⻛风格的 iterator(迭代器) JavaScript 1.6 array methods ⼿手动指定上下⽂文
  25. 25. compare ⾃自⼰己的上下⽂文⾥里没有 声明 score 变量,会往 Scope chain(作⽤用域链)的上层爬, 找到 match 上下⽂文⾥里的 score ⼿手动指定上下⽂文
  26. 26. 函数内的函数,都会形成 Closure (闭包),通过⾃自⼰己 的 Scope 存储上层函数的 “Variable object” (因此也能 访问到上层函数的 Scope 存储 的更上层 “Variable object” ) 假如 compare 被暴露给外部 访问,则 match 的上下⽂文会 ⼀一直保留,不会被 GC(垃圾 回收)
  27. 27. 创建 deck.js
  28. 28. 惰性初始化的getter
  29. 29. 可选参数和默认值的实现, JS 不⽀支持 ruby/python 中的 named arguments 或 *args
  30. 30. 缓存作⽤用域链上层的变量或函 数结果,常⽤用于性能热点优化 或节省字符 从 array 中删除
  31. 31. 假如有错误代码…
  32. 32. 静态分析对JS开发⾮非常重要
  33. 33. JSHint Vim ⾥里常⽤用的语法检查插件
  34. 34. 每个项⺫⽬目可以有不同的 JSHint 的配置
  35. 35. 测试动态环境中的debug
  36. 36. console 中的未捕获异常
  37. 37. 这个按钮⾮非常重要 addCard 的 execution context 回溯跳过第三⽅方/底层代码,找 到真正引发异常的逻辑
  38. 38. 创建 playingCard.js (更具体的『扑克牌』) 显式声明依赖,不直接 使⽤用全局变量 第⼀一次引⼊入对其他模块的依赖
  39. 39. 必须⼿手动调⽤用『⽗父类』的 构造函数,⼿手动绑定 this Object.create 可以直接⽤用⼀一个对 象为原型⽣生成新对象,不需要构 造函数和 new 继承的简单实现 继承的关键是原型链
  40. 40. 『⼦子类』的⽅方法 在『⼦子类』原型上扩展 出这些⽅方法
  41. 41. 牌⾯面花⾊色的存取器 默认值 快速检索常⽤用的 数据结构 让API的⾏行为尽量⼀一致
  42. 42. 改为静态⽅方法, 频繁执⾏行的函数会⽣生成⼤大量 ⼀一次性的数据,影响旧浏览 器的GC性能 静态⽅方法 模块内部使⽤用的数据
  43. 43. 牌⾯面⼤大⼩小的存取器 存储编号,之后查表转换
  44. 44. override『⽗父类』的 contents ⽅方法
  45. 45. 创建 playingCardDeck.js (更具体的『扑克牌桌』)
  46. 46. 『⼦子类』新增的初始化
  47. 47. 原⽣生⽅方法得到的是 nodeList 对象,不是 Array ⽤用以上model来修改视图,测试效果 nodeList 有 length 属 性、接受数 字键名,所 以能⽤用 Array 的迭代 器(duck typing) 作为配置的HTML应该尽可能抽象 和语义纯粹,⽤用于实现特定外观的 结构可由代码⽣生成
  48. 48. 在⻚页⾯面⾥里使⽤用之前写的 module ⽂文件时, 需要⼈人⼯工控制依赖关系(上下顺序) 导出 app 对象可以让应⽤用代码与具体⻚页⾯面解耦
  49. 49. 效果是这样
  50. 50. 视图相关代码常常包含 DOM 操作,与主要业务逻辑⽆无 关,它们⼀一定会越来越多,越来越繁琐,喧宾夺主。 直接在视图代码中访问和依 赖 model,会导致视图代码 ⽆无法进⼀一步抽象和通⽤用化, ⽆无法与业务逻辑解耦。 在进⼀一步开发之前,先审视 app.js。 考虑以上两点,可预⻅见 app.js 会越来越⻓长,越来越像 ⾯面条式脚本,难以维护、扩展和抽象。
  51. 51. 约定:不允许在 app.js 中直接操作 DOM 有了这个约定,就必须将 视图的具体实现拆分出去, app.js 仅⽤用于调⽤用和组合 view 和 model 模块、监听 消息、公开接⼝口,也就是 controller 创建 view.js 给 view 传递纯粹的、逻辑⽆无关 的数据,⽽而不是 model 本⾝身
  52. 52. Smalltalk-80 Cocoa Ruby On Rails ASP.NET ModelView Controller: History, theory and usage 很类似 Cocoa 的 MVC 分层
  53. 53. Cocoa 的 MVC 架构,来⾃自斯坦福CS193p
  54. 54. 因为这个项⺫⽬目不复⽤用第三⽅方代码,DOM 对象本 ⾝身就相当于各种视图组件,view.js ⽤用来组合这些 视图组件 重构完毕,可以继续开发了,开始实现交互
  55. 55. 加⼊入 view.js
  56. 56. 加⼊入内部结构的样式 描述新的状态
  57. 57. 快速测试不同状态,不依赖交互
  58. 58. 先设计视图接⼝口,视图不能依赖和 主动调⽤用 controller,只能⼲⼴广播消 息,类似 Cocoa 的UI组件向 target 转发 action controller 监听消息, 操作数据
  59. 59. controller 操作数据之后,需 要通知视图组件⽤用新的数据 更新 UI 不在初始化阶段填充 卡牌内容 每次翻牌时随机填充内容
  60. 60. 跳转表,将不同UI对象上的 交互动作通过选择器分发给 不同的handler函数 事件代理捕获整个应⽤用范围的 交互事件,⽤用跳转表分发 设计视图内部的接⼝口
  61. 61. ⼲⼴广播消息,传递视图 ⾃自⼰己加⼯工处理过的交 互信息(index)
  62. 62. 实现事件代理 原⽣生的 matchesSelector ⽅方法 在不同浏览器⾥里名称不同, 需要解决兼容性问题
  63. 63. 兼容性封装,⽣生成统⼀一的常量 尽可能⽤用特性侦测,⽽而 不是浏览器侦测(依靠 user agent) 动态⽣生成⽅方法名,JS 常⽤用 的元编程⼿手段
  64. 64. 为避免在赋值前被调⽤用, 变量声明放在顶部 函数声明可以放在任意位 置,由于函数通常封装了 不属于主要逻辑的具体实 现,为了让代码更抽象更 可读,应该拆分出去或移 到不显眼的位置(底部)
  65. 65. 加⼯工处理交互事件 对象,⽣生成更抽象 的、UI⽆无关的数据 分发给 handler 函数的事 件可能来⾃自不同的UI⼦子元 素,需要统⼀一 利⽤用 button 元素原⽣生的状态
  66. 66. 设计消息接⼝口 实现消息接⼝口
  67. 67. ⽐比构造函数更简单的对象⼯工⼚厂 缺点是每次⽣生成新对象都 需要重复⽣生成这些函数, 且不能继承。 但是在有必要的时候,这 个函数的内部可以很⽅方便 的重构为⽤用构造函数实现
  68. 68. ⼲⼴广播瞬时消息 监听/订阅/观察消息 取消订阅
  69. 69. 实现UI更新接⼝口
  70. 70. 加⼊入状态栏和 次数统计
  71. 71. 把 UI组件 / DOM 对象 组合到⾃自⼰己⾝身上,类似 Cocoa ⾥里的 outlet 更新状态栏⾥里的次数统计
  72. 72. 实现类似其他语⾔言的字符串 格式化
  73. 73. ⽤用相同的更新⽅方法 来初始化视图 实现更新卡牌接⼝口
  74. 74. 初始化时没有传⼊入数据, 所以字符串拼接时会把不 存在属性的值 undefined 转 成字符串
  75. 75. format 也能充当最 简单的JS模板转换 基于字符串的JS模板
  76. 76. 模板转换⽅方法都会⾃自动将 undefined 处理为空字符串
  77. 77. 除了UI的内容,也更新UI的状态 尽可能只在 JS 或主要代码逻辑⾥里处理状 态的迁移转换,不要实现状态细节, 将具体实现和描述交给 css 之类的配置 ⽂文件和 DSL (领域语⾔言)
  78. 78. 更新数据的 状态,从⽽而 更新视图的 状态
  79. 79. 交互(点击)后的效果
  80. 80. 在 css ⾥里描述状态的细节 (过渡效果动画) css3 的 transition ⾃自动为状 态的切换⽣生成过渡动画
  81. 81. 测试交互效果
  82. 82. 另⼀一种动画 实现,引⼊入 animate.css 中的⼀一个关 键帧动画
  83. 83. css 同样要⼿手动管理 依赖和先后顺序
  84. 84. 初始化动画配置 切换状态触发 关键帧动画
  85. 85. 测试交互效果
  86. 86. 解决交互之后,开始实现真正的游戏逻辑 创建 cardMatchingGame.js
  87. 87. 通过参数传递把 Deck 或其『⼦子类』的实例 『组合』进来,避免当前模块依赖具体的 Deck 模块
  88. 88. 翻牌时的游戏规则 改变 model 的状态
  89. 89. 游戏规则需要调⽤用 playingCard 的 match ⽅方法
  90. 90. 重载 Card 的 match ⽅方法,实现不同的 积分奖励
  91. 91. 奖励、惩罚和成本
  92. 92. ⽤用 HTML 配置来初始化游戏把扑克牌桌的实例 组合到游戏规则⾥里
  93. 93. ⽣生成数据、更新视图的代码不属于主 要业务逻辑,应该单独组织到⼀一起 牌桌被组合到游戏规则 ⾥里之后,controller不需 要维护⾃自⼰己的牌桌
  94. 94. 数据中的状态尽可能交给 model ⾃自⼰己来维护,在 controller ⾥里尽量只调⽤用 model 的抽象接 ⼝口,⽽而不是直接修改 model 中的 数据状态
  95. 95. 增加状态栏⾥里积分的更新接⼝口
  96. 96. 实现积分的更新接⼝口
  97. 97. 积分的UI
  98. 98. 游戏完成(线上demo)
  99. 99. Source code: https://github.com/dexteryy/JSMatchismo
  100. 100. • 著名开源项⺫⽬目 • 对⽐比:其他JS应⽤用开发框架 • oz.js⽀支持的模块化 • 复⽤用第三⽅方模块/组件(OzJS微框架) • ⽤用包管理⼯工具管理依赖 • 项⺫⽬目中的源代码⽂文件都必须是能直接在浏览器⾥里 使⽤用的静态⽂文件 App II - TodoMVC
  101. 101. 最终效果(线上demo)
  102. 102. 第三⽅方组件都会下载安装 到专⻔门的⺫⽬目录 包管理⼯工具(bower)⾃自动读 取的项⺫⽬目配置, 包含对第三⽅方组件的依赖 TodoMVC 项⺫⽬目提供的外观实现
  103. 103. ⽤用包管理⼯工具初始化项⺫⽬目,⾃自动 下载安装依赖的第三⽅方项⺫⽬目
  104. 104. ⾼高级浏览器⾥里不需要 ES5 shim 新增 main.js,相当于上个项⺫⽬目中⻚页⾯面内的 inline script,增加了模块相关的配置 把模块名关联到包管理⼯工具 的安装路径 oz.js实现的模块化机制的配置 禁⽌止使⽤用全局变量
  105. 105. 在动态环境⾥里⾃自动处理模块的依赖和加载,不需要⼿手动维护⽂文件的使⽤用和先后顺序
  106. 106. 有了第三⽅方model库(NervJS),model 模块 不但书写更简洁了,也更强⼤大了 扩展出条⺫⽬目model⾃自⼰己的⽅方法 model 中的数据模式 (schema)和默认值
  107. 107. 列表model的成员是条⺫⽬目model
  108. 108. view.js 仍然像上个项⺫⽬目⼀一样⽤用事件 代理(SovietJS)维护交互逻辑
  109. 109. 双击和键盘事件 事件代理的初始化
  110. 110. 因为可以复⽤用第三 ⽅方的 UI 组件了, view.js 现在主要承 担组合这些组件、 提供更抽象 API 的 ⼯工作,避免 UI组 件之间的耦合
  111. 111. view/ actionview 是 UI库⾥里 moui/ actionview 的 进⼀一步封装, 满⾜足项⺫⽬目的业 务需求 view.js ⾥里使⽤用 view组件的接 ⼝口,⽽而不是直 接⽤用 DOM 的 接⼝口
  112. 112. ⽤用 view/actionview 封装出更具体的 警告框和确认框组件
  113. 113. model 组件的初始化和操作 不再需要像上个项⺫⽬目⼀一样每次修 改 model 都需要⼿手动调⽤用 updateUI
  114. 114. 可以监听 model 的改变, ⾃自动更新 UI (View Model Binder) ⽤用 model ⾃自⼰己的⽅方法⽣生成 纯数据传给视图
  115. 115. 这个项⺫⽬目是包含多个 URL 的单⻚页 应⽤用,app.js 像服务器端web框架 的 controllter ⼀一样管理路由
  116. 116. Source code: https://github.com/dexteryy/todomvc/tree/gh-pages/labs/ architecture-examples/ozjs
  117. 117. • 真实的⾖豆瓣产品 • 对⽐比:服务器端web框架中的静态⽂文件 • 项⺫⽬目中的⽂文件都是源代码,不再兼任『静态⽂文 件』 • 静态环境中的预处理/编译/构建 • ⽤用任务管理⼯工具整合⼤大量⼯工具和⼯工作流 • 应⽤用本⾝身的组件化,业务逻辑的分层,与服务器 端视图解耦 App III - GalEdtitor
  118. 118. 增加了任务管理⼯工具 (Grunt)的配置 从包管理安装的⽂文件中⾃自动 提取项⺫⽬目需要的部分,按项 ⺫⽬目⾃自⼰己的组织结构来放置 (grunt-dispatch)
  119. 119. 因为第三⽅方组件的进⼀一步组 织,模块配置简单了很多 main.js 不再像上个项⺫⽬目那样初 始化应⽤用,⽽而是变成了单纯的 配置,相当于JS的构建脚本
  120. 120. JS模板也被拆分为独 ⽴立的源代码⽂文件 模板⽂文件被编译 成JS模块
  121. 121. css 也可以模块化和 复⽤用第三⽅方库( scss/ compass )
  122. 122. 项⺫⽬目构建过程中会将 js、css、 html、图⽚片、JS模板分别从源 ⽂文件编译为⺫⽬目标⽂文件,再构建 出发布⽂文件,再⽤用这些⽣生成的 静态⽂文件填充 public ⺫⽬目录
  123. 123. 静态环境中的构建 ⽤用 grunt-furnace 构建模板模块 ⽤用 Ozma 构建 JS 的静态⽂文件
  124. 124. 上个项⺫⽬目中在动态环境中处理的模块加 载改为在静态环境⾥里完成,Ozma 会将 项⺫⽬目⾥里的JS源⽂文件按需要打包到静态⽂文 件中(⼀一个或多个)
  125. 125. 浏览器⾥里只需要加载最少量的⽂文件
  126. 126. 在⻚页⾯面⾃自⾝身的代码中配置、 修改、扩展、初始化和组织 调⽤用 app 的 API docs/index.html ⽤用于 应⽤用本⾝身(离线客户 端)的演⽰示和调试, 没有特定的后端,所 以保存图⽚片是纯前端 模拟
  127. 127. ⽤用本地存储
  128. 128. 在真实产品中的⻚页 ⾯面模板(后端视 图)⾥里使⽤用时,实 现真正的保存图⽚片 功能
  129. 129. ⽤用后端视图中输出的 数据(相当于预加 载)初始化应⽤用,如 果没有数据,则另外 请求后端API 业务逻辑可抽象出组件、应⽤用、 ⻚页⾯面三个层次,前两个都可以是 通过包管理⼯工具引⼊入的⼦子项⺫⽬目 (独⽴立代码仓库)
  130. 130. 课后思考 • 将第⼀一个项⺫⽬目跟⾮非web的GUI开发⽅方式做对 ⽐比 • 三个项⺫⽬目中代码的相似之处 • 前两个项⺫⽬目的约束被解除之后带来的改变
  131. 131. THE END dexter.yy@gmail.com

×