引言
JavaScript 是每个程序员无法回避的编程语言。它依托浏览器的支持,牢牢占据着前端编程的市场,又凭借nodejs,在服务端编程也占有一席之地。很多程序员对它是爱恨交加,爱它的灵活方便,恨它过于灵活的类型转换,复杂的包管理等等。今天,我想以一个 非JavaScript程序员的角度,来聊聊我所了解的 JavaScript,作为这些年来对它反复浅尝辄止的一个总结。
网页
最早接触到 JavaScript,是十多年前的QQ空间。当时的QQ空间是一个精致的网页,可以由用户自行设置,好看的装扮需要用Q币购买,或者充值黄钻才能获得。而很多人不想充钱,又想让自己的空间不那么普通,于是有人发现直接在地址栏输入一段神秘代码,就能获取装扮。那时网上最常被搜索的就是“QQ空间装扮代码”。具体的原理我也没搞清,到底是开发人员故意留的免费装扮,还是被人找到了后门。总之,后来我在知道,那些代码里有些就是 JavaScript。
时间再往前追溯,到互联网刚刚出现的年代,最早的网络只有 HTML,毕竟网速和显示设备的限制摆在那里,人们对于能看到纯文字的网页已经很惊奇了。后来渐渐地人民群众对于美好生活的追求提高了,相应地网页也要有更丰富多彩的效果。于是有了 CSS样式,再后来,人们想在浏览器里运行代码,换句话说,在用户的设备上直接运行通用的代码。于是出现了各种备选项,比如 Java 等等。最后是网景公司推出的 JavaScript 获得了最广泛的采用,这个名字据说也是为了蹭当年 Java 的热度。
为了让普通人最快速度上手,获取最多的用户,JavaScript 也有了一些特殊的适配,例如在语法上,字符串可以用单引号,也可以用双引号;表达式的末尾可以加分号,也可以不加分号。这样避免了程序员因为语法上的好恶而放弃这门语言。
在功能上,提供方便的类型转换,数字、数组、函数都可以直接被 +
转换为字符串:
let a = 1;
a += "2" // '12'
let b = [1, 2, 3];
b += "2" // '1,2,32'
let c = () => {};
c += "2" // '() => {}2'
这样的设计也许是因为网页是需要显示给用户看的,数据被转换为字符串,是一种经常会使用到的操作。
OO
忘了是在什么时候,可能是刚刚接触编程的时候吧,就常常听到人说起:“JavaScript 里面一切都是对象”,那时候搞不懂啥是对象。现在我愿意将其理解为一种类似哈希表的结构,里面有一些预制的项,例如,.constructor.name
就表示这个对象是由哪个类创建的,默认是 "Object" 类。数字是 "Number" 类,false
是 "Boolean" 类。但我现在至少可以举出两个例子来反驳上面这种论断,undefined
和 null
都不是对象。
Object Oriendted(面向对象)的编程概念是由 Alan Kay 提出的,最早好像运用于 SmallTalk 语言里面。强调的是在编程时模拟现实世界的物体,比如汽车,苹果,动物等等,都可以看作是一个对象,对象之间通过消息传递来交流。JavaScript 里面有多少真正使用了面向对象的原则我不敢说,个人觉得,除了使用了 Object 这个名字,其它和面向对象的本意似乎没有多少关系,当然,面向对象和编程里的一些其它概念一样,早已严重偏离了其本意。
在 JavaScript 里有两种赋值,一种是值(value)传递,一种是引用(reference)传递。基础的数据结构,例如数字,布尔值,undefined
,null
是使用值传递,即赋值到新的变量时,是新建了一个数据, 与原来的数据再无瓜葛:
let a = 9;
let b = a;
a += 1; // 10
b // 9
而那些更复杂的对象,都是使用引用传递,即把数据的地址传递给新变量,相当于变量只不过是访问数据的一个入口,而不是数据本身:
let a = {x: 1};
let b = a;
a.x += 1;
b.x // 2
这种对reference(引用)的大量使用,似乎已经成为了面向对象的标志性特征。借助这种机制,程序员可以很方便地操作多个关系错综复杂的对象,这种情况在网页编程中十分常见。所以,也可以说是JavaScript为了适应网页编程而做出的设计。比如,一个页面上可能有十几个按钮,十几个输入框,各种文本框,它们之间可能存在复杂的交互,这时这种隐式指针操作起来就很方便:
let a = {x: 1}
let b = {child: a}
b.child.x += 1;
a.x // 2
函数
函数有什么特别的,哪个语言里面没有函数是吧?JavaScript 的函数也不例外,我们知道在一些别的语言里是有Module(模块)这种东西的,函数必须定义在某一个模块里面。JavaScript其实也差不多,函数必须属于某一个对象。有人说不对啊,我直接写一个:
function f() {
}
this
这样的函数是不是不属于任何对象呢?不是的,因为 JavaScript 本身是有一个全局的对象,在浏览器里就是 window
, 在 nodejs 里我不知道是什么,但肯定也是有一个全局对象的。在函数里面,使用 this
就可以访问它所在的对象:
let a = {
name: "a",
f: function() {
return this.name
}
}
a.f() // 'a'
但要注意的是,JavaScript 的函数分为两种,function
函数和箭头函数,箭头函数无论在哪里被调用,里面的this
都是其定义时所在定义域(scope)的this
:
let a = {
name: "a",
f: function() {
return () => this.name
}
}
a.f()() // 'a' function函数捕获了外层的对象,赋值到其定义域内的 this 上,内部的箭头函数继承了这一 this
定义域 scope
这里顺带提一嘴定义域的概念,在 JavaScript 里面,只有 Function 能够创造自己的定义域:
let a = {
x: x = 1
}
x // 1 普通对象是无法创造定义域的,所以我们能够从外部访问到 x
let f = () => {
y = 1
}
y // y is not defined 函数可以创造自己的定义域,从外部无法访问到
class 也是一种 Function。这个在其它语言里也算是比较常见的设计,将数据类型分为两大类:函数和值。对象是属于值
这一类的。
闭包
JavaScript 的 mutable 属性还导致函数有了一种神奇的玩法,也是面试官最爱 —— 闭包。说实话我觉得这东西一般人用不好,但不妨碍大神们把它玩出花来。简单来说,我们可以创造一个函数,同时创造一个只有这个函数能够访问的变量:
let add = (() => {
let x = 0;
return () => x += 1;
})();
add() // 1
add() // 2
这基于的还是Function的scope规则:函数体内定义的变量,在函数体外无法访问。这有什么好处呢?就是在其他语言里面有类似 “private” 关键词,可以定义对象私有的项(field),而 JavaScript 是没有这种东西的,对象所有的项都是公开的。闭包使得JavaScript 里也可以有私有项。
event loop
最后聊聊我理解的 event loop 吧。因为 JavaScript 一般情况下是单进程运行的,这个进程有一个消息队列,运行时会不定期地检查消息队列里的消息,或者说事件(Event),按照FIFO的顺序来处理这些事件:
setTimeout(() => console.log('1')) // 塞进 mailbox
setTimeout(() => console.log('2')) // 塞进 mailbox
console.log('0') // 打印
// 终于有时间可以检查 mailbox
// 0
// 1
// 2
个人感觉 JavaScript 是把单进程的能力开发到了极致,提供了各种语法和工具来方便程序员在单进程的前提下去写异步的代码。这不失为一条独特的道路。
总结
我很喜欢JavaScript这门语言,它简单明了,易于上手。尽管在平时工作中我用它的机会不多,但在需要时,它总能帮我很快地解决问题。也是因为它的灵活性,暗藏了一些坑,一不留神可能踩进去,我的建议是对于第三方库要审慎使用,多写测试用例。
https://www.bilibili.com/vide...
本文参与了SegmentFault 思否写作挑战赛,欢迎正在阅读的你也加入。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。