您的位置:首页 > Web前端 > JavaScript

JavaScript变量作用域和变量提升解释(JavaScript Variable Scope and Hoisting Explained)

2013-12-04 15:12 891 查看
原文:JavaScript Variable Scope and
Hoisting Explained

这篇文章,我们要学习的是javascript的变量作用域和变量的提升以及它们的特性。

理解javascript的变量作用域和变量提升对于学习javascript来说,是非常重要的。这些概念看起来似乎很直白,但是还是有些很重要的细微之处需要我们理解的。

变量的作用域

变量的作用域就是变量的生存环境。简单的说,变量的作用域就是你在什么地方可以访问到这个变量。

javascript中的变量作用域只用两种:局部作用域和全局作用域。

局部作用域(函数级作用域)

跟其他大多数编程语言不同的是,javascript没有块级作用域(比如java中的if语句块中声明的变量,作用域就是这个if语句块,但是javascript中,if语句块中的变量却是全局变量)。不过,javascript有函数级作用域。在一个函数内定义的变量,叫做局部变量。它只能在这个函数里或者这个函数的内部函数里被访问到。更多关于内部函数访问外部函数中定义的变量的内容,请参考Closures(闭包)这篇文章。

函数级作用域演示:

var name = "Richard";

function showName () {
var name = "Jack"; // 局部变量; 只能在 showName 函数中被访问
console.log (name); // Jack
}
console.log (name); // Richard: 全局变量
javascript中没有块级作用域,这是跟其他大部分编程语言都不一样的地方,需要特别注意

var name = "Richard";
// 这个if语句块没有生成一个新的环境
if (name) {
name = "Jack"; // 这个name是全局变量name,它的值被改成了"Jack"
console.log (name); // Jack: 这个还是全局变量
}

// 这里的name,还是全局变量,但是它在if语句块中被改变了值。
console.log (name); // Jack


如果你不声明局部变量的话,很容易出错

在使用局部变量之前,一定要记得先声明。你可以用JSHint 来检查你的语法。下面这个例子是不声明本地变量导致的问题:
//即使是在函数里面,声明一个变量时,没有使用var这个关键字,那这个变量依然是全局变量。
var name = "Michael Jackson";

function showCelebrityName () {
console.log (name);
}

function showOrdinaryPersonName () {
name = "Johnny Evers";
console.log (name);
}
showCelebrityName (); // Michael Jackson

//因为在函数里面没有对name进行声明,所以它用到的name是全局变量里面的name,只是把它的值改变成了"Johnny Evers"
showOrdinaryPersonName (); // Johnny Evers

// 全局变量name的值现在是Johnny Evers了,而不再是原来的"Michael Jackson"了。
showCelebrityName (); // Johnny Evers

// 下面是正确的在函数里声明本地变量的方法
function showOrdinaryPersonName () {
var name = "Johnny Evers"; // 现在这个name是本地变量了,不会覆盖掉全局变量里面的name.
console.log (name);
}

在函数里局部变量的优先权高于全局变量

如果你同时声明了一个全局变量,一个局部变量,两者的名字一样的话,在函数里面使用这个变量的时候,将优先使用本地声明的这个变量。在一个函数里面使用一个变量的时候,它会首先在本地环境里面查找这个变量,找到了就直接使用,只有在本地找不到该变量时,它才会到全局环境里去找全局变量。
var name = "Paul";

function users () {
// 这里声明了一个局部变量,跟全局变量的变量名都是name,但是在函数里,它的优先级比全局变量高
var name = "Jack";

// 首先在这个函数里面查找name变量,找不到的话,才会去查找函数外的全局变量。
console.log (name);
}

users (); // Jack


全局变量

所有在函数之外声明的变量都是全局变量。在浏览器里面,全局环境或者说全局作用域的作用范围是window对象(或者是整个html文档)

所有在函数外面声明或者初始化的变量都是全局变量,全局变量在整个应用中任何地方都可以被使用。比如:
// 下面几种都是声明全局变量的方式
var myName = "Richard";

// 或者
firstName = "Richard";

// 或者
var name;
name;

//需要注意的是,所有全局变量都跟window对象是关联上的。所有我们声明的全局变量可以像这样访问:

console.log(window.myName); // Richard;

// 或者
console.log("myName" in window); // true
console.log("firstName" in window); // true

如果变量初始化(赋值)之前,没有使用var 关键字进行声明,那这个变量自动被加到全局环境里面,变成全局变量了。
function showAge () {
// 这个age是全局变量
age = 90;
console.log(age);//
}

