Effective JavaScript Item 27 使用闭包而不是字符串来封装代码

本系列作为Effective JavaScript的读书笔记。

对于代码封装,在JavaScript中有两种方式可以办到。第一种就是使用function,第二种则是利用eval()函数,传入到该函数的字符串参数可以是一段代码。

当对使用哪种方式犹豫不决时,使用function。因为使用字符串的一个重要缺点是,传入的字符串并不是一个闭包,而function则可以代表一个闭包。关于闭包的特点,在Item
11中进行了描述。

下面是一段使用字符串来封装代码的例子:

function repeat(n, action) {
	for (var i = 0; i < n; i++) {
		eval(action);
	}
}

传入到eval的action是一个字符串,用来代表需要被执行的逻辑,该字符串中出现的变量都会被解释成全局变量,在多数环境下,即window对象上的变量。

比如在下面的例子中:

var start = [], end = [], timings = [];
repeat(1000, "start.push(Date.now()); f(); end.push(Date.now())");
for (var i = 0, n = start.length; i < n; i++) {
	timings[i] = end[i] - start[i];
}

repeat函数的第二个参数是一段代码,其中用到了两个变量start和end。

它们引用的是全局变量start和end。当上面的代码不在任何函数中时,还能够正常工作,如果将它们放到了函数中:

function benchmark() {
	var start = [], end = [], timings = [];
	repeat(1000, "start.push(Date.now()); f(); end.push(Date.now())");
	for (var i = 0, n = start.length; i < n; i++) {
		timings[i] = end[i] - start[i];
	}
	return timings;
}

此时的start和end变量仍然引用的是全局变量start和end。如果在运行时没有这两个全局变量的任意一个,就会报出ReferenceError异常,这还是最好的结果。如果不凑巧在全局对象中拥有这两个同名的属性,那么程序的运行结果就不可预测了。

更好的封装代码的方式是使用function:

function repeat(n, action) {
	for (var i = 0; i < n; i++) {
		action();
	}
}

此时,可以将上面的函数应用在任何函数的内部:

function benchmark() {
	var start = [], end = [], timings = [];
	repeat(1000, function() {
		start.push(Date.now());
		f();
		end.push(Date.now());
	});
	for (var i = 0, n = start.length; i < n; i++) {
		timings[i] = end[i] - start[i];
	}
	return timings;
}

start和end能够引用到benchmark函数内部定义的start和end数组。这得益于闭包的性质。

另外一个不使用eval的原因是:在JavaScript的执行引擎中,对于以字符串形式封装的代码往往很难优化。因为字符串也许是动态生成的,编译器/解释器无法在执行前得知它的具体信息,就无法对它们进行优化。

总结:

  1. 如果确实需要使用字符串来封装一段代码时,确保在其中不要依赖任何非全局变量
  2. 优先使用闭包进行代码封装而不是使用字符串。

时间: 2024-10-22 06:18:24

Effective JavaScript Item 27 使用闭包而不是字符串来封装代码的相关文章

[Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式传递给eval函数以达到同样的功能.程序员面临一个选择:应该将代码表示为函数还是字符串?毫无疑问,应该将代码表示为函数.字符串表示代码不够灵活的一个重要原因是:它们不是闭包. 闭包回顾 看下面这个图 js的函数值包含了比调用它们时执行所需要的代码还要多的信息.而且js函数值还在内部存储它们可能会引用

Effective JavaScript Item 11 掌握闭包

本系列作为Effective JavaScript的读书笔记. 掌握闭包,需要知道以下几个关键点: JavaScript允许在当前的function中访问该function外部的变量. function makeSandwich() { var magicIngredient = "peanut butter"; function make(filling) { return magicIngredient + " and " + filling; } return

Effective JavaScript Item 35 使用闭包来保存私有数据

本系列作为EffectiveJavaScript的读书笔记. JavaScript的对象系统从其语法上而言并不鼓舞使用信息隐藏(Information Hiding).由于当使用诸如this.name.this.passwordHash的时候,这些属性默认的訪问级别就是public的.在不论什么位置都可以通过obj.name,obj.passwordHash来对这些属性进行訪问. 在ES5环境中,也提供了一些方法来更方便的訪问一个对象上全部的属性,比方Object.keys(),Object.g

Effective C++ Item 27 少做转型操作

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie todo Item34 旧式转型 (T) expression 或 T (expression) 新式转型 const_cast<T>(expression) 通常被用来将对象的常量性转除(cast away the constness) dynamic_cast<T>(expression) 执行"安全向下转型",也就是用来决定某对象是否归属继承体系中的

Effective JavaScript Item 31 优先使用Object.getPrototypeOf,而不是__proto__

本系列作为Effective JavaScript的读书笔记. 在ES5中引入了Object.getPrototypeOf作为获取对象原型对象的标准API.可是在非常多运行环境中.也提供了一个特殊的__proto__属性来达到相同的目的. 由于并非全部的环境都提供了这个__proto__属性,且每一个环境的实现方式各不同样,因此一些结果可能不一致: // 在某些环境中 var empty = Object.create(null); // object with no prototype "__

Effective JavaScript Item 46 优先使用数组而不是Object类型来表示有顺序的集合

本系列作为Effective JavaScript的读书笔记. ECMAScript标准并没有规定对JavaScript的Object类型中的属性的存储顺序. 但是在使用for..in循环对Object中的属性进行遍历的时候,确实是需要依赖于某种顺序的.正因为ECMAScript没有对这个顺序进行明确地规范,所以每个JavaScript执行引擎都能够根据自身的特点进行实现,那么在不同的执行环境中就不能保证for..in循环的行为一致性了. 比如,以下代码在调用report方法时的结果就是不确定的

Effective JavaScript Item 49 对于数组遍历,优先使用for循环,而不是for..in循环

本系列作为Effective JavaScript的读书笔记. 对于下面这段代码,能看出最后的平均数是多少吗? var scores = [98, 74, 85, 77, 93, 100, 89]; var total = 0; for (var score in scores) { total += score; } var mean = total / scores.length; mean; // ? 通过计算,最后的结果应该是88. 但是不要忘了在for..in循环中,被遍历的永远是ke

Effective JavaScript Item 13 使用即时调用的函数表达式(IIFE)来创建局部域

本系列作为Effective JavaScript的读书笔记. 所谓的即时调用的函数表达式,这个翻译也许不太准确,它对应的英文原文是Immediately Invoked Function Expression (IIFE).下文也使用IIFE来表达这一概念. 首先看一个程序: function wrapElements(a) { var result = [], i, n; for (i = 0, n = a.length; i < n; i++) { result[i] = function

Effective JavaScript Item 39 绝不要重用父类型中的属性名

本系列作为Effective JavaScript的读书笔记. 如果需要向Item 38中的Actor对象添加一个ID信息: function Actor(scene, x, y) { this.scene = scene; this.x = x; this.y = y; this.id = ++Actor.nextID; scene.register(this); } Actor.nextID = 0; 同时,也需要向Actor的子类型Alien中添加ID信息: function Alien(