语言基础(基于ES6)(1)

309 阅读1分钟

目录

  • 语法
  • 数据类型
  • 流控制语句
  • 理解函数

3.1 语法

ECMAScript的语法较为宽松。

3.1.1 区分大小写

ECMAScript中一切都区分大小写,无论是变量、函数名还是操作符。换句话说,变量test和变量Test是两个不同的变量。

3.1.2 标识符

所谓标识符,就是变量、函数、属性或函数参数的名称。第一个字符必须是一个字母、下划线(_)或美元符号($),剩下的其他字符可以是字母、下划线、美元符号或数字。ECMAScript标识符使用驼峰大小写形式。关键字、保留字、true、false和null不能作为标识符。

3.1.3 注释

单行注释以两个斜杠字符(//)开头,块注释以一个斜杠和一个星号(/*)开头,以它们的反向组合(*/)结尾。

3.1.4 严格模式

ECMAScript 5增加了严格模式(strict mode)的概念。要对整个脚本启用严格模式,在脚本开头加上这一行"use strict";也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可。

3.1.5 语句

ECMAScript中的语句以分号结尾。即使语句末尾的分号不是必需的,也应该加上。
在if之类的控制语句中,最佳实践是始终在控制语句中使用代码块,即使要执行的只有一条语句。

3.2 关键字与保留字

按照规定,保留的关键字不能用作标识符或属性名。

3.3 变量

每个变量只不过是一个用于保存任意值的命名占位符。有3个关键字可以声明变量:var、const和let。其中,var在ECMAScript的所有版本中都可以使用,而const和let只能在ECMAScript 6及更晚的版本中使用。

3.3.1 var关键字

1. var声明作用域

使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁。不过,在函数内定义变量时省略var操作符,可以创建一个全局变量,但是不推荐。

2. var声明提升

使用var时,下面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部:

funtion foo() {
console.log(age);
age = 29;
}
foo();  //undefined

这就是所谓的“提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次使用var声明同一个变量也没有问题。

3.3.2 let声明

最明显的区别是,let声明的范围是块作用域,而var声明的范围是函数作用域。块作用域是函数作用域的子集,因此适用于var的作用域限制同样也适用于let。let也不允许同一个块作用域中出现冗余声明。
当然,JavaScript引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错,而这是因为同一个块中没有重复声明。对声明冗余报错不会因混用let和var而受影响。

1. 暂时性死区

在解析代码时,JavaScript引擎也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError。

2. 全局声明

与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量则会)。不过,let声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了避免SyntaxError,必须确保页面不会重复声明同一个变量。

3. 条件声明

在使用var声明变量时,由于声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。因为let的作用域是块,所以不可能检查前面是否已经使用let声明过同名变量,同时也就不可能在没有声明的情况下声明它。使用try/catch语句或typeof操作符也不能解决,因为条件块中let声明的作用域仅限于该块。对于let这个新的ES6声明关键字,不能依赖条件声明模式。

4. for循环中的let声明

在let出现之前,for循环定义的迭代变量会渗透到循环体外部。而在使用let声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量。这种每次迭代声明一个独立变量实例的行为适用于所有风格的for循环,包括for-in和for-of循环。

3.3.3 const声明

const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。但如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制。
虽然const变量跟let变量很相似,但是不能用const来声明迭代变量(因为迭代变量会自增)。不过,如果你只想用const声明一个不会被修改的for循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对for-of和for-in循环特别有意义。

3.3.4 声明风格及最佳实践

1.不使用var。有了let和const,大多数开发者会发现自己不再需要var了。限制自己只使用let和const有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。
2.const优先,let次之。使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用const来声明变量,只在提前知道未来会有修改时,再使用let。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。

3.4 数据类型

ECMAScript有6种简单数据类型(也称为原始类型): Undefined、Null、Boolean、Number、String和Symbol。Symbol(符号)是ECMAScript 6新增的。还有一种复杂数据类型叫Object(对象)。

3.4.1 typeof操作符

可用来确定任意变量的数据类型。调用typeof null返回的是"object"。这是因为特殊值null被认为是一个对空对象的引用。

3.4.2 Undefined类型

