一.UI层的松耦合

松耦合就是要求各层遵循“最少知识原则”,或者说是各层各司其职,不要越权:

  • HTML:结构层

  • CSS:表现层

  • JS:行为层

对于各层的职能,有一句比较贴切的解释:HTML是名词(n),CSS是形容词(adj)和副词(adv),JS是动词

因为三层联系紧密,实际应用中很容易越权:

1.从css中分离js

尽量不要用css表达式,如果非要用也应该把相应的代码放在hack中,便于维护

2.从js中分离css

不要用ele.style.attr及ele.cssText,应该用操作类名代替

3.从html中分离js

不要用onclick等属性直接指定事件处理函数,应该用添加事件处理器方式代替

一般不要用<script>标签直接嵌入js代码,尽量放在外部js文件中。当然,对于功能单一且不需要复用的代码可以直接嵌在HTML中,例如表单验证代码

4.从js中分离html

不要直接用innerHTML硬编码,可以用以下3种方式代替:

  1. 用Ajax从服务端获取HTML串,避免硬编码

  2. 用简单的客户端模版,有2种实现方式:

    • 用注释携带模式串

    • 用script标签携带模式串,把type属性设置为浏览器无法识别的值,还应该给script标签设置id属性以便于获取模式串,例如:

      <script type="text/x-my-template" id="list-item">
      <li><a href="%s">%s</li>
      </script>

      推荐使用这种方式,因为更容易获取模式串

  3. 用复杂的客户端模版,比如jade、ejs

P.S.因为html与css的解耦与js编程没有关系,所以书中也没有相应内容

二.少用全局变量

1.全局变量带来的麻烦

  • 命名冲突

  • 代码不健壮,函数需要的所有外部数据都应该用参数传进来,而不要用全局变量传参

  • 难以测试,需要重建整个全局环境

2.隐式全局变量

不要用隐式全局变量方式声明全局变量,建议所有变量声明都带上var关键字

为了避免隐式全局变量,还应该开启严格模式(”use strict”;),[IE10+]支持

3.单全局变量方式

  1. 用命名空间,提供一个避免命名空间冲突的方法:

    // 顶级命名空间
    var app = {
    /*
    * 创建/获取子命名空间,支持链式调用
    */
    namespace: function(ns) {
    var parts = ns.split("."),
    object = this,
    i, len; for (i = 0, len = parts.length; i < len; i++) {
    if (!object[parts[i]]) {
    object[parts[i]] = {};
    }
    object = object[parts[i]];
    } return object; // 支持链式调用
    }
    } // 测试
    app.namespace("Consts").homepage = "http://ayqy.net/";
    app.namespace("Consts").author = "ayqy";
    // http://ayqy.net/, ayqy
    alert(app.Consts.homepage + ", " + app.Consts.author);
  2. 模块化

    AMD/CMD,一点扩展知识如下:

    CommonJS是一套理论规范(比如js的理论规范是ES),而SeaJS, RequireJS都是对CommonJS的Modules部分的具体实现

    CommonJS是面向浏览器外(server端)的js制定的,所以是同步模块加载,SeaJS是CommonJS的一个实现,而RequireJS虽然也是对CommonJS的一个实现,但它是异步模块加载,算是更贴近浏览器的单线程环境

    总结:CommonJS的Modules部分提出了模块化代码管理的理论,为了让js可以模块化加载,而RequireJS, SeaJS等各种实现可以称为模块化脚本加载器

    • CMD:Common模块定义,例如SeaJS

    • AMD:异步模块定义,例如RequireJS

    都是用来定义代码模块的一套规范,便于模块化加载脚本,提高响应速度

    CMD与AMD的区别:

  • CMD依赖就近。便于使用,在模块内部可以随用随取,不需要提前声明依赖项,所以性能方面存在些许降低(需要遍历整个模块寻找依赖项目)

  • AMD依赖前置。必须严格声明依赖项,对于逻辑内部的依赖项(软依赖),以异步加载,回调处理的方式解决

4.零全局变量方式

用IIFE(匿名函数立即执行)实现,针对不需要复用的功能模块可以用IIFE完全消除全局变量,所以一般IIFE都是用来辅助命名空间/模块化方式的

三.事件处理

1.典型用法(不好)

// 事件处理器
function handleClick(event) {
var popup = document.getElementById("popup");
popup.style.left = event.clientX + "px";
popup.style.top = event.clientY + "px";
popup.className = "display";
} // 添加事件处理器
ele.addEventListener("click", handleClick);

