JavaScript 代码的组织方式
@CSS魔法
2015. 06. 18.
- 无组织
- 命名空间
- “模块”
- 模块
(30~45 分钟)
无组织
(这里给大家上真实代码)
var bindGetCodeAction = function () {
$('#getMobileCode').click(function (ev) {
countdown()
})
}
if ($('input[name=mobileCode]').length) {
bindGetCodeAction()
}
$('.form-submit').click(function (ev) {
$.ajax({
type: 'post',
url: '/foo/bar',
success: function (data) {
if (data.status) {
$('form').submit()
} else {
switch (data.code) {
case '10404':
//...
break
case '10403':
//...
break
}
}
}
})
})
var countdown = function () {
//...
}
- 无名可用
- 冲突导致 bug
- 长名难读
- 难调试
全局变量泄漏
var bindGetCodeAction = function () {
$('#getMobileCode').click(function (ev) {
countdown()
})
}
if ($('input[name=mobileCode]').length) {
bindGetCodeAction()
}
$('.form-submit').click(function (ev) {
$.ajax({
type: 'post',
url: '/foo/bar',
success: function (data) {
if (data.status) {
$('form').submit()
} else {
switch (data.code) {
case '10404':
//...
break
case '10403':
//...
break
}
}
}
})
})
var countdown = function () {
//...
}
- 难读
- 难调试、难测试
嵌套过深
(可能是为了避免太多全局变量)
var bindGetCodeAction = function () {
$('#getMobileCode').click(function (ev) {
countdown()
})
}
if ($('input[name=mobileCode]').length) {
bindGetCodeAction()
}
$('.form-submit').click(function (ev) {
$.ajax({
type: 'post',
url: '/xxx/yyy',
success: function (data) {
if (data.status) {
$('form').submit()
} else {
switch (data.code) {
case '10404':
//...
break
case '10403':
//...
break
}
}
}
})
})
var countdown = function () {
//...
}
- 静态:定义,备用
- 动态:调用定义好的,产生变化
“静态” 和 “动态” 夹杂
- 难读
- 真 TM 难读!
命名空间
JavaScript 中的命名空间
- 以树形结构组织代码
- 使用对象的嵌套来模拟树形结构
- 只暴露一个全局变量(树干)
命名空间带来的好处
- 避免全局变量泄漏
- 命名可读性好
- 易调试、易测试
var fabu = {
//...
}
- 确定一个全局变量
var fabu = {
init: function () {
//...
},
//...
}
fabu.init()
- 确定一个全局变量
- 动静分离
var fabu = {
init: function () {
this._bind()
},
_bind: function () {
var _this = this
if ($('input[name=mobileCode]').length) {
$('#getMobileCode').click(function (ev) {
_this.countdown()
})
}
$('.form-submit').click(function (ev) {
$.ajax({
//...
//...
//...
})
})
},
countdown: function () {
//...
}
}
fabu.init()
- 确定一个全局变量
- 动静分离
- 给大树增加枝叶
var fabu = {
init: function () {
this._bind()
},
_bind: function () {
var _this = this
if ($('input[name=mobileCode]').length) {
$('#getMobileCode').click(function (ev) {
_this.countdown()
})
}
$('.form-submit').click(function (ev) {
_this._ajax()
})
},
_ajax: function () {
$.ajax({
//...
//...
//...
})
},
countdown: function () {
//...
}
}
fabu.init()
- 确定一个全局变量
- 动静分离
- 给大树增加枝叶
- 把嵌套打平
- 确定一个全局变量
- 动静分离
- 给大树增加枝叶
- 把嵌套打平
- 继续打平
var fabu = {
init: function () {
this._bind()
},
_bind: function () {
//...
},
_ajax: function () {
var _this = this
$.ajax({
type: 'post',
url: '/xxx/yyy',
success: function (data) {
_this._success(data)
}
})
},
_success: function (data) {
//...
//...
//...
},
countdown: function () {...}
}
fabu.init()
- 确定一个全局变量
- 动静分离
- 给大树增加枝叶
- 把嵌套打平
- 继续打平
- 再打平
var fabu = {
init: function () {
this._bind()
},
_bind: function () {
//...
},
_ajax: function () {
//...
},
_success: function (data) {
if (data.status) {
$('form').submit()
} else {
switch (data.code) {
case '10404':
this._caseWrongInput()
break
case '10403':
this._caseNeedRecheck()
break
}
}
},
_caseWrongInput: function () {...},
_caseNeedRecheck: function () {...},
countdown: function () {...}
}
fabu.init()
- 确定一个全局变量
- 动静分离
- 给大树增加枝叶
- 把嵌套打平
- 继续打平
- 再打平
- 继续整理
var fabu = {
init: function () {
this._getElem()
this._bind()
},
_getElem: function () {
this.$btnSubmit = $('.form-submit')
this.$btnGetMobileCode = $('#getMobileCode')
this.$inputMobileCode = $('input[name=mobileCode]')
},
_bind: function () {
var _this = this
if (this.$inputMobileCode.length) {
this.$btnGetMobileCode.click(function (ev) {
_this.countdown()
})
}
this.$btnSubmit.click(function (ev) {
_this._ajax()
})
},
_ajax: function () {
//...
},
_success: function (data) {...},
_caseWrongInput: function () {...},
_caseNeedRecheck: function () {...},
countdown: function () {...}
}
fabu.init()
- 确定一个全局变量
- 动静分离
- 给大树增加枝叶
- 把嵌套打平
- 继续打平
- 再打平
- 继续整理
- 幻数 → 常量
var app = {
CODE_WRONG_INPUT: '10404',
CODE_NEED_RECHECK: '10403',
init: function () {
this._getElem()
this._bind()
},
_getElem: function () {...},
_bind: function () {...},
_ajax: function () {...},
_success: function (data) {
if (data.status) {
$('form').submit()
} else {
switch (data.code) {
case this.CODE_WRONG_INPUT:
this._caseWrongInput()
break
case this.CODE_NEED_RECHECK:
this._caseNeedRecheck()
break
}
}
},
_caseWrongInput: function () {...},
_caseNeedRecheck: function () {...},
countDown: function () {...}
}
app.init()
var fabu = {
CODE_WRONG_INPUT: '10404',
CODE_NEED_RECHECK: '10403',
init: function () {
this._getElem()
this._bind()
},
_getElem: function () {...},
_bind: function () {...},
_ajax: function () {...},
_success: function (data) {...},
_caseWrongInput: function () {...},
_caseNeedRecheck: function () {...},
countdown: function () {...}
}
fabu.init()
var bindGetCodeAction = function () {
$('#getMobileCode').click(function (ev) {
countdown()
})
}
if ($('input[name=mobileCode]').length) {
bindGetCodeAction()
}
$('.form-submit').click(function (ev) {
$.ajax({
type: 'post',
url: '/foo/bar',
success: function (data) {
if (data.status) {
$('form').submit()
} else {
switch (data.code) {
case '10404':
//...
break
case '10403':
//...
break
}
}
}
})
})
var countdown = function () {
//...
}
- 避免全局变量泄漏
- 命名可读性好
- 易调试、易测试
回顾一下我们的期望
避免全局变量泄漏
var app = {}
// 注册功能
app.reg = {
//...
}
// 登录功能
app.login = {
//...
}
// 发布功能
app.fabu = {
//...
}
// 初始化
app.reg.init()
app.login.init()
app.fabu.init()
每个功能都要占用一个全局变量?
树干只需要一个。
命名可读性好
// 发布的初始化方法
fabu.init()
// 发布的倒计时接口
fabu.countdown()
接近自然语言:
// 初始化
app.reg.init()
app.login.init()
app.fabu.init()
- 不仅要可读。
- 而且要 “可读”。
易调试、易测试
// 涉及的 DOM 元素
fabu.$btnSubmit
// 公开接口
fabu.countdown()
// 内部方法
fabu._success(data)
// 更细粒度的内部方法
fabu._caseWrongInput()
fabu._caseNeedRecheck()
所有变量、方法均可在全局访问。
新技能 Get
✓
“模块”
模块模式
- 要点:IIFE(立即调用的函数表达式)
- 改进:只向外暴露需要暴露的
“模块模式” 的各种变种。
有前面命名空间的基础很容易理解和掌握。
void function () {
'use strict'
//...
}()
- 新建一个 IIFE
void function () {
'use strict'
var CODE_WRONG_INPUT = '10404'
var CODE_NEED_RECHECK = '10403'
var $btnSubmit, $btnGetMobileCode, $inputMobileCode
function init() {...}
function _getElem() {...}
function _bind() {...}
function _ajax() {...}
function _success(data) {...}
function _caseWrongInput() {...}
function _caseNeedRecheck() {...}
function countdown() {...}
}()
- 新建一个 IIFE
- 把变量和函数塞进去
void function () {
'use strict'
var CODE_WRONG_INPUT = '10404'
var CODE_NEED_RECHECK = '10403'
var $btnSubmit, $btnGetMobileCode, $inputMobileCode
function init() {...}
function _getElem() {...}
function _bind() {...}
function _ajax() {...}
function _success(data) {...}
function _caseWrongInput() {...}
function _caseNeedRecheck() {...}
function countdown() {...}
var exports = {
init: init,
countdown: countdown
}
}()
- 新建一个 IIFE
- 把变量和函数塞进去
- 想好要暴露什么出来
var app = {}
void function () {
'use strict'
var CODE_WRONG_INPUT = '10404'
var CODE_NEED_RECHECK = '10403'
var $btnSubmit, $btnGetMobileCode, $inputMobileCode
function init() {...}
function _getElem() {...}
function _bind() {...}
function _ajax() {...}
function _success(data) {...}
function _caseWrongInput() {...}
function _caseNeedRecheck() {...}
function countdown() {...}
var exports = {
init: init,
countdown: countdown
}
// exports
app.fabu = exports
}()
app.fabu.init()
- 新建一个 IIFE
- 把变量和函数塞进去
- 想好要暴露什么出来
- 挂载到命名空间
- 避免全局变量泄漏
- 命名可读性好
- 易调试、易测试
再回顾一下我们的期望
易调试、易测试
按需暴露。
var app = {}
void function () {
'use strict'
var CODE_WRONG_INPUT = '10404'
var CODE_NEED_RECHECK = '10403'
var $btnSubmit, $btnGetMobileCode, $inputMobileCode
function init() {...}
function _getElem() {...}
function _bind() {...}
function _ajax() {...}
function _success(data) {...}
function _caseWrongInput() {...}
function _caseNeedRecheck() {...}
function countdown() {...}
var fabu = {
init: init,
countdown: countdown
}
// debug
fabu._caseWrongInput = _caseWrongInput
fabu._caseNeedRecheck = _caseNeedRecheck
// exports
app.fabu = fabu
}()
模块
真正的模块系统
- 每个模块一个文件(生产环境需对脚本资源打包)
- 不需要全局变量做命名空间
- 模块对外部一无所知,只决定向外暴露什么
- 依赖关系可声明
模块系统的规范及实现
- CommonJS (Node / Browserify ...)
- AMD (RequireJS ...)
- CMD (Sea.js ...)
- ES6 (...)
CommonJS
定义模块
// fabu.js
'use strict'
var CODE_WRONG_INPUT = '10404'
var CODE_NEED_RECHECK = '10403'
var $btnSubmit, $btnGetMobileCode, $inputMobileCode
function init() {...}
function _getElem() {...}
function _bind() {...}
function _ajax() {...}
function _success(data) {...}
function _caseWrongInput() {...}
function _caseNeedRecheck() {...}
function countdown() {...}
module.exports = {
init: init,
countdown: countdown
}
// page.js
var fabu = require('fabu')
fabu.init()
使用模块
对日常开发的建议
- 灵活对待各种场景
- 扬长避短
- 忌死板
Q & A
Thank You!
JavaScript 代码的组织方式
By CSS魔法
JavaScript 代码的组织方式
前端进阶之路(二)
- 1,225