js中作用域及var、let、const区别

前言

一直对js的作用域不是很熟悉,专门查了资料研究清楚了。

作用域

js中一共存在全局、函数、块三种作用域,块作用域在es6后出现。我们先讨论常规的全局、函数作用域。

块级作用域和函数级作用域

js的作用域和C、Java、Python类型的语言不一样,js的作用域是函数内的,Java的作用域是块内的。

如以下Java的代码,表明块级作用域

for语句定义的变量i只能在for语句中使用,外面使用不了。

外面对应的变量i,可以在内部的块中使用,块内部不能再定义同名变量。

private int func(){
    int i = 1;
    for(int j = 0; j < 10; j++){
        System.out.println(i+""+j);
    } //输出10到19
    System.out.println(i); //输出1
    //System.out.println(j); //报错
}

以下为js中的代码,表明函数作用域

函数内的为一个作用域,块内块外都是同一个作用域,可以相互访问

function t(flag){
    var a = 'hello';
    if(flag){
        console.log(a); //输出hello
        var s="ifscope";
        for(var i=0;i<2;i++) 
            ;
    }
    console.log(i); //输出2
    console.log(s); //输出ifscope
}
t(true);

js中全局作用域

js中如果不在函数内部声明变量, 或者没有声明变量直接赋值,则会属于全局作用域,实际上是绑定在全局对象上。在浏览器环境,最上级的全局对象是window,在node环境则是global。

如:

node环境

var name="global";
function t(){
    console.log(name);
    console.log(global.name);
}
t();
// 输出两个global

如果函数内再次定义了全局变量明的变量,那么会“屏蔽”全局变量。

如:

浏览器环境

var n = 10;
function func(){
    var n = 1;
    console.log(n);
    console.log(window.n);
}
func();
console.log(n);

会输出

1

1

10

我们还可以直接赋值而不定义

function func(){
    n = 1;
    console.log(n); // 1
}
func();
console.log(n); //1

就像alert()、location()这样的window下的api,我们不加window可以直接调用,js引擎会理解为全局变量下的api。所以我们也可以直接使用n,表示window.n=1;

全局作用域中var加与不加的区别

在全局作用域下,我们定义变量可以加var也可以不加,都能达到我们一般想要的效果,都会把变量放在全局对象的属性上。如:

var name="global";
// 或者
// name="global";
function t(){
    console.log(name);
}
t();

他们的区别在于:使用var定义变量不能删除这个属性

var name="global";
delete name; //false
console.log(name); // "global"

sex = "man";
delete man; //true
console.log(sex); //Uncaught ReferenceError: sex is not defined

Javascript的变量声明、创建属性以及每个Javascript中的每个属性都有一定的标志说明它们的属性----如只读(ReadOnly)不可枚举(DontEnum)不可删除(DontDelete)等等

当我们使用var定义变量name时,声明了它不可删除的属性,所以不能删除。而直接赋值使用的情况下,可以删除这个变量。

js作用域链和with语句

我们看这样的代码:

name="fay";
function t(){
    var name = "local name";
    (function (){
        var name = "local1 name";
        (function t2(){
            //a
            console.log(name);
        })()
        //b
        console.log(name);
    })();
    //c 
    console.log(name);
}
t();

它会输出

local1 name

local1 name

local name

js中存在一种作用域链。

因为js作用域是函数,代码执行后,会创建全局可执行环境,把name放入其中。

执行t函数后,会在全局环境下再开一个执行环境入栈,把里面name放入其中,这时name已经有一条从local name到fay的链了。

之后再执行内部函数会重复这个步骤,直到最里面的一层,那时的name的作用域链是local1 name到local name到fay,a处输出name时默认使用指针指向的及最里面的作用域。所以输出了一个local1 name,之后这个最里层函数t2所在的执行环境会出栈连带所有变量(代码中并没有)。

之后b处此时name的作用域链还没有变,输出local1 name。b输出后会弹出环境栈。

此时c处再输出时,name的作用域链已经是local name->fay 了,这样会输出local name。

总结

  1. 外层函数是无法读取内层函数的变量,除非内层函数声明的是全局变量,而内层函数因为作用域链的关系是可以访问到外层的变量的。

  2. 赋值时,只其他其他外层部分没有找到这个变量,才会绑定在全局变量中。

    如:

    function t(){
    var name = 1;
    function t1(){
        name =2;
        console.log(name);
    }
    t1();
    console.log(name);
    }
    t();
    console.log(name);
    //输出 
    //2
    //2
    //ReferenceError: name is not define