2.分离应用逻辑(稍好一点)

var app = {
// 事件处理
handleClick: function(event) {
this.showPopup(event);
}, // 应用逻辑
showPopup: function(event) {
var popup = document.getElementById("popup");
popup.style.left = event.clientX + "px";
popup.style.top = event.clientY + "px";
popup.className = "display";
}
}; // 添加事件处理器
// P.S.事件处理器是一个方法声明,而不是方法调用,无法传参,所以需要多一层匿名函数
ele.addEventListener("click", function() {
app.handleClick(event);
});

3.不要传递事件对象(最好)

var app = {
// 事件处理
handleClick: function(event) {
this.showPopup(event.clientX, event.clientY); // 参数变了
}, // 应用逻辑
showPopup: function(x, y) { // 形参变了
var popup = document.getElementById("popup");
popup.style.left = x + "px";
popup.style.top = x + "px";
popup.className = "display";
}
}; // 添加事件处理器
ele.addEventListener("click", function() {
app.handleClick(event);
});

不要传递事件对象”是一条优化原则,在js高程的优化部分也有提到过,但本书给出了详细理由

直接传递事件对象存在以下缺点:

  1. 接口定义不明确,参数作用未知

  2. 难以测试(重建一个event对象?)

四.少与null比较

1.检测基本值

用typeof检测,但要注意typeof null返回object,这不太科学,因为js认为null是一个空对象的引用

但用 === null检测DOM元素是合理的,因为null是document.getXXByXXX的可能输出之一

2.检测引用值

instanceof并不能准确地检测子类型,而且不要用它检测fun和arr,因为不能跨frame

  1. 检测fun

用typeof检测一般方法;用in检测DOM方法

  1. 检测arr

Object.prototype.toString.call(arr) === "[Object Array]"检测

注意:ES5有原生的Array.isArray()方法,[IE9+]支持

3.检测属性

用in配合hasOwnProperty()检测

注意:[IE8-]的DOM元素不支持hasOwnProperty(),用的时候要先检测

五.分离配置数据

1.配置数据有哪些?

  1. 硬编码的值

  2. 将来可能会变的值

比如:

  • URL

  • 需要显示给用户的字符串

  • 重复使用的唯一值

  • 设置(例如每页显示多少列表项)

  • 可能会变的任何值(不好维护的东西都算配置数据)

2.分离配置数据

先从应用逻辑中分离出配置数据,最简单的可以把所有配置数据分级存放在自定义的config对象中

3.存储配置数据

可以用js文件存储配置数据,便于加载,但配置数据要严格符合js语法,容易出错。作者建议把配置数据存储为格式简单的属性文件再用工具转换为JSON/JSONP/js格式文件用于加载,作者推荐一个自己写的工具props2js

六.抛出自定义错误

1.Error的本质

用来标志出乎意料的东西,避免静默失败,便于调试

2.用js抛出错误

不要抛其它类型,要抛Error对象,因为兼容性,例如:

throw "error: invalid args";    // 有些浏览器不显示该字符串

3.抛出错误的优点

精确定位错误,建议错误信息格式:函数名 + 错误原因

4.何时该抛出错误

只抛常用方法(工具方法)中的错误,一般原则:

  1. 修复了奇葩bug之后,应该添几个自定义错误,防止错误再次发生

  2. 如果写代码的时候觉得某些点可能引起大麻烦,就应该抛出自定义错误

  3. 如果代码是写给别人用的,应该想想别人使用的时候可能遇到哪些问题,应该在自定义错误中给出提示

5.try-catch语句

finally不常用,因为会影响catch中的return

不要留空的catch块,静默失败可能会把问题变得更麻烦

6.几种错误类型

可以抛出原生的几种错误类型实例,配合instanceof做针对性错误处理

P.S.关于具体的几种错误类型以及错误处理的更多信息,请查看黯羽轻扬:JS学习笔记8_错误处理

七.尊重对象所有权

1.哪些对象不属于我们?

  • 原生对象(Object、Array等等)

  • DOM对象(比如document)

  • BOM对象(比如window)

  • 库对象(比如JQuery)

