Skip to content

JavaScript 基础

JS 一开始被设计为专门制造恼人的网页弹窗(广告)。但是不知道哪位赛博尼采发现了 JS 的魅力,开始引导确立 JS 为网页首选的脚本语言。本来 JS 只是 Java 的拙劣仿制品,但 2009 年,“世纪罪人” Ryan Dahl 推出了(C++ 开发的)Node.js,把 JS 带入了 Java 的老家(带皇军进村)——服务端开发。于是,JS 彻底火了(悲剧的开始)。

JS 具有以下特点:

  • JS 是一种轻量级、基于对象的脚本语言,用于 Web 开发
  • JS 支持面向对象、函数式、命令式以及基于事件驱动的编程范式
  • JS 可以直接嵌入 HTML 页面中,并且可以通过 DOM 和 HTML API 来操作网页的结构、内容和样式
  • JS 具有动态类型系统,可以在运行时根据需要改变变量的数据类型
  • JS 是一种解释性语言,它不需要编译就可以直接执行

由于 JS 与 C 或者 Java 有太多相似之处,因此接下来的内容并不会事无巨细。如果您缺乏这些基础,请慎重阅读。

第一个程序

JS 无法独立运行,它必须依托于 JS 解释引擎(通常表现为浏览器)。为了使 JS 与 浏览器挂钩,可以新建一个 HTML 页面,使用 <script> 引用 JS 文件或直接在节点里编写:

html
<script src="Demo.js"></script>
<!-- OR -->
<script>
  <!-- JS Content -->
</script>

不同于 C 或 Java 等重量级语言,JS 程序执行是没有入口的,因此不用费心去设计任何的主函数或主方法。与 Python 类似,JS 会从上至下线性地执行代码。为了看到我们最喜欢的 Hello, xxx!,你需要这样写:

js
console.log("Hello, JS!"); // JS 在非严格模式下,语句末尾不需要结束符(半角分号)

console 表示控制台对象,其 log() 方法接收参数并将其解析为可读的形式、输出到控制台上。用浏览器打开 HTML 文件,按下 F12Ctrl + Shift + I(限 Microsoft Edge)打开开发人员工具,即可看到你最喜欢的一集

声明与数据类型

JS 是动态数据类型的,即它会自动推导变量的数据类型,而不由我们指定。

要想声明一个变量/常量,你可以:

js
var a = 10; // 定义全局变量
var aEmpty; // 定义空变量
let b = "jojo"; // 定义局部变量
const c = true; // 定义局部常量

WARNING

var 是 ES6 推出前唯一定义变量的方式,现在基于安全问题考虑,已经广泛不再使用。关于 ES 的具体内容,后面会讲到。

JS 中有六大基本数据类型和五大引用数据类型:

类型解释
number数字,包含一切有穷可表达的数值,支持科学计数法
string字符串,需要用引号对包裹
boolean布尔,表示逻辑的真、假,值只能取 truefalse
undefined未定义。当一个基本变量未赋值时,为 undefined
null空。当一个引用变量未给地址(或无效地址)时,为 null
symbol象征,可用于创建唯一标识符以避免命名冲突、保护类私有成员
类型解释
Function函数,对行为的打包,可接受自定义参数,只返回一个变量
Array数组,同一类型数据的堆叠
Object对象,键值对的集合
RegExp正则,用于描述文本规则
Date日期,用于处理日期和时间信息

所有的数据类型都可以使用对应的(包装)类构造器创建,这样更复杂,但能拥有更多功能:

js
let x = new Number(); // 当函数/方法不接收参数时,可以省略小括号对

作用域与变量提升

在上一节,我们了解了定义变量可以用 varlet,且前者由于安全问题正在被抛弃。

var 声明的变量存在变量提升,即在声明前使用该变量时,该变量会被自动初始化为 undefined,而 let 声明的变量不存在变量提升,即在声明前使用该变量会引发 ReferenceError 错误。

另外,var 声明的变量存在函数级作用域,而 let 声明的变量存在块级作用域。函数级作用域意味着在整个函数内部都可以访问该变量,而块级作用域意味着在 {} 内部声明的变量只能在该 {} 内部访问。当 var 在函数外使用时,它向页面中所有 JS 文件暴露,这容易引发全局变量污染软件危机

