js语法基础-基础语法和变量声明

432 阅读9分钟

ECMA-262第5版(ES5)定义的ECMAScript,是目前为止实现得最为广泛(即受浏览器支持最好)的一个版本。第6版(ES6)在浏览器中的实现(即受支持)程度次之。到2017年底,大多数主流浏览器几乎或全部实现了这一版的规范。接下来的内容主要基于ECMAScript第6版。

1 语法

1.1 区分大小写

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

1.2 标识符

标识符,就是变量、函数、属性或函数参数的名称。标识符可以由一或多个下列字符组成:

  1. 第一个字符必须是一个字母、下划线(_)或美元符号($);
  2. 剩下的其他字符可以是字母、下划线、美元符号或数字。

习惯上,ECMAScript标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写

注意 关键字、保留字、true、false和null不能作为标识符。

1.3 注释

ECMAScript采用C语言风格的注释,包括单行注释和块注释。单行注释以两个斜杠字符开头

// 单行注释

块注释以一个斜杠和一个星号(/)开头,以它们的反向组合(/)结尾

/*
多行注释
*/

1.4 严格模式

ECMAScript 5增加了严格模式(strict mode)的概念。严格模式是一种不同的JavaScript解析和执行模型,ECMAScript 3的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。要对整个脚本启用严格模式,在脚本开头加上这一行:

"use strict";

虽然看起来像个没有赋值给任何变量的字符串,但它其实是一个预处理指令。任何支持的JavaScript引擎看到它都会切换到严格模式。选择这种语法形式的目的是不破坏ECMAScript 3语法。

也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:

function doSomething() {
  "use strict";
  // function body (函数体)
}

所有现代浏览器都支持严格模式。

1.5 分号结尾

ECMAScript中的语句以分号结尾。省略分号意味着由解析器确定语句在哪里结尾

let sum = a + b      //没有分号也有效,但不推荐
let diff = a - b;    //加分号有效,推荐

即使语句末尾的分号不是必需的,也应该加上。记着加分号有助于防止省略造成的问题,比如可以避免输入内容不完整。此外,加分号也便于开发者通过删除空行来压缩代码(如果没有结尾的分号,只删除空行,则会导致语法错误)。加分号也有助于在某些情况下提升性能,因为解析器会尝试在合适的位置补上分号以纠正语法错误。

1.6 代码块

多条语句可以合并到一个C语言风格的代码块中。代码块由一个左花括号({)标识开始,一个右花括号(})标识结束:

let test = true;
if (test) {
  test = false;
  console.log(test);
}

1.7 关键字与保留字

按照规定,保留的关键字不能用作标识符或属性名。ECMA-262第6版规定的所有关键字如下:

break       do          in            typeof
case        else        instanceof    var
catch       export      new           void
class       extends     return        while
const       finally     super         with
continue    for         switch        yield
debugger    function    this
default     if          throw
delete      import      try

保留的关键字不能用作标识符或属性名

ECMA-262第6版为将来保留的所有词汇:

始终保留:
enum

严格模式下保留:

implements  package     public
interface   protected   static
let         private

模块代码中保留:

await

这些词汇不能用作标识符,但现在还可以用作对象的属性名。一般来说,最好还是不要使用关键字和保留字作为标识符和属性名,以确保兼容过去和未来的ECMAScript版

2 变量

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

2.1 var关键字

var a;
var b = "hello world"

这行代码定义了一个名为a的变量,可以用它保存任何类型的值。(不初始化的情况下,变量会保存一个特殊值undefined,下一节讨论数据类型时会谈到。)ECMAScript实现变量初始化,因此可以同时定义变量并设置它的值,比如上面的b变量就被初始化为"hello world"

2.1.1 不推荐改变变量保存值的类型

var a = "hello";
a = 100;

变量a首先被定义为一个保存字符串值hello的变量,然后又被重写为保存了数值100。虽然不推荐改变变量保存值的类型,但这在ECMAScript中是完全有效的。

