JavaScript学习笔记
1 JavaScript引入
1.1 在网页上输出 Hello world
JavaScript代码需要放在HTML文件中,下面是一个简单的HTML代码示例:
1 |
|
可以在.html
文件中的script标签中书写JavaScript代码,但是一般采用下面的方式进行外部引入。
1 | <script src="source/js/test.js"></script> |
该标签需要放在body标签的最后,以加快网页的加载速度。不要使用合并标签。在test.js
文件中书写下面的代码调用一个弹窗,显示Hello world!
。
1 | alert("Hello world!"); // 在弹窗中输出Hello World! |
alert
函数的参数是一个字符串,字符串可以用单引号引出,也可以用双引号。这一点和C语言不同。
弹窗命令可以用来方便地调试程序。前端写弹窗非常简单,至少相比于后端要简单不少。
1.2 JavaScript 简介
JavaScript是一门弱脚本语言(可能是世界上最流行的脚本语言),源代码不需要通过编译,而是由浏览器解释运行,用于控制网页的行为。JavaScript只能在浏览器引擎上运行,浏览器引擎类似发动机引擎,JavaScript相当于汽油,而浏览器本体相当于一台汽车。我们可以用汽油和发动机去驱动汽车,也可以去浇灌农田。类似地,使用JavaScript可以实现网页端的开发,在不同的运行环境下(应用场景不同)也可以发生不同的作用,例如使用Electron(NodeJS)框架实现桌面端应用的开发。
只掌握JavaScript只能说掌握了前端开发的基础。一般的前端开发可以分为原生开发和框架开发,说白了,就是是否重复造轮子的选择问题,或者说是否造好看的轮子的问题。目前流行的前端框架有Vue,UI框架有阿里巴巴出品的Ant-Design等。
==JavaScript的线上环境和开发环境可能不一致==:ECMAScript是JavaScript遵循的标准,最新已经更新到ES6了。但是大多数浏览器支持仍然停留在ES5,所以需要WebPack等工具将ES6代码打包成ES5,才能在大多数浏览器上运行。
1.3 CSS 和 CSS 预处理器
这一块知识和JavaScript关系不大,但是也记录在此,因为它和前端开发结合的相当紧密。
CSS层叠样式是一门标记语言,它本身并不是编程语言,它不支持自定义变量和引用,不具备任何语法支持特性。这成为了它的主要缺陷,CSS的语法不够强大,不能嵌套书写,模块化开发中需要写很多重复的选择器;没有合理的样式复用和维护机制,逻辑上相关的属性必须以字面量的形式重复输出,导致难以维护。为了解决这一问题,实际开发中通常使用一种称为CSS预处理器的工具。
CSS预处理器定义了一种新的语言,它的基本思想是,用一种专门的编程语言,为CSS增加一些编程的特性。将CSS作为目标生成文件(编译目标),而开发者需要书写生成CSS语言的逻辑就可以方便地实现网页样式的维护和更新。
常用的CSS预处理器由SASS和LESS,二者相比LESS更加简单,更常用。LESS基于NodeJS,功能比SASS简单,但是解析效率不如SASS。实际开发中LESS也够用了。
2 基本语法入门
2.1 变量
JavaScript中定义变量非常简单,甚至不需要显式地指定变量类型,统统用var
表示。变量名中可以添加$
,也可以以它开头,只不过不常用。甚至可以使用中文命名和赋值,不过应该和使用的中文编码方式有关,容易报错,感觉还是不要使用为妙。
1 | var a = 1; |
2.2 条件判断
1 | var score = 80; |
单条语句可以省略花括号。
2.3 基本的浏览器操作
调试除了用alert()
或者console.log()
函数输出信息外,还可以断点调试。这里以Edge浏览器为例。
点击F12打开控制台,在源代码处可以打断点。刷新网页即可直接运行。在右侧监视窗口可以添加变量名用于监视变量的变化。
除此之外,常用的窗口及其作用如下所示:
- 元素窗口(Element):一般用于查看网页元素和复刻网页;
- 控制台(Console):用于打印变量,执行控制命令等;
- 源代码(Source):查看源码,断点调试;
- 网络(Network):抓包;
- 应用程序(Application):查看Cookie。
2.4 常见数据类型
在网页上常见的数据类型不止数字和字符串。图像,文本,音频,视频等都是数据类型。JavaScript的数据类型非常庞杂。
2.4.1 数字 Number
JavaScript不区分小数和整数,所有数字统一都是number
类型。JavaScript支持直接表示整数、浮点数、负数、科学计数法(例如1.23e3
)、NaN
、Infinity
(无穷大)。所有数字都是number
类型。
2.4.2 字符串
可以用单引号引出,也可以用双引号引出。
2.4.3 布尔值
包含true和false,没有什么特别。
2.4.4 逻辑运算
与&&
或||
非!
。
2.4.5 判等运算符
=
:赋值符号,不是判断等于符号==
:等于(类型不同,但是值相同,也会返回true,例如1 == '1'
)===
:绝对等于(类型相同,值相同,==实际开发中常用绝对等于判断两个元素是否相等==)
等于和绝对等于可以看作JavaScript的一个缺陷,坚持一定使用绝对等于进行比较。下面是一些注意事项:
NaN
:不和任何数字相等,包括自己。如果要判断一个变量是否是NaN
只能使用isNaN()
函数。- 计算机表示浮点数的精度有限,在计算机中
1/3 === 1 - 2/3
一定会返回一个false
。尽量避免使用浮点数进行运算。
2.4.6 空指针和未定义
空指针null
没有什么稀奇,但是未定义undefined
在其他语言中并不常见。例如此时有一个数组,如果要打印的数组元素超过了数组的长度,JavaScript不会报错,而是会返回一个undefined
类型。
2.4.7 数组
JavaScript对变量的类型没有明确定义,那么对于数组中是否存储相同类型的元素也不关心。JavaScript中的数组可以存放任意类型的变量。数组可以使用[]
或者使用Array()
方法定义,但是使用方括号对于代码的可读性贡献更好。
1 | var arr = [1, 2, 3, 4, "hello", null, true, undefined]; |
2.4.8 对象
JavaScript中定义一个对象可以直接使用大括号{}
定义。任何数据类型都要用var
定义,对象也不例外。这里的对象并不是面向对象的概念,没有封装、继承、多态等概念。
1 | var person = { |
注意:JavaScript中定义对象不需要先定义一个类。内部变量通过键值对的方式给出,值同样可以是不同的类型,具体的格式和json格式非常相似,每个属性之间使用逗号分隔,最后一个键值对不需要加逗号。使用类似person.name
的方式访问元素即可,没有什么稀奇。
2.5 严格检查模式
由于JavaScript中对于变量的定义十分宽泛,甚至直接使用类似Python语法定义变量都不会报错:
1 | i = 0; |
实际上,上面的语句将i
定义为一个全局变量。如果采用全局变量,会导致代码之间程序耦合的问题,会带来很多毁灭性的bug。在ES5中,采用var
定义的变量是局部变量,而在ES6中,使用let
和const
定义变量才会被当作局部变量。
1 | let a = 0; |
在JS代码之前加入语句'use strict';
语句即可开启严格检查模式,写这一行代码必须写在第一行。编译器会检查变量是否被定义,未经定义的变量会直接报错。使用后还可以规避一些JavaScript中变量定义随意性导致的问题。
1 | ; |

2.6 分支与循环
分支循环的语法基本和C语言一模一样。注意某些特殊的写法如for ... in ...
语句或者某些对象的循环方法即可(例如forEach()
)。
3 数据类型详解及其属性和方法
3.1 字符串
正常的字符串使用单引号或者双引号引用。需要注意转义字符的引用,例如\n
、\'
、\r
、\t
等。
转义字符甚至可以直接打印ASCII编码字符和UniCode字符:
1 | console.log('\u4e2d'); // 中 |
JavaScript支持多行字符串编写,多行字符串需要使用`字符引用:
1 | let msg = `hello |
在ES6中,模板字符串用下面的格式进行书写,注意使用多行字符串符号引用。(ES6新特性)
1 | let name = 'Include everything'; |
字符串是不可变型变量。直接修改字符串中的某个字符不会报错,但是修改也不生效。
1 | let name = 'Zhang Yun'; |
下面是一些字符串常用的属性和方法:
1 | let name = 'Zhang Yun'; |
3.2 数组
JavaScript中的数组和其他语言有相同的地方,比如都可以使用下标访问元素。但是也有不同的地方:
- JavaScript的数组可以存放任意数据类型的元素
- 数组长度可变,且可以直接被赋值。如果赋值小于当前长度,多余元素会直接丢失;如果赋值大于当前长度,会直接添加几个空元素。空元素的类型是undefined。
1 | let arr = [1, 2, 3, 4, 5]; |
下面给出数组的一些常用方法:
indexOf()
1 | let arr = [1, 2, 3, 4, 5, "1", "2"]; |
slice()
:截取Array的一部分(返回一个新的数组)
1 | let arr = [1, 2, 3, 4, 5]; |
- 添加或删除元素(修改原来的数组)
1 | let arr = [1, 2, 3, 4, 5]; |
- 排序算法(修改原来的数组)
1 | let arr = ['B', 'C', 'A']; |
- 拼接(返回一个新的数组)
1 | let arr = ['B', 'C', 'A']; |
- 连接(返回一个字符串,由元素和连接符组成)
1 | let arr = ['B', 'C', 'A']; |
数组还有一个方法,可以实现类似循环的操作:forEach
,里面的参数是一个方法,方法的参数即为数组的元素。
1 | let arr = [3, 2, 44, 32, 1, 2]; |
for in
遍历数组下标还存在一个特性,它不仅会返回元素的下标,还会返回元素对应的键:
1 | let arr = [1, 2, 3, 4, 5]; |
3.3 对象
JavaScript中的对象用键值对的方式书写。这里的对象不是面向对象中的对象概念,没有封装、继承、多态的概念。所有的键都是字符串,值可以是任意类型。
可以使用delete命令动态删除对象中的某一属性。
1 | let person = { |

直接给不存在的属性赋值,不会报错,JavaScript会自动添加属性。
1 | person.tags = ['C', 'Verilog']; |

使用in
关键字判断属性是否在这个对象中:
1 | console.log('age' in person); // true |
判断属性是否属于自身:
1 | console.log(person.hasOwnProperty('toString')); // false |
对象定义中如果后面的定义和前面的定义冲突,那么以后面的为准(后面的定义会覆盖前面的):
1 | let Debussy = { |
3.4 Map和Set
Map和Set是ES6的新特性。Map在组成上很像Python的字典,由键值对组成。定义的时候可以通过一个二维数组来定义。
1 | let map = new Map([["tom", 90], ["bob", 80], ["alice", 70]]); |
Set是一个无序不重复的集合。一般只能用来判断某元素是否在Set中存在。定义时使用一个数组定义,但是多余的元素会被自动忽略。
1 | let set = new Set([1, 1, 1, 3]); |
3.5 iterator 迭代器
迭代器是Python中一个经典的概念,JavaScript中也有类似的结构。迭代器是ES6的新特性,迭代器的主要作用是遍历对象中所有的元素,实际上很多数据结构都可以自己写函数或者方法来实现迭代器的功能,但是利用迭代器可以简化代码书写,提高效率。
在数组中可以使用for in
和for of
迭代所有的元素,前者迭代下标,后者迭代元素:
1 | // for (index in object) |
for of
这种方法在Map和Set中也同样适用。只有支持迭代器的对象才可以使用for of
遍历。
1 | let map = new Map([["tom", 90], ["bob", 80], ["alice", 70]]); |
4 函数和面向对象
方法是一种函数。对象内定义的函数称为方法,其他函数称为普通函数。方法和函数的格式相同,只是二者的定义的位置不同。
4.1 函数
下面是一个简单的返回绝对值的函数:
1 | function abs(x) { |
如果函数没有正常返回,或者传参错误,JavaScript会返回NaN或者Undefined,而不是报错。

函数也可以使用下面的方式定义,定义一个匿名函数,并且将该函数赋值给一个变量,这个变量就会称为该函数的名称,通过abs
就可以调用函数。两种函数定义的方式等价。
1 | let abs = function (x) { |
JavaScript的参数的个数甚至都可以不确定。可以传递任意个参数,甚至不传递参数,函数在运行时都不会报错。这就需要程序员在设计函数时考虑到这些异常。
如果传入参数不存在,或者类型错误,可以使用typeof
关键字获取参数类型,并通过throw
关键字抛出一个异常:
1 | let abs = function (x) { |

arguments
是JavaScript免费赠送的关键字,它会获取传入函数的所有参数,并且返回一个对应的数组:
1 | let abs = function (x) { |
使用arguments
会有一个问题:它包含所有的参数,但是有时我们又恰恰想用剩余的参数来进行操作。这时可以使用rest
关键字(ES6新特性)获取除了已经定义的参数之外的所有参数。
如果不使用缺省参数关键字...
获取剩余的参数:
1 | function func(a, b) { |
如果使用...
关键字,注意要在参数栏写明可能有缺省参数,且它只能写在最后,必须使用...
标识表明rest是缺省的,...
后是缺省参数的数组名称:
1 | function func(a, b, ...rest) { |
经过测试,rest并不是一个关键字,它相当于承载剩余参数的一个变量名而已,该变量是一个数组。
4.2 变量的作用域
4.2.1 函数之间变量作用域
JavaScript中的变量虽然可以在首次赋值时默认被直接声明,但是不可以在未定义时赋值给其他变量。下面的程序会立即报错,因为x = x + 1
中等号右边的x
没有被定义。可见使用var
定义的变量是有作用域的。
1 | function func1() { |
如果两个函数中使用了相同的变量名,则两个变量名互不影响。
1 | function func1() { |
JavaScript的函数声明或者函数体甚至可以写在函数内部。下面的表达依然是合法的。并且内部函数可以使用外部函数的变量,但是外部函数不能访问内部函数的变量。
1 | // 外部函数可以访问内部函数变量,反之不可以 |
如果内部函数和外部函数声明了相同的变量名,则内部变量使用内部变量,外部变量使用外部变量。虽然从结果上看互不影响,但是官方的说法是:JavaScript函数查找变量从自身函数开始,从内向外查找。假设外部存在这个同名变量,则内部变量屏蔽外部函数变量。
4.2.2 同一函数先后定义变量作用域
JavaScript中,虽然使用未定义的变量给其他变量赋值会导致程序报错,但是如果这个变量在后面的程序段中定义,则不会报错,而是会将定义提前,但是定义时的赋值并不会提前。这时使用这个变量会返回一个undefined
。也可以理解为编译器会自动将所有变量的定义提前,但是赋值并不会提前。所以通用的做法是将所有变量写在程序的头部,以预防这种情况的发生。这是在JavaScript建立之初就存在的特性,有一定的优点,但是不利于代码维护。
1 | var x = 1 + y; |
4.2.3
全局作用域和window
对象
JavaScript中,window
是一个特殊的对象,它代表浏览器,同时也是JavaScript的唯一的全局作用域。直接定义的变量都是全局变量,同时也是window
的子属性。有些函数可以直接调用,例如alert()
,
它是一个全局函数,它是window
对象的子方法。==任何变量(函数也可以视为变量)如果没有在函数的作用范围内找到,就会向外查找,直到全局作用域window
。如果在全局作用域都没有找到才会报错。==
1 | var x = "Hello!"; |
JavaScript中的变量可以是任意类型,当然也可以是一个函数。我们当然可以使用一个与原有函数同名的变量,将另一个函数赋值给这个变量,则原函数就会被新函数覆盖。
1 | var x = "Hello!"; |
所有的全局变量都会绑定到window
对象下,包括不同文件中定义的全局变量。假设不同文件中定义了相同的变量,就会导致冲突。解决方法是在每个JS文件中定义一个特殊的独一无二的对象,将这个对象作为当前文件的全局空间,可以在一定程度上减少冲突。
1 | var Debussy = {}; // 当前文件的全局变量 |
4.2.4
局部作用域let
和常量const
let
和const
都是ES6的新特性。现在已不建议使用var
关键字定义常量,否则可能出现这样的Bug(特性?):
1 | function func1 () { |
但是这样的特性很多时候是不利于代码编写的。所以使用关键字let
定义i
就不存在这样的问题。
1 | function func1 () { |
const
的官方名是只读变量,它只可以在定义时被赋值,之后都不能被赋值。const
声明的变量不可修改,数组和对象除外(数组和对象仅仅不能全部更改)。
1 | const year = 2024; // 使用const必须要在声明时就初始化数值 |
4.3 方法的定义和调用
JavaScript中的方法定义十分简单。放置在对象中的函数就称为方法,甚至方法定义可以直接写在对象中:
1 | let Debussy = { |
在对象中只有两种元素:属性和方法。this
始终指向调用它的对象。如果将方法采用下面的写法,同样可以定义:
1 | let Debussy = { |
在其他编程语言中,一般而言this
指向的对象都不可更改,但是在JavaScript中可以通过apply()
函数定义当前调用这个函数的对象。所有的函数对象都有apply方法。例如,将上面代码块中的getName()
改为下面的形式就可以正常输出:
1 | getName.apply(Debussy); |
4.4 原型继承和类继承
4.4.1 原型继承
早期的JavaScript程序并没有类定义,所以只能采用这种方法。如果一个对象想要获得另一个对象的部分属性,需要指定对象的__proto__
属性来继承原有的对象,即指向一个原型。这样的编程方法虽然实现了类似面向对象的效果,但是没有强调面向对象的概念。
1 | let Student = { |
如果需要更改继承对象,只需要更改__proto__
指向的对象即可(可能是原型继承唯一的优点)。任何对象都有原型属性,且这个原型属性可以追本溯源到object
对象,包括object
对象自身。
4.4.2 类继承
类继承也是ES6新增的方法。在ES6之前,通过下面的方法创建一个学生类:
1 | // 创建一个学生类(指定一个函数当作构造函数) |
在ES6之后才可以使用class
关键字:
1 | class Student { |
使用下面的方法继承:
1 | class Student { |
简单的回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念。
——摘自《javascript高级程序设计》

5 内部对象
JavaScript中万物皆对象。常见的对象类型有如下几种(可以用typeof
关键字获取)
'number'
:所有数字均为'number'
类型,部分整数和浮点数。包括NaN
。'string'
'boolean'
'function'
:函数名称的类型为'function'
'object'
:数组和对象均为'object'
'undefined'
:undefined
独成一个类型,类型就是'undefined'
5.1 Date对象
可以使用Date
获取当前时间和计算时间:
1 | let now = new Date(); |
可以使用时间戳创建时间:
1 | let time = new Date(1714713565465); |
5.2 JSON对象
JSON是一种数据格式,用于传输特定数据,本质上就是一串字符串。JavaScript中一切皆为对象,任何JavaScript支持的类型都可以用JSON来表示。
1 | let Debussy = { |
6 BOM和DOM
6.1 BOM对象
在Web开发中,常用B(Browser)代表浏览器,S(Server)代表服务端。BOM全称为Browser Object Module,即浏览器对象模型。
6.1.1 window
(重要)
window
对象可以有两层含义:一可以指浏览器窗口,二是可以指唯一的全局作用域。window
有下面的一些常用的方法和属性:
1 | window.alert('Hello'); |

6.1.2
navigator
(不建议使用)
navigator
封装了浏览器的信息,它是window
的一个属性。它比较常用的用法是获取当前的浏览器信息和用户信息,比如可以获知当前的网页打开使用的是什么浏览器,是手机浏览器还是PC浏览器,浏览器内核是什么,PC的OS信息等:

大多数时候不会使用navigator
对象,因为它可以被人为修改。不建议使用这些属性来判断和编写代码。
6.1.3 screen
代表屏幕信息。浏览器的功能远比想象的强大,它甚至可以操作浏览器之外的很多部件,获取很多信息。

6.1.4 location
(重要)
location
可以获取当前页面的URL信息。
1 | console.log(location.host); // 主机 127.0.0.1:5500 |
6.1.5 document
document
代表当前的页面的html文件中的DOM文档树。可以通过document
获取当前浏览器中文档树的节点。能获取就能做到增删改查。

1 | <dl id="abc"> |

document
还可以获取cookie。cookie包含了客户端的一些本地信息,通过这些信息,可能直接通过劫持这些cookie来非法登录网页,非法获取隐私等。服务器端可以设置cookie为httpOnly,就可以规避这个问题,提高网页使用安全性。
6.1.6 history
代表浏览器的历史记录,通过这个对象可以前进和后退,但是不建议使用这种方法。浏览器网页的跳转还是建议在服务端实现。
1 | history.forward(); |
6.2 DOM对象
DOM全称Document Object Module,即文件对象模型。整个浏览器网页就是一个树形结构。通过操作DOM,可以实现网页元素的行为。要操作一个Dom节点,就必须先获得这个Dom节点。
- 更新:更新Dom节点,例如给某个节点添加某个属性;
- 遍历:得到Dom节点,例如可以通过
id
属性得到节点; - 删除:删除Dom节点;
- 添加:添加一个新的节点。
一个标签就是一个节点,在网页上就体现为一个元素。
6.2.1 获取Dom节点
首先介绍(复习)一些常见的Dom节点及其作用:
div
:div
HTML 元素是流式内容的通用容器。它对内容或布局没有影响。除非以某种方式使用 CSS 对其进行样式设置(例如,直接应用样式,或者对其父元素应用某种布局模型,如弹性盒子),否则它对内容或布局没有影响。dl
:list列表dt
:title表头dd
:description描述
1 | <div id="father"> |
上面的代码是原生代码,后续基本不使用。之后一般使用jQuery()
获取Dom节点,但是了解原生写法还是很有必要的。
About this Post
This post is written by Yun Zhang, licensed under CC BY-NC 4.0.