Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Learning JavaScript in Three Web Apps(中文)

5.311 Aufrufe

Veröffentlicht am

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

Veröffentlicht in: Technologie
  • Als Erste(r) kommentieren

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

×