Effective JavaScript Item 12 理解Variable Hoisting

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

JavaScript中并没有Block Scoping,只有Function
Scoping。

因此如果在一个Block中定义了一个变量,那么这个变量相当于是被定义到了这个Block属于的Function中,比如:

function isWinner(player, others) {
	var highest = 0;
	for (var i = 0, n = others.length; i < n; i++) {
		<span style="color:#ff0000;">var player = others[i];</span>
		if (player.score > highest) {
			highest = player.score;
		}
	}
	return player.score > highest;
}

上面的代码中,在for循环中声明了一个变量player,因为Variable
Hoisting的原因,这个变量实际上被声明成了下面这个样子:

function isWinner(player, others) {
	<span style="color:#ff0000;">var player;</span>
	var highest = 0;
	for (var i = 0, n = others.length; i < n; i++) {
		<span style="color:#ff0000;">player = others[i];</span>
		if (player.score > highest) {
			highest = player.score;
		}
	}
	return player.score > highest;
}

因此,传入到function中的player参数被覆盖掉了。程序不能按预期行为运行。

在JavaScript中,对于变量的声明实际上包含了两个部分:

  1. 声明本身
  2. 赋值

JavaScript会通过Variable Hoisting将第一个部分,也就是声明的部分,放到包含变量的function的头部。

在下面这个例子中:

function trimSections(header, body, footer) {
	for (var i = 0, n = header.length; i < n; i++) {
		header[i] = header[i].trim();
	}
	for (var i = 0, n = body.length; i < n; i++) {
		body[i] = body[i].trim();
	}
	for (var i = 0, n = footer.length; i < n; i++) {
		footer[i] = footer[i].trim();
	}
}

因为VariableHoisting的缘故,i和n都会被放到function的开始处,所以实际上只声明了两个变量,在for循环中,会对它们进行赋值,实际的行为是这样的:

function trimSections(header, body, footer) {
	<span style="color:#ff0000;">var i, n;</span>
	for (i = 0, n = header.length; i < n; i++) {
		header[i] = header[i].trim();
	}
	for (i = 0, n = body.length; i < n; i++) {
		body[i] = body[i].trim();
	}
	for (i = 0, n = footer.length; i < n; i++) {
		footer[i] = footer[i].trim();
	}
}

正因为VariableHoisting的存在,一些程序员偏好将变量定义在function的开始处,相当于完成了一次手动地Hoisting。这样做的目的是为了避免歧义性,让代码更加清晰。

但是,有一个特殊情况,在try…catch语句中,catch
block中的参数会被绑定到该block中:

function test() {
	var x = "var", result = [];
	result.push(x);
	try {
		throw "exception";
	} catch (x) {
		<span style="color:#ff0000;">x = "catch";</span>
	}
	result.push(x);
	return result;
}
test(); // ["var", "var"]

重点:

  1. 在Block中声明的变量会被隐式地Hoisted到包含它们的function的开始处。(除了上面提到的catch
    block)
  2. 在一个function中,多次声明一个变量最终只会声明一个变量。
  3. 为了避免歧义性,考虑将变量声明在function的开始处。

时间: 2024-08-01 16:59:56

Effective JavaScript Item 12 理解Variable Hoisting的相关文章

Effective JavaScript Item 30 理解prototype, getPrototypeOf和__proto__的不同

本系列作为Effective JavaScript的读书笔记. prototype,getPropertyOf和__proto__是三个用来访问prototype的方法.它们的命名方式很类似因此很容易带来困惑. 它们的使用方式如下: prototype: 一般用来为一个类型建立它的原型继承对象.比如C.prototype = xxx,这样就会让使用new C()得到的对象的原型对象为xxx.当然使用obj.prototype也能够得到obj的原型对象. getPropertyOf: Object

Effective JavaScript Item 18 理解Function, Method, Constructor调用之间的区别

本系列作为Effective JavaScript的读书笔记. Function绝对是JavaScript中的重中之重.在JavaScript中,Function承担了procedures, methods, constructors甚至是classes以及modules的功能. 在面向对象程序设计中,functions,methods以及class constructor往往是三件不同的事情,由不同的语法来实现.但是在JavaScript中,这三个概念都由function来实现,通过三种不同的

JavaScript大杂烩12 - 理解Ajax

AJAX缘由 再次谈起这个话题,我深深的记得就在前几年,AJAX被炒的如火如荼,就好像不懂AJAX,就不会Web开发一样.要理解AJAX为什么会出现,就要先了解Web开发面临的问题. 我们先来回忆一下Web页面的申请过程,这个咱们在第一篇中就介绍过了:Web页面开发就是在无连接和无状态的HTTP协议上管理页面的状态.每次申请页面的时候,服务器都会返回完整的HTML文本(当然还有其他的文本文件),浏览器就负责解析这个文本并在浏览器中显示. 在这个过程中,不管当前页面的内容是不是都变化了,服务器都会

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 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,