模式是解决或者避免一些问题的方案。

在JavaScript中,会用到一些常用的编码模式。下面就列出了一些常用的JavaScript编码模式,有的模式是为了解决特定的问题,有的则是帮助我们避免一些JavaScript中容易出现的错误。

单一var模式

所谓“单一var模式”(Single var pattern)就是指在函数顶部,只使用一个var进行变量声明的模式。例如:

function func() {
var a = 1,
b = 2,
sum = a + b,
myObject = {},
i,
j; // other code
}

使用这个模式的好处:

  • 在函数顶部展示了所有函数中使用的局部变量
  • 防止变量提升引起的问题

变量提升

JavaScript允许在函数的任意地方声明变量,但是效果都等同于在函数顶部进行声明,这个是所谓的变量提升(Hoisting)。

看一个例子:

var num = 10;

function func() {
alert(num); // undefined
var num = 1;
alert(num); // 1
} func();

从这个例子可以看到,第一次alert的值并不是10,而是undefined。所以,应该尽量使用“单一var模式”来避免类似的问题。

关于变量提升的细节,请参考我前面一篇JavaScript的执行上下文

for-in循环

在JavaScript中,for-in循环主要用来枚举对象的属性。

但是,由于JavaScript中原型链的存在,一般都会结合hasOwnProperty()来使用for-in循环,从而过滤原型链上的非该对象的属性。

var wilber = {
name: "Wilber",
age: 28,
gender: "male"
}; Object.prototype.printPersonalInfo = function() {
console.log(this.name, "is", this.age, "years old");
}; for(var prop in wilber) {
if(wilber.hasOwnProperty(prop)) {
console.log(prop, ":", wilber[prop]);
}
}

开放的大括号位置

根据开发人员的习惯,开放大括号的位置会有不同的选择,可以和语句放在同一行,也可以放在新的一行:

var total = 10;

if(tatal > 5) {
console.log("bigger than 5");
} if(tatal > 5)
{
console.log("bigger than 5");
}

两种形式的代码都能实现同样的逻辑,但是,JavaScript允许开发人员省略分号,JavaScript的分号插入机制(semicolon insertion mechanism)会负责加上省略的分号,这时开放大括号的位置不同就可能产生不同的结果。

看一个例子:

function func() {
return
{
name: "Wilber"
};
} alert(func());
// undefined

之所以得到的结果是undefined就是因为JavaScript的分号插入机制,在return语句之后自动添加了分号。

调整一下开放的大括号的位置就可以避免这个问题:

function func() {
return {
name: "Wilber"
};
} alert(func());
// [object]

所以,关于开放的大括号位置,建议将开放的大括号放置在前面语句的同一行

强制new模式

JavaScript中,通过new关键字,可以用构造函数来创建对象,例如:

function Person(name, city) {
this.name = name;
this.city = city; this.getInfo = function() {
console.log(this.name, "lives at", this.city);
}
} var will = new Person("Will", "Shanghai"); will.getInfo();
// Will lives at Shanghai

但是,如果开发人员忘记了new关键字,那么构造函数中的this将代表全局对象(浏览器中就是window对象),所有的属性将会变成全局对象的属性。

function Person(name, city) {
this.name = name;
this.city = city; this.getInfo = function() {
console.log(this.name, "lives at", this.city);
}
} var will = Person("Will", "Shanghai");
console.log(will.name);
// Uncaught TypeError: Cannot read property 'name' of undefined
console.log(window.name);
// Will
console.log(window.city);
// Shanghai
window.getInfo();
// Will lives at Shanghai

所以,为了避免这类问题的方式,首先是从代码规范上下手。建议对于所有的JavaScript构造函数的命名方式都遵循,构造函数使用首字母大写的命名方式

这样当我们看到首字母大写的函数,就要考虑是不是漏掉了new关键字。

自调用构造函数

当然除了规范之外,还可以通过代码的方式来避免上面的问题。

具体的做法就是,在构造函数中检查this是否为构造函数的一个实例,如果不是,构造函数可以通过new关键字进行自调用。

下面就是使用自调用构造函数对上面的例子进行改进:

function Person(name, city) {
if(!(this instanceof Person)) {
return new Person(name, city);
} this.name = name;
this.city = city; this.getInfo = function() {
console.log(this.name, "lives at", this.city);
}
} var will = Person("Will", "Shanghai");
console.log(will.name);
// Will
console.log(will.city);
// Shanghai
will.getInfo();
// Will lives at Shanghai
window.getInfo();
// Uncaught TypeError: window.getInfo is not a function

