基本语法
注释
1 | // 单行注释 |
变量声明
var、let 和 const
1 | // var - 函数作用域,可以重复声明 |
数据类型
Number
JavaScript不区分整数和浮点数,统一用Number表示,以下都是合法的Number类型:
1 | 123; // 整数123 |
要注意,JavaScript的Number不区分整数和浮点数,也就是说,12.00 === 12
字符串
字符串是以单引号’或双引号"括起来的任意文本,比如'abc',"xyz"等等。请注意,''或""本身只是一种表示方式,不是字符串的一部分,因此,字符串'abc'只有a,b,c这3个字符。
数组
数组是一组按顺序排列的集合,集合的每个值称为元素。JavaScript的数组可以包括任意数据类型。例如:
1 | [1, 2, 3.14, 'Hello', null, true]; |
上述数组包含6个元素。数组用[]表示,元素之间用,分隔。
另一种创建数组的方法是通过Array()函数实现:
1 | new Array(1, 2, 3); // 创建了数组[1, 2, 3] |
indexOf
与String类似,Array也可以通过indexOf()来搜索一个指定的元素的位置:
1 | let arr = [10, 20, '30', 'xyz']; |
注意了,数字30和字符串'30'是不同的元素。
slice
slice()就是对应String的substring()版本,它截取Array的部分元素,然后返回一个新的Array:
1 | let arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; |
注意到slice()的起止参数包括开始索引,不包括结束索引。
如果不给slice()传递任何参数,它就会从头到尾截取所有元素。利用这一点,我们可以很容易地复制一个Array:
1 | let arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; |
push和pop
push()向Array的末尾添加若干元素,pop()则把Array的最后一个元素删除掉:
1 | let arr = [1, 2]; |
unshift和shift
如果要往Array的头部添加若干元素,使用unshift()方法,shift()方法则把Array的第一个元素删掉:
1 | let arr = [1, 2]; |
sort
sort()可以对当前Array进行排序,它会直接修改当前Array的元素位置,直接调用时,按照默认顺序排序:
1 | let arr = ['B', 'C', 'A']; |
能否按照我们自己指定的顺序排序呢?完全可以,我们将在后面的函数中讲到。
reverse
reverse()把整个Array的元素给调个个,也就是反转:
1 | let arr = ['one', 'two', 'three']; |
splice
splice()方法是修改Array的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:
1 | let arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle']; |
concat
concat()方法把当前的Array和另一个Array连接起来,并返回一个新的Array:
1 | let arr = ['A', 'B', 'C']; |
请注意,concat()方法并没有修改当前Array,而是返回了一个新的Array。
实际上,concat()方法可以接收任意个元素和Array,并且自动把Array拆开,然后全部添加到新的Array里:
1 | let arr = ['A', 'B', 'C']; |
join
join()方法是一个非常实用的方法,它把当前Array的每个元素都用指定的字符串连接起来,然后返回连接后的字符串:
1 | let arr = ['A', 'B', 'C', 1, 2, 3]; |
对象
JavaScript的对象是一组由键-值组成的无序集合,例如:
1 | var person = { |
JavaScript对象的键都是字符串类型,值可以是任意数据类型。上述person对象一共定义了6个键值对,其中每个键又称为对象的属性,例如,person的name属性为'Bob',zipcode属性为null。
要获取一个对象的属性,我们用对象变量.属性名的方式:
1 | person.name; // 'Bob' |
如果访问一个不存在的属性会返回什么呢?JavaScript规定,访问不存在的属性不报错,而是返回undefined
由于JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性:
1 | let xiaoming = { |
strict模式
JavaScript在设计之初,为了方便初学者学习,并不强制要求用var申明变量。这个设计错误带来了严重的后果:如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量:
1 | i = 10; // i现在是全局变量 |
在同一个页面的不同的JavaScript文件中,如果都不用var申明,恰好都使用了变量i,将造成变量i互相影响,产生难以调试的错误结果。
使用var申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内(函数的概念将稍后讲解),同名变量在不同的函数体内互不冲突。
为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式,在strict模式下运行的JavaScript代码,强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。
启用strict模式的方法是在JavaScript代码的第一行写上:
1 | ; |
这是一个字符串,不支持strict模式的浏览器会把它当做一个字符串语句执行,支持strict模式的浏览器将开启strict模式运行JavaScript。
字符串
多行字符串
由于多行字符串用\n写起来比较费事,所以最新的ES6标准新增了一种多行字符串的表示方法,用反引号...表示:
1 | `这是一个 |
模板字符串
要把多个字符串连接起来,可以用+号连接:
1 | let name = '小明'; |
如果有很多变量需要连接,用+号就比较麻烦。ES6新增了一种模板字符串,表示方法和上面的多行字符串一样,但是它会自动替换字符串中的变量:
1 | let name = '小明'; |
条件判断
JavaScript使用if () { ... } else { ... }来进行条件判断
1 | let age = 3; |
循环
for
for循环,通过初始条件、结束条件和递增条件来循环执行语句块:
1 | let x = 0; |
for … in
for循环的一个变体是for ... in循环,它可以把一个对象的所有属性依次循环出来:
1 | let o = { |
while
while循环只有一个判断条件,条件满足,就不断循环,条件不满足时则退出循环。比如我们要计算100以内所有奇数之和,可以用while循环实现:
1 | let x = 0; |
do … while
最后一种循环是do { ... } while()循环,它和while循环的唯一区别在于,不是在每次循环开始的时候判断条件,而是在每次循环完成的时候判断条件:
1 | let n = 0; |
用do { ... } while()循环要小心,循环体会至少执行1次,而for和while循环则可能一次都不执行。
iterable
遍历Array可以采用下标循环,遍历Map和Set就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型。
具有iterable类型的集合可以通过新的for ... of循环来遍历。
用for ... of循环遍历集合,用法如下:
1 | let a = ['A', 'B', 'C']; |
然而,更好的方式是直接使用iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数
Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身:
1 | let s = new Set(['A', 'B', 'C']); |
函数
函数定义
有两种:
1 | function abs(x) {} |
和
1 | let abs = function (x) {}; |
在这种方式下,function (x) { ... }是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs,所以,通过变量abs就可以调用该函数。
调用就直接abs(10);
浏览器端
DOM(文档对象模型)是HTML文档的编程接口,它将文档表示为节点和对象的树状结构,使编程语言(如JavaScript)能够与页面交互。
1 | <!-- HTML结构 --> |
操作DOM
由于HTML文档被浏览器解析后就是一棵DOM树,要改变HTML的结构,就需要通过JavaScript来操作DOM。
始终记住DOM是一个树形结构。操作一个DOM节点实际上就是这么几个操作:
- 更新:更新该DOM节点的内容,相当于更新了该DOM节点表示的HTML的内容;
- 遍历:遍历该DOM节点下的子节点,以便进行进一步操作;
- 添加:在该DOM节点下新增一个子节点,相当于动态增加了一个HTML节点;
- 删除:将该节点从HTML中删除,相当于删掉了该DOM节点的内容以及它包含的所有子节点。
在操作一个DOM节点前,我们需要通过各种方式先拿到这个DOM节点。最常用的方法是document.getElementById()和document.getElementsByTagName(),以及CSS选择器document.getElementsByClassName()。
由于ID在HTML文档中是唯一的,所以document.getElementById()可以直接定位唯一的一个DOM节点。document.getElementsByTagName()和document.getElementsByClassName()总是返回一组DOM节点。要精确地选择DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。
例如:
1 | // 返回ID为'test'的节点: |
第二种方法是使用querySelector()和querySelectorAll(),需要了解selector语法,然后使用条件来获取节点,更加方便:
1 | // 通过querySelector获取ID为q1的节点: |
注意:低版本的IE<8不支持querySelector和querySelectorAll。IE8仅有限支持。
严格地讲,我们这里的DOM节点是指Element,但是DOM节点实际上是Node,在HTML中,Node包括Element、Comment、CDATA_SECTION等很多种,以及根节点Document类型,但是,绝大多数时候我们只关心Element,也就是实际控制页面结构的Node,其他类型的Node忽略即可。根节点Document已经自动绑定为全局变量document。
更新DOM
拿到一个DOM节点后,我们可以对它进行更新。
可以直接修改节点的文本,方法有两种:
一种是修改innerHTML属性,这个方式非常强大,不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树:
1 | // 获取<p id="p-id">...</p> |
用innerHTML时要注意,是否需要写入HTML。如果写入的字符串是通过网络拿到的,要注意对字符编码来避免XSS攻击。
第二种是修改innerText或textContent属性,这样可以自动对字符串进行HTML编码,保证无法设置任何HTML标签:
1 | // 获取<p id="p-id">...</p> |
两者的区别在于读取属性时,innerText不返回隐藏元素的文本,而textContent返回所有文本。另外注意IE<9不支持textContent。
修改CSS也是经常需要的操作。DOM节点的style属性对应所有的CSS,可以直接获取或设置。因为CSS允许font-size这样的名称,但它并非JavaScript有效的属性名,所以需要在JavaScript中改写为驼峰式命名fontSize:
1 | // 获取<p id="p-id">...</p> |
插入DOM
当我们获得了某个DOM节点,想在这个DOM节点内插入新的DOM,应该如何做?
如果这个DOM节点是空的,例如,<div></div>,那么,直接使用innerHTML = '<span>child</span>'就可以修改DOM节点的内容,相当于“插入”了新的DOM节点。
如果这个DOM节点不是空的,那就不能这么做,因为innerHTML会直接替换掉原来的所有子节点。
有两个办法可以插入新的节点。一个是使用appendChild,把一个子节点添加到父节点的最后一个子节点。例如:
1 | <!-- HTML结构 --> |
把<p id="js">JavaScript</p>添加到<div id="list">的最后一项:
1 | let |
现在,HTML结构变成了这样:
1 | <!-- HTML结构 --> |
因为我们插入的js节点已经存在于当前的文档树,因此这个节点首先会从原先的位置删除,再插入到新的位置。
更多的时候我们会从零创建一个新的节点,然后插入到指定位置:
1 | let |
这样我们就动态添加了一个新的节点:
1 | <!-- HTML结构 --> |
动态创建一个节点然后添加到DOM树中,可以实现很多功能。举个例子,下面的代码动态创建了一个<style>节点,然后把它添加到<head>节点的末尾,这样就动态地给文档添加了新的CSS定义:
1 | let d = document.createElement('style'); |
可以在Chrome的控制台执行上述代码,观察页面样式的变化。
insertBefore
如果我们要把子节点插入到指定的位置怎么办?可以使用parentElement.insertBefore(newElement, referenceElement);,子节点会插入到referenceElement之前。
还是以上面的HTML为例,假定我们要把Haskell插入到Python之前:
1 | <!-- HTML结构 --> |
可以这么写:
1 | let |
新的HTML结构如下:
1 | <!-- HTML结构 --> |
可见,使用insertBefore重点是要拿到一个“参考子节点”的引用。很多时候,需要循环一个父节点的所有子节点,可以通过迭代children属性实现:
1 | let |
删除DOM
删除一个DOM节点就比插入要容易得多。
要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉:
1 | // 拿到待删除节点: |
注意到删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。
当你遍历一个父节点的子节点并进行删除操作时,要注意,children属性是一个只读属性,并且它在子节点变化时会实时更新。
例如,对于如下HTML结构:
1 | <div id="parent"> |
当我们用如下代码删除子节点时:
1 | let parent = document.getElementById('parent'); |
浏览器报错:parent.children[1]不是一个有效的节点。原因就在于,当<p>First</p>节点被删除后,parent.children的节点数量已经从2变为了1,索引[1]已经不存在了。
因此,删除多个节点时,要注意children属性时刻都在变化。
操作文件
通常,上传的文件都由后台服务器处理,JavaScript可以在提交表单时对文件扩展名做检查,以便防止用户上传无效格式的文件:
1 | let f = document.getElementById('test-file-upload'); |
回调函数
1 | const fs = require('fs'); |
AJAX

用JavaScript执行异步网络请求。
Web的运作原理:一次HTTP请求对应一个页面
如果要让用户留在当前页面中,同时发出新的HTTP请求,就必须用JavaScript发送这个新请求,接收到数据后,再用JavaScript更新页面,这样一来,用户就感觉自己仍然停留在当前页面,但是数据却可以不断地更新。
AJAX请求是异步执行的,也就是说,要通过回调函数获得响应。
在现代浏览器上写AJAX主要依靠XMLHttpRequest对象,如果不考虑早期浏览器的兼容性问题,现代浏览器还提供了原生支持的Fetch API,以Promise方式提供。使用Fetch API发送HTTP请求代码如下:
1 | // 定义一个异步函数,接收URL参数 |
get('./content.html').then(data => {}) 这是一个 Promise 链式调用
1 | get('./content.html').then(data => { |
data参数是get函数中return result的值
Promise
在JavaScript的世界中,所有代码都是单线程执行的。
由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。
举一个promise例子:
1 | const fs = require('fs').promises; |
async函数
用async定义异步函数,用await调用异步函数
用关键字async配合await调用Promise,实现异步操作,但代码却和同步写法类似:
1 | async function get(url) { |
使用async function可以定义一个异步函数,异步函数和Promise可以看作是等价的,在async function内部,用await调用另一个异步函数,写起来和同步代码没啥区别,但执行起来是异步的。
也就是说:
1 | let resp = await fetch(url); |
自动实现了异步调用,它和下面的Promise代码等价:
1 | let promise = fetch(url); |
如果把await去掉,调用实际上发生了,但我们拿不到结果,因为我们拿到的并不是异步结果,而是一个Promise对象
因此,在普通function中调用async function,不能使用await,但可以直接调用async function拿到Promise对象,后面加上then()和catch()就可以拿到结果或错误了:
1 | async function get(url) { |
前端发送请求
使用 fetch()
1 | // GET 请求 |
使用 jQuery 发送请求
1 | // GET |
使用 XMLHttpRequest(老方式)
1 | let xhr = new XMLHttpRequest(); |
jQuery
jQuery 是一个用 JavaScript 编写的 轻量级库(Library)
主要作用:
- 简化 DOM 操作
- 简化 事件绑定
- 简化 AJAX 请求
- 提供丰富的 动画效果
- 提供 跨浏览器兼容性
引入方式:
1 | <!-- 从 CDN 引入 jQuery --> |
jQuery - JavaScript教程 - 廖雪峰的官方网站
Node.js端
简易HTTP服务
1 | // server.js |
处理 GET / POST 请求数据
GET 示例
1 | const http = require('http'); |
POST 示例
1 | const http = require('http'); |
Express框架
Express 是 Node.js 最流行的 Web 应用框架之一,简化了服务器的创建和路由管理。
一个简单的 Express 应用
1 | const express = require('express'); |
路由管理
1 | app.get('/about', (req, res) => { |
中间件
在 Express 中,中间件是处理请求和响应的函数。可以用于日志记录、请求解析、身份验证等。
在 Express 中,中间件(middleware) 本质上就是一个函数,用来在「请求 (Request) → 响应 (Response)」的过程中做拦截、处理或放行。
创建中间件
1 | app.use((req, res, next) => { |
使用现成的中间件
可以使用 body-parser 中间件解析请求体:
1 | const bodyParser = require('body-parser'); |
数据库操作
Node.js 可以与多种数据库进行交互,包括 MongoDB、MySQL、PostgreSQL 等。以下是使用 MongoDB 的示例。
连接 MongoDB
1 | const { MongoClient } = require('mongodb'); |
CRUD 操作
向 MongoDB 数据库中插入一个新的文档(数据行),并打印出新建文档的 ID
1 | async function createDocument(client, newDocument) { |