Skip to main content

基本语法

变量

var

var作用域为函数,如果在函数中定义,在这个函数销毁时,变量也会销毁。

function test(){
var a = 1;
}
console.log(a); // a is not defined

同时也可以允许不使用 var 关键字就在函数中定义变量,这样的变量会被定义为全局变量。

function test(){
a = 1;
}
console.log(a); // 1

var 声明提升

function test(){
console.log(a); // undefined
var a = 1;
}
test(); // undefined

这时 js 会自动把 var a ; 提升到函数的最前面,所以在函数中使用 var 声明变量时,变量会被提升到函数的最前面,但是不会提升赋值。相当于:

function test(){
var a;
console.log(a); // undefined
a = 1;
}
test(); // undefined

let

let 作用域为块级,如果在块级中定义,在这个块级销毁时,变量也会销毁。

if (true) {
let a = 1;
}
console.log(a); // a is not defined

let 关键字不允许重复定义变量,否则会报错。

let a = 1;
let a = 2; // Identifier 'a' has already been declared

且 let 声明的变量不会被提升。

function test(){
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 1;
}
test();

for循环中的声明

在 let 和 var 中,for循环中的声明会有不同的表现。

for (var i = 0; i < 10; i++) {
console.log(i); // 0 1 2 3 4 5 6 7 8 9
}
console.log(i); // 10

在 var 声明时,for循环中的声明会被提升到函数的最前面,所以在循环结束后,i 仍然存在。会渗透到循环外部。

for (let i = 0; i < 10; i++) {
console.log(i); // 0 1 2 3 4 5 6 7 8 9
}
console.log(i); // i is not defined

在 let 声明时,for循环中的声明不会被提升,所以在循环结束后,i 销毁。

在下面的情况中;

for (var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i); // 10 10 10 10 10 10 10 10 10 10
}, 0);
}

由于 setTimeout 是异步函数,所以会在循环结束后执行,所以会打印出 10 个 10。这是由于 for 循环完成后才执行 setTimeout,所以此时 i 已经为 10。但是在 let 中不是这样:

for (let i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i); // 0 1 2 3 4 5 6 7 8 9
}, 0);
}

这是因为 JS 引擎在后台会为每个迭代的声明生成一个新的迭代变量,所以在 setTimeout 中的 i 是一个不同的实例,所以不会渗透到循环外部。

const

const 作用域为块级,如果在块级中定义,在这个块级销毁时,变量也会销毁。

const a = 2;
if (true) {
const a = 1;
}
console.log(a); // 2

const 声明的变量不允许修改,否则会报错。

const a = 1;
a = 2; // Assignment to constant variable.

这也决定了我们不能在循环中使用 const 声明变量。

for (const i = 0; i < 10; i++) {
console.log(i); // TypeError: Assignment to constant variable.
}

最佳实践

不使用 var 声明变量,使用 let 和 const。且 const 优先于 let。

// bad
var a = 1;
var b = 2;
// good
let a = 1;
const b = 2;

数据类型

ECMAScript 6 中有 5 种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number 和 String。还有一种复杂数据类型(也称为引用数据类型):Object。分别表示的是:未定义、空、布尔值、数字和字符串。以及对象。

typeof 特殊情况

调用 typeof null 会返回 "object",因为 null 实际上是一个空对象指针。

typeof null; // object

undefinednull

undefinednull 都表示“没有值”,它们是两个不同的值,undefined 是一个表示“无”的原始值,而 null 是一个表示“空”的对象。但是 undefined 是由 null 派生而来的,所以它们的相等性测试会返回 true

undefined == null; // true

Boolean

Boolean 类型只有两个值:true 和 false。

let a = true;
let b = false;

且它与数值不同。可以使用 Boolean() 函数将任意值转换为布尔值。

Boolean(0); // false
Boolean(1); // true
Boolean(''); // false
Boolean('a'); // true
Boolean(null); // false
Boolean(undefined); // false
数据类型转换为 true 的值转换为 false 的值
Booleantruefalse
String任何非空字符串""(空字符串)
Number任何非零数字值(包括无穷大)0 和 NaN
Object任何对象null
Undefinedn/a(不存在)undefined

Number

Number 类型除了十进制外,还可以使用八进制和十六进制。

let a = 10; // 十进制
let b = 010; // 八进制
let c = 0x10; // 十六进制

浮点数:

let a = 0.1;
let b = 0.2;
let c = .3; // 可行,但不推荐

对于非常大或者非常小的数值,可以使用科学计数法。

let a = 5e2; // 500
let b = 5e-2; // 0.05

浮点数的精确度可达 17 位小数。但是,由于浮点数的存储方式,会导致一些小数无法精确表示。

0.1 + 0.2; // 0.30000000000000004

