一、作用域

(一)作用域是什么

了解作用域之前先看一下变量和函数,变量和函数都有一定的访问权限,就是必须满足条件或者在某个范围之内才能访问,这个范围就是作用域。

它具体表现形式就是一段特定的代码,在该代码段中的变量和函数是封闭的、独立的,这样变量才不会泄露、污染。

var cat = '有鱼';
function Person(){
    var name = '张三';
    console.log(name);//张三,在函数内部,跟变量name在同一个作用域内
}
console.log(cat);//有鱼
console.log(name);//没有结果显示,因为name在函数内部是独立的,访问不了
复制代码

(二)作用域分类

作用域一共有三种,分别为全局作用域、函数作用域、块作用域,其中块作用域是ES6新增的,同时函数作用域和块作用域统称为局部作用域。

全局作用域

在代码的最外层的变量和函数称为全局作用域,有以下特点:

①在代码的任何地方都可以访问得到

②变量声明时如果省略var关键字,则为全局变量,拥有全局作用域

③window对象的属性和方法,拥有全局作用域

④浏览器打开时开启,浏览器关闭或者页面关闭时销毁

var cat = '有鱼';
function Person(){
    var name = '张三';
    per = '卡卡';// 省略var,视为全局变量
    console.log(cat);//有鱼 cat为全局变量,函数Person内部可以访问
}
console.log(cat);//有鱼 cat为全局变量,最外层代码可以访问
console.log(per);//卡卡 per为全局变量,最外层代码可以访问
复制代码

全局作用域也有弊端,如:变量命名时容易重复,造成变量污染;

函数作用域

在函数内部的变量权限称为函数作用域,有以下特点:

①每个函数都有自己的作用域,而且调用一次就会生成新的作用域

②只能在函数内部才能访问,外部是没有权限访问的

③进入函数内部时开启,函数执行完毕后销毁

var cat = '有鱼';
function Person(){
    var name = '张三';
    console.log(name);//张三 name为函数作用域,函数Person内部可以访问
}
console.log(name);//有鱼 name为函数作用域,最外层代码没有权限访问
复制代码

块作用域(ES6新增)

凡是由{}符号包裹起来的都是块作用域,ES6新增的知识点,有以下特点:

①只能在{}内部起作用

②变量声明时使用let或const关键字

③变量名不能重复,否则会报错

④变量不可以在声明之前使用

使用var进行展示:

if(true){
    var cat = '有鱼';
    console.log(cat);//有鱼 
}
console.log(cat);//有鱼
复制代码

使用let进行展示:

if(true){
    let cat = '有鱼';
    console.log(cat);// 有鱼
    // let cat = '年年';// Uncaught SyntaxError: Identifier 'cat' has already been declared

}
console.log(cat);// 访问不了cat变量  报:Uncaught ReferenceError: cat is not defined
复制代码

注意:块作用域一般用在for等循环语句中,可以避免变量外泄

(三)变量提升与函数提升

变量提升

①var关键字存在变量提升,let、const不存在;

②变量提升的意思是在程序执行前,先去整个代码中查看是否含有变量声明,即是否有var关键字,如果有则先执行声明,然后再去执行其他部分。

这里我我总结了4句话:

①js程序执行的顺序,先看var声明,声明部分在第一行,其他(赋值)按照正常顺序执行

②如果局部变量里面有var声明,就是局部变量,不用管外面的全局变量

③如果局部变量里面没有var声明,就去找外面的全局变量

④在局部变量里面,如何使用全局变量,使用window对象调用

var a = "Hello";
function person(){
    var a;
    console.log(a);//undifined
    a = "World";
    console.log(a);//World
}
person();
console.log(a);//Hello
// 先看整个脚本文件的声明部分,全局作用域有个变量a,局部作用域也有个变量a,而且都有var声明,说明他们是完全两个不同的变量,此时函数内部不用管外面的全局变量,此时变量a只是声明而没有赋值,所以输出为 undifined;
// 接着还是局部作用域里面,给a进行了赋值,此时输出时为world,且仍是局部变量;
// 函数外部的输出时,因为局部作用域已经结束,则又恢复到全局变量中,所以此时输出的是全局变量啊,值为hello。
复制代码
var a = "Hello";
function person(){
    console.log(a);//Hello
   	a = "World";
    console.log(a);//World
}
person();
console.log(a);//World
// 先看整个脚本文件的声明部分,全局作用域有个变量a,局部作用域里面没有var声明,则此时a是一个全部变量,所以输出时a的值就是全部变量hello;
// 局部作用域里面对全局变量a进行了重新赋值,所以此时输出时为world
// 函数外部的输出时,因为跳出局部作用域,又恢复到全局变量中,之前因为已经重新赋值为world,此时输出时为world
复制代码
var a = "Hello";
function person(){
   console.log(a);//undifined
   var a = "World";
   console.log(a);//World
}
person(); 
console.log(a);// Hello
// 先看整个脚本文件的声明部分,全局作用域有个变量a,局部作用域也有个变量a,且有var声明,说明他们是完全两个不同的变量,此时函数内部不用管外面的全局变量,进行变量提升,记住只是声明提升,赋值不进行提升,所以输出为 undifined;
// 还是局部作用域里面,给a进行了赋值,此时输出时为World,且仍是局部变量
// 函数外部的输出时,因为局部作用域已经结束,则又恢复到全局变量中,所以此时输出时为Hello
复制代码
var a = "Hello";
function person(){
    console.log(window.a);  //Hello  因为是window对象的属性,此时a是全局变量
    var a = "World";     //局部变量a在这行定义
    console.log(a);  //World
}
person();
console.log(a);//Hello 全局变量a
// 在局部作用域使用全局变量使用window.全局变量名字
复制代码

函数提升

①函数有两种形式:函数表达式 var per = function(){} 、函数声明 function per(){},只有函数声明有函数提升特性

②提升的意思是允许先调用,后声明

per();//可以先调用
function per(){
    console('我是函数声明形式,允许提升');
}
复制代码

二、作用域链

(一)什么是作用域链

每一层作用域其实就是一个执行环境(作用域和执行环境是有区别的,这里为了容易理解,姑且这样),在该环境内执行程序时,会创建一个变量对象,对象包含该有属于自己的变量和函数;

在当前执行环境下找不变量时,就会向上一层作用域里面寻找,具体过程还是先找到上一层的变量对象,再去里面找对应的变量,如果还是找不到,继续向上……

直到最外层的作用域,也就是全局作用域,这样就会形成一个链条,称为作用域链。

(二)作用域特点

①作用域链最外层是全局作用域,最内层是当前作用域

②内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数

③由于变量的查找是沿着作用域链来实现的,所以也称作用域链为变量查找的机制

var cat = '有鱼';
function Person() {
    var name = '张三';
    function Student() {
        var age = 18;
        console.log(name);//张三
        console.log(cat);//有鱼 
    }
    Student();
    console.log(age);//Uncaught ReferenceError: age is not defined
}
Person();

//Student函数内部属于最内层作用域,找不到name,向上一层作用域Person函数内部找,找到了输出‘张三’;
//在Student内部输出cat时找不到,向上一层作用域Person函数找,还找不到继续向上一层找,即全局作用域,找到了输出‘有鱼’;
//在Person函数内部输出age时找不到,向上一层作用域找,即全局作用域,还是找不到输出‘age is not defined’;
复制代码