前言

从这篇起我们来一起学习 JS。

在二十一世纪二十年代的今天,想必不会有人再对 JS 作为一门正儿八经的编程语言的合理地位提出质疑了。而想要获得一门编程语言的比较完备的知识,我们就至少需要从文法(语法词法)、语义、编译时、运行时四个角度去考虑。

由于 JS 在大部分情况下是 解释执行 的,一般不考虑编译时的东西,因此它的基础知识结构就如下图所示:
JS

当然,我们不是语言学家,作为工程技术人员对语义这个东西并不需要过分解释,它就是指一段代码的意思。所以我们学 JS ,说白了就是在学它的文法和运行时

我们之后的内容也会围绕着这两部分展开。但为了真正弄明白 JS 这门语言的设计思路,我觉得还是有必要先从它的历史沿革说起。

1 JS 简史

1.1 仓促诞生

JavaScript 诞生于网景(Netscape)公司。最初 Netscape Navigator 浏览器只能展示静态页面,缺乏动态交互的能力,因此在 1995 年,网景公司决定向浏览器中加入一个 “脚本语言”,他们设想了两条实现路径:

  • 与 Sun 公司合作,嵌入 Java
  • 嵌入 Scheme 语言

然而网景的管理者最终觉得最好的办法是设计一门语法 类似 Java,而不是像现存的 Scheme 等脚本语言的新语言出来。

因此,原本被招来负责嵌入 Scheme 的 Brendan Eich 就花了几周时间搞出来了这么个东西,一开始被称为 Mocha,然后改为 LiveScript,三个月又后改为 JavaScript。

JavaScript 这个名字使它看起来像 Java 派生出来的,误导了不少小白。其实两者的关系也仅仅在语法设计层面,起这么个名字往难听了说就是蹭热度,只能说当时格局小了。

另外据说 Brendan 对要求自己照着 Java 语法设计语言这件事有些不爽,还在网上吐槽过自己的某个“光头老板”。

1.2 明争暗斗

为了与网景分庭抗礼,1996 年,微软在自己的 IE 浏览器中内置了一门新的脚本语言,称为 JScript,去蹭 JavaScript 的热度。这个 JScript 跟 JS 在实现方式上又很大不同,因此开发者很难解决兼容性问题,使得当年许多网页上都会有类似 “best viewed in Netscape” 和 “best viewed in Internet Exploer” 这也的 logo。

同年,网景把 JavaScript 提交到了 ECMA 国际,意在形成一个让所有浏览器厂商遵守的标准规范。1997 年,ECMA 基于 JavaScript 发布了 ECMAScript 的第一个正式版本。

虽然自此有了一个规范摆在那里,但代码怎么执行还是浏览器说了算,在实现上多一些少一些原创一些都有可能,因此 JavaScript 这个称呼还是保留了下来,代指这些差不多又不那么规范的东西。

此后几年,ECMAScript 陆续发布了 2、3、4 版本,然而到 2000 年时,IE 浏览器已经占据了 95% 的市场,可以说 JScript 反倒成了事实上的客户端脚本标准。

其实在 ECMAScript 制定的初期,微软还依据 JScript 提出了一些建议,但后来逐渐停止了跟 ECMA 的合作,而 ES 4 由于争议性比较大最终被弃用了,看起来似乎 JScript 要一统江山了。(有意思的是,ES 4 里最具争议的几个特性,比如生成器、迭代器、解构赋值等在最近的版本里又被加了回来)

1.3 融合统一

天道好轮回,Netscape 的后继者 Mozilla 基金会又发行了 Firefox 浏览器,由于广受好评,没过几年又抢占了 IE 的许多市场份额。

2005 年,Jesse James Garrett 发布了论文:《Ajax: A New Approach to Web Applications》,描述了一种以 JavaScript 为基础的技术,使得页面无需全部重新加载即可响应变化。这种技术的出现极大地丰富了网页的表现能力,引发了 JavaScript 的复兴,在开源社区的主导下诞生了 jQuery、Prototype、Dojo Toolkit、MooTools 等著名的库或工具集。

2008 年,Google 推出 Chrome 浏览器,由于其 JavaScript 引擎在性能上的优势,又一次引发了浏览器市场的洗牌。

也许是终于意识到在内置语言这个问题上争论不休不利于 Web 技术的发展,2008 年 7 月,相关厂商在奥斯陆(Oslo)举办了会议,认为应当携手推动语言的发展,并基于 ES 4 开展了标准的重修工作。

会议后不久,ES 5 正式发布,同时,在这次会议上提出的代号为 Harmony 的项目最终于 2015 年开花结果,也就是我们熟知的 ES 6(ES 2015)。

