Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Learning JavaScript in Three Web Apps(中文)

Cb4e79a629b12581cdba6dc22445c42b?s=47 Dexter.Yy
August 08, 2013

Learning JavaScript in Three Web Apps(中文)

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

Cb4e79a629b12581cdba6dc22445c42b?s=128

Dexter.Yy

August 08, 2013
Tweet

Transcript

  1. Learning JavaScript in Three Web Apps Dexter.Yy @ ⾖豆瓣

  2. Overview • JSMatchismo • TodoMVC • GalEditor

  3. • 斯坦福iOS应⽤用开发课程 (CS193p, Winter 2013)⾥里 的卡牌游戏 • 对⽐比:Cocoa等传统客户端开发环境 • 从零搭建,不引⼊入任何依赖,不使⽤用任何库、框

    架、编译⼯工具 • 模块化和MVC分层 App 1 - JSMatchismo
  4. 把HTML看作配置⽂文件, ⽽而不是数据和内容 ⽤用button.card的个数来配置卡牌数 量,⽤用classname和属性来配置状态 先创建⼀一个./index.html

  5. 创建./css/main.css 把 CSS 看作描述状态的 配置⽂文件 随便找⼀一张图⽚片做 卡牌背⾯面

  6. DOM 是平台(浏览器) ⾃自带的、 特定状态下的、 彼此之间 存在关系的, 能被 JS 访问 和调⽤用的

    『UI/视图组件』 实例对象 编辑 HTML 就是在 编辑 runtime 中 对象的状态和关系 HTML 和 CSS 是前端开发者的画板和 Interface Builder
  7. 创建 app.js

  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. None
  10. 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
  11. 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
  12. 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
  13. Global context Function context Function context Execution context (执⾏行上下⽂文) Stack

  14. 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: {...}, ... }
  15. export public API data hiding、private member (Traditional) Module Pattern global

    namespace
  16. 相同的 Global context

  17. 创建 model/ 和 card.js

  18. 进⼊入 execution context 解析形式参数和所有declaration(函数声明和变 量声明),填充当前上下⽂文的 Variable object { Card: function(){...}

    exports: function(){...} } 从上⾄至下执⾏行代码 退出函数上下⽂文,返回上⼀一级的上下⽂文继续执 ⾏行代码,刚才的 Variable object 作为 Scope 被 Card、exports 等还能继续访问的函数继续保存
  19. 假如代码是这样… 函数表达式和普通的变量声明 对未声明变量赋值,其实是 window.b = 10 的省略写法 函数表达式 函数声明 形式参数

  20. 进⼊入 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, ... }
  21. Contructor(构造函数) 是 普通函数,不是Class prototype 是普通对象 Prototype based model of OOP

    new 操作符⽤用函数的 prototype 属性作 为模板,复制出新的对象,Card 本⾝身的 return 只要不是对象就被忽略 以⼯工⼚厂函数 / Wrapper / 模块对象作为 public API,避免紧耦合等问题
  22. 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
  23. jQuery style 的存取器 (Ad-Hoc Polymorphism) ⽤用下划线前缀显式声明private member,但不存在真正的约束 this._contents 使⽤用前不需要声明,因为 JS

    的 object 都是 dynamic mutable object, 不存在的属性、没有实参的形参、未赋值的 变量,访问得到的都是 undefined prototype 只应该⽤用来定义⽅方法,属性必 须在构造函数内定义才能保证每个实例 都持有⾃自⼰己的属性(引⽤用类型)
  24. 此处类似 Duck Typing (Parametric Polymorphism) 因为从后⾯面的实现可以看到, 函数的⾏行为总是⼀一致 ⾃自省/类型判断:typeof, Array.isArray, toString,

    constructor, instanceof, ...
  25. block ⻛风格的 iterator(迭代器) JavaScript 1.6 array methods ⼿手动指定上下⽂文

  26. compare ⾃自⼰己的上下⽂文⾥里没有 声明 score 变量,会往 Scope chain(作⽤用域链)的上层爬, 找到 match 上下⽂文⾥里的

    score ⼿手动指定上下⽂文
  27. 函数内的函数,都会形成 Closure (闭包),通过⾃自⼰己 的 Scope 存储上层函数的 “Variable object” (因此也能 访问到上层函数的

    Scope 存储 的更上层 “Variable object” ) 假如 compare 被暴露给外部 访问,则 match 的上下⽂文会 ⼀一直保留,不会被 GC(垃圾 回收)
  28. 创建 deck.js

  29. 惰性初始化的getter

  30. 可选参数和默认值的实现, JS 不⽀支持 ruby/python 中的 named arguments 或 *args

  31. None
  32. 缓存作⽤用域链上层的变量或函 数结果,常⽤用于性能热点优化 或节省字符 从 array 中删除

  33. 假如有错误代码…

  34. 静态分析对JS开发⾮非常重要

  35. JSHint Vim ⾥里常⽤用的语法检查插件

  36. 每个项⺫⽬目可以有不同的 JSHint 的配置

  37. 测试动态环境中的debug

  38. console 中的未捕获异常

  39. 这个按钮⾮非常重要 addCard 的 execution context 回溯跳过第三⽅方/底层代码,找 到真正引发异常的逻辑

  40. 创建 playingCard.js (更具体的『扑克牌』) 显式声明依赖,不直接 使⽤用全局变量 第⼀一次引⼊入对其他模块的依赖

  41. 必须⼿手动调⽤用『⽗父类』的 构造函数,⼿手动绑定 this Object.create 可以直接⽤用⼀一个对 象为原型⽣生成新对象,不需要构 造函数和 new 继承的简单实现 继承的关键是原型链

  42. 『⼦子类』的⽅方法 在『⼦子类』原型上扩展 出这些⽅方法

  43. 牌⾯面花⾊色的存取器 默认值 快速检索常⽤用的 数据结构 让API的⾏行为尽量⼀一致

  44. 改为静态⽅方法, 频繁执⾏行的函数会⽣生成⼤大量 ⼀一次性的数据,影响旧浏览 器的GC性能 静态⽅方法 模块内部使⽤用的数据

  45. 牌⾯面⼤大⼩小的存取器 存储编号,之后查表转换

  46. override『⽗父类』的 contents ⽅方法

  47. 创建 playingCardDeck.js (更具体的『扑克牌桌』)

  48. 『⼦子类』新增的初始化

  49. 原⽣生⽅方法得到的是 nodeList 对象,不是 Array ⽤用以上model来修改视图,测试效果 nodeList 有 length 属 性、接受数

    字键名,所 以能⽤用 Array 的迭代 器(duck typing) 作为配置的HTML应该尽可能抽象 和语义纯粹,⽤用于实现特定外观的 结构可由代码⽣生成
  50. 在⻚页⾯面⾥里使⽤用之前写的 module ⽂文件时, 需要⼈人⼯工控制依赖关系(上下顺序) 导出 app 对象可以让应⽤用代码与具体⻚页⾯面解耦

  51. 效果是这样

  52. 视图相关代码常常包含 DOM 操作,与主要业务逻辑⽆无 关,它们⼀一定会越来越多,越来越繁琐,喧宾夺主。 直接在视图代码中访问和依 赖 model,会导致视图代码 ⽆无法进⼀一步抽象和通⽤用化, ⽆无法与业务逻辑解耦。 在进⼀一步开发之前,先审视

    app.js。 考虑以上两点,可预⻅见 app.js 会越来越⻓长,越来越像 ⾯面条式脚本,难以维护、扩展和抽象。
  53. 约定:不允许在 app.js 中直接操作 DOM 有了这个约定,就必须将 视图的具体实现拆分出去, app.js 仅⽤用于调⽤用和组合 view 和

    model 模块、监听 消息、公开接⼝口,也就是 controller 创建 view.js 给 view 传递纯粹的、逻辑⽆无关 的数据,⽽而不是 model 本⾝身
  54. Smalltalk-80 Cocoa Ruby On Rails ASP.NET Model View Controller: History,

    theory and usage 很类似 Cocoa 的 MVC 分层
  55. Cocoa 的 MVC 架构,来⾃自斯坦福CS193p

  56. 因为这个项⺫⽬目不复⽤用第三⽅方代码,DOM 对象本 ⾝身就相当于各种视图组件,view.js ⽤用来组合这些 视图组件 重构完毕,可以继续开发了,开始实现交互

  57. 加⼊入 view.js

  58. 加⼊入内部结构的样式 描述新的状态

  59. 快速测试不同状态,不依赖交互

  60. None
  61. 先设计视图接⼝口,视图不能依赖和 主动调⽤用 controller,只能⼲⼴广播消 息,类似 Cocoa 的UI组件向 target 转发 action controller

    监听消息, 操作数据
  62. controller 操作数据之后,需 要通知视图组件⽤用新的数据 更新 UI 不在初始化阶段填充 卡牌内容 每次翻牌时随机填充内容

  63. 跳转表,将不同UI对象上的 交互动作通过选择器分发给 不同的handler函数 事件代理捕获整个应⽤用范围的 交互事件,⽤用跳转表分发 设计视图内部的接⼝口

  64. ⼲⼴广播消息,传递视图 ⾃自⼰己加⼯工处理过的交 互信息(index)

  65. 实现事件代理 原⽣生的 matchesSelector ⽅方法 在不同浏览器⾥里名称不同, 需要解决兼容性问题

  66. 兼容性封装,⽣生成统⼀一的常量 尽可能⽤用特性侦测,⽽而 不是浏览器侦测(依靠 user agent) 动态⽣生成⽅方法名,JS 常⽤用 的元编程⼿手段

  67. 为避免在赋值前被调⽤用, 变量声明放在顶部 函数声明可以放在任意位 置,由于函数通常封装了 不属于主要逻辑的具体实 现,为了让代码更抽象更 可读,应该拆分出去或移 到不显眼的位置(底部)

  68. 加⼯工处理交互事件 对象,⽣生成更抽象 的、UI⽆无关的数据 分发给 handler 函数的事 件可能来⾃自不同的UI⼦子元 素,需要统⼀一 利⽤用 button

    元素原⽣生的状态
  69. 设计消息接⼝口 实现消息接⼝口

  70. ⽐比构造函数更简单的对象⼯工⼚厂 缺点是每次⽣生成新对象都 需要重复⽣生成这些函数, 且不能继承。 但是在有必要的时候,这 个函数的内部可以很⽅方便 的重构为⽤用构造函数实现

  71. ⼲⼴广播瞬时消息 监听/订阅/观察消息 取消订阅

  72. 实现UI更新接⼝口

  73. 加⼊入状态栏和 次数统计

  74. 把 UI组件 / DOM 对象 组合到⾃自⼰己⾝身上,类似 Cocoa ⾥里的 outlet 更新状态栏⾥里的次数统计

  75. 实现类似其他语⾔言的字符串 格式化

  76. ⽤用相同的更新⽅方法 来初始化视图 实现更新卡牌接⼝口

  77. 初始化时没有传⼊入数据, 所以字符串拼接时会把不 存在属性的值 undefined 转 成字符串

  78. format 也能充当最 简单的JS模板转换 基于字符串的JS模板

  79. 模板转换⽅方法都会⾃自动将 undefined 处理为空字符串

  80. 除了UI的内容,也更新UI的状态 尽可能只在 JS 或主要代码逻辑⾥里处理状 态的迁移转换,不要实现状态细节, 将具体实现和描述交给 css 之类的配置 ⽂文件和 DSL

    (领域语⾔言)
  81. 更新数据的 状态,从⽽而 更新视图的 状态

  82. 交互(点击)后的效果

  83. 在 css ⾥里描述状态的细节 (过渡效果动画) css3 的 transition ⾃自动为状 态的切换⽣生成过渡动画

  84. 测试交互效果

  85. 另⼀一种动画 实现,引⼊入 animate.css 中的⼀一个关 键帧动画

  86. css 同样要⼿手动管理 依赖和先后顺序

  87. 初始化动画配置 切换状态触发 关键帧动画

  88. 测试交互效果

  89. 解决交互之后,开始实现真正的游戏逻辑 创建 cardMatchingGame.js

  90. 通过参数传递把 Deck 或其『⼦子类』的实例 『组合』进来,避免当前模块依赖具体的 Deck 模块

  91. 翻牌时的游戏规则 改变 model 的状态

  92. 游戏规则需要调⽤用 playingCard 的 match ⽅方法

  93. 重载 Card 的 match ⽅方法,实现不同的 积分奖励

  94. 奖励、惩罚和成本

  95. ⽤用 HTML 配置来初始化游戏 把扑克牌桌的实例 组合到游戏规则⾥里

  96. ⽣生成数据、更新视图的代码不属于主 要业务逻辑,应该单独组织到⼀一起 牌桌被组合到游戏规则 ⾥里之后,controller不需 要维护⾃自⼰己的牌桌

  97. 数据中的状态尽可能交给 model ⾃自⼰己来维护,在 controller ⾥里尽量只调⽤用 model 的抽象接 ⼝口,⽽而不是直接修改 model 中的

    数据状态
  98. 增加状态栏⾥里积分的更新接⼝口

  99. 实现积分的更新接⼝口

  100. 积分的UI

  101. 游戏完成(线上demo)

  102. Source code: https://github.com/dexteryy/JSMatchismo

  103. • 著名开源项⺫⽬目 • 对⽐比:其他JS应⽤用开发框架 • oz.js⽀支持的模块化 • 复⽤用第三⽅方模块/组件(OzJS微框架) • ⽤用包管理⼯工具管理依赖

    • 项⺫⽬目中的源代码⽂文件都必须是能直接在浏览器⾥里 使⽤用的静态⽂文件 App II - TodoMVC
  104. 最终效果(线上demo)

  105. 第三⽅方组件都会下载安装 到专⻔门的⺫⽬目录 包管理⼯工具(bower)⾃自动读 取的项⺫⽬目配置, 包含对第三⽅方组件的依赖 TodoMVC 项⺫⽬目提供的外观实现

  106. ⽤用包管理⼯工具初始化项⺫⽬目,⾃自动 下载安装依赖的第三⽅方项⺫⽬目

  107. None
  108. ⾼高级浏览器⾥里不需要 ES5 shim 新增 main.js,相当于上个项⺫⽬目中⻚页⾯面内的 inline script,增加了模块相关的配置 把模块名关联到包管理⼯工具 的安装路径 oz.js实现的模块化机制的配置

    禁⽌止使⽤用全局变量
  109. 在动态环境⾥里⾃自动处理模块的依赖和加载,不需要⼿手动维护⽂文件的使⽤用和先后顺序

  110. 有了第三⽅方model库(NervJS),model 模块 不但书写更简洁了,也更强⼤大了 扩展出条⺫⽬目model⾃自⼰己的⽅方法 model 中的数据模式 (schema)和默认值

  111. 列表model的成员是条⺫⽬目model

  112. view.js 仍然像上个项⺫⽬目⼀一样⽤用事件 代理(SovietJS)维护交互逻辑

  113. 双击和键盘事件 事件代理的初始化

  114. 因为可以复⽤用第三 ⽅方的 UI 组件了, view.js 现在主要承 担组合这些组件、 提供更抽象 API 的

    ⼯工作,避免 UI组 件之间的耦合
  115. view/ actionview 是 UI库⾥里 moui/ actionview 的 进⼀一步封装, 满⾜足项⺫⽬目的业 务需求

    view.js ⾥里使⽤用 view组件的接 ⼝口,⽽而不是直 接⽤用 DOM 的 接⼝口
  116. ⽤用 view/actionview 封装出更具体的 警告框和确认框组件

  117. None
  118. model 组件的初始化和操作 不再需要像上个项⺫⽬目⼀一样每次修 改 model 都需要⼿手动调⽤用 updateUI

  119. 可以监听 model 的改变, ⾃自动更新 UI (View Model Binder) ⽤用 model

    ⾃自⼰己的⽅方法⽣生成 纯数据传给视图
  120. 这个项⺫⽬目是包含多个 URL 的单⻚页 应⽤用,app.js 像服务器端web框架 的 controllter ⼀一样管理路由

  121. None
  122. Source code: https://github.com/dexteryy/todomvc/tree/gh-pages/labs/ architecture-examples/ozjs

  123. • 真实的⾖豆瓣产品 • 对⽐比:服务器端web框架中的静态⽂文件 • 项⺫⽬目中的⽂文件都是源代码,不再兼任『静态⽂文 件』 • 静态环境中的预处理/编译/构建 •

    ⽤用任务管理⼯工具整合⼤大量⼯工具和⼯工作流 • 应⽤用本⾝身的组件化,业务逻辑的分层,与服务器 端视图解耦 App III - GalEdtitor
  124. None
  125. 增加了任务管理⼯工具 (Grunt)的配置 从包管理安装的⽂文件中⾃自动 提取项⺫⽬目需要的部分,按项 ⺫⽬目⾃自⼰己的组织结构来放置 (grunt-dispatch)

  126. 因为第三⽅方组件的进⼀一步组 织,模块配置简单了很多 main.js 不再像上个项⺫⽬目那样初 始化应⽤用,⽽而是变成了单纯的 配置,相当于JS的构建脚本

  127. JS模板也被拆分为独 ⽴立的源代码⽂文件 模板⽂文件被编译 成JS模块

  128. css 也可以模块化和 复⽤用第三⽅方库( scss/ compass )

  129. 项⺫⽬目构建过程中会将 js、css、 html、图⽚片、JS模板分别从源 ⽂文件编译为⺫⽬目标⽂文件,再构建 出发布⽂文件,再⽤用这些⽣生成的 静态⽂文件填充 public ⺫⽬目录

  130. 静态环境中的构建 ⽤用 grunt-furnace 构建模板模块 ⽤用 Ozma 构建 JS 的静态⽂文件

  131. 上个项⺫⽬目中在动态环境中处理的模块加 载改为在静态环境⾥里完成,Ozma 会将 项⺫⽬目⾥里的JS源⽂文件按需要打包到静态⽂文 件中(⼀一个或多个)

  132. 浏览器⾥里只需要加载最少量的⽂文件

  133. 在⻚页⾯面⾃自⾝身的代码中配置、 修改、扩展、初始化和组织 调⽤用 app 的 API docs/index.html ⽤用于 应⽤用本⾝身(离线客户 端)的演⽰示和调试,

    没有特定的后端,所 以保存图⽚片是纯前端 模拟
  134. ⽤用本地存储

  135. 在真实产品中的⻚页 ⾯面模板(后端视 图)⾥里使⽤用时,实 现真正的保存图⽚片 功能

  136. ⽤用后端视图中输出的 数据(相当于预加 载)初始化应⽤用,如 果没有数据,则另外 请求后端API 业务逻辑可抽象出组件、应⽤用、 ⻚页⾯面三个层次,前两个都可以是 通过包管理⼯工具引⼊入的⼦子项⺫⽬目 (独⽴立代码仓库)

  137. 课后思考 • 将第⼀一个项⺫⽬目跟⾮非web的GUI开发⽅方式做对 ⽐比 • 三个项⺫⽬目中代码的相似之处 • 前两个项⺫⽬目的约束被解除之后带来的改变

  138. THE END dexter.yy@gmail.com