结合构造函数的命名约定和自调用的构造函数,这下就不用担心漏掉new关键字的情况了。

数组性质检查

当在JavaScript中判断一个对象是不是数组的时候,不能直接使用typeof,因为我们会得到object

在ECMA5中提出了Array.isArray()这个函数,我们可以直接使用来判断一个对象是不是数组类型。

对于不支持ECMA5的环境,我们可以通过下面的方式自己实现Array.isArray()这个函数。

if(typeof Array.isArray === "undefined") {
Array.isArray = function(arg){
return Object.prototype.toString.call(arg) === "[object Array]";
};
} var arr = [];
console.log(Array.isArray(arr));
// true

立即执行函数

立即执行函数是JavaScript中非常常用的一种模式,形式如下:

(function() {
// other code
}());

通过这个模式可以提供一个局部的作用域,所以函数代码都会在局部作用域中执行,不会污染其他作用域。

现在的很多JavaScript库都直接使用了这种模式,例如JQuery、underscore等等。

立即执行函数的参数

关于立即执行函数另外一点需要注意的地方就是立即执行函数的参数。

我们可以像正常的函数调用一样进行参数传递:

(function(name, city) {
console.log(name, "lives at", city);
}("Wilber", "Shanghai"));
// Wilber lives at Shanghai

在立即执行函数中,是可以访问外部作用域的(当然包括全局对象),例如:

var name = "Wilber";
var city = "Shanghai"; (function() {
console.log(name, "lives at", city);
}());
// Wilber lives at Shanghai

但是,如果立即执行函数需要访问全局对象,常用的模式就是将全局对象以参数的方式传递给立即执行函数

var name = "Wilber";
var city = "Shanghai"; (function(global) {
console.log(global.name, "lives at", global.city);
}(this));
// Wilber lives at Shanghai

这样做的好处就是,在立即执行函数中访问全局变量的属性的时候就不用进行作用域链查找了,关于更多JavaScript作用域链的内容,可以参考理解JavaScript的作用域链

初始化时分支

初始化时分支(Init-time Branching)是一种常用的优化模式,就是说当某个条件在整个程序声明周期内都不会发生改变的时候,不用每次都对条件进行判断,仅仅一次判断就足够了。

这里最常见的例子就是对浏览器的检测,在下面的例子中,每次使用utils.addListener1属性的时候都要进行浏览器判断,效率比较低下:

var utils = {
    addListener: function(el, type, fn) {
        if (typeof window.addEventListener === 'function') {
            el.addEventListener(type, fn, false);
        } else if (typeof document.attachEvent === 'function') { // IE
            el.attachEvent('on' + type, fn);
        } else { // older browsers
            el['on' + type] = fn;
        }
    },
    removeListener: function(el, type, fn) {
        // pretty much the same...
    }
};

所以,根据初始化时分支模式,可以在脚本初始化的时候进行一次浏览器检测,这样在以后使用utils的时候就不必进行浏览器检测了:

// the interface
var utils = {
    addListener: null,
    removeListener: null
}; // the implementation
if (typeof window.addEventListener === 'function') {
    utils.addListener = function(el, type, fn) {
        el.addEventListener(type, fn, false);
    };
    utils.removeListener = function(el, type, fn) {
        el.removeEventListener(type, fn, false);
    };
} else if (typeof document.attachEvent === 'function') { // IE
    utils.addListener = function(el, type, fn) {
        el.attachEvent('on' + type, fn);
    };
    utils.removeListener = function(el, type, fn) {
        el.detachEvent('on' + type, fn);
    };
} else { // older browsers
    utils.addListener = function(el, type, fn) {
        el['on' + type] = fn;
    };
    utils.removeListener = function(el, type, fn) {
        el['on' + type] = null;
    };
}

命名空间模式

JavaScript代码中,过多的全局变量经常会引发一些问题,比如命名冲突。

结合命名空间模式就可以一定程度上减少代码中全局变量的个数。

下面就看一个通用命名空间函数的实现:

