入门JavaScript(一):印象与语法
学习JavaScript之前,我们得知道这是一门怎样奇葩的语言:
- 这门语言有长达近30年的历史
- 设计这门语言只用了十天
- 曾长期没有官方的包管理机制
- 直到现在,面向对象系统也非常粗糙
- 违反直觉的隐式类型转换能把新手干懵
- 最初设计的目的只是让网页更生动,但现在啥都能用JS写
- Python,C等语言一般只有一个主流的解释器/编译器,但JS有浏览器(Chrome/Edge)内置的V8解释器、服务端的虚拟机nodejs(以及更多,Deno、Bun等),两者的应用场景、支持的功能都有本质不同
理所当然地,JavaScript存在各种演化遗留问题,甚至显得滑稽臃肿,与“优雅”毫不沾边。但无可质疑的是:
- 这是唯一被所有浏览器支持的编程语言
- 极其关注事件的异步处理
- 拥有最繁荣的开发社区
- 有许多语言在编写之后,需要转换为JS运行。例如TypeScript,Dart,Kotlin
这也为我们的学习带来便利。只要你打开浏览器再按下 F12,就会出现一个控制台(console),在里面就可以写JS了!比如,打印一个 Hello World!

那么,让我们深吸一口气,推开JavaScript的大门吧!
// print
console.log('Hello, World!');
/* multi-line
comments */
const name = 'william';
let age = 25;
console.log(name + ' ,' + age + ' years old.');
console.log(`${name}, ${age} years old.`);
如上方代码所示,JS单行注释使用 //,多行注释使用 /* */,每句代码末尾需要 ;。其实 ;并非严格要求,但为了避免错误,最好都加上。
用 const关键字声明常量,let关键字声明变量。一个好习惯是除非有必要,默认使用常量。你可能会看到有人用 var声明变量,但这种语法已经过时了。
单引号与双引号没有区别。反引号(键盘Tab上方的那个键)可以用于插值表达式,在内部以 ${x}的形式引用变量。
数据类型
JS的数据类型有 String, Number, Boolean, Array, Object等,后两者分别是列表和键值对。可以通过类似 x instanceof Array的方式判定
String object
对于字符串,常用的方法有
// 取字符
s[0]
// 按起止索引取字符串
s.substring(0, 5)
// 检测子字符串
s.includes('wo')
// 返回字符串的位置,找不到返回-1
s.indexOf('wo')
// 拼接
s1 + s2
// 字符串插值
`hello, ${name}`
// 分割
s.split('\n')
// 去除前后空格
s.trim()
// 转为string原始类型
x.toString()
// 增加对null, undefined的支持
String(x)
使用 //包裹正则表达式
// 正则匹配,返回Array object的匹配列表
s.match(/pattern/)
// 正则匹配,返回boolean原始类型
/pattern/.test(s)
Number object
对于数字,常用的方法有
// 转为number原始类型,null返回0,undefined返回NaN
Number(x)
// 保留两位小数,返回string
x.toFixed(2)
// 保留两位有效数字,返回string
x.toPrecision(2)
NaN属于 Number类型,代表Not a Number,它与任何值计算都会得到 NaN
null和 undefined代表空值,只在少数时候区分两者
Boolean object
Boolean(x)
// true
1 == true
0 == false
用 && || !进行逻辑运算。JS近期引入了控制合并运算符,如 a ?? b,意为当a不为空时返回a,为空时返回b
Array object
数组,可嵌套,可包含任意类型,可具名。常用的方法有
let l = [1,'B', [2, 'C']]
l[0]
// 快速创建数组
l = Array.from({ length: 5 }, (_, index) => index * 2)
// 返回元素个数
l.length
// 返回元素索引
l.indexOf('B')
// 按起止索引切片
l.slice(0,1)
// 向末尾添加元素
l.push()
// 向头部添加元素
l.unshift()
// 排序,无返回值
l.sort()
// 返回新array。会展平追加的array
l.concat(4,[5,6])
// 合并array为字符串
l.join(', ')
// 判断包含,但仅对一维数组有效
l.includes('hello')
// 判断包含,对二维及以上的数组
l.some(item => item[0] === "A" && item[1] === "B")
// 筛选
l.fliter(item => item > 2)
// 根据子元素的count属性排序
l.sort((x, y) => {return x.count - y.count})
可以使用解构赋值,快速为新变量赋值
let [el1, , el3] = l
let [el1, ...el23] = l
Object object
无序键值对,键需要是string,部分情况可以省略引号;值可以是任意类型
从面向对象的角度看,每个键都是对象的属性,当值是函数时,也就给对象绑定了方法
// 无论key是否加引号,都被转为字符串
// 直接书写变量名的项,会自动解析为'变量名':值
const city = 'Beijing'
let d = {name:'C', age:20, 'mid-school': 'No.1', city}
// .和[]是等效的
d.name
d['mid-school']
d?.name
// 删除元素
delete d.age
// 判断是否包含某属性
'age' in d
// 获取键,返回数组
keys(d)
// 获取值,返回数组
values(d)
// 获取键值对,返回数组
Object.entries(d)
// 合并
const d3 = { ...d1, ...d2}
// 判断属性是否存在
"age" in d
// 合并其他Object的属性
Object.assign(res, d1, d2, d3)
可使用解构赋值,顺序不敏感,甚至可以指定默认值
// 使用key作为新变量名
let {name, age} = d
// 指定新的变量名
let {name: var1, age: var2} = d
// 指定默认值
let {name: var1, age: var2, birthday='0102'} = d
可以像这样将变量的值指定为key,称为计算属性
const keyA = "age";
let d = {name:'C', [keyA]:20, 'mid-school': 'No.1'}
创建属性时,可以直接引用变量,达到简写的效果
const name = "william"
let d1 = {name: name, age: 20}
let d2 = {name, age: 20}
由于Object存储的是引用而不是值,即使用 const声明变量,仍然可以修改属性。除非换掉整个Object
const d = {name:"william"}
// 正常
d.age = 20
// 报错
d = {name: "sigma"}
使用可选链 ?.避免报错
const d = {A:1}
d.B // 不报错
d.B.C // 报错
d.B?.C // 不报错
d.B?.C?.D // 不报错
原始类型与引用类型
我们按照其他编程语言的思路,概览了JS的基本数据类型。但JS有个特点,是可感知地区分了原始类型和引用类型,以上所述是根据引用类型划分的。
形如 "123", 123, true的值属于原始类型,而 new String("123")的值属于引用类型。原始类型只有 undefined, boolean, string, number, object, function, symbol七类,使用 typeof查看
引用类型都是 object原始类型,包括以上所述 String, Array, Object等。其中 String, Number, Boolean与原始类型 string, number, boolean对应
原始类型并不支持如 s.substring的方法,实际上方法本就是 object的特权。当我们使用 let x = 'hello'时,创建了一个 string原始类型。在调用如 s.substring的方法时,后台会先创建一个 String引用类型实例(有时也叫包装类型),调用相应方法,再销毁实例还原到原始类型
所以我们才会看到这样的结果
typeof("123") // string
typeof(String("123")) // string
typeof(new String("123")) // object
"123" instanceof Object // false
"123" instanceof String // false
new String("123") instanceof Object // true
new String("123") instanceof String // true
形如 [1,2,3], {a:1}的值本就是引用类型,typeof的结果都是 object
typeof([1,2,3]) // object
typeof({a:1}) // object
[1,2,3] instanceof Object // true
[1,2,3] instanceof Array // true
({a:1}) instanceof Object // true
判断相等
原始类型与引用类型的区分,在判断相等时可能导致预期外的结果。我们仔细梳理一下
对于原始类型 string, number, boolean之间的比较,==会尝试转换为相同的原始类型,然后再比。===则不会转换
// true
1 == '1'
1 == true
0 == false
'1' == true
String(1) === '1'
// false
1 === '1'
对于原始类型 object之间的比较, ==仅在两者在同一个内存地址时,才返回 true。注意通过 new关键字声明的 Number, String等也是 object
// false
({a:1} == {a:1})
new Object({a:1}) == new Object({a:1})
[1, 2] == [1, 2]
new Number(2) == new Number(2)
对于 object和其他原始类型的比较,JS会尝试将 object转换为其他原始类型,然后比较
// true
new Number(2) == 2
总结就是,在大多数场合,建议使用 ===比较。JS不能判断两个 Array, Object内的元素是否相同(这种操作被称为“深度比较”),需要通过第三方包如 Lodash的 isEqual函数实现。
在浏览器的控制台中,需要用JS创建一个 <script>标签,通过CDN链接载入 Lodash包。如果你是在 Github等网站调出的控制台,由于网站的安全策略,该操作会被阻止。可尝试换一个网站开启控制台,比如百度
let script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js';
document.head.appendChild(script);
载入 Lodash包后,包内的方法通过 _.xxx调用。你可能疑惑直接占用 _是否会造成命名冲突,但JS里大家就这么约定俗成地用了下来
_.isEqual({a: 1}, {a: 1});
函数
创建带返回值的函数,有三种形式
function foo(a, b) { return a + b }
const foo = (a, b) => a + b
const foo = (a, b) => { return a + b }
前者是声明式函数,后两者称为箭头函数。return需要显示指定,除非采用第二种方法,即函数体简短、不使用 {}的箭头函数
当箭头函数有0个或多个参数时,()不可省略。仅有一个参数时,()可以省略
JS早期版本不支持设置参数默认值,所以老代码中你可能会看到这样设置默认值
b = b || "default B value"
调用方法时,JS只支持位置参数,不支持关键字参数。所以你会看到许多函数借助解构赋值,实现类似关键字参数的效果
function foo(a=1, b) { return a + b }
foo(b=2) // 报错
function foo({a=1, b}) { return a+b }
foo({b:2})
如果函数绑定到Object中,就被称为方法。声明式函数内部,可以调用 this代指当前对象
let d = {
name: "william",
sayHi: function sayHi() {
return "Hi, " + this.name
}
}
d.sayHi()
// 简写
let d = {
name: "william",
sayHi() {
return "Hi, " + this.name
}
}
而箭头函数的 this,并不代指当前对象,反而代指更外层的上下文
let d = {
name: "william",
sayHi: () => { return "Hi, " + this.name }
}
d.sayHi() // Hi,
let d = {
name: "william",
sayHi() {
const f = () => { return "Hi, " + this.name }
return f()
}
}
d.sayHi() // Hi, william
流程控制
条件语句
if (cond) {cmd1} else if (cond2) {cmd2} else {cmd3}
// 简写
cond ? cmd1 : cmd2
循环语句
// while
while (cond) {cmd}
// do while
do {cmd} while (cond)
// for
for (begin; cond; step) {cmd}
循环体中,使用 break关键字跳出循环,continue关键字立即开始下一个循环。如果需要跳出多层循环,可使用break标签
对于object,可使用 for...in语句遍历所有键
for (const key in d) {
console.log(key)
console.log(d[key])
}
对于Array,可使用 for...of语句遍历所有值
for (const value of l) {cmd}
由于数组也是object,所以也可用 for...in遍历索引,但通常不建议这么做
由于 Object.entries()返回键值对的数组,所以可以如此遍历Object键值对
for (const [key, value] of Object.entries(d)) {
console.log(key, value);
}
此外,数组可以使用 .forEach或 .map方法,将一个函数应用到每个元素上。前者无返回值,后者返回新的 Array
let l = []
d = [{foo:1}, {foo:2}]
d.forEach( item => l.push(item.foo) )
let l = [1, 2, 3]
l = l.map(value => value*2)
// 每个遍历项都可以拆分为value和index
l = l.map((value, index) => value*2 + index)
错误处理
// 错误捕获结构
try {cmd1} catch (err) {cmd2} finally {cmd3}
// 主动抛出异常
throw new Error("Something is wrong")
嵌入HTML
在实际使用中,JS都要插入在HTML文档。可以新建一个文本文件,像这样在 <script>标签中插入JS代码。alert函数的作用是弹出提醒

然后把文件后缀名改为 .html,双击打开,就会看到JS代码的效果。如果你使用 console.log()输出到控制台,记得按 F12查看

当然,你也可以导入另一个JS脚本。但注意指定 src=引入脚本后,当前 <script>标签内部的代码就不会生效了
<script src="/path/to/t.js"></script>
结语
本文我们学习了JavaScript的数据类型、函数、流程控制,以及如何在浏览器控制台、HTML文档中使用。有了这些知识,你可以写出一些打印、算术的小玩意了。
如果需要更多参考,这份JavaScript现代教程和MDN JavaScript参考非常适合。
最后来张梗图,玩得开心!