showAge (); // 90

// 因为age是全局变量,所以在函数外也能被访问到。
console.log(age); // 90

另一个例子:

// 尽管第二个firstName被包含在{}中,这两个都是全局变量。因为javascript中没有块级作用域这一说。
var firstName = "Richard";
{
var firstName = "Bob";
}

// 第二个firstName只是又声明了一次并且覆盖了第一个firstName的值
console.log (firstName); // Bob


另一个例子:


for (var i = 1; i <= 10; i++) {
console.log (i); // 输出 1, 2, 3, 4, 5, 6, 7, 8, 9, 10;
};

// i是全局变量,所以下面的函数里面可以访问到。
function aNumber () {
console.log(i);
}

// aNumber函数里面的i是全局变量,它的值在上面的for循环里面被改变了,最后的值是for循环结束前的11.
aNumber ();  // 11


setTimeout 的变量会被放在全局域里执行

注意所有在setTimeout里面的函数都会在全局域里面执行。 这是比较棘手的一点,看下面这个:
// 在setTimeout里面的 "this" 对象表示的是 window 对象,而不是 myObj

var highValue = 200;
var constantVal = 2;
var myObj = {
highValue: 20,
constantVal: 5,
calculateIt: function () {
setTimeout (function  () {
console.log(this.constantVal * this.highValue);
}, 2000);
}
}

//setTimeout 函数里面的 "this" 对象使用全局的变量 highValue 和 constantVal,因为setTimeout函数里面的 "this" 的引用时全局的window对象,不是myObj

myObj.calculateIt(); // 400
// This is an important point to remember.

不要过度使用全局变量

如果你想成为一个javascript高手的话,这个应该是毫无疑问的(否则你现在应该在看选美小姐什么的而不是在读这篇文章了),你一定要尽量避免在全局域中创建过多的变量,像下面这样:
// 这两个变量现在在全局域里面,但是更好的方法是把它们放到函数里面
var firstName, lastName;

function fullName () {
console.log ("Full Name: " + firstName + " " + lastName );
}

下面这个是改良之后的版本

// 在函数里面声明变量,它们就成了局部变量了。

function fullName () {
var firstName = "Michael", lastName = "Jackson";

console.log ("Full Name: " + firstName + " " + lastName );
}


这上面这个例子中,函数fullName依然是在全局域中。

变量提升

在javascript中,有个变量声明提升的概念。在一个函数里声明的变量,它的声明会被提到函数的顶部。在函数外定义的变量,它的声明则会被提到全局环境的顶部。

需要强调的是,只有函数的声明会被提升,变量的初始化或者赋值都不会被提升。

变量提升的例子:

function showName () {
console.log ("First Name: " + name);
var name = "Ford";
console.log ("Last Name: " + name);
}

showName ();
// First Name: undefined
// Last Name: Ford

// 第一行打印出来的是undefined的原因就是局部变量name被提升到了函数的顶部。
// 第一行的name调用的就是这个name
// 下面的代码是javascript引擎实际的执行过程:

function showName () {
var name; // name的声明被提升到函数的顶部,由于赋值不会被提升,所以此时name的值为undefined
console.log ("First Name: " + name); // First Name: undefined

name = "Ford"; // name在原来这个地方被赋值

// 现在name的值变为了Ford
console.log ("Last Name: " + name); // Last Name: Ford
}


在提升的时候函数声明覆盖变量声明

函数的声明和变量的声明都会被提升到所在作用域的顶部。如果函数的名字和变量的名字相同的话,函数的声明会覆盖掉变量的声明(但是不能覆盖变量的赋值)。像上面提到的,变量的赋值是不会被提升的,函数的赋值也不会被提升。附:函数的赋值的格式 var myFunction = function () {}。

下面是一个基本的演示例子:

// 函数和变量的名字都是 myName
var myName;

function myName () {
console.log ("Rich");
}

//函数的声明覆盖了变量的名字
console.log(typeof myName); // function


// 但是在这个例子中,变量的赋值又覆盖了函数的声明
var myName = "Richard"; // 变量赋值(初始化)覆盖函数的声明

function myName () {
console.log ("Rich");
}

console.log(typeof myName); // string


值得注意的是,像下面这样的函数表达式,是不会被提升的。

var myName = function () {
console.log ("Rich");
}


在strict 模式下,如果你给一个没有被声明的变量赋值,会产生错误。所以一定在变量赋值之前要先声明变量。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: