JavaScript 的诞生

76 阅读9分钟

诞生背景

"1994 年,网景公司(Netscape)发布了 Navigator 浏览器 0.9 版。这是历史上第一个比较成熟的网络浏览器,轰动一时。但是,这个版本的浏览器只能用来浏览,不具备与访问者互动的能力。......网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。" 网页脚本语言到底是什么语言?网景公司当时有两个选择:一个是采用现有的语言,比如 Perl、Python、Tcl、Scheme 等等,允许它们直接嵌入网页;另一个是发明一种全新的语言。
这两个选择各有利弊。第一个选择,有利于充分利用现有代码和程序员资源,推广起来比较容易;第二个选择,有利于开发出完全适用的语言,实现起来比较容易。
就在这时,发生了另外一件大事:1995 年 Sun 公司将 Oak 语言改名为 Java,正式向市场推出。
Sun 公司大肆宣传,许诺这种语言可以"一次编写,到处运行"(Write Once, Run Anywhere),它看上去很可能成为未来的主宰。
网景公司动了心,决定与 Sun 公司结成联盟。它不仅允许 Java 程序以 applet(小程序)的形式,直接在浏览器中运行;甚至还考虑直接将 Java 作为脚本语言嵌入网页,只是因为这样会使 HTML 网页过于复杂,后来才不得不放弃。

Brendan Eich


此时,34 岁的系统程序员 Brendan Eich 登场了。1995 年 4 月,网景公司录用了他。Brendan Eich 的主要方向和兴趣是函数式编程,网景公司招聘他的目的,是研究将 Scheme 语言作为网页脚本语言的可能性。
仅仅一个月之后,1995 年 5 月,网景公司做出决策,未来的网页脚本语言必须"看上去与 Java 足够相似",但是比 Java 简单,使得非专业的网页作者也能很快上手。这个决策实际上将 Perl、Python、Tcl、Scheme 等非面向对象编程的语言都排除在外了。Brendan Eich 被指定为这种"简化版 Java 语言"的设计师。
但是,他对 Java 一点兴趣也没有。为了应付公司安排的任务,他只用 10 天时间就把 Javascript 设计出来了。
总的来说,他的设计思路是这样的:

  • 借鉴 C 语言的基本语法;
  • 借鉴 Java 语言的数据类型和内存管理;
  • 借鉴 Scheme 语言,将函数提升到"第一等公民"(first class)的地位;
  • 借鉴 Self 语言,使用基于原型(prototype)的继承机制。

所以,Javascript 语言实际上是两种语言风格的混合产物----(简化的)函数式编程+(简化的)面向对象编程。这是由 Brendan Eich(函数式编程)与网景公司(面向对象编程)共同决定的。
如果不是公司的决策,Brendan Eich 绝不可能把 Java 作为 Javascript 设计的原型。作为设计者,他一点也不喜欢自己的这个作品。

微软采纳

JavaScript 推出后在浏览器上大获成功,微软公司在不久后就为 Internet Explorer 3 浏览器推出了 JScript,以与处于市场领导地位的网景产品同台竞争。JScript 也是一种 JavaScript 实现,这两个 JavaScript 语言版本在浏览器端共存意味着语言标准化的缺失,发展初期,JavaScript 的标准并未确定,同期有网景的 JavaScript,微软的 JScript 双峰并峙。除此之外,微软也在网页技术上加入了不少专属对象,使不少网页使用非微软平台及浏览器无法正常显示,导致在浏览器大战期间网页设计者通常会把“用 Netscape 可达到最佳效果”或“用 IE 可达到最佳效果”的标志放在主页。随着 Internet Explorer 4 的发布,微软引入了动态 HTML 的概念,但语言实现和不同专有化的文档对象模型的差异仍然存在,成为网络上普及使用 JavaScript 的阻碍。

标准化

1996 年 11 月,网景正式向 ECMA(欧洲计算机制造商协会)提交语言标准。1997 年 6 月,ECMA 以 JavaScript 语言为基础制定了 ECMAScript 标准规范 ECMA-262。JavaScript 成为了 ECMAScript 最著名的实现之一。除此之外,ActionScript 和 JScript 也都是 ECMAScript 规范的实现语言。尽管 JavaScript 作为给非程序人员的脚本语言,而非作为给程序人员的脚本语言来推广和宣传,但是 JavaScript 具有非常丰富的特性。
一般来说,完整的 JavaScript 包括以下几个部分:

  • ECMAScript,描述了该语言的语法和基本对象;
  • 文档对象模型(DOM),描述处理网页内容的方法和接口;
  • 浏览器对象模型(BOM),描述与浏览器进行交互的方法和接口。

JavaScript 的基本特点如下:

  • 是一种解释性脚本语言(代码不进行预编译);
  • 主要用来向 HTML 页面添加交互行为;
  • 可以直接嵌入 HTML 页面,但写成单独的 js 文件有利于结构和行为的分离。

JavaScript 常用来完成以下任务:

  • 嵌入动态文本于 HTML 页面;
  • 对浏览器事件作出响应;
  • 读写 HTML 元素;
  • 在数据被提交到服务器之前验证数据;
  • 检测访客的浏览器信息;
  • 控制 cookies,包括创建和修改等。