在实际开发过程中,var 的变量提升和函数级作用域可能导致意外的行为,而 let 的块级作用域更符合我们的直觉。因此,在 ES6 中,letconst 替代了 var,成为了更好的变量声明方式。

对象与 JSON

在 JS 中,对象可以脱离而存在,表现为键值对集合的形式,如:

js
let dangdang = {
  name: "Dai Tsingtong",
  age: 19,
  school: {
    name: "Huangshan University",
    address: "44 Daizhen Rd., Huangshan, Anhui 245041",
  },
  play: function () {
    console.log("Daiisuki!");
  },
};

要访问对象成员可以用以下两种形式:

js
const name = dangdang.name;
const age = dangdang["age"];

JSON 使用可读字符串的形式序列化 JS 对象,上例转换为 JSON 就会变成这样:

json
{
  "name": "Dai Tsingtong",
  "age": 19,
  "school": {
    "name": "Huangshan University",
    "address": "44 Daizhen Rd., Huangshan, Anhui 245041"
  }
}

所有变量名(键)变为字符串,值尽可能保留原样。这样的形式便于存储和传输数据。

常用 JSON 类提供的两个静态方法来进行转换:

函数描述
parse()用于将一个 JSON 字符串转换为 JavaScript 对象
stringify()用于将 JavaScript 对象转换为 JSON 字符串

严格模式

严格模式(Strict Mode)是 ES5 新增的一种运行模式。使用严格模式,你就要像编写 C++ 程序一样严谨,这能让你产出高质量的代码。

严格模式有以下的意义:

  1. 取消了 JavaScript 语言的一些不合理、不安全的部分,比如禁止使用 with 语句。
  2. 严格模式强制进行变量声明,不允许直接使用未声明的变量,从而减少由于拼写错误或者其他原因导致变量被隐式定义所带来的异常情况。
  3. 函数中的 this 指向是 undefined而非默认情况下的全局对象 window,避免了因为函数被意外调用而导致 this 指向失误的问题。
  4. 禁止删除变量或函数,从而避免了意外破坏对象的问题。

为了启用它,需要在文件或函数开头(取决你需要的作用域)添加:

js
"use strict";

类与原型

类是用于创建对象的模板。 我们使用 class 关键字来创建一个类,类体在一对大括号中,我们可以在大括号中定义类成员的位置,如方法或构造函数。

每个类中包含了一个特殊的方法 constructor(),它是类的构造方法,这种方法用于创建和初始化一个由类创建的对象。

创建一个类的语法格式如下:

js
class ClassName {
  constructor() {
    // operation...
  }
}

类声明和类表达式的主体都执行在严格模式下。 比如,构造方法、静态方法、原型方法、getter 和 setter 都在严格模式下执行。如果你没有遵循严格模式,则会出现错误。

TIP

虽然 JS 支持面向对象特性,但并不是万物基于对象(object),而是基于原型(prototype)。

事实上,直至 ES6 发布前,JS 想实现类的效果,还必须依靠构造函数原型链来模拟。而就算之后 JS 有了类,也是个基于原型来包装的语法糖,即其与 Java 等语言实现类的方法并不完全相同。

因此说(模块和)函数永远是 JS 里的”一等公民“,制造类和方法只会增加代码的复杂性。

在 JavaScript 中,每一个对象都有一个内部属性 [[Prototype]],它指向了一个原型对象。当我们访问一个对象的属性时,如果该属性不存在于该对象上,则会沿着该对象的 [[Prototype]] 指向的原型对象查找,直到找到该属性或者到达原型链的顶端为止。

原型对象可以是其它对象(非狭义的对象),也可以是 null。当我们访问一个对象的属性时,如果该属性不存在,则该对象会去查询它的原型对象,以此类推一直到原型链的顶端。如果都没有找到,则返回 undefined

原型链就是由一系列原型对象组成的链表结构,每个对象都拥有一个指向它原型对象的引用,这样便形成了一个从当前对象逐级向上查找的链式结构。

我们可以使用 Object.create() 方法和构造函数来创建对象,并继承原型。当我们使用构造函数创建一个对象时,该对象的 [[Prototype]] 指向构造函数的原型对象。

若想进一步了解 JS 设计的哲学,可以阅读关于原型的分析

遵从 CC BY-NC-SA 4.0