这也就告诉我们,不能这样干:

if (a + b === 0.3) {
console.log('a + b 等于 0.3');
} else {
console.log('a + b 不等于 0.3');
}

上面的代码可能会有不确定性的输出,如果 a =0.05 和 b = 0.25,那么 a + b 就等于 0.3。但是如果 a = 0.1 和 b = 0.2,那么 a + b 就不等于 0.3 了。

JavaScript 中的数值是有范围的,通常最大值是 2^53 - 1,最小值是 -2^53 + 1。

Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324

如果超出了这个范围,就会返回 Infinity(正无穷)或者 -Infinity(负无穷)。

Number.MAX_VALUE + 1; // Infinity
Number.MIN_VALUE - 1; // -Infinity

如果分母为 0,则会返回 Infinity 或者 -Infinity。

let a = 1 / 0; // Infinity
let b = -1 / 0; // -Infinity

还有个特殊的数值 NaN,表示“非数值”。

let a = 1 / 'a'; // NaN

涉及到 NaN 的运算,结果都是 NaN

NaN + 1; // NaN
NaN - 1; // NaN
NaN * 1; // NaN
NaN == NaN; // false,这一点要特别注意

为此,ES6 提供了 Number.isNaN() 函数,用来判断一个值是否为 NaN。

isNaN(NaN); // true
isNaN(1); // false
isNaN('a'); // true

isNaN会先调用 ValueOf() 方法,判断能不能返回为数字,如果不能,就会调用 toString() 方法,再测试其返回值。

数值转换

有三个函数可以将非数值转为数值:Number()parseInt()parseFloat()Number 函数可以将任意类型的值转为数值,而 parseInt()parseFloat() 只能转换字符串。

Number('123'); // 123
Number('123abc'); // NaN
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(""); // 0
Number(undefined); // NaN
Number({}); // NaN
Number([]); // 0
Number([1, 2, 3]); // NaN
parseInt('123abc'); // 123
parseInt('abc123'); // NaN
parseFloat('123.456abc'); // 123.456
parseFloat('abc123.456'); // NaN
parseFloat('123.456.789'); // 123.456

String

String 类型表示零或多个 16 位 Unicode 字符组成的字符序列。

let a = 'abc';
let b = "abc";
let c = `abc`;

有一些字符字面量,可以直接表示特殊字符。

'\n'; // 换行
'\t'; // 制表符
'\b'; // 退格
'\r'; // 回车
'\f'; // 换页
'\\'; // 反斜杠
'\''; // 单引号
'\"'; // 双引号
'\xnn'; // 16 进制,nn 为两位 16 进制数
'\unnnn'; // 16 位 Unicode 字符,nnnn 为四位 16 进制数

字符串的长度可以通过 length 属性获取。

let a = 'abc';
a.length; // 3

字符串中值是不可变的,如果要改变字符串,只能重新赋值。

let a = 'abc';
a = 'def'; // 'def'
let b = 'abc';
a+b; // 'defabc',虽然 a 和 b 的值没有改变,但是可以通过 + 运算符连接两个字符串,实际上是生成了一个新的字符串

这有一些转换为字符串的实例。

let a = 123;
a.toString(); // '123'
a.toString(2); // '1111011'
a.toString(8); // '173'
a.toString(16); // '7b'

ECMAScript 6 新增了一种模板字符串,用反引号表示。

let a = 5;
let b = 10;
`Fifteen is ${a + b} and not ${2 * a + b}.`; // "Fifteen is 15 and not 20."

同时也可以在里面书写 HTML 代码。换行会被保留。

let a = `
<ul>
<li>first</li>
<li>second</li>
</ul>
`;

字符串中可以进行插值,用 ${} 表示。

let a = 5;
let b = 10;
`Fifteen is ${a + b} and not ${2 * a + b}.`; // "Fifteen is 15 and not 20."

Symbol

Symbol 是 ECMAScript 6 引入的一种新的原始数据类型,表示独一无二的值。可以理解为一种记号。

let a = Symbol('a');
let b = Symbol('b');
a === b; // false

Object

Object 是 ECMAScript 6 引入的一种新的原始数据类型,表示一组数据的集合。

let a = {
name: 'a',
age: 18
};

每个 Object 实例有以下的属性和方法。

属性描述
constructor返回对创建此对象的 Object 函数的引用
hasOwnProperty(propertyName)用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在
isPrototypeOf(object)用于检查传入的对象是否是传入对象的原型
propertyIsEnumerable(propertyName)用于检查给定的属性是否能够使用 for-in 语句进行枚举
toLocaleString()返回对象的字符串表示,该字符串与执行环境的地区对应
toString()返回对象的字符串表示
valueOf()返回对象的字符串、数值或布尔值表示