一、作用域
(一)作用域是什么
了解作用域之前先看一下变量和函数,变量和函数都有一定的访问权限,就是必须满足条件或者在某个范围之内才能访问,这个范围就是作用域。
它具体表现形式就是一段特定的代码,在该代码段中的变量和函数是封闭的、独立的,这样变量才不会泄露、污染。
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’;