目录
1. 作用域定义
2. 全局作用域和函数作用域
3. 块级作用域
4.欺骗词法作用域
1.作用域定义
在编程语言当中,变量存储在哪,怎么找到该变量,会有严格规则,这些规则便是作用域
作用域的种类可以简单的分为三种:函数作用域 ,全局作用域 ,块级作用域
js是一种弱类型、动态语言,弱类型指的是编译时并不会确定变量的类型,而是通过赋的值来确定变量类型;动态指的是js可以在代码执行的时候编译
js代码执行的过程 中两个角色:编译器 (编译代码),引擎(执行编译好的代码)
2.全局作用域与函数作用域
全局作用域:全局作用域是整个 JavaScript 程序的最外层作用域,包括了所有其他作用域
函数作用域:函数作用域是在函数内部创建的作用域,其内部的变量和函数对外不可见
通过一些简单的例子来理解全局作用域和函数作用域之间的关系
在代码1中
函数内部的变量对内部可见,对外部不可见,所以第二个console.log(a+b)
报错ReferenceError,在外部访问不了函数内部变量,a,b不存在
js
//1
//函数作用域中的变量以及函数只能在内部访问
function foo(){
Var b=2
Var a=1
console.log(a+b)
}
foo()//输出结果为3
console.log(a+b)//报错ReferenceError,在外部访问不了函数内部变量,a,b不存在
在代码2中
我们将a1定义为全局变量,在引擎在函数作用域找不到a1的值时时便会一层一层往外找,直到全局作用域 在全局作用域当中成功找到了a1的值
js
//2
Var a1=1//我们将a1放在外面
function foo1(){
Var b2=2
console.log(a1+b1)
}
foo1()//结果仍然是3
在代码3中
我们定义了两个a2,实际上console.log(a2+b2)输出结果还是3
那是因为在引擎在函数作用域当中已经找到了a2的值便会停止寻找,所以a2的值还是2
js
//3
Var a2=2
function foo2(){
Var a2=1
Var b2=2
console.log(a2+b2)
}
var定义变量时可能出现的一些陷阱
1.提升
js
Function foo(){
console.log(b)
Var b=9
}
Foo()//结果为undefined
此段代码居然没有报错,而是输出了一个值"undefined",
说明了b是存在的只是没有赋值. 一般我们会认为在变量定义之前使用该变量,程序应该会报错"ReferenceError "
这和代码具体执行过程有关,在执行foo()时,编译器编译过程中只会执行var a,告知引擎这里有个变量a,而之后的"=2"则需要引擎来执行 而当引擎去寻找a的值时,会被编译器告知a存在但是a并没有被赋值,因为赋值语句在后面才会被执行,所以此时a存在但是没有值"undefined"我们将这种现象称为提升
以上代码等价于↓
js
Function foo(){
Var b
console.log(b)
b=9
}
Foo()
2.for和if
for语句()使用关键词var定义的变量与
if语句后面{}的代码中使用var定义的变量的作用域是全局作用域,
也就是说当你在后面定义同名变量使用时便容易产生一些错误比如使用了之前的变量值
js
for(var i=0;i<10;i++){}
console.log(i)
//i可以直接被使用,因为i的作用域是全局作用域而不是块级作用域
3.在同一作用域当中可以重复定义同名变量
js
var a=1
var a=4
console.log(a)
//最中结果为4而不是一开始的1
3.块级作用域
1.Let、const在{}定义变量形成块级作用域,变量对{}外面的作用域不可见
2.with在修改对象属性时,在{}修改对象已有属性的语句也属于块级作用域对外不可见,当然with方法已经被废弃
形成块级作用域的两个关键词
Let
-
相对于var的提升,let有"暂时性死区",也就是let 定义的变量在声明前是不可见的
jsConsole.log(a) let a=1//报错ReferenceError,暂时性死区变量声明前面,变量不可见
-
相对于var来说,同一作用域当中不能定义相同的变量,否则会报错
-
当let与{}结合时便会产生块级作用域, 例如
js{ let a=3} console.log(a)//报错ReferenceError ,
因为a只在{}里可见,对外不可见
const
与let的用法基本相同,
- 都有暂时性死区的特性
- 同一作用域下不可重复声明同名变量
- 在{}定义变量对外不可见
- 但是 const定义的变量为常量,声明的同时必须赋值,之后不可以修改值但是let可以
js
const a = 5; // 正确,给a赋了初始值
a = 7; // 错误,不能重新赋值
let b = 5; // 正确,给b赋了初始值
b = 7; // 正确,可以重新赋值
js
const a
a=5//报错,声明变量的同时必须赋值
4.欺骗词法作用域
词法作用域并不是另一个作用域而是指就是当该代码书写的位置在哪,那么它的作用域也就在哪
欺骗词法作用域也就是绕过了正常的词法作用域规则
eval
eval(""),该方法能够在eval的词法作用域动态执行传入函数里面字符串中存储的代码,就好像之前这里就写了这段代码
js
var x = 10;
var y = 20;
var code = 'console.log(x + y);';
eval(code); // 执行 code 字符串中的代码,输出 30
以上代码相当于↓
js
var x = 10;
var y = 20;
var code = 'console.log(x + y);';
console.log(x + y);
但是要知道的是js当中通过编译知道变量的位置,以及词法作用域规则,优化代码执行的速度,而eval()则绕过了这一规则,也就是欺骗词法作用域
虽然eval可以给我们带来便利,但会严重影响程序的性能,因此一般情况下要避免使用eval()方法
对作用域中未定义的变量赋值,会生成一个同名的全局变量
js
// 非严格模式下
function createGlobalVar() {
globalVar = "全局变量-"; // 没有使用 var、let 或 const 声明
}
createGlobalVar();
console.log(globalVar); // 输出 "全局变量",globalVar 是一个全局变量
按照上述我们说的函数作用域,执行console.log(globalVar)应该会报错"ReferenceError"
但是在非严格模式下
引擎在执行"=2"这个赋值操作时去寻找变量声明,如果直到全局作用域还没找到,便会在全局作用域声明该同名变量,然后再赋值
相当于↓
js
// 非严格模式下
var a
function createGlobalVar() {
globalVar = "全局变量-"; // 没有使用 var、let 或 const 声明
}
createGlobalVar();
console.log(globalVar); // 输出 "全局变量",globalVar 是一个全局变量
这样也绕过了词法作用域,故也是欺骗词法作用域
with语句的变量查找不明确问题
也正是因为with存在许多问题现在已被弃用,具体也不赘述