JavaScript 基础语法

JavaScript 是用于实现网页动态效果的一种高级语言。作为一个只使用了10天就被开发出来的语言,具有很多为网络编程而生的特点,以及很多有趣的特性。在这一篇中,对 JavaScript 的基础语法进行介绍。后面还有一篇,对JS操作 HTML 的方法和异步特性进行单独介绍。

1. 变量

JavaScript 是一种 弱类型 或者说 动态语言。所有的变量都使用 var 声明。 这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据。

最新的 ECMAScript 标准中定义了7种原始数据类型如下:

  • Boolean - truefalse
  • Null - 用 type of 检验 null数据类型时为Object,但它不是对象,这是JS的一个bug
  • Undefined
  • Number - JavaScript中的所有数字都是浮点数,没有整数
  • BigInt
  • String
  • Symbol (ES6)

除上述原始类型以外,所有变量都为 Object 类型。

JS 的对象是 若干键值对的无序集合。其中,键(属性名)必须为字符串类型,值可以为任意类型(如果键不是字符串类型,需要使用 Map)。

重点类型:参考 JavaScript标准内置对象

Object 类型的变量,可以使用继承自 Object.prototype 的方法,例如:is() - 可以用于判断两个 NaN相等, toString()..

String 类型的变量,可以使用继承自 String.prototype 的方法,例如:charAt(), concat(), slice(), replace()

数组类型的变量,可以使用继承自 Array.prototype 的方法,例如:concat(), filter(), forEach(), join(), indexOf()

Function 类型的变量,可以使用继承自Function.prototype 的方法,例如:apply(), bind(), call()

解构赋值:在变量赋值时,JS 支持解构赋值,也就是按顺序为数组中的多个元素赋值,如下:

1
2
3
4
5
// 赋值后,x = hello, y = JavaScript, z = ES6
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];

// 还支持从对象中获取属性值 - 可以用于函数参数列表传递
({x,y} = {hehe:"test", x: 200, y:100});

2. 函数

可以以下面两种形式声明函数,例子如下:

1
2
3
4
5
6
7
8
function abs(x){
if(x >= 0) return x;
return -x;
}
var abs = function(x){
if(x >= 0) return x;
return -x;
}

参数:传入函数的内容称为参数。 函数声明时,可以用 rest 关键字,接收多传入的关键字,e.g. foo(a, b ...rest)。函数调用时,JS 可以通过 arguments 关键字,获取传入函数中的参数。

JS 中变量的 作用域 最小为一个函数。而 for(...) 等控制流语句或直接 {... } 构成的程序块不是一个作用域,此时,可以使用 let 关键字,声明改变量仅在该代码块中有效。

方法:在一个对象中绑定函数,该函数称为该对象的方法。需要注意的是,在 JS 中,this 关键字永远指向调用该 his 关键字所在方法的对象。因此,某些情况例如函数嵌套时,内部的嵌套函数就无法获取当前对象。可以使用下面方法解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
var studentA = {
birth: 1990,
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
// 如果上面 that 为 this,会报错,找不到 birth 属性
studentA.age();

更灵活地为函数中的 this 绑定对象,可以使用 函数对象的 apply() 方法。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}

var studentA = {
birth: 1990,
age: getAge
};

studentA.age(); // 25
// 如果直接调用 getAge() 函数,报错
// 使用 apply 方法,将 目标对象 与 this 绑定,可以直接调用
// 指定函数中的 this 指向 studentA, 第二个参数为原先应该传入该函数的参数,这里为空
getAge.apply(studentA, []);

Note: apply()方法还可以用于实现对原方法的包装(装饰器)。

高阶函数:接收参数为 函数 的函数称为高阶函数。JS 中 Array 的很多方法为高阶函数。例如,map(), reduce(), filter(), sort()等。在这类函数中,传入的参数可以使用箭头函数,即 =>, 可简化函数体书写,省略函数名,return 关键字(如果函数体只有一句)。

1
2
3
4
5
6
7
function pow(x) {
return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
var arr = [1, 3, 5, 7, 9];
arr.reduce((x, y) => x + y); // 求和

闭包:函数返回值为函数,这一做法称为闭包(closure)。闭包的一个作用是实现 private 的函数成员变量,如下:

1
2
3
4
5
6
7
8
9
10
11
12
function create_counter(initial) {
var x = initial || 0;
// 返回一个匿名对象
return {
inc: function () {
x += 1;
return x;
}
}
}
var c2 = create_counter(10); // 只能在构建返回对象时传入参数
c2.inc(); // 后续无法再获得 x 的值

generator 称为生成器,它类似函数,但可以多次”返回”,例子如下:

1
2
3
4
5
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}

生成器执行时,每遇到一次 yield 就会进行一次返回,然后进入暂停状态-暂停至 yield 进行返回的位置处。可以使用 generator 的 next() 方法将其再次唤醒(or 使用 for(var 'yield_value' of 'generator') 的方法,使它自动执行并逐次返回 yield 的内容),从而从暂停处开始继续执行。直到执行至 return 处,generator 才真正执行完毕。

作用:执行中多次返回,‘记住’函数执行状态;把异步回调代码变为‘同步’代码(ref-AJAX part)

3. 面向对象编程

JS 中使用原型(prototype)的概念实现对象之间的继承关系(注意本质上,JS中没有类和类的实例的区别,都是对象实例)。

具体地,通过将对象的原型属性(obj.__proto__)指向它需要继承的对象,来实现方法的继承。‘类’的编写与写函数一样,但在生成该类型的对象时,需要使用 new 关键字而不是直接调用函数。参考如下的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// tips: 用于构造对象的函数 首字母大写~ 
// 一些工具可以借此帮助检测是否缺少 new 操作
function Student(name) {
this.name = name;
}
// 将公用的方法绑定在 父对象的 prototype 上,提升代码的复用性。
// 如果写在 student 函数中,会在每次 new 时,
// 为每个对象单独开拓一块空间存放单独的 hello 方法
Student.prototype.hello = function () {
console.log('Hello, ' + this.name + '!');
};
// 生成对象 studentA, 继承 Student 的方法
// 使用 new 关键字!!
var studentA = new Student('A');

上述 studentA 对象的原型链如下:studentA ----> Student.prototype ----> Object.prototype ----> null

从ES6开始,JS 引入了关键字 class, 面向对象的实现更加简单了。但需要注意的是,本质上,JS 的继承的内部机制没有变。

使用 class, extends 关键字实现继承的代码例子如下:

1
2
3
4
5
6
7
8
9
10
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}

myGrade() {
console.log('I am at grade ' + this.grade);
}
}

4. 异常处理

类似其他高级语言,JS 中也使用 try ... catch ... finally ... 的形式来处理错误(note: JS 中,程序异常等称为 Error,JS提供一个标准的 Error 对象表示程序错误、异常)。

在 JS 中,需要特别注意的是异步程序执行时,对 Error 的 catch 可能会被跳过。例子如下:

1
2
3
4
5
6
7
8
9
10
function printTime() {
throw new Error();
}

try {
setTimeout(printTime, 1000);
console.log('done');
} catch (e) {
console.log('error');
}

由于 printTime 函数延迟1s 执行,执行 printTime 函数时,catch 函数已经被跳过,所以无法再捕获其中的 Error。因此,对于回调函数中的错误,不可在调用时进行捕获,而应该在回调函数本体内进行捕获。

Ref:

JS中一切都是对象吗?
MDN-web-docs-JavaScript
廖雪峰-JavaScript教程