可以说,直到 ES 6 发布,用于浏览器端的编程语言 才真正有了统一的广受认可的标准。实际上从此时开始,ECMAScript 每年都会发布新的版本,其虽然脱胎于 JavaScript,但显然背后的推动力量已经上了一个新台阶。

ES 6 在技术层面也是具有里程碑意义的重要版本,它补充了一些十分关键但不被旧版浏览器支持的特性,促进了浏览器的新一轮迭代,丰富了语言的能力,甚至形成了新的编程风格。在过去的几年里,使用 ES 6 改写代码、处理使用 ES 6 导致的兼容性问题也是前端的重要工作之一。

虽然严格意义上一统江湖的是 ECMAScript,但由于 JavaScript 这个名字已经太过深入人心,我们在大部分情况下还是习惯性地用它来代指这门语言。

前面提到,最初对 JS 的定位就是一门 “脚本语言(Script,解释执行的语言)”,但越来越多的人不再把 JS 当作一门脚本语言。实际上 “解释执行”“编译执行” 并非是语言本身的特性,而是 编译器/解释器/引擎 需要做的事情,因此 脚本语言/非脚本语言 这种分类方式其实并不严谨。

现代浏览器的颠覆性革新之一就是使用 JIT(Just In Time)技术,对一段 JS 代码动态地选择究竟使用解释还是编译执行,所以忘掉 JavaScript 叫 Script 这件事吧。(另外其实 Java 也采用了类似的技术,使得它可以被解释执行)

2 JS 面向对象

2.1 面向对象与基于对象

在 JavaScript 诞生至今的二十五六年里,对于它到底是不是面向对象的编程语言这件事经常有争议,也有不少人强调,JS 不是 “面向对象(Object-Oriented)”,而是 “基于对象(Object-Based)”

这个问题,怎么说呢,一部分是文字游戏,一部分又涉及语言的本质,需要认真理解。

首先,ECMAScript 规范中明确指出 ES 是面向对象的编程语言。

ECMAScript is an object-oriented programming language for performing computations and manipulating computational objects within a host environment.

当然,我们可以暂且认为这只代表了编写规范的人的想法,究竟是不是面向对象,还要看面向对象的定义又是什么。

oh,不好意思,面向对象至今仍然没有统一的定义。

实际上计算机领域里少有公理一般的概念,同一个术语在一个时期里并存多种解释的情况是广泛存在的,这些解释里有的认可度高,有的认可度低,有的更抽象,有的更贴切,有的全面一些,有些片面一些。

如何回答什么是面向对象,与问题的语境密切相关。在 Java 领域,你就得咬死面向对象三大特性——封装、继承、多态。最近一朋友去面字节,反馈还是有这种问题,他称之为 “Java 八股”。😂

说白了就是,一部分工程师(以 Java 为代表)认为 “面向对象” 是一个内涵确定的概念,判断一个语言是否是面向对象,存在一组必要条件,不符合其中之一,就不是面向对象,即使有对象这个东西在,也只能称为 “基于对象”

然而面向对象,其实属于不断被丰富又不断被抽象的一个概念。

丰富,意思是不断地有新的特性被加入,这些特性使得面向对象这种设计能够展现更大的能力。

而抽象,意思是面向对象的核心概念在收敛,从而扩大了满足面向对象基本要求的语言范围。

但作为前端工程师,对面向对象这个问题就可以认识地更灵活一些。面向对象语言的基本条件就是,它是 “面向” -> “对象” 的。面向,就是针对或以···为主体的意思,而对象这个东西,我们认为它具有以下特征:

  • 唯一标识性:每个对象是唯一且可以标识的,即使看起来完全相同,也并非同一个
  • 对象有状态:对象具有状态,同一对象可能处于不同状态之下
  • 对象具有行为:即对象的状态,可能因为它的行为产生变迁

“对象” 又是一个可能变化的概念,但上述特征是比较贴近我们对现实世界中的事物的认知的本质的,因此我们姑且可以遵循这个定义去判断一个语言是否实现了完备的对象系统。

对于 JS 来说,每个对象在内存中都有独立的地址,且可以通过对象名访问,符合唯一标识性。JS 的对象有状态属性、有行为属性,符合后两个特征,它的对象系统是完备的。

只不过,JS 走了一条与 C++、Java 等老大哥不同的道路,那就是基于 “原型” 来描述对象。

2.1 基于原型的面向对象系统

在面向对象的世界里,使用 “类(Class)” 这个概念描述对象是如此成功,以至于人们认为面向对象一定要有类这个东西。

然而,JS 的设计者,Brendan 老哥使用了另一套理念去实现对象系统,那就是基于 “原型” 描述对象。