不同于服务器端脚本语言,例如 PHP 与 ASP,JavaScript 主要被作为客户端脚本语言在用户的浏览器上运行,不需要服务器的支持。所以在早期程序员比较青睐于 JavaScript 以减少对服务器的负担,而与此同时也带来另一个问题:安全性。而随着服务器变得强大,现在的程序员更喜欢运行于服务端的脚本以保证安全,但 JavaScript 仍然以其跨平台、容易上手等优势大行其道。同时,有些特殊功能(如 AJAX)必须依赖 JavaScript 在客户端进行支持。随着引擎如 V8 和框架如 Node.js 的发展,及其事件驱动及异步 IO 等特性,JavaScript 逐渐被用来编写服务器端程序。且在近几年中,Node.js 的出世,让 JavaScript 也具有了一定的服务器功能,且在某些方面比 PHP 的效果更为显著。

Javascript 的 10 个设计缺陷

  1. 不适合开发大型程序。
    Javascript 没有名称空间(namespace),很难模块化;没有如何将代码分布在多个文件的规范;允许同名函数的重复定义,后面的定义可以覆盖前面的定义,很不利于模块化加载。

  2. 非常小的标准库
    Javascript 提供的标准函数库非常小,只能完成一些基本操作,很多功能都不具备。

  3. null 和 undefined
    null 属于对象(object)的一种,意思是该对象为空;undefined 则是一种数据类型,表示未定义。

    typeof null; // object
    typeof undefined; // undefined
    

    两者非常容易混淆,但是含义完全不同。

    var foo;
    alert(foo == null); // true
    alert(foo == undefined); // true
    alert(foo === null); // false
    alert(foo === undefined); // true
    

    在编程实践中,null 几乎没用,根本不应该设计它。

  4. 全局变量难以控制
    Javascript 的全局变量,在所有模块中都是可见的;任何一个函数内部都可以生成全局变量,这大大加剧了程序的复杂性。

    a = 1;
    (function(){
       b=2;
       alert(a);
    })(); // 1
    alert(b); //2
    
  5. 自动插入行尾分号
    Javascript 的所有语句,都必须以分号结尾。但是,如果你忘记加分号,解释器并不报错,而是为你自动加上分号。有时候,这会导致一些难以发现的错误。比如,下面这个函数根本无法达到预期的结果,返回值不是一个对象,而是 undefined。

    function(){
      return
       {
        i=1
       };
    }
    

    原因是解释器自动在 return 语句后面加上了分号。

    function(){
      return;
       {
         i=1
       };
    }
    
  6. 加号运算符
    +号作为运算符,有两个含义,可以表示数字与数字的和,也可以表示字符与字符的连接。

    alert(1+10); // 11
    alert("1"+"10"); // 110
    

    如果一个操作项是字符,另一个操作项是数字,则数字自动转化为字符。

    alert(1+"10"); // 110
    alert("10"+1); // 101
    

    这样的设计,不必要地加剧了运算的复杂性,完全可以另行设置一个字符连接的运算符。

  7. NaN
    NaN 是一种数字,表示超出了解释器的极限。它有一些很奇怪的特性:

    NaN === NaN; //false
    NaN !== NaN; //true
    alert( 1 + NaN ); // NaN
    

    与其设计 NaN,不如解释器直接报错,反而有利于简化程序。

  8. 数组和对象的区分
    由于 Javascript 的数组也属于对象(object),所以要区分一个对象到底是不是数组,相当麻烦。Douglas Crockford 的代码是这样的:

    if ( arr &&
       typeof arr === 'object' &&
       typeof arr.length === 'number' &&
       !arr.propertyIsEnumerable('length')){
             alert("arr is an array");
    
    }
    
  9. == 和 ===
    ==用来判断两个值是否相等。当两个值类型不同时,会发生自动转换,得到的结果非常不符合直觉。

    "" == "0" // false
    0 == "" // true
    0 == "0" // true
    false == "false" // false
    false == "0" // true
    false == undefined // false
    false == null // false
    null == undefined // true
    " \t\r\n" == 0 // true
    

    因此,推荐任何时候都使用"==="(精确判断)比较符。

  10. 基本类型的包装对象
    Javascript 有三种基本数据类型:字符串、数字和布尔值。它们都有相应的建构函数,可以生成字符串对象、数字对象和布尔值对象。

    new Boolean(false);
    new Number(1234);
    new String("Hello World");
    

    与基本数据类型对应的对象类型,作用很小,造成的混淆却很大。

    alert( typeof 1234); // number
    alert( typeof new Number(1234)); // object
    

如何看待 Javascript 的设计缺陷

既然 Javascript 有缺陷,数量还不少,那么它是不是一种很糟糕的语言?有没有前途?

回答是 Javascript 并不算糟糕,相反它的编程能力很强大,前途很光明。

首先,如果遵守良好的编程规范,加上第三方函数库的帮助,Javascript 的这些缺陷大部分可以回避。

其次,Javascript 目前是网页编程的唯一语言,只要互联网继续发展,它就必然一起发展。目前,许多新项目大大扩展了它的用途,node.js 使得 Javascript 可以用于后端的服务器编程,coffeeScript 使你可以用 python 和 ruby 的语法,撰写 Javascript。

最后,只要发布新版本的语言标准(比如 ECMAscript 5),就可以弥补这些设计缺陷。当然,标准的发布和标准的实现是两回事,上述的很多缺陷也许会一直伴随到 Javascript 存在的最后一天。