Javascript点滴0x05:第一滴血
全局变量是魔鬼。在 Javascript 遇到魔鬼的可能性比任何其它语言都大。作为一个 Coder, 尽量少召唤魔鬼是一种至高美德。你的模块应该生活在一个单一的全局变量里,比如 MYAPP:
var MYAPP = MYAPP || {};添加一个模块:
MYAPP.module = MYAPP.module || {};使用 || 检查模块名是否已经存在是很好的习惯。然而一旦模块层次多了起来,这样的代码很快就会变成一种负担(MYAPP.module2.model1.obj0 怎么定义?)。
所以下面这个 namespace() 方法应该作为你全局变量的第一个方法:
MYAPP.namespace = function(ns_str) {
var parts = ns_str.split('.'),
parent = MYAPP,
i;
if (parts[0] === 'MYAPP') {
parts = parts.slice(1);
}
for (i=0; i<parts.length; i+=1) {
if (typeof parent[parts[i]] === 'undefined') {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
}有了它,上面括号里的问题答案就变成了:
var obj0 = MYAPP.namespace('MYAPP.module2.model1.obj0');或者省略参数中的MYAPP:
var obj0 = MYAPP.namespace('module2.model1.obj0');有了这第一滴血,增加人品就轻而易举了。
Javascript点滴0x04:动态规划
还记得动态规划吗?它曾经是我高中最大的困惑。动态规划这个词译自 Dynamic Programming —— 一个不知所云的短语。相比之下我更喜欢另一个描述同样东西的字:Memoization 。至少它暗示了动态规划的精髓:保存运算的结果,下次碰到同样的运算则直接使用该结果,从而剩下重复运算过程花费的时间。
Javascript 的运行环境决定了动态规划这类节约时间的算法有额外的价值。问题是再哪里保存代码执行的结果?显然全局变量是最糟糕的答案,比放在某个 Object 的属性里还烂。最好的地点其实是运算代码本身所在的 function 实例里面。
function 在 Javascript 里只不过是 object 的衍生类型,意味它可以有自己的属性甚至方法。如果使用 function 来执行了某个时间开销大的运算过程,那么不妨把每次运算使用的参数和结果保存在这个 function 自身,下次运算的时候先检查得到的参数是否已经有了结果,如果是就直接返回,没有再开始运算。具体请看下面这段代码:
var expensiveOperation = function () {
var cachekey = JSON.stringify(Array.prototype.slice.call(arguments)),
results;
if (!expensiveOperation.cache[cachekey]) {
result = {};
// 执行运算
expensiveOperation.cache[cachekey] = result;
}
return expensiveOperation.cache[cachekey];
};
expensiveOperation.cache = {};把所有的参数转换成一个字串作为键,运算的结果作为值,保存到 function 的 cache 属性里。每次被调用的时候先检查这次参数在 cache 里是否有对应值,如果没有,则进行正常的计算,否则略过计算过程直接返回保存在 cache 里的结果。
千万不要忘了最后那一行:cache 的初始化。
Javascript点滴0x03:会变身的原生类型
Javascript 有几个原生的数据类型:number, string, boolean, null 和 undefined,剩下的"东西"全都是 object 及其衍生类。其中的 number,string 和 boolean 各自还有一个增强版本,可以使用对应首字母大写的构造函数 生成。
这些增强版本比原生类型"强"在了自带的各种方法:Number的toFixed(),String的charAt()等等。
很多人错以为原生类型和它们对应的增强类型是一样的。这很正常。因为在原生类型上调用增强类型的方法是合法的:Javascript 碰到这种情形会自动把前者转换成后者。下面用代码演示一下:
var prim = "hEllo", // 原生string,用字面值建立
augt = new String("WorLD"); // 增强类型,由构造函数建立
alert(prim.toUpperCase()); // 输出 "HELLO"
alert(augt.toUpperCase()); // 输出 "WORLD"这里 prim 是原始生类型,augt 是增强类型,可以看到在二者调用 toUpperCase() 效果是一样的。 那他们之间的区别在哪?请继续看代码:
alert(typeof prim); // "number" alert(typeof augt) // "object" prim.n_of_l = 2; augt.n_of_l = 1; alert(prim.n_of_l); // "undefined" alert(augt.n_of_l); // "1"
上面的代码演示了Javascript的原生类型与其增强版本之间的两个区别:
1 typeof 可以高速我们二者的本质区别:增强类型就是普通的 object 了。这点从new的使用也能体现出来。
2 由于增强类型都是 object ,可以自由的给它赋予新的属性和方法;原生类型就没有这样的特性。给原生类型添加属性不会引发错误,但这么做没有效果。
Javascript点滴0x02:真假数组
如果有人给你一个 Javascript 的 Object,然后声称她是一个数组,你怎么判断这个人是不是在说谎呢?
用 typeof ?不行,它只会告诉你类型是 Object ,没错,Array 也是 Object。
用 instanceOf Array 总可以吧?对不起,在 IE 里跨 frame 检查的时候它会给你错误结果。
检查一下她长没长 .slice() 什么的这些方法?万一那人故意放上了与 Array 所有方法同名的方法,所有属性同名的属性,怎么办?
ECMAScript 5 标准为这个问题准备了一个答案: Array.isArray() 方法接受一个参数,返回 true 或者 false 来告诉你那个参数里的东西是不是真的 Array。
那在 ECMAScript 5 还没有实现的环境里就没办法判断 Object 是不是数组了吗?
靠谱的办法还是有的。Object.prototype.toString() 会返回一个代表类型的字串。数组的返回结果是 "[object Array]" ,而冒牌货的结果一般是 "[object Object]" 。所以下面的代码可以完美的解决这个问题:
if (typeof Array.isArray === "undefined") {
Array.isArray = function(obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
}Javascript点滴0x01:勿忘var
Javascript是灵活的,但灵活不代表有优势。忘记使用var关键字来声明变量就是 一个例证。
function hello() {
msg = "Hello, ";
return msg;
}
function hello_world() {
msg = "world!";
return hello() + msg;
}
alert(hello_world()); // 咦,怎么口吃了?hello_world() 在这里返回的结果对不熟悉Javascript的开发者来说可能有些意外。代码本来的意图是返回 "Hello, world!" 字串,但运行的结果却是 "Hello, Hello, "。这是为什么?
一个function遇到var关键字就时,回在自己的作用域里建立跟在它后面的变量。假如对一个从未定义过的变量进行赋值操作,Javascript不仅不会报错,而且还自动建立一个变量并用赋值语句中的值来初始化它。问题是无论这种情况发生在什么地方,自动建立的变量都是全局变量,是全局作用域(通常是window)的属性。
hello_world() 建立了一个全局变量 msg, 然后调用了 hello()。后者对 msg 也有访问权(注意这里不是闭包,因为 hello() 不是在 hello_world() 里面定义的),并且把它的值修改成了 "Hello, "。所以不管是谁,从此返回的 msg 都是 "Hello, "了。
这种没有经过 var 产生的变量可以通过 detete 来删除,因为本质上它只是 windows 的一个属性。
另外,在 ECMAScript 5 的 strict 模式下,对没有通过 var 声明的变量赋值会引起一个 error。