2.具体原则

  1. 不要重写方法

  2. 不要添新方法,非要改库功能的话,可以给库开发插件

  3. 不要删除方法,不想用的不要删除,标明过时即可

    注意:delete对原型属性无效,只对实例属性有用

    function Fun() {
    this.attr1 = 1; // 实例属性
    }
    Fun.prototype.attr2 = 2; // 原型属性 // 测试
    var obj = new Fun();
    alert(obj.attr1 + ", " + obj.attr2); // 1, 2
    delete obj.attr1;
    delete obj.attr2;
    alert(obj.attr1 + ", " + obj.attr2); // undefined, 2
    delete Fun.prototype.attr2;
    alert(obj.attr1 + ", " + obj.attr2); // undefined, undefined

3.更好的方式

  1. 基于对象的继承

    也就是clone一个新对象,新对象是属于自己的,可以随便改

  2. 基于类型的继承

    注意不要继承DOM/BOM/Array,因为支持性不好

    P.S.关于对象继承/类型继承的具体实现,请查看黯羽轻扬:重新理解JS的6种继承方式

  3. 外观模式

    其实就是组合,因为继承/组合都是实现代码复用的方式,关于外观模式的更多信息请查看黯羽轻扬:设计模式之外观模式(Facade Pattern)

    一点题外话:facade与adapter的区别是前者创建了新接口,后者只是实现了已存在的接口,作者一针见血

4.polyfill的优缺点

polyfill 是“在旧版浏览器上复制标准 API 的 JavaScript 补充”。“标准API”指的是 HTML5 技术或功能,例如 Canvas。“JavaScript补充”指的是可以动态地加载 JavaScript 代码或库,在不支持这些标准 API 的浏览器中模拟它们。因为 polyfill 模拟标准 API,所以能够以一种面向所有浏览器未来的方式针对这些 API 进行开发,最终目标是:一旦对这些 API 的支持变成绝对大多数,则可以方便地去掉 polyfill,无需做任何额外工作。

关于polyfill的更多信息请查看博客园:[译]shim和polyfill有什么区别?

优点:不需要的时候很容易去掉

缺点:如果polyfill的实现与标准不完全一致就麻烦了

建议不要用polyfill,应该用原生方法 + 外观模式来代替,这样更灵活

5.防篡改

应该开严格模式,因为非严格模式下静默失败难以调试

八.浏览器检测

1.UA(User Agent)检测

非要UA检测的话,尽量向后检测而不是向前,因为后面的不会再变了

P.S.作者认为不用管UA会不会变,理由是会设置UA的用户应该也知道这样做的后果

2.特性检测

特性检测的一般格式如下:

  1. 尝试标准方式

  2. 尝试特定浏览器的实现方式

  3. 不支持的话要给出逻辑反馈(比如return null)

例如:

function setAnimation (callback) {
// 1.尝试标准方式
if (window.requestAnimationFrame) { // standard
return requestAnimationFrame(callback);
}
// 2.尝试特定浏览器的实现方式
else if (window.mozRequestAnimationFrame) { // Firefox
return mozRequestAnimationFrame(callback);
}
else if (window.webkitRequestAnimationFrame) { //WebKit
return webkitRequestAnimationFrame(callback);
}
else if (window.oRequestAnimationFrame) { // Opera
return window.oRequestAnimationFrame(callback);
}
else if (window.msRequestAnimationFrame) { // IE
return window.msRequestAnimationFrame(callback);
}
// 3.不支持的逻辑反馈
else {
return setTimeout(callback, 0);
}
}

3.杜绝特性推断

不能根据一个特性推断另一个特性,因为长得像鸭子的东西不一定也像鸭子一样呱呱叫

4.杜绝浏览器推断

不要根据特性去推断浏览器,比如典型的:

if (document.all) { // IE
// ...
}

这样做是不对的,不应该尝试用特征去描述一个东西,很容易因为条件过少或者过多导致描述不准确

5.到底应该用哪一个?

尽量用直接特性检测,不行才用UA检测。至于推断,就根本不考虑了,没有任何理由去用推断

参考资料

  • 《Maintainable JavaScript》

