js基础-类型、值和变量(下)

/ 0评 / 2

15、变量

首先,我们需要了解一下变量的定义:

变量是存储信息的容器。

为什么需要变量呢,不止js里面,程序里面的数据来源都要有根有据,比如

alert(a);

为什么会发生错误呢,a是什么东西,有声明过了吗,所以也就需要变量来存储数据了,让数据来源有根据,而不是无中生有(当然也有特殊情况,下面我们在讨论)。在js程序中,如果我们使用变量之前会先声明变量。变量的声明用var关键字。

var a="";
var b=true;
var fn=function(){
    //dosomething
}
var date=new Date();
var obj={};
var arr=[];
var num=123;
var nullStr=null
var uf=undefined;
var pat= /\d+/;//匹配一个或多个数字

比如从上面可以看出,变量的类型是任意的,js是弱类型语言,js的变量可以是任意类型的。

上面代码,多数用到var,我们可以简写多个变量声明的方式,比如

var a=1
var b=2;

//等价于

var a=1,
    b=2;

值得注意的是:如果围在var什么语句中给变量赋值,也就是只是声明了,但没有值,那么在给这个变量赋值之前,这个变量的值是undefined

var a;
console.log(a);     //undefined

这里涉及到了变量的什么提前的知识,有兴趣的可以参考我之前写的文章js(四):提升。这里我们不深入研究下去。

当我们使用var对同一变量重复声明是没问题的;

var a=1;
var a=2;
var a=3;

这种重复声明和赋值有点类型我们css中,同名选择器的时候,后面的样式会覆盖前面的属性。

但是要记得的是,给一个没有什么过的变量赋值,在非严格模式下是没有错误的,但是在严格模式下会报错。

a=2;
console.log(a);
"use strict";
a=2;
console.log(a);

上面例子,一个在非严格模式下执行的时候,a会变成window下的属性,也就是全局变量。

另一个在严格模式下执行,程序会报错。

因此,我们写代码的时候,应该始终使用var来声明变量。(关于这些知识,都涉及到了js更加深层次的知识与原理性的东西,以后我们会详细来研究)

既然我们用var声明了变量,那么这些变量的作用范围,我们可以先理解为变量的作用域了。

在全局作用域中声明的变量是全局变量,相对的也有局部变量。类似

var win="window"
function fn(){
    var win="obj";
    console.log(win);
}
console.log(win);
fn();
console.log(win);

在上面例子中我们假设,win所处的环境是全局环境,那么win就是全局变量,并且变量win的值为'windoq',而在函数fn里面的win变量所处于函数fn的局部环境中,所有里面的win是局部变量,第一个输出和第二个输出我们是毫无疑问的,第三个再次输出win也是全局的,因为两个所处不同环境的两个分别用var定义的win变量是不一样的。我们改一下代码可以看出区别:

var win="window"
function fn(){
    win="obj";     //改动地方   没有用win声明
    console.log(win);
}
console.log(win);
fn();
console.log(win);

同名属性,在局部中没有用var什么,但是赋值了,也就相当于改变了全局作用域下的同名属性的值,所有执行函数fn后,win的值被改变了。(值得注意的是,前面说过:在严格模式下,给未经声明的变量赋值会包类型错误,这个例子就算在严格模式下也不会保存,因为win已经在全局作用域下用var声明过了,函数体内仅仅只是赋值操作)。

15、函数作用域和声明提前

块级作用域

在一些强类型编程语言中,比如C,花括号{}内的每一段代码都有各种的作用域,而且变量在声明他们的代码段之外是不可见的,这就是块级作用域,而js在ES6之前的js版本是没有块级作用域的。这里我们先不涉及到ES6的知识,后面我们再详细了解。所以取而代之地使用了函数作用域。 变量再声明他们的函数体以及这个函数体嵌套的任何函数体内都是有定义的。例如

function foo(){
    var a=1;    //这里面的变量只有再函数里面才可以访问
    var b=2;
}

js的函数作用域是指函数内声明的所有变量再函数体内始终可见,这意味这变量再声明之前甚至已经可用。js的这个特性别非正式的称为声明提前,即js函数里声明的变量(但不涉及复制)都被“提前"只函数体的顶部。我们直接看代码