var MYAPP = MYAPP || {};
MYAPP.namespace = function (ns_string) {
var parts = ns_string.split('.'),
parent = MYAPP,
i;
// strip redundant leading global
if (parts[0] === "MYAPP") {
parts = parts.slice(1);
}
for (i = 0; i < parts.length; i += 1) {
// create a property if it doesn't exist
if (typeof parent[parts[i]] === "undefined") {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
};

结合这个通用命名空间函数的,就可以实现代码的模块化:

// assign returned value to a local var
var module2 = MYAPP.namespace('MYAPP.modules.module2');
module2 === MYAPP.modules.module2; // true // skip initial `MYAPP`
MYAPP.namespace('modules.module51'); // long namespace
MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property');

声明依赖关系

JavaScirpt库通常是通过命名空间来进行模块化,当我们在代码中使用第三方的库的时候,可以只引入我们代码依赖的模块。

所谓声明依赖关系,就是指在函数或者模块的顶部是声明代码需要依赖哪些模块,这个声明包括创建一个局部变量,并将它们指向你需要的模块:

var myFunction = function () {
// dependencies
var event = YAHOO.util.Event,
dom = YAHOO.util.Dom;
// use event and dom variables
// for the rest of the function...
};

通过声明依赖关系这种模式,会给我们带来很多好处:

  • 明确的依赖声明可以向你的代码的使用者表明这些特殊的脚本文件需要被确保包含进页面
  • 数头部的声明解,让发现和处理依赖关系更加简单
  • 局部变量(比如:dom)通常比使用全局变量(比如:YAHOO)快,比访问全局对象的属性(比如:YAHOO.util.Do)更快,可以得到更好的性能,全局符号只会在函数中出现一次,然后就可以使用局部变量,后者速度更快。
  • 压缩工具比如YUICompressor 和 Google Closure compiler会重命名局部变量,产生更小的体积的代码,但从来不会重命名全局变量,因为那样是不安全的

代码复用模式

下面就看看JavaScript中的代码复用模式。一般来说,通常使用下面的方式来实现代码的复用:

  • 继承
  • 借用方法

继承

在JavaScript中可以很方便的通过原型来实现继承。

关于原型式继承,ECMA5通过新增Object.create()方法规范化了原型式继承。这个方法接收两个参数:

  • 一个用作新对象原型的对象
  • 一个为新对象定义额外属性的对象(可选的)

看一个使用Object.create()的例子:

utilsLibC = Object.create(utilsLibA, {
sub: {
value: function(){
console.log("sub method from utilsLibC");
}
},
mult: {
value: function(){
console.log("mult method from utilsLibC");
}
},
}) utilsLibC.add();
// add method from utilsLibA
utilsLibC.sub();
// sub method from utilsLibC
utilsLibC.mult();
// mult method from utilsLibC
console.log(utilsLibC.__proto__);
// Object {add: (), sub: (), __proto__: Object}
console.log(utilsLibC.__proto__.constructor);
// function Object() { [native code] }

关于JavaScript继承的更多信息,可以参考关于JavaScript继承的那些事

借用方法

有时候可能只需要一个已经存在的对象的一个或两个方法,但是又不想通过继承,来建立额外的父子(parent-child)关系。

这时就可以考虑使用借用方法模式完成一些函数的复用。借用方法模式得益于function的方法call()和apply()。

这种模式一个常见用法就是借用数组方法。

数组拥有有用的方法,那些类数组对象(array-like objects)比如arguments类数组对象(array-like objects)比如arguments没有的方法。所以arguments可以借用数组的方法,比如slice()方法,看一个例子:

function f() {
var args = [].slice.call(arguments, 1, 3);
return args;
}
// example
f(1, 2, 3, 4, 5, 6); // returns [2,3]

总结

本文主要介绍了JavaScript中常用的编码模式,通过这些模式可以使代码健壮、可读。

主要参考《JavaScript patterns》。

常用的JavaScript模式的更多相关文章

  1. GOF提出的23种设计模式是哪些 设计模式有创建形、行为形、结构形三种类别 常用的Javascript中常用设计模式的其中17种 详解设计模式六大原则

    20151218mark 延伸扩展: -设计模式在很多语言PHP.JAVA.C#.C++.JS等都有各自的使用,但原理是相同的,比如JS常用的Javascript设计模式 -详解设计模式六大原则 设计 ...

  2. 《JavaScript 模式》知识点小抄本(上)

    介绍 最近开始给自己每周订个学习任务,学习结果反馈为一篇文章的输出,做好学习记录. 这一周(02.25-03.03)我定的目标是<JavaScript 模式>的第七章学习一遍,学习结果的反 ...

  3. javascript 模式(1)——代码复用

    程序的开发离不开代码的复用,通过代码复用可以减少开发和维护成本,在谈及代码复用的时候,会首先想到继承性,但继承并不是解决代码复用的唯一方式,还有其他的复用模式比如对象组合.本节将会讲解多种继承模式以实 ...

  4. 【读书笔记】读《JavaScript模式》 - 函数复用模式之现代继承模式

    现代继承模式可表述为:其他任何不需要以类的方式考虑得模式. 现代继承方式#1 —— 原型继承之无类继承模式 function object(o) { function F() {}; F.protot ...

  5. 【读书笔记】读《JavaScript模式》 - 函数复用模式之类式继承模式

    实现类式继承的目标是通过构造函数Child()获取来自于另外一个构造函数Parent()的属性,从而创建对象. 1.类式继承模式#1 —— 默认方式(原型指向父函数实例) function Paren ...

  6. 初涉JavaScript模式系列 阶段总结及规划

    总结 不知不觉写初涉JavaScript模式系列已经半个月了,没想到把一个个小点进行放大,竟然可以发现这么多东西. 期间生怕对JS的理解不到位而误导各位,读了很多书(个人感觉JS是最难的oo语言),也 ...

  7. 常用的JavaScript正则匹配规则代码收藏,很实用

    收集一些常用的JavaScript正则表达式匹配规则,比如匹配电话号码.Email.中文字符.身份证号.邮编.QQ号.过滤空白行.匹配特定数字等.觉得这玩意是很有用的,只不过自己水平菜,老是自己写不出 ...

  8. 精读JavaScript模式(六),Memoization模式与函数柯里化的应用

    假期就这么结束了!十天假就有三天在路上,真的难受!想想假期除了看了两场电影貌似也没做什么深刻印象的事情.流浪地球,特效还是很赞,不过对于感情的描写还是逃不掉拖沓和尴尬的通病,对于国产科幻还是抱有支持的 ...

  9. 来自极客头条的 15个常用的javaScript正则表达式

    摘要收集整理了15个常用的javaScript正则表达式,其中包括用户名.密码强度.整数.数字.电子邮件地址(Email).手机号码.身份证号.URL地址. IPv4地址. 十六进制颜色. 日期. Q ...

随机推荐

  1. gcc shared object

    介绍一个生成动态链接库*.so的例子: 首先新建1个头文件test.h: #include <stdio.h> void first(); void second(); void thir ...

  2. java 基础加强--书籍+题目+上机测试

    scjp test( 在线测试网站):http://scjptest.com/mock-test.xhtml <SCJP Sun® Certified Programmer for Java™ ...

  3. 初识windows程序需要了解的知识点

    初识一件事物我们会有陌生,我们慢慢地去了解它就会懂,让我带你们一起了解吧. 一.Form是.Net Framework 定义好的一个最基本的窗体类,具有窗体基本属性和方法 属性            ...

  4. 前端mvc框架backbone.js入门

    关于backbone.js的优缺点,这里就不详谈了,网上关于这方面的讨论很多了,而且各种框架之所以长久生存,通常都是有其特定优势和擅长点的. 使用backbone.js作为前端框架的应用通常都是htm ...

  5. java集合-LinkedList

    一.概述 LinkedList 与 ArrayList 一样实现 List 接口,只是 ArrayList 是 List 接口的大小可变数组的实现,LinkedList 是 List 接口链表的实现. ...

  6. 多平台下Modbus通信协议库的设计(一)

    1.背景 1.1.范围 MODBUS 是 OSI 模型第 7 层上的应用层报文传输协议, 它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信. 自从 1979 年出现工业串行链路的事实标准以 ...

  7. (转)JavaScript一:为什么学习JavaScript?

    Web程序不论是B/S(Browser/Server)还是C/S(Client/Server)架构,分为客户端程序与服务器端程序两种.ASP.NET是开发服务器端程序的强大工具,但是有时候为了降低服务 ...

  8. 【iScroll源码学习03】iScroll事件机制与滚动条的实现

    前言 想不到又到周末了,周末的时间要抓紧学习才行,前几天我们学习了iScroll几点基础知识: 1. [iScroll源码学习02]分解iScroll三个核心事件点 2. [iScroll源码学习01 ...

  9. 浅析css布局模型1

    css是网页的外衣,好不好看全凭css样式,而布局是css中比较重要的部分,下面来分析一下常见的几种布局. 流动模型 流动模型是网页布局的默认模式,也是最常见的布局模式,他有两个特点: 1.块状元素都 ...

  10. arcmap Command

    The information in this document is useful if you are trying to programmatically find a built-in com ...