JS编程常识的更多相关文章

  1. JS编程

    JS编程常识   一.UI层的松耦合 松耦合就是要求各层遵循“最少知识原则”,或者说是各层各司其职,不要越权: HTML:结构层 CSS:表现层 JS:行为层 对于各层的职能,有一句比较贴切的解释:H ...

  2. JS编程最佳实践

    最近花了一周时间把<编写可维护的js> 阅读了一遍, 现将全书提到的JS编程最佳实践总结如下, 已追来者! 1.return 之后不可直接换行, 否则会导致ASI(自动分号插入机制)会在r ...

  3. 【js 编程艺术】小制作一

    最近在看js编程艺术,照葫芦画瓢,做了一个小网页.作为一枚前端渣渣,遇到了好多坑,在这里就不提了. 首先是html代码 /*gallery.html*/<!DOCTYPE html> &l ...

  4. C#网络程序设计(1)网络编程常识与C#常用特性

        网络程序设计能够帮我们了解联网应用的底层通信原理!     (1)网络编程常识: 1)什么是网络编程 只有主要实现进程(线程)相互通信和基本的网络应用原理性(协议)功能的程序,才能算是真正的网 ...

  5. JS编程题练习

    JS编程题练习 1. 两个数组合并成一个数组排序返回 先依次比较两个数组,按照小的就传入新的数组.当这次比较完之后可能有一个数组的长度很长,留下一些数组,然后在新数组的末尾插入即可. function ...

  6. Java 并发编程常识 —— by 梁飞

    参考 :梁飞 并发编程常识

  7. Js编程实践

    js编程实践思维导向图 ---欢迎收藏 ^ - ^

  8. 面向对象的js编程 Call和apply方法

    JavaScript中有一个call和apply方法,其作用基本相同,但也有略微的区别. 一.方法定义 1.call 方法 语法:call([thisObj[,arg1[, arg2[, [,.arg ...

  9. Java并发编程常识

    这是why的第 85 篇原创文章 写中间件经常要做两件事: 1.延迟加载,在内存缓存已加载项. 2.统计调用次数,拦截并发量. 就这么个小功能,团队里的人十有八九写错. 上面这句话不是我说的,是梁飞在 ...

随机推荐

  1. MySQL大数据分页的优化思路和索引延迟关联

    之前上次在部门的分享会上,听了关于MySQL大数据的分页,即怎样使用limit offset,N来进行大数据的分页,现在做一个记录: 首先我们知道,limit offset,N的时候,MySQL的查询 ...

  2. XidianOJ 1120 Gold of Orz Pandas

    题目描述 Orz Panda is addicted to one RPG game. To make his character stronger, he have to fulfil tasks ...

  3. 理解docker容器和镜像(layer,ufs)和docker命令解释

    博客好文1:http://blog.csdn.net/x931100537/article/details/49633107(理解docker容器和镜像,理解简单,从原理入手,什么是layer,什么是 ...

  4. IRelationalOperator空间关系接口简介

    几何对象之间都存在某种关联关系,如包含,相等,在内部,相交,叠加等.这些关联关系的获得都可以通过IRelationalOperator接口来获得,关系运算是在两个几何对象间进行的,通过IRelatio ...

  5. mysql - 行号

    1. 初始化数据 - 列唯一 DROP TABLE IF EXISTS `sales`; CREATE TABLE `sales` ( `empid` ) NOT NULL, `mgrid` ) NO ...

  6. 【BZOJ2595】游览计划(状压DP,斯坦纳树)

    题意:见题面(我发现自己真是越来越懒了) 有N*M的矩阵,每个格子有一个值a[i,j] 现要求将其中的K个点(称为关键点)用格子连接起来,取(i,j)的费用就是a[i,j] 求K点全部连通的最小花费以 ...

  7. HTML5和CSS3基础教程(第8版)-读书笔记(2)

    第7章 CSS构造模块 7.1 构造样式规则 样式表中包含了定义网页外观的规则.样式表中的每条规则都有两个主要部分:选 择 器(selector) 和 声 明 块(declaration block) ...

  8. Spring +SpringMVC 实现文件上传功能。。。

    要实现Spring +SpringMVC  实现文件上传功能. 第一步:下载 第二步: 新建一个web项目导入Spring 和SpringMVC的jar包(在MyEclipse里有自动生成spring ...

  9. [转] 关于linux下通过shell命令(自动)修改用户密码

    关于linux下通过shell命令(自动)修改用户密码 2012-04-23 18:47:39 分类: 原文地址:关于linux下(自动)修改用户密码 作者:ubuntuer 本文章总结了如何手动.自 ...

  10. main与对象初始化 in C++

    没有学过代码编译的原理,以前也没有兴趣去学编译器的相关原理,但是近期通过阅读google开源项目gtest,对我稍有触动. 代码: main test示例 TEST宏定义 #define TEST(t ...