JavaScript(通常缩写为JS)是一门基于原型和头等函数的多范式高级解释型编程语言,它支持面向对象程序设计、指令式编程和函数式编程。它提供方法来操控文本、数组、日期以及正则表达式等。不支持I/O,比如网络、存储和图形等,但这些都可以由它的宿主环境提供支持。它由ECMA(欧洲电脑制造商协会)通过ECMAScript实现语言的标准化。目前,它被世界上的绝大多数网站所使用,也被世界主流浏览器(Chrome、IE、Firefox、Safari和Opera)所支持。
JavaScript与Java在名字和语法上都很相似,但这两门编程语言从设计之初就有很大不同。JavaScript在语言设计上主要受到了Self(一种基于原型的编程语言)和Scheme(一门函数式编程语言)的影响,在语法结构上它和C语言很相似(如if条件语句、switch语句、while循环和do-while循环等)。
对于客户端来说,JavaScript通常被实现为一门解释语言,但如今它已经可以被即时编译(JIT)。随着HTML5和CSS3语言标准的推行,它还可以用于游戏、桌面和移动应用程序的开发,以及在服务器端网络环境运行(如Node.js)。
历史
肇始于网景
1993年,国家超级电脑应用中心(NCSA)发表了NCSA Mosaic,这是最早流行的图形接口网页浏览器,它在万维网的普及上发挥了重要作用。1994年,Mosaic的主要开发人员创立了Netscape公司,并雇用了许多原来的NCSA Mosaic开发者用来开发Netscape Navigator,该公司的目标是取代NCSA Mosaic成为世界第一的网页浏览器。在四个月内,已经占据了四分之三的浏览器市场,并成为1990年代互联网的主要浏览器。网景预见到网络需要变得更动态。公司的创始人马克·安德森认为HTML需要一种胶水语言,让网页设计师和兼职程序员可以很容易地使用它来组装图片和插件之类的组件,且代码可以直接编写在网页标记中。
1995年,网景招募了布兰登·艾克,目标是把Scheme语言嵌入到Netscape Navigator浏览器中。但更早之前,网景已经跟昇阳合作,计划在Netscape Navigator中支持Java,这时网景内部产生激烈的争论,后来网景决定发明一种与Java搭配使用并且语法上类似的辅助脚本语言。这个决策导致排除了采用现有的语言,例如Perl、Python、Tcl和Scheme。为了在其他竞争提案中捍卫JavaScript这个想法,公司需要有一个可以运作的原型。艾克在1995年5月仅花了十天时间就把原型设计出来了。
最初命名为Mocha,1995年9月在Netscape Navigator 2.0的Beta版中改名为LiveScript,同年12月,Netscape Navigator 2.0 Beta 3中部署时被重命名为JavaScript。当时网景公司与昇阳电脑公司组成的开发联盟为了让这门语言搭上Java这个编程语言“热词”,因此将其临时改名为JavaScript,日后这成为大众对这门语言有诸多误解的原因之一。
微软采纳
微软公司于1995年首次推出Internet Explorer,从而引发了与Netscape的浏览器大战。微软对Netscape Navigator解释器进行了逆向工程,创建了JScript,以与处于市场领导地位的网景产品同台竞争。JScript也是一种JavaScript实现,这两个JavaScript语言版本在浏览器端共存意味着语言标准化的缺失。发展初期,JavaScript的标准并未确定,同期就有网景的JavaScript和微软的JScript。除此之外,微软也在网页技术上加入了不少专属对象,使不少网页使用非微软平台及浏览器无法正常显示。这导致在浏览器大战期间网页设计者通常会把“用Netscape可达到最佳效果”或“用IE可达到最佳效果”的标志放在主页。
标准化
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元素;
- 在数据被提交到服务器之前验证数据;
- 检测访客的浏览器信息;
- 控制Cookie,包括创建和修改等;
特性
不同于服务器端脚本语言(如PHP和ASP),JavaScript主要被作为客户端脚本语言在用户的浏览器上运行,不需要服务器的支持。所以在早期程序员比较青睐于JavaScript以减少对服务器的负担,而与此同时在安全性上出现了问题。随着服务器变得强大,现在的程序员更喜欢运行于服务端的脚本以保证安全,但JavaScript仍然以其跨平台、容易上手等优势大行其道。同时,有些特殊功能(如AJAX)必须依赖JavaScript在客户端提供支持。随着引擎(如V8)和框架(如Node.js)的发展,以及事件驱动和异步IO等特性,JavaScript也被逐渐用来编写服务器端程序。
以下是ECMAScript通常所实现的特性。
指令式与结构化
JavaScript支持许多C语言的结构化编程语法(如if条件语句、while循环、switch语句和do-while循环等),但作用域是一个例外。JavaScript在过去只支持使用var
关键字来定义变量的函数作用域,但ECMAScript 2015中加入了let
关键字来支持块级作用域。这意味着JavaScript现在既支持函数作用域又支持块级作用域。JavaScript还支持自动在语句末添加分号,允许忽略语句末尾的分号。
弱类型
Javascript是弱类型的,这意味着变量可以被隐式转换为另一个类型。
- 二元运算符
+
会把两个操作数转换为字符串,除非两个操作数都为数字类型。这是因为+
也表示字符串连接操作; - 二元操作符
-
会把两个操作数转换为数字类型; - 一元操作符,包括
+
和-
,都会把操作数转换为数字。
下列为变量转换为字符串的例子:
- 字符串类型不变;
- 数字会转换为其字符串表示;
- 数组的元素会转换为字符串,然后连接成通过逗号
,
分隔的长字符串; - 其它对象会转换为
[object Object]
,其中Object
中该对象的构造函数名。
类型的隐藏转换是JavaScript受到批评的原因之一,因为隐藏转换增加了规则的复杂度和发生错误的可能性。
左操作数 | 操作符 | 右操作数 | 结果 |
---|---|---|---|
[] (空数组) | + | [] (空数组) | "" (空字符串) |
[] (空数组) | + | {} (空对象) | "[object Object]" (字符串) |
false (布尔值) | + | "" (空字符串) | "false" (字符串) |
"123" (字符串) | + | 1 (数字) | "1231" (字符串) |
"123" (字符串) | - | 1 (数字) | 122 (数字) |
动态化
类型
JavaScript是动态类型的语言,其类型与值而不是与变量相关联。例如变量可以为数字,随后又可被赋值为字符串。JavaScript提供了包括鸭子类型在内的方法来检测变量类型。
运行时估值
Javascript提供eval()
函数,可以在运行时直接执行Javascript语句。
基于原型的面向对象
在JavaScript中,对象是关联数组,通过原型(prototype
,见下)进行扩充。每一个字符串键值提供对象的一个属性的名称,可以通过使用点号(obj.x
)或使用方括号(obj['x']
)这两种效果相同的方式来访问。属性可以在运行时添加、重定义或删除。一个对象的大多数属性(包括来自原型继承链的属性)都可以通过 for...in
循环访问。
原型
JavaScript使用原型,而许多其它面向对象语言使用类用于实现继承。原型使得在JavaScript中模拟基于类的面向对象特征变成可能。
函数作为对象构造器
函数在JavaScript中兼作为对象构造函数。在函数调用前加上new
会创建一个原型的实例,并继承来自构造函数的属性和方法(包括来自Object
原型)。ECMAScript 5提供Object.create
方法,可以显式地创建实例而不是自动从Object
继承。构造函数的prototype
属性决定了用于新对象的内部原型。可以通过修改构造函数的原型的方法来为对象添加新的方法,也可以修改JavaScript的内部对象的原型(如Array
或Object
)。尽管可以这么做,但对Object
原型进行修改并不是好的做法。因为大多数JavaScript对象都会从Object
继承,且不会希望其原型被修改。
函数作为方法
和大多数面向对象的语言不同,在JavaScript中函数定义和方法定义没有明显区别。唯一的区别在于调用时:当函数被作为方法调用时,函数的this
会指向调用此函数的对象。
传统的类定义与使用格式
ECMAScript 2015中加入了对class
和extends
关键字的支持,使得类的定义与继承更类似于其他的面向对象语言,同时也更易使用。
函数式
在JavaScript中,函数是一等的,函数也被认为是对象。因此,函数可以有属性和方法,例如call()
和bind
等。嵌套函数指定义于其它函数内部的函数,在外部函数被调用时,嵌套函数会被创建。另外,嵌套函数是一个闭包,在外部函数的作用域(包括常量,局部变量和参数)都成为内部函数状态的一部分,甚至在外部函数执行完毕后,内部函数的状态依然保留。JavaScript同时也支持匿名函数。
其它
运行时环境
JavaScript通常依赖于运行时环境(例如浏览器)来提供对象和方法,脚本可以通过这些对象和方法与环境(例如网页DOM)进行交互。它还依赖于运行时环境来提供包或导入脚本(例如HTML的<script>
元素)的功能。这本身不是语言功能,但在大多数JavaScript实现中很常见。
异步
JavaScript一般来说是单线程的。为了并发地处理事件,JavaScript程序输入或输出时使用事件和回调函数执行。这意味着JavaScript可以在等待数据库查询返回信息之前处理鼠标单击。ECMAScript 2015引入了Promise
用于处理异步事件,其可以使得传统的基于回调的异步代码更加清晰简单。
变长参数
JavaScript中函数参数的长度是可变的,在函数内部可以通过arguments
对象访问这些参数。
编程
JavaScript是一门脚本语言,其源代码在客户端执行前不需经过编译,而是将文本格式的字符代码发送给浏览器,由浏览器解释执行。解释型语言的弱点是安全性较差,而且在JavaScript中,如果一条语句执行不了,通常它下面的语言也就无法执行。解决办法是使用异常处理try {} catch () {}
︰
console.log("a"); //这是正确的
console.log("b"); //这是正确的
console.logg("c"); //这是错误的,并且到这里会停下来
console.log("d"); //这是正确的
console.log("e"); //这是正确的
/* 解決辦法 */
try { console.log("a"); } catch (e) {} //这是正确的
try { console.log("b"); } catch (e) {} //这是正确的
try { console.logg("c"); } catch (e) {} //这是错误的,但是到这里不会停下来,而是跳过
try { console.log("d"); } catch (e) {} //这是正确的
try { console.log("e"); } catch (e) {} //这是正确的
JavaScript被归类为解释型语言,因为主流的引擎都是每次执行时加载代码并解译。V8是将所有代码解译后再开始执行,其他引擎则是逐行解译(SpiderMonkey会将解译过的指令暂存,以提高性能,称为即时编译)。但由于V8的核心部分多数用JavaScript撰写(而SpiderMonkey是用C++),因此在不同的测试上,两者性能互有优劣。
与其相对应的是编译语言(如C语言),以编译语言编写的程序在执行之前必须经过编译,将代码编译为机器代码,才可以执行。
示例
以下是一个简单的JavaScript Hello World︰
<!DOCTYPE HTML>
<html>
<head>
<title>简单的JavaScript Hello World</title>
<script>
document.write("Hello, world!"); // 直接插入页面中
alert("Hello, world!"); // 弹窗显示
console.log("Hello, world!"); // 在控制台(console)里显示,需要先开启开发工具控制台
</script>
</head>
<body>
HTML内容……
</body>
</html>
或是在浏览器的地址栏中使用javascript:
,以交互方式表示:
javascript:alert("Hello world!");
版本
JavaScript最初开发于1996年,被使用于Netscape Navigator网页浏览器。同年微软在Internet Explorer发布了一个实现。由于商标问题,这项实现被命名为JScript。1997年,JavaScript已经被网景公司提交给ECMA制定为标准,称之为ECMAScript,标准编号ECMA-262。使用显式版本号声明对语言的引用,作为一项Mozilla的特性,已在较新版本中被移除(至少为Firefox 59)。Firefox 4是最后一个需要显式地在引用时声明明确版本号的版本(1.8.5)。
下列表格的信息基于多个参考来源:
版本 | 发布日期 | 基于 | Netscape Navigator | Mozilla Firefox | Internet Explorer | Opera | Safari | Google Chrome |
---|---|---|---|---|---|---|---|---|
旧版本,不再支持: 1.0 | 1996年3月 | 2.0 | 3.0 | |||||
旧版本,不再支持: 1.1 | 1996年8月 | 3.0 | ||||||
旧版本,不再支持: 1.2 | 1997年6月 | 4.0 - 4.05 | 3 | |||||
旧版本,不再支持: 1.3 | 1998年10月 | ECMA-262 1st + 2nd edition | 4.06 - 4.7x | 4.0 | 5 | |||
旧版本,不再支持: 1.4 | Netscape Server | 6 | ||||||
旧版本,不再支持: 1.5 | 2000年11月 | ECMA-262 3rd edition | 6.0 | 1.0 | 5.5(JScript 5.5) 6(JScript 5.6) 7(JScript 5.7) 8(JScript 5.8) | 7.0 | 3.0-5 | 1.0 - 10.0.666 |
旧版本,不再支持: 1.6 | 2005年11月 | 1.5 + Array extras + Array and string generics + E4X | 1.5 | |||||
旧版本,不再支持: 1.7 | 2006年10月 | 1.6 + Pythonic generators (页面存档备份,存于互联网档案馆) + Iterators + Let | 2.0 | 28.0.1500.95 | ||||
旧版本,不再支持: 1.8 | 2008年6月 | 1.7 + Generator expressions + Expression closures | 3.0 | 11.50 | ||||
旧版本,不再支持: 1.8.1 | 1.8 + native JSON support + Minor updates | 3.5 | ||||||
旧版本,不再支持: 1.8.2 | 2009年6月22日 | 1.8.1 + Minor updates | 3.6 | |||||
旧版本,不再支持: 1.8.5 | 2010年7月27日 | 1.8.2 + New features for ECMA-262 5th edition compliance | 4.0 |