当使用var或let声明了变量但没有初始化时,就相当于给变量赋予了undefined值。一般来说,永远不用显式地给某个变量设置undefined值。
无论是声明还是未声明,typeof返回的都是字符串"undefined"。
undefined是一个假值。因此,如果需要,可以用更简洁的方式检测它。不过要记住,也有很多其他可能的值同样是假值。所以一定要明确自己想检测的就是undefined这个字面值,而不仅仅是假值。

3.4.3 Null类型

Null类型同样只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针,这也是给typeof传一个null会返回"object"的原因。在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值。任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用null来填充该变量。这样就可以保持null是空对象指针的语义,并进一步将其与undefined区分开来。

3.4.4 Boolean类型

image.png 理解以上转换非常重要,因为像if等流控制语句会自动执行其他类型值到布尔值的转换。

3.4.5 Number类型

Number类型使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值)。最基本的数值字面量格式是十进制整数,也可以用八进制(以8为基数)或十六进制(以16为基数)字面量表示。
由于JavaScript保存数值的方式,实际中可能存在正零(+0)和负零(-0)。正零和负零在所有情况下都被认为是等同的。

1. 浮点值

要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字。因为存储浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换为整数。对于非常大或非常小的数值,浮点值可以用科学记数法来表示。浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确,因此永远不要测试某个特定的浮点值。

2. 值的范围

由于内存限制,ECMAScript可以表示的最小数值保存在Number.MIN_VALUE中,这个值在多数浏览器中是5e-324;可以表示的最大数值保存在Number.MAX_VALUE中,这个值在多数浏览器中是1.797693134862315 7e+308。如果某个计算得到的数值结果超出了JavaScript可以表示的范围,那么这个数值会被自动转换为一个特殊的Infinity(无穷)值。
要确定一个值是不是有限大(即介于JavaScript能表示的最小值和最大值之间),可以使用isFinite()函数。

3. NaN

用于表示本来要返回数值的操作失败了(而不是抛出错误),意思是not a number。
NaN有几个独特的属性。首先,任何涉及NaN的操作始终返回NaN(如NaN/10),在连续多步计算时这可能是个问题。其次,NaN不等于包括NaN在内的任何值。
把一个值传给isNaN()后,该函数会尝试把它转换为数值, 任何不能转换为数值的值都会导致这个函数返回true。

4. 数值转换

有3个函数可以将非数值转换为数值:Number()、parseInt()和parseFloat()。
Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。
ParseInt中因为不传底数参数相当于让parseInt()自己决定如何解析,所以为避免解析出错,建议始终传给它第二个参数。
因为parseFloat()只解析十进制值,因此不能指定底数,且第二次出现的小数点无效,如果字符串表示整数(没有小数点或者小数点后面只有一个零),则parseFloat()返回整数。

3.4.6 String类型

字符串可以使用双引号(")、单引号(')或反引号标示。

1. 字符字面量

如\n, \t, \b等。字符串的长度可以通过其length属性获取。

2. 字符串的特点

ECMAScript中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。

3. 转换为字符串

有两种方式把一个值转换为字符串。首先是使用几乎所有值都有的toString()方法。这个方法唯一的用途就是返回当前值的字符串等价物。null和undefined值没有toString()方法。如果你不确定一个值是不是null或undefined,可以使用String()转型函数,它始终会返回表示相应类型值的字符串。

4. 模板字面量

与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串。

5. 字符串插值

字符串插值通过在${}中使用一个JavaScript表达式实现,任何JavaScript表达式都可以实现。

3.4.7 Symbol类型

符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

1. 符号的基本用法

符号需要使用Symbol()函数初始化。因为符号本身是原始类型,所以typeof操作符对符号返回symbol。调用Symbol()函数时,也可以传入一个字符串参数作为对符号的描述(description),将来可以通过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关。

3.4.8 Object类型

开发者可以通过创建Object类型的实例来创建自己的对象,然后再给对象添加属性和方法。
每个Object实例都有如下属性和方法: constructor,hasOwnProperty(propertyName), isPrototypeOf(object)propertyIsEnumerable(propertyName),toLocaleString(),toString(),valueOf()。因为在ECMAScript中Object是所有对象的基类,所以任何对象都有这些属性和方法。