执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为,每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境知道应用程序退出时才会被销毁)。
——《JavaScript高级程序设计(第3版)》
作用域:全局作用域、私有作用域、块级作用域(ES6)
全局作用域
在Web浏览器中,全局作用域被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。
当打开web页面的时候,会提供一个供js代码执行的环境(全局作用域),会默认提供一个最大的window对象。
全局变量
在全局作用域中声明的变量。
全局变量和window对象的关系
在window对象上的方法等,可以直接去除前面的window,直接通过属性名使用,比如
window.alert("1");
可直接写为alert("1")
;
在全局作用域下声明的变量,相当于给window对象添加了一个属性,属性名就是变量名,属性值就是变量值。(针对var
、function
、直接写一个变量名
。ES6阻断了与window的关系,故而let
、const
声明的变量不会添加到window对象中。)
判断一个对象有没有某个属性
属性名 in 对象
返回值是布尔数据类型,true代表包含,false代表不包含
var obj = {name : "lili"};
"name" in obj;//返回值是布尔数据类型
带var和不带var的全局变量的异同
相同点
在全局作用域下声明都是给window添加了一个属性和属性值。
不同点
- 带var的全局变量有变量提升;不带var的全局变量没有变量提升
- 带var的全局变量通过
delete window.属性名
删不掉;不带var的全局变量可以删掉
var a=3;
b=6;
delete window.a;//false
delete window.b;//true
私有作用域
每个函数都有自己的执行环境。
函数执行的时候形成的作用域就是私有的,保护里面的变量不受外界干扰。
私有变量
- 在私有作用域声明的变量
- 形参
// 形参也是私有变量
function fn(x) {
// 私有变量
var m = 3;
console.log(m);//3
}
console.log(window.fn());//undefined
注意
函数未被调用时,函数体以字符串的形式存储在堆内存中。
function fn(b){
var x = 3;
n = 3;
}
// 函数未执行,没有为window对象添加属性n及其值3
console.log(n);// n is not defined
块级作用域
为了解决块级作用域的问题,ES6引入了新的关键字
let
,用let
替代var
可以申明一个块级作用域的变量。
在块级作用域下,所有的变量都在定义的花括号内,从定义开始到花括号结束这个范围内可以使用,出了这个范围就无法访问。
// 块级作用域
{
let s=3;
console.log(s);//3
}
console.log(s);//s is not defined
练习
test 1
在私有作用域中只有以下两种情况是私有变量:
- 形参
- 声明过的变量(var、function、let 等)
剩下的都不是私有变量,需要基于作用域链向上查找
/*
全局作用域
变量提升:var a,b,c;function fn=aaafff111
代码自上而下执行
*/
var a=12,b=13,c=14;
function fn(a){
/*
私有作用域
形参赋值:a=12
变量提升:var b
代码自上而下执行
*/
console.log(a,b,c);//12 undefined 14
var b=c=a=20;
console.log(a,b,c);//20 20 20
}
fn(a);
console.log(a,b,c);//12 13 20
test 2
如果函数传进来的参数是引用数据类型,此时形参拿到的是地址值,虽然形参在函数里面是私有变量,但是真正更改的还是那个引用地址中存储的内容,所以全局下的变量也会发生变化。
/*
全局作用域
变量提升:var ary;function fn=aaafff111;
代码自上而下执行
*/
var ary=[12,13];//赋值 ary=aaafff222
function fn(ary){
/*
私有作用域
形参赋值:ary=aaafff222
变量提升:无
*/
console.log(ary);//[12,13]
ary[0]=100;//[100,13]
ary=[100];//赋值 ary=aaafff333
ary[0]=0;//[0]
console.log(ary);//[0]
}
fn(ary);
console.log(ary);//[100,13]
作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链(保证对执行环境有权访问的所有变量和函数的有序访问)。
当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments 和其他命名参数的值来初始化函数的活动对象。在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。
函数执行过程中查找变量的时候,先看自己私有作用域中有没有(首先查找的都是当前执行代码所在环境的变量对象),如果有就使用自己私有的变量,如果没有就向上一级作用域继续查找,如果上一级作用域也没有,就一直向上找,直到找到window(全局作用域)为止,如果window也没有,根据所执行操作不同会得到不同的结果:
1、获取操作
报错:variable is not defined
function fn(b){
var x=3;
console.log(n);//n is not defined
}
fn(1);
2、赋值操作
相当于给全局window对象添加了一个属性名和属性值
function fn(b){
var x=3;
n=3;
console.log(n);//3
}
fn(1);
console.log(n);//3
上级作用域
当前函数执行会形成一个私有作用域A,这个私有作用域A的上级作用域是谁,跟函数在哪执行无关,跟函数在哪定义(创建)有关系,在哪定义,私有作用域A的上级作用域就是谁。
简而言之,上级作用域是谁,跟函数在哪执行没有关系,跟它在哪定义有关系。
案例 1
//C的上级作用域就是B,B的上一级作用域就是A,A的上级作用域就是window
function A(){
function B(){
function C(){
}
}
}
案例 2
var n=3;
function fn(){
console.log(n);
}
function sum(){
var n=6;
fn();//fn()的上一级作用域是全局作用域
}
fn();//3
sum();//3
案例 3
/*
var n
function fn=aaafff111 上级作用域为全局作用域
var x
*/
var n=10;
function fn(){
/*
var n
function f=aaafff222 上级作用域为fn()
*/
var n=20;
function f(){
n++;
console.log(n);
}
f();
return f;
}
//fn()执行结束后将结果赋值给x
var x=fn();//x=aaafff222即x=f //21
x();//f() //22
x();//f() //23
console.log(n);//10
案例 4
//函数每次执行都会开辟一个新的栈内存
function fn(x){
return function fn1(y){
console.log(x,y);
}
}
var f=fn(1);
f(2);
fn(1)(2);//fn1(2)