前言
本系列是之前的学习积累,主要针对JS教程入门时进行学习的记录整理。使用到的参考资料有菜鸟教程、阮一峰JS入门教程系列、网道等多个网站。内容主要基于 ECMAScript 5.1 版本,这是学习 JavaScript 语法的基础。
简介
关于JS的基本介绍
JavaScript 是一种轻量级的脚本语言。所谓“脚本语言”(script language),指的是它不具备开发操作系统的能力,而是只用来编写控制其他大型应用程序(比如浏览器)的“脚本”。
JavaScript 也是一种嵌入式(embedded)语言。它本身提供的核心语法不算很多,只能用来做一些数学和逻辑运算。JavaScript 本身不提供任何与 I/O(输入/输出)相关的 API,都要靠宿主环境(host)提供,所以 JavaScript 只合适嵌入更大型的应用程序环境,去调用宿主环境提供的底层 API。
目前,已经嵌入 JavaScript 的宿主环境有多种,最常见的环境就是浏览器,另外还有服务器环境,也就是 Node 项目。(现在部分前后端开发结合性的基础)
例如:PhoneGap 项目就是将 JavaScript 和 HTML5 打包在一个容器之中,使得它能同时在 iOS 和安卓上运行。Facebook 公司的 React Native 项目则是将 JavaScript 写的组件,编译成原生组件,从而使它们具备优秀的性能。
JavaScript 的核心语法部分相当精简,只包括两个部分:基本的语法构造(比如操作符、控制结构、语句)和标准库(就是一系列具有各种功能的对象比如Array、Date、Math等)。
以浏览器为例,它提供的额外 API 可以分成三大类:
- 浏览器控制类:操作浏览器
- DOM 类:操作网页的各种元素(后期可以结合DOM设计艺术进行专门学习记录)
- Web 类:实现互联网的各种功能
宿主环境是服务器,则会提供各种操作系统的 API,比如文件操作 API、网络通信 API等等。这些你都可以在 Node 环境中找到。
著名程序员 Jeff Atwood 甚至提出了一条 “Atwood 定律”:“所有可以用 JavaScript 编写的程序,最终都会出现 JavaScript 的版本。”(Any application that can be written in JavaScript will eventually be written in JavaScript.)
基本语法
基本语法较为简单,简单过一遍。有点语言基础的入门还是很快的。JS也是因此获得众多好评。
1.语句
JavaScript 程序的执行单位为行(line),也就是一行一行地执行。一般情况下,每一行就是一个语句。以分号结尾。
var a = 1 + 3 ; var b = 'abc';
以下也都是语句(虽然不具备任何意义):
;;;
1 + 3;
'abc';
2.变量
变量是对“值”的具名引用。变量就是为“值”起名,然后引用这个名字,就等同于引用这个值。变量的名字就是变量名。
JavaScript 是一种动态类型语言,也就是说,变量的类型没有限制,变量可以随时更改类型。
1.
var a = 1;
// 基本等同
a = 1;
2.
//只是声明变量而没有赋值
var a;
a // undefined
3.
//直接使用而没有声明,会报错。
x
// ReferenceError: x is not defined
4.
a = 'hello';//变量a已经存在,类型转换无限制
5.
var x = 1;
var x = 2;//第二次声明无效,赋值有效
// 等同于
var x = 1;
var x;
x = 2;
变量提升
JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。
console.log(a);
var a = 1;
实际上不会报错。因为存在变量提升,真正运行的是下面的代码。
var a;
console.log(a);
a = 1;
//=>undefined,表示变量a已声明,但还未赋值
3.标识符
标识符(identifier)指的是用来识别各种值的合法名称。最常见的标识符就是变量名,以及后面要提到的函数名。JavaScript 语言的标识符对大小写敏感。
标识符命名规则如下。
- 第一个字符,可以是任意 Unicode 字母(包括英文字母和其他语言的字母),以及美元符号($)和下划线(_)。
- 第二个字符及后面的字符,除了 Unicode 字母、美元符号和下划线,还可以用数字0-9。
1a // 第一个字符不能是数字
23 // 同上
*** // 标识符不能包含星号
a+b // 标识符不能包含加号
-d // 标识符不能包含减号或连词线
中文是合法的标识符,可以用作变量名,惊呆了吧小伙伴?!
var 临时变量 = 1;
4.注释
- 单行注释,用//起头
- 多行注释,放在/和/之间。
- JavaScript 可以兼容 HTML 代码的注释,所以也被视为合法的单行注释。
5.区块
JavaScript 使用大括号,将多个相关的语句组合在一起,称为“区块”(block)。
特别的是对于var命令来说,JavaScript 的区块不构成单独的作用域(scope)。
{
var a = 1;
}
a // 1
单独使用区块并不常见,区块往往用来构成其他更复杂的语法结构,比如for、if、while、function等
6.条件语句
if语句
if (布尔值)
语句;
// 或者
if (布尔值) 语句;
注意在判断时优先采用“严格相等运算符”(===),而不是“相等运算符”(==)
if (m === 0) {
// ...
} else if (m === 1) {
// ...
} else if (m === 2) {
// ...
} else {
// ...
}
switch语句
switch (fruit) {
case "banana":
// ...
break;
case "apple":
// ...
break;
default:
// ...
}
三元运算符
(条件) ? 表达式1 : 表达式2
7.循环语句
while (条件) {
语句;
}
for (初始化表达式; 条件; 递增表达式)
语句
// 或者
for (初始化表达式; 条件; 递增表达式) {
语句
}
do
语句
while (条件);
// 或者
do {
语句
} while (条件);
支持break、continue
8.标签
标签可以是任意的标识符,但不能是保留字,语句部分可以是任意语句。
label:
语句
top:
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (i === 1 && j === 1) break top;
console.log('i=' + i + ', j=' + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
break命令后面加上了top标签(注意,top不用加引号),满足条件时,直接跳出双层循环。如果break语句后面不使用标签,则只能跳出内层循环,进入下一次的外层循环。同样支持break top或者continue top
数据类型一
1.1概述
JavaScript 的数据类型,共有六种。(ES6 又新增了第七种 Symbol 类型的值)
- 数值(number):整数和小数(比如1和3.14)
- 字符串(string):文本(比如Hello World)。
- 布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)
- undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
- null:表示空值,即此处的值为空。
- 对象(object):各种值组成的集合。
对象是最复杂的数据类型,又可以分成三个子类型。
- 狭义的对象(object)
- 数组(array)
- 函数(function)
数值、字符串、布尔值这三种类型,合称为原始类型(primitive type)的值,即它们是最基本的数据类型,不能再细分了。对象则称为合成类型(complex type)的值,因为一个对象往往是多个原始类型的值的合成,可以看作是一个存放各种值的容器。至于undefined和null,一般将它们看成两个特殊值。
1.2typeof运算符
JavaScript 有三种方法,可以确定一个值到底是什么类型:
- typeof运算符:可以返回一个值的数据类型。
- instanceof运算符
- Object.prototype.toString方法
// 正确的写法
if (typeof v === "undefined") {
// ...
}
typeof window // "object"
typeof {} // "object"
typeof [] // "object",但是instanceof运算符可以区分数组和对象
typeof null // "object",这是由于历史原因造成的
2.1null和undefined
JavaScript 的设计者 Brendan Eich设计初衷区别是这样的:null是一个表示“空”的对象,转为数值时为0;undefined是一个表示"此处无定义"的原始值,转为数值时为NaN。
对于null和undefined,大致可以像下面这样理解:
- null表示空值,即该处的值现在为空。调用函数时,某个参数未设置任何值,这时就可以传入null,表示该参数为空。比如,某个函数接受引擎抛出的错误作为参数,如果运行过程中未出错,那么这个参数就会传入null,表示未发生错误。
- undefined表示“未定义”
// 变量声明了,但没有赋值
var i;
i // undefined
// 调用函数时,应该提供的参数没有提供,该参数等于 undefined
function f(x) {
return x;
}
f() // undefined
// 对象没有赋值的属性
var o = new Object();
o.p // undefined
// 函数没有返回值时,默认返回 undefined
function f() {}
f() // undefined
2.2布尔值
- 前置逻辑运算符: ! (Not)
- 相等运算符:===,!==,==,!=
- 比较运算符:>,>=,<,<=
转换规则是除了下面六个值被转为false,其他值都视为true。
undefined
null
false
0
NaN
""或''(空字符串)
注意,空数组([])和空对象({})对应的布尔值,都是true。(虽然都是些奇奇怪怪的用法……)
3.1数值
JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1与1.0是相同的,是同一个数
1 === 1.0 // true
0.1 + 0.2 === 0.3
// false
0.3 / 0.1
// 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1)
// false
这就是说,JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。
由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心
数值范围
JavaScript 提供Number对象的MAX_VALUE和MIN_VALUE属性,返回可以表示的具体的最大值和最小值。
数值的科学计数法
科学计数法允许字母e或E的后面,跟着一个整数,表示这个数值的指数部分。还有些偏门的自动转换成科学计数法的规则。
123e3 // 123000
123e-3 // 0.123
-3.1E+12
.1e-23
数值的进制
- 十进制:没有前导0的数值。
- 八进制:有前缀0o或0O的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。
- 十六进制:有前缀0x或0X的数值。
- 二进制:有前缀0b或0B的数值。
ES5 的严格模式和 ES6,已经废除了部分冲突语法(比如0888啥的),但是浏览器为了兼容以前的代码,目前还继续支持这种表示法。
特殊数值
JavaScript 内部实际上存在2个0:一个是+0,一个是-0,区别就是64位浮点数表示法的符号位不同。它们是等价的
唯一有区别的场合是,+0或-0当作分母,返回的值是不相等的
(1 / +0) === (1 / -0) // false
3.2NaN与Infinity
- NaN是 JavaScript 的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。
5 - 'x' // NaN
Math.acos(2) // NaN
Math.log(-1) // NaN
Math.sqrt(-1) // NaN
0 / 0 // NaN
NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number
运算规则:
NaN不等于任何值,包括它本身。
NaN === NaN // false
数组的indexOf方法内部使用的是严格相等运算符,所以该方法对NaN不成立。
[NaN].indexOf(NaN) // -1
NaN在布尔运算时被当作false。
Boolean(NaN) // false
NaN与任何数(包括它自己)的运算,得到的都是NaN
NaN + 32 // NaN
NaN - 32 // NaN
NaN * 32 // NaN
NaN / 32 // NaN
- Infinity表示“无穷”,用来表示两种场景。一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0,得到Infinity
// 场景一
Math.pow(2, 1024)
// Infinity
// 场景二
0 / 0 // NaN
1 / 0 // Infinity
Infinity有正负之分,Infinity表示正的无穷,-Infinity表示负的无穷
Infinity === -Infinity // false
1 / -0 // -Infinity
-1 / -0 // Infinity
由于数值正向溢出(overflow)、负向溢出(underflow)和被0除,JavaScript 都不报错,所以单纯的数学运算几乎没有可能抛出错误(C语言患者表示有救了)
Infinity大于一切数值(除了NaN),-Infinity小于一切数值(除了NaN)
运算规则: 四则运算,符合无穷的数学计算规则
5 * Infinity // Infinity
5 - Infinity // -Infinity
Infinity / 5 // Infinity
5 / Infinity // 0
3.3与数值相关的全局方法
(1)parseInt()
parseInt方法用于将字符串转为整数
parseInt('123') // 123
parseInt(' 81') // 81
如果parseInt的参数不是字符串,则会先转为字符串再转换
parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1
字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分
parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15
如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN。
parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') // NaN
parseInt('+1') // 1
所以,parseInt的返回值只有两种可能,要么是一个十进制整数,要么是NaN。
有关进制转换规则
以0x或0X开头,parseInt会将其按照十六进制数解析
parseInt('0x10') // 16
以0开头,将其按照10进制解析
parseInt('011') // 11
还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。默认情况下,parseInt的第二个参数为10,即默认是十进制转十进制。
parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512
(2)parseFloat()
parseFloat方法用于将一个字符串转为浮点数
parseFloat('3.14') // 3.14
支持科学计数法转换:
parseFloat('314e-2') // 3.14
parseFloat('0.0314E+2') // 3.14
parseFloat('3.14more non-digit characters') // 3.14
自动过滤字符串前导的空格
parseFloat('\t\v\r12.34\n ') // 12.34
参数不是字符串,或者字符串的第一个字符不能转化为浮点数,则返回NaN
parseFloat([]) // NaN
parseFloat('FF2') // NaN
parseFloat('') // NaN 震惊!我堂堂空字串变成了NaN!
(3)isNaN()
isNaN方法可以用来判断一个值是否为NaN。
但是,isNaN只对数值有效,如果传入其他值,会被先转成数值。比如,传入字符串的时候,字符串会被先转成NaN,所以最后返回true,这一点要特别引起注意。也就是说,isNaN为true的值,有可能不是NaN,而是一个字符串
出于同样的原因,对于对象和数组,isNaN也返回true.但是,对于空数组和只有一个数值成员的数组,isNaN返回false(原因是这些数组能被Number函数转成数值)
isNaN(NaN) // true
isNaN(123) // false
isNaN('Hello') // true
// 相当于
isNaN(Number('Hello')) // true
isNaN({}) // true
// 等同于
isNaN(Number({})) // true
isNaN(['xzy']) // true
// 等同于
isNaN(Number(['xzy'])) // true
isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false
因此,使用isNaN之前,最好判断一下数据类型.安全使用方法,利用NaN为唯一不等于自身的值的这个特点,进行判断。
function myIsNaN(value) {
return value !== value;
}
或者
function myIsNaN(value) {
return typeof value === 'number' && isNaN(value);
}
(4)isFinite()
isFinite方法返回一个布尔值,表示某个值是否为正常的数值。
除了Infinity、-Infinity、NaN和undefined这几个值会返回false,isFinite对于其他的数值都会返回true(让我来看康是哪个小朋友不是正常的数值)
isFinite(Infinity) // false
isFinite(-Infinity) // false
isFinite(NaN) // false
isFinite(undefined) // false
isFinite(null) // true
isFinite(-1) // true
4.1字符串
字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。
'abc'
"abc"
单引号字符串的内部,可以使用双引号。双引号字符串的内部,可以使用单引号
'key = "value"'
"It's a long journey"
如果要在单引号字符串的内部,使用单引号,就必须在内部的单引号前面加上反斜杠,用来转义。双引号字符串内部使用双引号,也是如此
'Did she say \'Hello\'?'
// "Did she say 'Hello'?"
"Did she say \"Hello\"?"
// "Did she say "Hello"?"
如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。
var longString = 'Long \
long \
long \
string';
longString
// "Long long long string"
转义
反斜杠(\)在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符
4.2字符串与数组
字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。
var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"
// 直接对字符串使用方括号运算符
'hello'[1] // "e"
'abc'[3] // undefined
'abc'[-1] // undefined
'abc'['x'] // undefined
字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符
length属性返回字符串的长度,该属性也是无法改变的
var s = 'hello';
s.length // 5
s.length = 3;
s.length // 5
s.length = 7;
s.length // 5
4.3字符集相关
JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode 表示。
JavaScript 不仅以 Unicode 储存字符,还允许直接在程序中使用 Unicode 码点表示字符,即将字符写成\uxxxx的形式,其中xxxx代表该字符的 Unicode 码点。
解析代码的时候,JavaScript 会自动识别一个字符是字面形式表示,还是 Unicode 形式表示。输出给用户的时候,所有字符都会转成字面形式。
var f\u006F\u006F = 'abc';
foo // "abc"
每个字符在 JavaScript 内部都是以16位(即2个字节)的 UTF-16 格式储存。也就是说,JavaScript 的单位字符长度固定为16位长度,即2个字节
统一将字符长度限制在两字节,导致无法识别四字节的字符。JavaScript 返回的字符串长度可能是不正确的。
Base64转码
有时,文本里面包含一些不可打印的符号,比如 ASCII 码0到31的符号都无法打印出来,这时可以使用 Base64 编码,将它们转成可以打印的字符。另一个场景是,有时需要以文本格式传递二进制数据,那么也可以使用 Base64 编码
Base64 就是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、+和/这64个字符组成的可打印字符。使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理。
JavaScript 原生提供两个 Base64 相关的方法。
- btoa():任意值转为 Base64 编码
- atob():Base64 编码转为原来的值
注意,这两个方法不适合非 ASCII 码的字符,会报错
var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"
btoa('你好') // 报错
要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。
function b64Encode(str) {
return btoa(encodeURIComponent(str));
}
function b64Decode(str) {
return decodeURIComponent(atob(str));
}
b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
这块在后续进行中文字符串处理时会经常用到。