系列
JavaScript教程
变量的作用域及生存期
变量的作用域,指的是变量的有效范围——哪部分代码可以访问该变量。
全局作用域及函数内部作用域
在网页上,任何函数之外声明的变量,都是全局的变量,该页面的所有脚本,不管是函数内的,还是函数外的,都是可以访问这个变量的。
const apple = "Apple"; // 全局变量
function printApple() {
console.log(apple); // 可以直接访问全局变量
}
printApple();
而函数内声明的变量,则只有函数内可以使用:
function printApple() {
const apple = "Apple";
console.log(apple);
}
console.log(apple); // 不可以访问函数内的变量apple,程序报错“apple is not defined”
编码风格探讨——函数内外同名变量
如果这样写代码,调用printApple
的结果是什么呢?
const apple = "Apple"; // 全局变量
function printApple() {
const apple = "Orange"; //函数内部变量
console.log(apple);
}
printApple();
结果是程序打印出了"Orange",也就是说,函数执行会优先使用函数内的这个局部变量。虽然程序不会报错,但这种命名方式是不是很糟糕呢?
代码块作用域
很多其他的编程语言,一个代码块创建了一个作用域(scope),变量只在这个作用域内有效。
在JavaScript中,一个代码块是括号{}
之内的代码,但是代码块却没有创建一个新的作用域!在JavaScript里,只有函数作用域,在函数内定义的变量,在函数内部任何地方都是可用的。
结果呢?我们都知道下面这段代码是会报错的:变量a
未定义
function doSomething() {
a = a + 1;
}
doSomething();
但为什么这样的代码就好使呢?
function doSomething() {
a = a + 1;
var a = 1;
console.log(a);
}
doSomething();
原因就是,JavaScript里只有函数作用域,好使的代码里在第三行定义了变量a,虽然是在第二行使用之后定义的!还没晕的话,再看一个例子:
function doSomething() {
console.log(a); // 这里可以访问变量a
for (var a = 1; a < 5; a++) {
console.log(a); // 这里也可以访问变量a
}
console.log(`a=${a}`); // 这里还可以访问变量a
}
doSomething();
如果你有其他编程语言经验的话,会不会感觉JavaScript的变量定义很酸爽?
var
关键字的这个行为,就是JavaScript语言本身最开始的时候不良设计造成的。
所以,为了纠正上面的不良设计,ES6里面新增的let关键字,增加了代码块作用域,用let的话,上面怪异的代码就变得正常了:
function doSomething() {
// a = a + 1; // 这里也不可以访问变量a
for (let a = 1; a < 5; a++) {
console.log(a); // 这里也可以访问变量a,变量a只在for语句块内可见
}
// console.log(`a=${a}`); // 这里不可以访问变量a,注释掉
}
doSomething();

let
,就不要用var
来定义变量变量生存期
全局变量由于需要满足任何代码在任何时候都能访问,因此生存期和整个程序的生存期是一样的。函数内变量,则会随着函数调用结束而被全部释放,下次该函数被调用时会重新创建内部变量。
编程新手喜欢把变量都定义成全局变量,这样使用起来最方便。但是因为任何代码都可以在任何时候修改全局变量,如果由于某个编程错误对全局变量做了错误修改的话,那么这样的代码将会很难调试。

this关键字
在全局执行(即任何函数的外部)的JavaScript的代码中,this
都指代全局对象。在浏览器上,this
指的是window
对象;在Node.js中,this
指的是global
对象。
console.log(window === this);
在函数内部,非严格模式下,指的是全局对象;在严格模式下,如果当前函数没有执行上下文(execution context),那么this的值是undefined:
function doSomething() {
console.log(this); // 非严格模式
}
function doSomethingInStrictMode() {
"use strict"; // 严格模式
console.log(this);
}
doSomething();
doSomethingInStrictMode();
什么时候函数有执行的上下文?常见情况下,就是我们调用某个对象的方法的时候,这些方法就有执行上下文了,即当前对象:
const toy = {
name: "Teddy",
getThis: function () {
return this; // this即对象toy
},
sayName: function () {
console.log(`My name is ${this.name}`); // 可以通过this访问对象的属性或方法
}
};
console.log(toy.getThis() === toy);
toy.sayName();
但是有时候,我们写代码,需要在JavaScript的函数里动态创建并调用另一个函数,或者在一个异步函数里把当前对象的函数作为callback,这时候,this就不一样了:
const toy = {
name: "Teddy",
sayName: function () {
const printName = function () {
"use strict"; // 这里使用严格模式
console.log(`My name is ${this.name}`);
}
printName();
}
};
toy.sayName();
这时,this
竟然是undefined
,也就是说printName这个内部函数没有执行上下文,怎么办?如果你是写过一定量JavaScript代码的人,你一定会发现这个错误太熟悉了,这简直就是JavaScript的一个大坑啊。。。
两个办法可以选择:
- Function的bind方法
const toy = {
name: "Teddy",
sayName: function () {
const printName = function () {
"use strict";
console.log(`My name is ${this.name}`);
}.bind(this); // 创建了一个匿名函数,然后调用bind函数再创建一个新的和匿名函数具有相同函数体和作用域的函数,但是这个新函数的执行上下文是this,即toy。
printName();
}
};
toy.sayName();
- 用箭头函数
const toy = {
name: "Teddy",
sayName: function () {
const printName = () => { // 箭头函数自动绑定当前this作为执行上下文
"use strict";
console.log(`My name is ${this.name}`);
};
printName();
}
};
toy.sayName();
一般情况下,建议使用箭头函数,这样就不会当发现代码代码出错之后,回过头来才发现原来函数忘了和this
做绑定。