变量

XiLaiTL大约 11 分钟

变量

变量

在C语言中,我们学习了常量与变量的定义方式。变量的定义体系中包括了以下部分:

存储类别 const 数据类型 变量名 = 值;

在C++的类内属性定义中,还延伸添加了 访问修饰符

存储类别关键字有auto,extern,register与static,即声明了变量的作用域和生存期,const关键字声明常量,数据类型声明了数据以后处理的方式和占用的空间,还包括了数组[]和指针*,函数()另记。

在这里,我们看到了声明一个程序处理的数据所需要的内容,也暗暗看到了变量的生命周期:声明、初始化、赋值,以及离开作用域后销毁与否。

在C语言里,数据存储的方式等都是由所声明的数据类型决定(void *除外),而为已经声明数据类型的数据赋值会报错或是会进行数据类型的转换,如char->int->float->double等。这样定义数据的方式被成为强类型,变量数据类型一经定义,就无法再更改(即使强制类型转换也无法更改变量类型)。这样的好处是,方便代码理解与交互,也方便编译器的运行、编译时快速找到错误提高程序安全性,并减少运行时的开销。

而JavaScript的变量声明为弱类型,也就是在程序中变量可以赋入不同数据类型的值;并且只声明变量,而不用声明变量的数据类型,而该变量暂时的数据类型只由值决定。

变量定义

JavaScript采用var关键字声明变量或定义变量:

var 变量名;

var 变量名 = 值;

未赋值的变量暂时值为 undefined

在JavaScript中,变量名也有具体的要求:

  • 名称可包含字母、数字、下划线和美元符号
  • 名称必须以字母开头
  • 名称也可以 $ 和 _ 开头(但是在本教程中我们不会这么做)
  • 名称对大小写敏感(y 和 Y 是不同的变量)
  • 保留字(比如 JavaScript 的关键词)无法用作变量名称

当然也有一些声明变量的语法糖:

var lastname="Doe", age=30, job="carpenter";
//声明也可横跨多行:

var lastname="Doe",
age=30,
job="carpenter";
//一条语句中声明的多个变量不可以同时赋同一个值:

var x,y,z=1;
//x,y 为 undefined, z 为 1。

在变量重复进行声明中,变量值不会丢失:

var carname="Volvo";
var carname;

变量 carname 的值依然是 "Volvo"

变量提升

var 关键字定义的变量可以在使用后声明,也就是变量可以先使用再声明,称为变量提升。这一有违我们C语言直觉的操作事实上来自于解释器的”声明提升“:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。

但是,初始化变量的值不会提升到最顶部,例如,以下在语句var b=7;之前可以使用b变量,但是不能得到b=7的这一个值。

a=1;
var a;
//在这里的语句b值为undefined
var b=7;

JavaScript数据类型

上面提到,C语言变量的数据类型由强类型的声明决定,JavaScript变量虽然为弱类型,但是也有数据类型的概念,用于定义处理数据的方法等。

JavaScript的数据类型事实上是一种类,可以理解为跟C语言结构体那样的自定义类型。插句题外话,在C++中,结构体内部处理方法也可以自定义,而Class更可以自定义。而JavaScript的数据类型更像是C++的这种”类“。

我们给变量赋值的过程就相当于从类中实例化了一个对象出来,换句话说就是,构造了这个”类“的一个具体例子。

JavaScript有下列具体的数据类型:

  • 数字:小数整数都是一种类型,统一由浮点数表示,还包括了NaN,Infinity,数字还可以用科学计数法。在赋值时不能用引号包围。

  • 布尔(逻辑):true/false,C语言没有布尔类型,C++引入了并与数值有关(true为1,false为0)。JavaScript的布尔型为一种独特的类型。在进行数值转换时有如下规则:

    • 以下都为false:

    • 0 //Number
      NaN //Number
      '' //String
      false //Boolean
      null //Object
      undefined //Undefined
      
    • 任何非0数字转换为布尔值后为true,而0转换为布尔值为false。

    • 任何非空字符串转换为布尔值后为true,而空格转换为布尔值为false。

    • 如果把布尔值转换为字符串,则true为“true”,false为“false”。

    • 如果把布尔值转换为数值,则true为1,false为0。

  • 字符串:用双引号或单引号包围,如果字符串内有双引号,则用单引号来包围。

  • 引用型数据:一般引用特定位置的值,称为引用型。

    • 数组:方括号分隔。在括号内部数组元素以逗号分隔。数组的定义有多种方式,事实上都是语法糖。数组的本质是Array()对象。
    • 对象:花括号分隔。在括号内部,对象的属性以名称和值对的形式 (name : value) 来定义。属性由逗号分隔。这里的对象类型和后续的面向对象的”对象“有相似之处,也有区别之处。主要是声明的字面量的区别。
    • 函数:甚至函数都可以算作一种广义的数据类型,在定义中可以传入匿名函数,也可以定义非匿名函数。这里函数赋值的形式类似于C语言的函数指针赋值的形式。
  • Null:空值,为null,用于定义不存在的引用,也可以用来清除变量的值。如果当一个变量的值为null,则表明它的值不是有效的对象、数组、数值、字符串和布尔型等。事实上是一种特殊的对象。

  • Undefined:未定义值,为undefined,当变量未赋值时,值和类型都为undefined。

var a;									//undefined
var b = null;							  //置空
var length = 7;                             // 数字
var lastName = "Gates";                      // 字符串
var isboy = false;							  //布尔
var cars = ["Porsche", "Volvo", "BMW"];         // 数组
var x = {firstName:"Bill", lastName:"Gates"};    // 对象 
var buycar = function(){};							//函数