with语句

person={name:"fay",age:22,birthday:{year:2000}};
with(person.birthday){
    console.log(year); //输出2000
}

with语句主要用来临时扩展作用域链,将语句中的对象添加到作用域的头部。

js变量提升

function func(){
    console.log(a);//undefined
    var a = 10;
    console.log(b); //ReferenceError: b is not defined
}
func();

var定义变量并赋值时,变量的定义实际上会被提升至作用域开始,这样var a =10;会被分解为

var a;
console.log(a);
a=10;

所以,a会提示undefined,而b就会提示变量不存在了。

题外:函数声明提升

函数声明的方式

  1. 函数声明:function test(){};
  2. 函数表达式:var test = function(){};

代码一:

test();//输出:函数声明
var test = function(){
    console.log("函数表达式");
}
test();//输出:函数表达式
function test(){
    console.log("函数声明");
}
test();//输出:函数表达式

解释:函数function test()被声明提升了,并且优先级高于了变量声明。但是后来的test函数表达式又覆盖了之前的函数声明;

代码二:

console.log(test);/*输出:ƒ test(){console.log("函数申明");}*/
function test(){
    console.log("函数申明");
}
var test;
console.log(test);/*输出:ƒ test(){console.log("函数申明");}*/
var test = 1;
console.log(test);//1

解释:函数声明的优先级高于了变量声明,并且再次声明后,变量也test不会覆盖之前的函数声明。如果赋值了test=1,test才会被(覆盖原则)改变为1

es6后的let和const

let和const与var拥有不同的作用域、变量提升

快速使用

let就时我们熟悉的java和c类型语言的作用域,使用的是块作用域。

如:

function t(){
    for(let  i = 0;i<10;i++){
        console.log(i);
    }
    console.log(i);
}
t();

//输出
//每行0~9
//ReferenceError: i is not defined

而在使用var时,还会输出10

const

const是定义常量的,就像java中的final一样,当我们使用const定义了一个Object类型变量,那么我们实际上是限定了这个变量的引用指向,而Object中的内容是可以改变的。如:

const person = {
    "name":"fay",
    "age":20
}
console.log(person.age);
person.age=21;
console.log(person.age);

会正常输出20和21

另外注意,const必须在声明的时候赋值

全局使用

let name = 1;
console.log(name); // 1
console.log(global.name); //undefined

let和const在全局使用时,也不会绑定在全局对象上。

变量提升

let和const不存在变量提升,并且作用域是从定义开始到块结束,所以存在“变量死区”概念。你用c和java的变量作用范围去理解就很好理解了。

function func(){
    console.log(name);
    let name = "fay";
    console.log(name);
}
func();

ReferenceError: Cannot access 'name' before initialization

使用建议

乱用var会导致一些意想不到的“错误”:

  • 一个变量可以使用两次var(重声明)

  • 默认情况下,顶级变量是全局变量(全局对象)

  • 可以在声明前使用变量(提升)

  • 循环中的变量重复使用同样的引用(闭包)

    尽量使用let、const代替var,但是如果你要兼容es6之前的代码环境,那么还是需要使用var

如果你是小程序中的js代码,那么尽情的使用let吧,这样才是真正意义上的变量。

总结

js的作用域分析主要是针对es6前只有var声明变量的情况,我们可以清楚看到js的作用域以及变量提升等特性,这些特性十分特别。

在es6后,为了让代码更加清晰规范,带来了let和const,和常见的其他语言的使用方法类似。

我们尽可能使用let和const代替var声明变量,但是也要考虑兼容性。

参考

https://blog.csdn.net/yueguanghaidao/article/details/9568071

https://blog.csdn.net/qq_30216191/article/details/81042842

http://t.zoukankan.com/Zxq-zn-p-11441435.html

点赞
  1. Hui说道:
    Google Chrome Windows 10

    最近在搞JavaScript吗?

    javascript.info 怎么样

    1. lemon lemon说道:
      Firefox Windows 7

      最近补一下前端基础知识哈哈哈。
      javascript.info好网站啊!mark了,Thanks

lemon进行回复 取消回复

电子邮件地址不会被公开。必填项已用 * 标注