语言基础
语法
语法方面可以参考C语言。
区分大小写
ECMAScript中的一切都区分大小写。
标识符
即变量、函数、属性或者函数参数的名称。
标识符可以由一个或者多个下列字符组成:
- 第一个字符必须是一个字母、下划线
_
或者美元符号$
; - 剩下的字符可以是字母、下划线、美元符号或者数字。
驼峰命名法:是一种惯例,不做强制要求。即第一个单词的首字母小写,后面每个单词的首字母大写。例如:firstSecond myCar doSomething
注释
//这是单行注释
/*这是
多行
注释*/
严格模式
严格模式是一种不同的JS解析和执行模型。ES3中的一些不规范写法在严格模式下会被处理,对于不安全的活动将抛出错误。
要对整个脚本启用严格模式,在脚本开头加上这一行:"use strict";
这看起来是一个字符串,但是其实是一个预处理指令。
也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:
function doSomething(){
"use strict";
//函数体
}
语句
ES中的语句以分号结尾。省略分号意味着由解析器来确定语句在哪里结尾。
let sum = a + b //没有分号能正确执行
let diff = a - b;//推荐加分号
多条相关语句可以合并到一个C语言风格的代码块中,代码块由一个左花括号{
开始,一个右花括号}
结束。
if(isTrue){
isTrue = false;
console.log(isTrue);
}
关键字和保留字
关键字有特殊的用途,比如表示语句的开始和结束,或者执行特定的操作。按照规定,保留的关键字不能用作标识符或者属性名。
常见的关键字有:
break do if else switch case while
等等。
还有一些保留字,虽然在语言中没有特定的用途,但是是保留给将来做关键字用的嗯,所以保留字同样不能用作标识符或属性名。
变量
ES变量是松散类型的,即变量可以保存任何类型的数据。每个变量只不过是用于保存任意值的命名占位符。
声明变量的关键字(3个):
var
ES中的任何版本都有效;let const
ES6及后面的版本有效;
Var 关键字
声明语法:var 标识符
,例如:var message
即声明了一个名为message的变量。 可以用该变量保存任何类型的值,不初始化该变量的情况下,变量会保存一个特殊值 undefined
变量初始化:定义变量的同时设置它的值。
var message = "hi";
上面的操作只是将message定义为一个保存字符串"hi"的变量,像这样初始化变量不会将它标识成字符串类型,只是一个简单为赋值而已。随后,不仅可以改变变量的值,还可以改变值的类型。(一般不推荐改变变量值的类型)
var声明作用域
使用var定义的变量会成为包含它的函数的局部变量。 比如,在一个函数内部使用var定义一个变量,就意味着该变量在函数退出时被销毁。
function test(){
var msg = "hello";//局部变量
}
test();//执行函数
console.log(msg);//出错
但是在函数内部定义变量时省略var操作符,则可以创建一个全局变量。
function test(){
msg = "hi";
}
test();
console.log(msg);//"hi"
不推荐在局部作用域中定义全局变量,因为这样会导致变量很难维护。
在严格模式下,给未声明的变量赋值,则会抛出ReferenceError。
一次定义多个变量,可以在一条语句中用逗号隔开每个变量。
var msg = "hi",
age = 19,
isTrue = false;
var声明提升
提升(hoist):就是把所有变量的声明都拉到函数作用域的顶部。
function foo(){
console.log(age);
var age = 28;
}
foo();//"undefined"
之所以不报错,就是因为变量提升了,ES运行时将上面的代码等价成下面的代码:
function foo(){
var age;//变量未初始化,自动赋值undefined
console.log(age);//所以会输出undefined
age = 28;
}
除此之外,var关键字还有一个特性就是,可以多次声明同一个变量。
var age = 18;
var age = 28;
var age = 38;
console.log(age);//38
let声明
let和var的作用差不多。但是却有很大的区别。
作用域
let声明的范围是 块级作用域
var声明的范围是 函数作用域
块级作用域,即一对花括号{}
内部范围。
if(true){
var name = "Maccx";//if不是函数,所以在里面定义的变量,在if外面也可以访问
console.log(name);//"Maccx"
}
console.log(name);//"Maccx"
if(true){
let age = 28;
console.log(age);//28
}
console.log(age);//ReferenceError:age undefined
上面的代码中,age变量之所以不能在if外部引用,就是因为它的作用域仅限于该块内部。
块作用域是函数作用域的子集,所以适用于var的作用域限制同样适用于let。
重复声明
let不允许在同一个块作用域中重复声明同一变量。
var name;
var name;
let age;
let age;//SyntaxError;
JS引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错。
if(true){
let age = 28;
console.log(age);//28
if(true){
let age = 38;
console.log(age);//38
}
}
对声明冗余报错不受混用let和var影响。这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关的作用域中如何存在。
暂时性死区
let与var的另一个区别就是,let声明的变量不会在作用域中被提升。
//name 被提升
console.log(name);//"undefined"
var name = "Maccx";
//age 不会被提升
console.log(age);//ReferenceError
let age = 28;
在解析代码的时候,JS引擎也会注意到出现在块后面的let声明,只不过在此之前不能以任何方式来引用声明的变量。在let声明之前的执行瞬间被称为“暂时性死区”。
全局声明
使用let在全局作用域中声明的变量不会成为window对象的属性(也就是全局变量,var声明的变量则会。)
var name = 'Maccx';
console.log(window.name);//"Maccx"
let age = 28;
console.log(window.age);//undefined
不过,let声明仍然是在全局作用域中发生的,相应的变量会在页面的声明周期内存续。因此,必须确保页面不会重复声明同一个变量。
条件声明
使用var声明变量时,由于变量提升,JS引擎会自动将多余的声明在作用域的顶部合并为一个声明。但是let的作用域是块,所以不可能检查前面是否已经使用let声明过同名变量,同时也就不可能在没有声明的情况下声明它。
啥意思呢,我理解的就是,当页面不确定页面中的某个变量是否已经声明同名变量的时候,就假设还没有声明过。然后此时如果是使用var进行变量声明则没问题,会被合并掉,但是如果是使用let则会报错。
<script>
var name = "Maccx";
let age = 28;
</script>
<script>
var name = "Mac";//这里没有问题
let age = 18;//这里则会报错
</script>
即使使用try/catch语句或者typeof操作符也不能解决问题,因为条件块中的let声明的作用域仅限于该块。
意思就是,假如你不确定某个变量是否已经声明,然后你想通过try/catch语句或者typeof操作符来实现:如果这个变量未声明,则声明它,使用let关键字是实现不了的。
for循环中的let声明
在let出现前,for循环定义的迭代变量会渗透到循环体外部。
改成let之后,这个问题就消失了,因为迭代变量的作用域仅限于for循环内部。
for(var i = 0; i < 10; i++){
//循环体
}
console.log(i);//10
for(let i = 0; i < 10; i++){
//循环体
}
console.log(i);//ReferenceError: i undefined
这里就常用的实例就是 setTimeout()
for(var i = 0; i < 5; i++){
setTimeout(() => console.log(i),0);
}
//设想中输出:0,1,2,3,4
//实际输出:5,5,5,5,5
原因就是,迭代变量渗透到循环体外面,在之后执行的超时逻辑时,所有的i都是同一个变量,因而输出的都是同一个值。
然而如果改成使用let声明迭代变量,JS引擎在后台为每个迭代循环声明一个新的迭代变量,每个setTimeout()引用的都是不同的变量实例,所以console出了我们期望的值。
for(let i = 0; i < 5; i++){
setTimeout(() => console.log(i),0);
}
//输出:0,1,2,3,4
这一点特性适用于所有风格的for循环,包括for-in、for-of
const声明
const的行为基本上与let相同,唯一重要的区别就是使用const声明变量时必须同时初始化变量,尝试修改const声明的变量会导致运行抛错。
const age = 18;
age = 19;//TypeError
//不允许重复声明
const name = "Maccx";
const name = "Mac";//SyntacError
//作用域也是块级作用域
if(true){
const name = "maccx";
}
console.log(name);//ReferenceError
还有一点很重要,const声明的限制只适用于它指向的变量的引用。 即如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制。
const person = {};
person.age = 18;//这个是可以的
声明风格和最佳实践
- 不使用var,限制自己只使用let和const有助于提升代码质量,因为变量有了明确的作用域,声明位置以及不变的值。
- const优先,let次之。能迅速发现因为意外赋值导致的非预期行为。