我们前面提到,JS 在设计之初被要求贴近 Java 的语法,因此 Brendan “被迫” 引入了一些语言特性,使得它能够用类似 Java 语言的方式去操纵对象。这些特性只是一种模拟,或者说是语法糖,并没有运行时的支撑。

什么意思呢?Java 的每个类都是一种真正存在的类型,而 JS 中的对象均属于 Object 这一种类型,所谓的 Class 只不过是对象的一个私有属性。

而且,JS 初期对 “类系统” 的模拟主要依靠 new、this 等关键字,不但无法模仿继承等关键特性,还存在一些反直觉的现象。而崇尚类系统的开发者们又折腾出了不同的解决方案,使得 JS 中对对象的使用五花八门,造成了许多的麻烦。直到 ES 6 问世,提供了 class 关键字来定义类,才使得 JS 对类的模拟方式终于统一起来。

但是我们大可不必非要去模仿类系统,对于 ES 6 之后版本的 JS 来说,使用原型系统本身的特性是十分自然的。

原型这个概念乍一看不太好理解,感觉背后有很深的含义在,但其实原型就是原型的字面意思。

在 Java 中,假如我们想要获得一个英短金渐层对象,需要先定义一个英短金渐层类,这个类可能继承自猫类,然后根据类 new 一个对象出来。

而 JS 中,我们可以直接根据已经存在的英短银渐层这个原型(也是一个对象)去创建金渐层对象,不需要再定义一个类,对象与原型之间也不必有抽象层次的限制,只需要描述与原型的区别即可。

而且,从运行时的角度来说,根据原型创建的对象不需要保存原型的属性,但却可以顺着 “原型链” 访问自己没有而原型有的属性。

什么是原型链?一个属性如果对象没有,就会根据 [[prototype]] 字段访问原型,查找原型中的字段。在 JS 中,所有对象的原型都可以追溯到 Null,到 Null,或者说其实是 Object,但 Object 本身的原型是 Null。

因此,我们也可以通过原型对象控制所有基于它实现的对象的行为,这是基于类的系统所不具备的能力。JS 也算是在一定程度上证明了基于原型这条路一样走得通,孰优孰劣目前还下不了定论。

在语法层面,今天的 JS 有四套操纵对象的方式:

  • {} / . / [] / Obejct.defineProperty,不依赖类或原型创建对象、访问属性、定义新属性的基础方法
  • Obejct.create / Object.setPrototypeOf / Object.getPrototypeOf,基于原型的描述方法
  • new / class / extends,模拟类的方式
  • new / function /prototype,原始的模拟类的方式,ES 3 版本之后不推荐使用

这就造成了同样的一门语言,不同的公司、不同的人写起来可能完全是不同的风格,新手可能会因为这种现象大为头疼。但是我们应该认识到,几种写法背后其实是同一套机制在支撑。

好了,这篇文章我们先从 JS 原型系统这么一个关键点切入,踏出理解 JS 本质的第一步。在之后的文章中,我们会继续深入剖析 JS 的运行时。

下一步

下一篇文章,我们就来详细探讨一下 JavaScript 的类型系统,理解各种类型在内存中的组织方式以及使用时的注意事项。


记忆拾遗:

JS 这门语言,有太多的众说纷纭、似是而非。我们虽然可以通过 ECMA 标准来论证真伪,但有时候一些错误的说法在实践上却可以取得正确的结果,因此我们自己可能都感觉不出来有问题。

也是受到 winter 大神的启发,我才意识到理解 JS 的本质应该从运行时入手,纵使语法特性不断迭代,JS 的运行时依旧遵循着最初的设计路线。

然而,各类面经又热衷于传递拗口的语法层面的概念。大家如果自己参与过出题这种活动就会明白,有些题的出处可能就是哪个人在博客或者什么地方随便说了一句话,被出题的人搜到了拿来凑数,没想到一传十十传百,最后比定义还像定义,你不知道还显得像个菜鸡。

网上严谨一些的教程,比如 MDN,大部分时候解释的是对的,但就是遇到稍微复杂一点的问题,看着感觉十分费劲。这也是因为 MDN 的中文并非原创,而是从英文原版翻译而来,首先外国人的思维模式跟我们有出入,再加上可能并不那么专业的翻译(MDN 是一个开源项目),相当于被加了两层 debuff。

所以有时候我们学习知识确实比英语国家要费劲一些,希望有一天我们能在更多领域让别人试图理解我们的意思吧。嗯,从这个层面来说,对尤雨溪大神,我真的 respect,同叫 Evan,我很惭愧。😝

年后的第一个三天小长假,大半天算是陪各位度过了,明天好好休息一天,下周见。

点赞(0) 打赏

Comment list 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部