1、作用域就是一个变量或者函数的有效作用范围,在JS中作用域一共有三种,分别是全局作用域、局部作用域(函数作用域)、块级作用域;
2、变量声明的三种方式:var、let、const
1、全局作用域:声明在函数外部的变量(声明在script标签中的变量和函数),在代码中任何地方都能访问到的对象拥有全局作用域;var和let变量在全局作用域中都是全局变量;
注意:所有没有var直接赋值的变量都属于全局变量;
2、全局作用域中声明的变量和函数会作为window对象的属性和方法保存,可以通过 window.变量名 去调用它;
3、全局作用域在页面打开时被创建,页面关闭时被销毁;
(1)最外层函数和在最外层函数外面定义的变量拥有全局作用域,例如:
var authorName="波妞";
function doSomething(){
var blogName="中介";
function innerSay(){
console.log(blogName);
}
innerSay();
}
console.log(authorName); //波妞
doSomething(); //中介
console.log(blogName); //错误
innerSay() //错误
(2)所有末定义直接赋值的变量自动声明为拥有全局作用域,例如:
function doSomething(){
var authorName="波妞";
blogName="中介";
console.log(authorName);
}
doSomething(); // 波妞
console.log(blogName); //中介
console.log(authorName); //错误
在函数中用
var
、let
、const
声明的所有变量,都是函数的局部变量,作用范围为局部作用域,即:只能在函数内部使用,函数外部使用是不行的。无论是通过var还是let定义在局部作用域的变量都是局部变量;1、调用函数时,函数作用域被创建;函数执行完毕,函数作用域被销毁;
2、每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的;
3、在函数作用域中可以访问到全局作用域的变量,在函数外无法访问到函数作用域内的变量;
4、在函数作用域内访问变量/函数时,会现在自身作用域中查找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域;
注意:所有没有var直接赋值的变量都属于全局变量
任何一对花括号{ }中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。
在ES6中只要 { } 没有和函数结合在一起,那么就是“块级作用域”。ES6之前没有块级作用域。使用
let
关键字或者const
关键字来实现块级作用域。
let
和const
声明的变量只在let
或const
命令所在的代码块 { } 内有效,在 { } 之外不能访问。注意:
- 块级作用域中,var定义的变量是全局变量,let定义的变量是局部变量;
- 块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。但函数funtion(){ }里面的{ }不属于块级作用域,而是局部作用域(函数作用域);
4.1、在块级作用域中通过var定义的变量是全局变量
{
//块级作用域
var num = 123;//全局变量
}
console.log(num); // 123
4.2、在局部作用域中通过var定义的变量是局部变量
function test() {
var value = 666;//局部变量
console.log(value);
}
test(); // 666
console.log(value);//value is not defined
5.1、验证在块级作用域中
{
var num1 = 678;//全局变量
num2 = 678;//全局变量
let num3 = 678;//局部变量
}
console.log(num1);//678
console.log(num2);//678
console.log(num3);//num3 is not defined
5.2、验证在局部作用域中
function f() {
num1 = 456;//全局变量
var num2 = 678;//局部变量
let num3 = 123;//局部变量
}
f();
console.log(num1);//456
console.log(num2);//num2 is not defined
console.log(num3);//num3 is not defined
1. 通过这个 栗子加深一下理解
var num1 = 123;//**全局变量**
function f() {
var num2 = 456//局部变量
}
function test() {
num3 = 666;//局部作用域,没有var或者let修饰。**全局变量**
}
{
var num4 = 789;//块级作用域、**全局变量**
}
{
let num5 = 789;//块级作用域、let定义变量。局部变量
}
{
let value7 = 123;
{
//注意点:在不同的作用域范围中,可以有同名变量
let value7 = 456;//不会报错。
}
}
2. 为什么需要块级作用域
ES5只有全局作用域和函数作用域,没有块级作用域,会带来以下问题:
1) 变量提升导致内层变量可能会覆盖外层变量
var i = 5;
function func() {
console.log(i);
if (true) {
// var i = 6;
}
}
func(); // 5
var i = 5;
function func() {
console.log(i);
if (true) {
let i = 6;
}
}
func(); // 5
var i = 5;
function func() {
console.log(i);
if (true) {
var i = 6;
}
}
func(); // undefined
2) 用来计数的循环变量泄露为全局变量
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(i); // 10
for (let i = 0; i < 10; i++) {
console.log(i);
}
console.log(i); // i is not defined
首先我们要知道,js的执行顺序是由上到下的,但这个顺序,并不完全取决于你,因为js中存在变量的声明提升。
栗子1
console.log(a) //undefined
var a = 100
fn('zhangsan')
function fn(name){
age = 20
console.log(name, age) //zhangsan 20
}
结果:
打印a的时候,a并没有声明,为什么不报错,而是打印undefined。
执行fn的时候fn并没有声明,为什么fn的语句会执行?
这就是变量的声明提升,代码虽然写成这样,但其实执行顺序是这样的。
var a
function fn(name){
age = 20
console.log(name, age)
}
console.log(a)
a = 100
fn('zhangsan')
js会把所有的声明提到前面,然后再顺序执行赋值等其它操作,因为在打印a之前已经存在a这个变量了,只是没有赋值,所以会打印出undefined,为不是报错,fn同理。
变量提升:JS在解析代码时,会将所有的声明提前到所在作用域的最前面
栗子2
console.log(name); //undefined
var name = '波妞';
console.log(name); //波妞
function fun(){
console.log(name) //undefined
console.log(like) //undefined
var name = '大西瓜';
var like = '宗介'
}
fun();
相当于
var name;
console.log(name); //undefined
name = '波妞';
console.log(name); //波妞
function fun(){
var name;
var like;
console.log(name) //undefined
console.log(like) //undefined
name = '大西瓜';
like = '宗介'
// 此时再打印
console.log(name) //大西瓜
console.log(like) //宗介
}
fun();
注意:是提前到当前作用域的最前面
这里也要注意函数声明和函数表达式的区别。上例中的fn是函数声明。接下来通过代码区分一下。
栗子3
// 函数声明
fn1('abc')
function fn1(str){
console.log(str)
}
// 函数表达式
fn2('def')
var fn2 = function(str){
console.log(str)
}
结果:
可以看到fn1被提升了,而fn2的函数体并没有被提升。
效果等同于:
var fn2
fn2('def')
fn2 = function(str){
console.log(str)
}
这下大家应该就明白报错原因了吧!
好了,上面我们已经了解了作用域与变量提升的概念,下面我们来看一下作用域链;
很简单,直接看 栗子
console.log(name); //undefined (1)
var name = '波妞';
var like = '宗介'
console.log(name); //波妞 (2)
function fun(){
console.log(name); //波妞 (3)
console.log(eat) //ReferenceError: eat is not defined (4)
(function(){
console.log(like) //宗介 (5)
var eat = '肉'
})()
}
fun();
前后呼应:为什么第一个打印是"undefined",而不是"ReferenceError: name is not defined"。原理就是JS的变量提升
JavaScript高级程序设计中对闭包的定义:闭包是指有权访问另外一个函数作用域中变量的函数。
从概念上,闭包有两个特点:
1、函数嵌套;(有外部函数, 有内部函数)
2、内部函数可以引用外部函数的变量/函数;
以下是我在学习中做的笔记:
1. 引入----代码有点长,但很经典,可以自己运行理解一下哦
1. 如何产生闭包?
* 当一个被嵌套的内部(子) 函数引用了嵌套其的外部(父) 函数的变量(函数)时, 就产生了闭包
2. 闭包到底是什么?
* 使用chrome调试查看
* 理解一: 闭包是嵌套的内部函数(绝大部分人)
* 理解二: 包含被引用变量(函数)的对象(极少数人)
* 注意: 闭包存在于被嵌套的内部函数中
3. 产生闭包的条件?
* 函数嵌套 (有外部函数,有内部函数)
* 内部函数引用了外部函数的变量/函数
* 执行外部函数;(必须执行外部函数才会产生闭包) ---产生几个闭包要看执行多少次外部函数
栗子
由于种种原因,我们有时候需要得到函数内的局部变量。但是,在正常情况下,这是办不到的,那么怎么才能得到函数内部的局部变量呢?
那就是闭包,在函数的内部,再定义一个函数,然后把这个函数返回。
function F1(){
var a = 100
//返回一个函数 (函数作为返回值)
return function (){
console.log(a)
}
}
//f1得到一个函数
var f1 = F1()
var a = 200
f1() // 100
在本中就实现了闭包,简单的说,闭包就是能够读取其他函数内部变量的函数。
下面解释一下为什么打印的是100,
看这句 var f1 = F1(); F1这个函数执行的结果是返回一个函数,所以就相当于把F1内的函数付给了f1变量,类似于这样:
var f1 = function(){ console.log(a) //这里的a是一个自由变量 }
这里的a是一个自由变量,所以根据作用域链的原理,就应该去上一级作用域去找。之前说过,作用域链在定义时确定,和执行无关,那就去想上找,这个函数定义定义在F1中,所以会在F1中找a这个变量,所以这里会打印的100。
通过这种方式,我们在全局下就读取到了F1函数内部定义的变量,这就是闭包。谈到这里那就说说闭包的作用吧!
好了,就到这里,我这只是冰山一角,一点皮毛而已,如果哪里有问题,希望各位大佬指出。DayDayUp!!!