前言
在讲作用域之前,我们先来聊聊js的编译和执行
JavaScript是弱类型编程语言,创建时不声明变量类型。如下所示
js
var a=5; //在赋值前不知道变量类型
var b='hello'
强类型编程语言如Java、.net、Python、C++、C,创建时需要声明变量类型
c
int i=1; //在赋值前通过'int'知道声明的变量是整型
char c='hello'
那Js在函数调用时怎么知道变量是什么类型呢,函数在执行后,Js会编译其作用域中的变量,并获取其数据类型。
在编译时,会通过找到某个域当中的有效标识符来获取数据类型。但是函数在没有调用的时候是不执行的,也就是不会进行编译。所以函数作用域并不是和全局作用域一起编译,而是先不编译,等要执行的时候再编译,编译永远只发生在执行的前一步.
而函数执行时,变量的查找会是从内到外的查找,即先查找执行函数的作用域,再去外层查找。
js
var a=1
function foo(){
var a =2
console.log(a);
}
foo()
// 打印 2
js
var a=1
function foo(){
var a =2
}
console.log(a);
foo()
// 打印 1
不能从外到内的查找,即不能查找执行函数作用域内部的作用域变量。
js
var a = 1
function foo(){
var a = 2
function bar(){
console.log(a);
}
}
foo()
bar()
// undefined
上述代码报错原因是因为函数bar()未被定义,函数bar()是在函数foo() 的函数作用域内的,而bar()是在全局作用域中被调用执行的,又因为函数执行不能从外到内,所以上述代码执行不了
什么是作用域
作用域是可访问的变量的集合。它决定了代码区块中这些变量和其他资源的可见性(可访问性)。
作用域的类型
全局作用域、函数作用域、块级作用域、欺骗词法作用域。
1.全局作用域
任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
js
// 全局变量
var a = 1
function foo(){
console.log(a);
}
foo();
// 打印 1
foo();//调用函数执行编译
2.函数作用域
函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在此函数作用域内部。这些变量只能在函数内部访问,不能在函数以外去访问。
js
var a = 1 // 全局编译
function foo() // 全局编译
{
console.log(a);// 函数编译 未调用时不编译
}
foo() // 调用函数执行,编译器暂停,进行函数编译,函数编译完成后再重新执行全局编译
// 打印 1
声明提升
var 声明的变量存在声明的提升,提升到当前作用域的顶峰
js
// 表面上的代码
console.log(a);
var a = 1
// 实际上的代码
var a
console.log(a);
a = 1
// undefined
函数声明会整体提升
js
//表面上的代码
function foo(){
console.log('123')
}
foo()
//实际上的代码
foo()
function foo(){
console.log('123')
}
怎么避免声明提升呢
用let代替var定义可以避免变量提升
let虽然可以避免变量提升
js
console.log(a)
let a = 1
// 报错
但是不能重复声明同一变量
js
let a = 1
let a = 2
console.log(a)
// 报错
var重复声明同一变量会覆盖
js
var a = 1
var a = 2
console.log(a)
// 打印 2
const 定义也不会发生声明提升 const虽然可以避免变量提升
js
console.log(a)
const a = 1
// 报错
但是不能重复声明同一变量
js
const a = 1
const a = 2
console.log(a)
// 报错
而且const声明的变量值不能被修改
js
const a = 1
a = 'hello'
// 报错
3.块级作用域
let + {} 会形成块级作用域
const + {} 也会形成块级作用域
js
if(1){
var a = 1
}
console.log(a);
// 打印1
if(1){
let a = 1 // or const a = 1
}
console.log(a);
//报错
let a = 1
if(true){
console.log(a); //暂时性死区
let a = 2
}
//报错
4.欺骗词法作用域
eval( ) 让原本不属于这里的代码变成就是写在这里的
js
function foo(str){
eval(str) //var b = 2
var a= 1
console.log(a,b);
}
foo('var b = 2');
// 打印 1,2
with 可以批量化修改对象的属性
js
var obj = {
a: 1,
b: 2,
c: 3,
}
// 使obj里面所有元素都加1
// 直接定义
obj.a = 2
obj.b = 3
obj.c = 4
with(obj) { // 批量修改obj内的值
a = 2,
b = 3,
c = 4
}
但当修改对象中不存在的属性时,该属性会泄漏到全局成为全局变量
js
function foo(obj){
with(obj){
a=2
}
}
var o1={
a:3
};
var o2={
b:3
};
foo(o2);
console.log(a);// 此时a被泄漏到全局作用域上了
// 打印 2