2.1.2 var声明作用域

  1. 使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:
function fun (){
    // 局部变量
    // 使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:
    var a = 100;
}
fun()
// 报错 (a is not defined),此时出了函数作用域,a这个变量已经被销毁
console.log(a); 
  1. 不使用var在一个函数内部定义一个变量,就意味着该变量是一个全局变量
<script>
    function fun (){
        // 全局变量
        a = 100;
    }
    fun()
    // 此时就不会报错,但需要注意的是,需要在输出之前,调用一下fun这个函数哦
    console.log(a) 
</script>

去掉之前的var操作符之后,a就变成了全局变量。只要调用一次函数fun(),就会定义这个变量,并且可以在函数外部访问到。

tips: 不推荐这么定义全局变量

tips: 在严格模式下,这种方式(给未声明的变量赋值),会抛出错误:Uncaught ReferenceError: a is not defined

2.1.3 var声明提升

function fun (){
    console.log(a)
    var a = 100;
}
fun();

输出:undefined

之所以不会报错,为使用这个关键字声明的变量会自动提升到函数作用域顶部,是因为ECMAScript运行时把它看成等价于如下代码:

function fun (){
     var a;
    console.log(a);
    a = 100;
}
fun();

2.2 let关键字声明(和var的区别)

let跟var的作用差不多,但有着非常重要的区别。

  1. let声明的范围是块作用域,而var声明的范围是函数作用域。
if (true) {
    var name = "hello";
    console.log(name); // hello
}
// var 声明的变量,出了代码块还是有效的
console.log(name); // hello

if (true) {
    let age = 26;
    console.log(age); // 26
}
// let 声明的变量,出了代码块就会被销毁
console.log(age); // ReferenceError: age没有定义

在这里,age变量之所以不能在if块外部被引用,是因为它的作用域仅限于该块内部。块作用域是函数作用域的子集, 因此适用于var的作用域限制同样也适用于let。

  1. let也不允许同一个块作用域中出现冗余声明,var是允许的。这样会导致报错:
var a = 100;
var a = 2000;

let b = 100;
// SyntaxError: Identifier 'b' has already been declared
let b = 2000;
var name;
let name; // SyntaxError

let age;
var age; // SyntaxError
  1. let声明的变量不会在作用域中被提升。(“暂时性死区”(temporal dead zone))
console.log(a);
//  ReferenceError: Cannot access 'a' before initialization
let a = "t-mac"

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

  1. 全局声明

与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量则会)。

<script>
  var a = "var声明的变量会成为window对象的属性";

  let b = "let声明的变量会成为window对象的属性";

  // 输出:var声明的变量会成为window对象的属性
  console.log(window.a);
  // 输出:undefined
  console.log(window.b);
</script>
  1. for循环中的let声明

在let出现之前,for循环定义的迭代变量会渗透到循环体外部:

var b = [1, 2, 3, 4];
    for (var i = 0; i < b.length; i++) {
    console.log(i + ":", b[i]);
}
console.log(i); // 4 

改用let就不会这样了

var b = [1, 2, 3, 4];
    for (let j = 0; j < b.length; j++) {
    console.log(j + ":", b[j]);
}
console.log(j); // ReferenceError: j is not defined

2.3 const声明

const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。

const age = 26;
age = 36; // TypeError:给常量赋值
// const也不允许重复声明
const name = 'Matt';
const name = 'Nicholas'; // SyntaxError

const声明的作用域也是块

const name = "t-mac";
if(true){
    const name = "kobe";
}
console.log(name) // t-mac

const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制。

2.4 var ,let ,const如何选择

  1. 不使用var 有了let和const,大多数开发者会发现自己不再需要var了。限制自己只使用let和const有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。

  2. const优先,let次之

使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用const来声明变量,只在提前知道未来会有修改时,再使用let。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。