JS编程

JS编程常识

一.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属性以便于获取模式串,例如:

      1

      2

      3

      <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. 用命名空间,提供一个避免命名空间冲突的方法:


    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    // 顶级命名空间

    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.典型用法(不好)


1

2

3

4

5

6

7

8

9

10

// 事件处理器

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.分离应用逻辑(稍好一点)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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.不要传递事件对象(最好)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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对象,因为兼容性,例如:


1

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对原型属性无效,只对实例属性有用


    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    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)

例如:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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.杜绝浏览器推断

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


1

2

3

if (document.all) { // IE

    // ...

}

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

5.到底应该用哪一个?

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

参考资料

  • 《Maintainable JavaScript》
时间: 2024-10-18 10:42:18

JS编程的相关文章

JS编程最佳实践

最近花了一周时间把<编写可维护的js> 阅读了一遍, 现将全书提到的JS编程最佳实践总结如下, 已追来者! 1.return 之后不可直接换行, 否则会导致ASI(自动分号插入机制)会在return 后插入一个分号. 2.一行语句最多不超过80个字符, 如果超过则应该在运算符后换行,并且追加两个缩进. 3.采用驼峰式命名,变量前缀为名词如:myName 函数应该以动词开始如:getName,常量应该以大写字母命名,如:MAX_COUNT, 构造函数首字母大写. 4.数字的写法: 整数:coun

node.js编程规范

B.1缩进 因为Node.js代码中很容易写出深层的函数嵌套,过多的空格会给阅读带来不便,因此我们选择两空格缩进 B.2行宽 为了保证在任何设备上都可以方便地阅读,我们建议把行宽限制为80个字符. B.3 语句分隔符 建议一律使用分号( ; ),哪怕一行只有一个语句,也不要省略分号. B.4 变量定义 永远使用var 定义变量,而不要通过赋值隐式定义变量.因为通过赋值隐式定义的变量总是全局变量,会造成命名空间污染. 使用var 定义变量时,确保每个语句定义一个变量,而不要通过逗号( , )把多个

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

JavaScript中有一个call和apply方法,其作用基本相同,但也有略微的区别. 一.方法定义 1.call 方法 语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]]) 参数 thisObj  可选项.将被用作当前对象的对象. arg1, arg2,  , argN 可选项.将被传递方法参数序列. 说明 call 方法可以用来代替另一个对象调用一个方法.call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象.  

Win8 HTML5与JS编程学习笔记(二)

近期一直受到win8应用的Grid布局困扰,经过了半下午加半个晚上的奋斗,终于是弄明白了Grid布局方法的规则.之前我是阅读的微软官方的开发教程,书中没有详细说明CSS3的布局规则,自己鼓捣了半天也是一头雾水,于是又找到了官方的启蒙教程的布局一张,仔细阅读了一遍,又思考了代码,并在代码的基础上实验,终于是明白了布局方法.官方教程地址是:http://msdn.microsoft.com/zh-cn/library/windows/apps/jj841108.aspx 微软基于CSS3开发了Gri

Js编程原则和良好习惯

1.预留退路:在禁止Js或者Js不被支持的情况下,网页还能正常工作.也就是说,虽然某些功能无法使用,但最基本的操作仍能顺利完成. 案例:点击链接,打开一个新窗口.就如本站的登录(弹出一个模拟窗口),与其让用户在点击链接的时候被带离当前页面,不如让用户仍停留在当前页面,并用一个弹出窗口来显示相关信息,这无疑是一种更好的解决方法.一些错误的写法:伪协议:<a href="javascript:showWindow('login', this.href);" >登录</a&

JS编程常识

一.UI层的松耦合 松耦合就是要求各层遵循“最少知识原则”,或者说是各层各司其职,不要越权: HTML:结构层 CSS:表现层 JS:行为层 对于各层的职能,有一句比较贴切的解释:HTML是名词(n),CSS是形容词(adj)和副词(adv),JS是动词 因为三层联系紧密,实际应用中很容易越权: 1.从css中分离js 尽量不要用css表达式,如果非要用也应该把相应的代码放在hack中,便于维护 2.从js中分离css 不要用ele.style.attr及ele.cssText,应该用操作类名代

模块化JS编程 seajs简单例子

1.引入sea.js test.html 1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>My Frist SeaJs</title> 6 </head> 7 <body> 8 <div id="container"> 9 10 </div> 11 12 &

js编程-面相对象

//js面相对象编程 //定义constructor构造方法 function myFn(name,sex){ this.name = name; this.sex = sex; } //用prototype追加属性方法 myFn.prototype.getName = function(inter){ console.log(this.name); console.log("兴趣:" + inter); return this.name; } //实例化myFn var newMy

IE JS编程需注意的内存释放问题

1.给DOM对象添加的属性是一个对象的引用.范例:var MyObject = {};document.getElementById('myDiv').myProp = MyObject;解决方法:在window.onunload事件中写上: document.getElementById('myDiv').myProp = null; 2.DOM对象与JS对象相互引用.范例:function Encapsulator(element) {    this.elementReference =