Effective JavaScript Item 37 认识this的隐式指向

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

CSV数据通常都会被某种分隔符进行分隔,所以在实现CSV Reader时,需要支持不同的分隔符。那么,很自然的一种实现就是将分隔符作为构造函数的参数。

function CSVReader(separators) {
	this.separators = separators || [","];
	this.regexp =
		new RegExp(this.separators.map(function(sep) {
			return "\\" + sep[0];
		}).join("|"));
}

对于CSV Reader而言,它的工作原理是首先将读入的字符串根据换行符切分成为一个个的行,然后对每行根据分隔符进行切分成为一个个的单元。所以,可以使用map方法进行实现:

CSVReader.prototype.read = function(str) {
	var lines = str.trim().split(/\n/);
	return lines.map(function(line) {
		return line.split(this.regexp); // wrong this!
	});
};
var reader = new CSVReader();
reader.read("a,b,c\nd,e,f\n"); // [["a,b,c"], ["d,e,f"]], wrong result

可是上述代码中有一个错误:传入到map函数中的回调函数的this指向有问题。即其中的this.regexp并不能正确的引用到CSVReader实例的regexp属性。因此,最后得到的结果也就是不正确的了。

对于这个例子,在map的回调函数中this指向的实际上是全局对象window。关于this在各种场景下的指向,在Item
18和Item 25中进行了介绍。

为了克服this的指向问题,map函数提供了第二个参数用来指定在其回调函数中this的指向:

CSVReader.prototype.read = function(str) {
	var lines = str.trim().split(/\n/);
	return lines.map(function(line) {
		return line.split(this.regexp);
	}, this); // forward outer this-binding to callback
};
var reader = new CSVReader();
reader.read("a,b,c\nd,e,f\n");
// [["a","b","c"], ["d","e","f"]]

但是,并不是所有的函数都如map考虑的这么周全。如果map函数不能接受第二个参数作为this的指向,可以使用下面的方法:

CSVReader.prototype.read = function(str) {
	var lines = str.trim().split(/\n/);
	var self = this; // save a reference to outer this-binding
	return lines.map(function(line) {
		return line.split(self.regexp); // use outer this
	});
};
var reader = new CSVReader();
reader.read("a,b,c\nd,e,f\n");
// [["a","b","c"], ["d","e","f"]]

这种方法将this的引用保存到了另外一个变量中,然后利用闭包的特性在map的回调函数中对它进行访问。通常会使用变量名self来保存this的引用,当然使用诸如me,that也是可行的。

在ES5环境中,还可以借助于函数的bind方法来绑定this的指向(在Item
25中,对该方法进行了介绍):

CSVReader.prototype.read = function(str) {
	var lines = str.trim().split(/\n/);
	return lines.map(function(line) {
		return line.split(this.regexp);
	}.bind(this)); // bind to outer this-binding
};
var reader = new CSVReader();
reader.read("a,b,c\nd,e,f\n");
// [["a","b","c"], ["d","e","f"]]

总结

  1. 根据函数的调用方式的不同,this的指向也会不同。
  2. 使用self,me,that来保存当前this的引用供其他函数使用。

时间: 2024-10-28 22:51:20

Effective JavaScript Item 37 认识this的隐式指向的相关文章

Effective C++ Item 37 绝不重新定义继承而来的缺省参数值

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:绝对不要重新而来的缺省参数值,因为缺省参数值都是静态绑定,而 virtual 函数 -- 你唯一应该覆写的东西 -- 却是动态绑定 示例: class Shape{ public: enum ShapeColor {Red, Green, Blue}; virtual void draw(ShapeColor color = Red) const = 0; }; class Rect

Effective JavaScript Item 22 使用arguments来创建接受可变参数列表的函数

本系列作为Effective JavaScript的读书笔记. 在Item 21中,介绍了结合apply方法实现的可变参数列表函数average,它实际上只声明了一个数组作为参数,但是利用apply方法,实际上可以接受若干元素作为参数: function averageOfArray(a) { for (var i = 0, sum = 0, n = a.length; i < n; i++) { sum += a[i]; } return sum / n; } averageOfArray.a

Effective JavaScript Item 24 使用一个变量来保存arguments的引用

本系列作为Effective JavaScript的读书笔记. 假设需要一个API用来遍历若干元素,像下面这样: var it = values(1, 4, 1, 4, 2, 1, 3, 5, 6); it.next(); // 1 it.next(); // 4 it.next(); // 1 相应的实现可以是: function values() { var i = 0, n = arguments.length; return { hasNext: function() { return

Effective JavaScript Item 25 使用bind方法来得到一个固定了this指向的方法

本系列作为Effective JavaScript的读书笔记. 当需要将方法抽取出来作为回调函数使用的时候,常常会因为this的指向不明而发生错误,比如: var buffer = { entries: [], add: function(s) { this.entries.push(s); }, concat: function() { return this.entries.join(""); } }; 如果想利用其中的add作为回调函数对一组数据进行添加: var source

Effective JavaScript Item 23 永远不要修改arguments对象

本系列作为Effective JavaScript的读书笔记. arguments对象只是一个类似数组的对象,但是它并没有数组对象提供的方法,比如shift,push等.因此调用诸如:arguments.shift(),arguments.push()是错误的. 在Item 20和Item 21中,知道了函数对象上存在call和apply方法,那么是不是可以利用它们来让arguments也能够利用数组的方法呢: function callMethod(obj, method) { var shi

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(

Effective JavaScript Item 28 不要依赖函数的toString方法

本系列作为Effective JavaScript的读书笔记. 在JavaScript中,函数对象上存在一个toString方法,它能够方便地将函数的源代码转换返回成一个字符串对象. (function(x) { return x + 1; }).toString(); // "function (x) {\n return x + 1;\n}" toString方法不仅仅会让一些黑客找到攻击的方法,而且该方法也存在严重的限制. 首先,toString方法的实现方式并没有被ECMASc

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

本系列作为Effective JavaScript的读书笔记. 对于代码封装,在JavaScript中有两种方式可以办到.第一种就是使用function,第二种则是利用eval()函数,传入到该函数的字符串参数可以是一段代码. 当对使用哪种方式犹豫不决时,使用function.因为使用字符串的一个重要缺点是,传入的字符串并不是一个闭包,而function则可以代表一个闭包.关于闭包的特点,在Item 11中进行了描述. 下面是一段使用字符串来封装代码的例子: function repeat(n,

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