var a="global";    //全局变量、
function foo(){
    console.log(a);
    var a="local"   //局部变量
    console.log(a);
}

第一个console.log输出什么?是undefined,而不是global或者local,而第二个毫无疑问是local了。

上述过程等价与:将函数内的声明提前至函数顶部,同时变量初始化留在原来的位置,有兴趣深入了解原理的同学可以参考我写的文章提升。

由于js没有块级作用域,因此我们常常可以见到别人写代码的时候将变量声明放在函数体顶部,而不是将声明靠近放在使用变量之外。这种做法使得他们的源代码非常清晰地反应真实的变量作用域

function foo(){
     var iTarget=null;   //现在函数体顶部声明
     ....

    iTarget=....     //只用用与赋值等等操作了
}

作为属性的变量

当声明一个JavaScript全局变量时,实际上是定义了全局对象的一个属性。当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说这个变量无法通过delete运算符删除。可能你已经注意到了,如果你没有使用严格模式并给一个未声明的变量赋值的话,JavaScript会自动创建一个全局变量。以这种方式创建的变量是全局对象的正常的可配值属性,并可以删除它们:

var a = 1; // 声明一个不可删除的全局变量 
b = 2; // 创建全局对象的一个可删除的属性 ,(在严格模式下会报错)
this.c = 3; // 同上  
delete a // => false: 变量并没有被删除 
delete b // => true: 变量被删除 
delete this.c // => true: 变量被删除

为什么使用var的变量就不可配置了,这个涉及到了属性的特性的知识,有兴趣的朋友可以查下资料了解和深入下,这里我们简单的了解一下:

1. 作为对象,每个属性(变量)下都有几个属性(属性的特性),其中configurable,这个属性确定了属性(变量)名字能否更改,变量能否被删除。true的话,可以更改,可删除;反之,不能更改,不能删除。
2. 在用var 声明变量时,JS解析器会默认把configurable设为false,所以它不能改名字,不能被删掉。

js全局变量是全局对象的属性,上面在非严格模式下,this在全局下指向的是全局对象,(关于this指向这个知识,我们以后再来详细探讨)也就是window。

作用域链

简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。在学习作用域链之前,我们简单看几个例子了解下全局作用域和局部作用域。

全局作用域

var a="window";
functin a(){
    //dosomethind
}

假设上面代码代码的最顶级的位置(也就是没有任何函数嵌套,所有当前的作用域就是全局作用域),全局作用域就是在全局环境下变量与函数的可访问范围,这个范围就是控制着变量和函数的可见性和声明周期,任何在全局作用域下的都可以访问到全局变量a和全局函数a(),

局部作用域

function b(){
    var foo="123";
    function bar(){
        //dosomething
    }
}
console.log(foo)  //报错 
bar() 报错

我们知道,js里面函数也是对象,在函数花括号{}里面的定义的变量、函数等,我们在函数外面是访问不到的,这就是函数的局部作用域。和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所有在一些地方也会看到有人把这种作用域称为函数作用域。

局部作用域拥有对全局作用域的访问权限,在函数嵌套中,被嵌套的函数拥有对外层函数作用域的访问权限,比如

//全局作用域

function foo(){
     var a=1;     //foo函数作用域(局部作用域)
     function bar(){
          var b=2;
          function fn(){
               var c=3
          }
     }
}

上面函数里面嵌套了多层函数,a、bar()是foo的局部变量,以此类推,变量c在fn()定义,fn()的作用域包含着fn()花括号{}里面的作用域、bar()、foo()和全局作用域,在fn中可以访问到变量它的外层作用域包括全局作用域,以此类推。但是全局作用域不能访问到变量a以及所嵌套的标识符(变量、函数,对象等),我们把作用域想象成一条线条,他们组成了一个单向的,由内而外的线,如

图中,黄色的线的箭头代表可以访问的作用域,他们形成一系列的链条就是作用域链了(本人理解),作用域链定义了一系列作用域嵌套时候,变量与函数的可见性和生命周期。

当定义一个函数时,它实际上保存一个作用域链。当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会重新定义一遍。

js基础-类型、值和变量到这里了。

简书求关注http://www.jianshu.com/u/bfd55badb1f4

原创文章,本文仅代表个人观点,如有错误,欢迎讨论交流~

 

 

发表评论

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