JS学习系列1-简介、基本语法、数据类型一

264 阅读16分钟

前言

本系列是之前的学习积累,主要针对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') // "你好"

这块在后续进行中文字符串处理时会经常用到。