数据转换

前面说到,各个数据只是类里的一个实例,那么自然可以调用类里的方法。与C语言里的强制类型转换不同,JavaScript的转换大多数采用调用方法(内置函数)的方式。

如:字符串转换为数值

var str = "123.30";
var a = parseInt(str);//返回数值123
var b = parseFloat(str);//返回数值123.3
//或者采用运算的方法
var a = str * 1;

数值转换为字符串

var a = 100;
var c = a.toString();//转换为字符串
//或者采用运算的方法
var c = a + "";

数值的其他转字符串方法

方法描述
toExponential()返回字符串,对数字进行舍入,并使用指数计数法来写。
toFixed()返回字符串,对数字进行舍入,并使用指定位数的小数来写。
toPrecision()返回字符串,把数字写为指定的长度。
var x = 9.656;
x.toExponential(2);     // 返回 9.66e+0
x.toExponential(4);     // 返回 9.6560e+0
x.toExponential(6);     // 返回 9.656000e+0

var x = 9.656;
x.toFixed(0);           // 返回 10
x.toFixed(2);           // 返回 9.66
x.toFixed(4);           // 返回 9.6560
x.toFixed(6);           // 返回 9.656000

var x = 9.656;
x.toPrecision();        // 返回 9.656
x.toPrecision(2);       // 返回 9.7
x.toPrecision(4);       // 返回 9.656
x.toPrecision(6);       // 返回 9.65600

全局方法进行转换

  • Number() 转换数值
  • String() 转换字符串
  • Boolean() 转换布尔值。

例如:

String(100 + 23)

ES6变量定义与作用域

在ES6中,JavaScript引入了新的定义变量的形式并规定了变量的作用域。

ES6以前的作用域:

局部作用域和全局作用域。

作用域即代码执行过程中的变量、函数或者对象的可访问区域,作用域决定了变量或者其他资源的可见性,函数内部定义的变量从函数外部是不可访问的(不可见的);计算机安全中一条基本原则即是用户只应该访问他们需要的资源,而作用域就是在编程中遵循该原则来保证代码的安全性。除此之外,作用域还能够帮助我们提升代码性能、追踪错误并且修复它们。

JavaScript 中的作用域主要分为全局作用域与局部作用域两大类,在 ES5 中定义在函数内的变量即是属于某个局部作用域,而定义在函数外的变量即是属于全局作用域。变量也因此分为了局部变量和全局变量。局部变量会在函数完成时被删除,函数参数也是函数内的局部变量;全局变量会在关闭页面时被删除。

与C语言不同的是,ES6以前没有块级作用域,定义在块{}内的变量要不被外界访问需要封装为function。

ES6的作用域与新关键字:

ES6引入了块级作用域。块级作用域即是类似于 if、switch 条件选择或者 for、while 这样的循环体{}内的语句块,无大括号的if下辖语句也属于块级作用域。块级作用域内的变量离开语句块即销毁。

由于ES6以前只有var关键字进行变量的定义,因此关于变量的诸多特性依然保留在var关键字下。因此,实现块级作用域的任务交给了新关键字。

const

const 声明一个只读变量(所谓的常量),声明之后不允许通过赋值再次修改(const声明的对象的属性值、列表的值其实可以再次修改)。这意味着,标识符一旦声明必须初始化,否则会报错。

const PI=3.14159

const不存在变量提升,即不能够实现先使用变量后操作;为块级作用域。

其实 const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动。简单类型和复合类型保存值的方式是不同的。对于简单类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,因此 const 声明的简单类型变量等同于常量;而复杂类型(对象 object,数组 array,函数 function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据结构变不变就无法控制了,所以使用 const 声明复杂类型对象时要慎重。

let

let用于替代var实现块级作用域的变量定义,let 声明的变量只在 let 关键字所在的代码块 {}内有效,在 {} 之外不能访问,let的块作用域跟C语言的auto关键字的作用相似。

let不存在变量提升;不能多次定义。

例如for循环的循环变量,需要使用let关键字for(let i=1;i<=10;i++){}才能达到C语言中,for(int i=1;i<=10;i++){}的效果。否则使用var关键字时,出循环体时变量不销毁。

在函数内使用let关键字则变量被声明为局部变量,在函数外为则声明为全局变量的,这一点与var相似。但对于window对象来说,var声明的变量可以作为window的属性而可以被window.xxx引用,而let则不能。

重复声明变量

在相同的作用域或块级作用域中,不能用let关键字重置var关键字定义的变量;同样,var关键字也不能重置let关键字定义的变量;也不能使用 let 关键字来重置 let 关键字声明的变量。但是let 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的。

词法作用域

局部变量缺省情况

词法作用域是 JavaScript 闭包特性的重要保证。一般来说,在编程语言里我们常见的变量作用域就是词法作用域与动态作用域,绝大部分的编程语言都是使用的词法作用域。

词法作用域,或者称之为静态作用域,注重的是所谓的 Write-Time,即编程时的上下文;而动态作用域以及常见的 this 的用法,都是 Run-Time,即运行时上下文。词法作用域关注的是函数在何处被定义,而动态作用域关注的是函数在何处被调用。JavaScript 是典型的词法作用域的语言,即一个符号参照到语境中符号名字出现的地方,局部变量缺省有着词法作用域。

function foo() {
    document.write( a ); // 词法作用域输出2 ,动态作用域输出3
}
function bar() {
	var a = 3;
    foo();
}
var a = 2;
bar();

假设去除var a=2;则输出3。

参考资料:

上次编辑于:
贡献者: XiLaiTL