JavaScript 每日一篇,记录自学JavaScript语言的点点滴滴。
探究JavaScript作用域
JavaScript 作用域链是如何工作的
JavaScript作为一门面向对象,函数式编程语言,其最大的优势就是你可以将函数的方法组合起来,形成一个链式的方法进行编程。其中,作用域链是JavaScript编程语言中的重要组成部分。
JavaScript分为三种作用域------全局作用域、块作用域和函数作用域。
仔细看下面这段代码,通过分析这段代码,体现JavaScript的全局作用域、块作用域和函数作用域。
javascript
'use strict';
function calcAge(birthYear) {
const age = 2037 - birthYear;
// console.log(firstName);
function printAge() {
const output = `You are ${age}, born in ${birthYear}`;
console.log(output);
}
printAge();
return age;
}
const firstName = 'Jones';
calcAge(2001);
通过运行,可以得出一下结果:
解析下面这段结构:
实际上,JavaScript在被解释或及时编译的一瞬间,它会将相应的函数和全局变量放入栈中进行调用。全局作用域会被放入栈底,方便调用其他函数。而被调用的函数或其他变量会被依次入栈,这样当调用的函数需要某些变量时,引擎会依次自底向上进行搜索。
在JavaScript中,函数属于块作用域,你可以将定义在函数内部的函数看作外层函数的一个变量,外层函数的所有变量和函数都可以被内层函数所调用。
如果此时你调用函数块内部的printAge()
函数或age
变量,程序会报错,这位这两个变量并非全局变量,而是函数内部的块作用域的变量。
javascript
'use strict';
function calcAge(birthYear) {
const age = 2037 - birthYear;
// console.log(firstName);
function printAge() {
const output = `You are ${age}, born in ${birthYear}`;
console.log(output);
}
printAge();
return age;
}
const firstName = 'Jones';
calcAge(2001);
// printAge(); // Uncaught ReferenceError: printAge is not defined
// console.log(age); // Uncaught ReferenceError: age is not defined
但作为全局作用域变量firstName
,你可以在代码的任何地方进行调用,无论是在函数作用域内,还是在块作用域内。例如在下面的代码中,你完全可以在calcAge()
的函数内部的printAge()
内部调用全局变量的firstName
内部的内容。
javascript
'use strict';
function calcAge(birthYear) {
const age = 2037 - birthYear;
// console.log(firstName);
function printAge() {
const output = `${firstName}, you are ${age}, born in ${birthYear}`;
console.log(output);
}
printAge();
return age;
}
const firstName = 'Jones';
calcAge(2001);
// printAge(); // Uncaught ReferenceError: printAge is not defined
// console.log(age); // Uncaught ReferenceError: age is not defined
关于块作用域的使用
在最新的ES6
标准中提供了关于JavaScript
块作用域的功能。在你使用let
或const
关键字时,你只有声明此变量的代码块内进行访问,无权在块外使用。例如下面这段代码中我们在if
块内声明了一个变量名为str
的常量用来存储模版字符串。访问该常量时,你只能在if
块作用域 内进行声明。如果你在函数printAge()
内进行调用是无效的。
javascript
'use strict';
function calcAge(birthYear) {
const age = 2037 - birthYear;
// console.log(firstName);
function printAge() {
const output = `${firstName}, you are ${age}, born in ${birthYear}`;
console.log(output);
if (birthYear >= 1981 && birthYear <= 1996) {
const str = `哦,你是千禧一代啊!Oh, and you're a millenial ${firstName}`;
console.log(str);
}
console.log(str);
}
printAge();
return age;
}
const firstName = 'Zoe';
calcAge(1994);
// printAge(); // Uncaught ReferenceError: printAge is not defined
// console.log(age); // Uncaught ReferenceError: age is not defined
在过去老版本的JavaScript
中,常常用var
关键字声明变量。用var
关键字声明的变量是函数作用域。就比如下面这段代码中用var
关键字声明的millenial
变量,即便在if
作用域块内,用var
关键字声明的millenial
变量也能不受块约束的调用在块的外部。例如在printAge()
内部的任何区域。
javascript
function calcAge(birthYear) {
const age = 2037 - birthYear;
// console.log(firstName);
function printAge() {
const output = `${firstName}, you are ${age}, born in ${birthYear}`;
console.log(output);
if (birthYear >= 1981 && birthYear <= 1996) {
var millenial = true; // In the same function, so we can also call this variable.
// 因为此变量使用var关键字进行变量的声明,具有函数作用域,
// 因此在同一个函数你仍然可以访问到
const str = `哦,你是千禧一代啊!Oh, and you're a millenial ${firstName}`;
console.log(str);
}
// console.log(str);
console.log(millenial); // millenial => true
}
printAge();
return age;
}
⚠️注意:当然我们不建议这样做,因为在某些情况下,如使用var
关键字声明的循环索引变量很容易在循环运行的过程中出错。另外频繁的使用var
关键字也容易对程序后期的维护造成较大困难😷,所以尽可能避免使用var
关键字。
有关严格模式下的作用域
在严格模式状态下,函数是块作用域
javascript
'use strict';
function calcAge(birthYear) {
const age = 2037 - birthYear;
// console.log(firstName);
function printAge() {
const output = `${firstName}, you are ${age}, born in ${birthYear}`;
console.log(output);
if (birthYear >= 1981 && birthYear <= 1996) {
var millenial = true; // In the same function, so we can also call this variable.
// 因为此变量使用var关键字进行变量的声明,具有函数作用域,
// 因此在同一个函数你仍然可以访问到
const str = `哦,你是千禧一代啊!Oh, and you're a millenial ${firstName}`;
console.log(str);
// 在严格模式下,函数是块作用域
function add(a, b) {
return a + b;
}
}
// console.log(str);
console.log(millenial); // millenial => true
add(2, 3);
}
printAge();
return age;
}
const firstName = 'Zoe';
calcAge(1994);
但如果取消严格模式,函数也可以在外部作用域被调用
javascript
function calcAge(birthYear) {
const age = 2037 - birthYear;
// console.log(firstName);
function printAge() {
const output = `${firstName}, you are ${age}, born in ${birthYear}`;
console.log(output);
if (birthYear >= 1981 && birthYear <= 1996) {
var millenial = true; // In the same function, so we can also call this variable.
// 因为此变量使用var关键字进行变量的声明,具有函数作用域,
// 因此在同一个函数你仍然可以访问到
const str = `哦,你是千禧一代啊!Oh, and you're a millenial ${firstName}`;
console.log(str);
// 在严格模式下,函数是块作用域
function add(a, b) {
return a + b;
}
}
// console.log(str);
console.log(millenial); // millenial => true
console.log(add(2, 3)); // call the add function
}
printAge();
return age;
}
但如果此时打开严格模式,此函数将无法访问
JavaScript中的就近原则
下面这段代码中我们有两个名称为firstName
的变量。但是JavaScript
在编译的过程中会遵循就近原则------只应用同一变量名中寻找当前作用域最近的变量。例如在全局作用域下有firstName
变量,但在块作用域下的函数printAge()
下的if
块内也有firstName
变量,此时,引擎会优先引入if
块内的firstName
变量。因此结果会显示"李琳",而不是全局作用域下的"Zoe"。
javascript
function calcAge(birthYear) {
const age = 2037 - birthYear;
// console.log(firstName);
function printAge() {
const output = `${firstName}, you are ${age}, born in ${birthYear}`;
console.log(output);
if (birthYear >= 1981 && birthYear <= 1996) {
var millenial = true; // In the same function, so we can also call this variable.
// 因为此变量使用var关键字进行变量的声明,具有函数作用域,
// 因此在同一个函数你仍然可以访问到
const firstName = '李琳';
const str = `哦,你是千禧一代啊!Oh, and you're a millenial ${firstName}`;
console.log(str);
// 在严格模式下,函数是块作用域
function add(a, b) {
return a + b;
}
}
// console.log(str);
console.log(millenial); // millenial => true
// console.log(add(2, 3)); // call the add function
}
printAge();
return age;
}
const firstName = 'Zoe';
calcAge(1994);
尽管有两个相同的firstName
变量,但是因为这两个变量在不同的作用域上,所以尽管他们有相同的变量名称,但实际上它们是两个完全不同的变量。以后在分析V8引擎对JavaScript
的解释和编译策略时会提到这一点。
javascript
function calcAge(birthYear) {
const age = 2037 - birthYear;
// console.log(firstName);
function printAge() {
let output = `${firstName}, you are ${age}, born in ${birthYear}`;
console.log(output);
if (birthYear >= 1981 && birthYear <= 1996) {
var millenial = true; // In the same function, so we can also call this variable.
// 因为此变量使用var关键字进行变量的声明,具有函数作用域,
// 因此在同一个函数你仍然可以访问到
const firstName = '李琳';
const str = `哦,你是千禧一代啊!Oh, and you're a millenial ${firstName}`;
console.log(str);
// 在严格模式下,函数是块作用域
function add(a, b) {
return a + b;
}
output = `新的输出!`;
}
// console.log(str);
console.log(millenial); // millenial => true
// console.log(add(2, 3)); // call the add function
}
printAge();
return age;
}
另外,需要注意的是,随着JavaScript
编程语言在后端的大量使用,目前JavaScript
已经脱离了解释,改为了即时编译或解释➕编译的混合编译手段,即JIT
(Just in time)即时编译策略。这使得JavaScript
也能和编译语言一样拥有较快的编译和运行速度。