本文翻译JavaScript Versions: How JavaScript has changed over the years
原文写于2020/12/18
文章目录
JavaScript版本变迁史
JavaScript是世界上最流行和广泛传播的编程语言之一 (其他几个大概是C, C++, Java, Python) 。这门语言诞生于1995年,之后几经变迁最终以JavaScript的名称而流传于世。
JavaScript由Brendan Eich创造,在1997年成为一个ECMA标准。ECMAScript是这门语言的官方名称,ECMAScript版本包括ES1, ES2, ES3, ES5, ES6。
ECMA是一个国际标准化组织(Ecma International is an industry association dedicated to the standardization of information and communication systems)。JavaScript符合ECMA制定的ECMAScript标准(或者说ECMAScript就是JavaScript的官方称呼),具体来说符合ECMA-262和ECMA-402两个标准。这两个标准都经过了多次修订,具有多个版本。关于ECMAScript的标准变迁可以查看developer.mozilla.org的说明。
在2020的末尾我们回顾JavaScript的变迁史,以便更好理解如何使用这门编程语言。我们将介绍
- JavaScript之前的网站
- JavaScript早期
- JavaScript标准化
- JavaScript的版本
- ECMAScript5的详细介绍
- ES6的具体变化
- 进一步了解JavaScript
JavaScript之前的网站
在JavaS诞生之前,Web页面是相较今天是非常静态的。列表、日期和链接都在HTML中硬编码,所有种类的动态功能都需要在HTML的head中定义。一个非常著名的仍然运行的非JavaScript的网站是San Francisco FogCam:
这个网站于1994年创建,以”世界上最古老的网络摄像头“而闻名。这是人类能从网络上看到动态图像的开始。随着时间的流逝,这个网站并没有变。它仍然使用90年代的静态HTML和CSS。网站每20秒刷新一次(通过HTML文档头部的<meta>来实现)。在FogCam创建之时,JavaScript还未正式发行,但是我们已经能看到对于动态加载图片的需要。
另一个在运行的未使用JavaScript的代表网站是创建于90年代后期参议员Bob Dole’s的总统竞选结果。
该网站是一个静态网站,它使用HTML路由 (HTML routing) 进行页面之间的跳转。网站的一切都是硬编码的,Netscape Communicator注意到了这个问题并决定创建一种允许动画、表单创建以及其他动态交互的脚本语言。
这便是JavaScript的开始。
JavaScript的开端
实际上JavaScript一开始叫做Mocha。创建这门语言的初衷在于作为设计师和非程序员使用的高层语言。(译注:这里表述不准确,大概意思是作为易学易用的语言)当Mocha随着Netscape Navigator 2.0被提供时,其名称改为LiveScript,并在之后的版本改为JavaScript(为了蹭Java的热度)。
Netscape与Sun Microsystems(美国一家出售计算机等产品的公司)在创建JavaScript的合作上引领了潮流。随着Netscape在浏览器领域取得了越来越多的成功,其他浏览器也需要提出一些新东西来跟上Netscape。
由于法律原因,微软创造了他们自己的JavaScript版本并命名为JScript。这些新的语言提升了用户体验和用户与网站之间的交互。随着JScript的创建,微软的Internet explorer浏览器的市场份额逐渐增加。但对于JavaScript来说,这种语言分流使得标准化变得困难。即便如此,由于JavaScript在语法上和Java的相似性,JavaScript在”脚本语言的战争“中成为了赢家,随着Java越来越流行,JavaScript也成长了起来。
Java和JavaScript风马牛不相及。Java是使用虚拟机或者浏览器来执行代码的编译语言;而JavaScript则是面向浏览器的语言(在浏览器之外可以用Node.js来执行js代码)
JavaScript的各种版本
Version | Official Name | Description |
---|---|---|
ES1 | ECMAScript 1(1997) | First edition |
ES2 | ECMAScript 2 (1998) | Editorial changes |
ES3 | ECMAScript 3 (1999) | Added regular expressions & try/catch |
ES4 | ECMAScript 4 | Not released |
ES5 | ECMAScript 5 (2009) | Added "strict mode", JSON support, String.trim(), Array.isArray(), & Array iteration methods |
ES6 | ECMAScript 2015 | Added let and const, default parameter values, Array.find(), & Array.findIndex() |
ES6 | ECMAScript 2016 | Added exponential operator & Array.prototype.includes |
ES6 | ECMAScript 2017 | Added string padding, Object.entries, Object.values, async functions, & shared memory |
ES6 | ECMAScript 2018 | Added rest / spread properties, asynchronous iteration, Promise.finally(), & RegExp |
ECMAScript
Netscape Communicator在1997年向ECMA标准化组织提交了文档。在之后,ECMA结合Netscape的JavaScript和微软的JScript创建了一个标准,将之命名为ECMAScript(这是JavaScript和JScript共同的语言规范)。之所以命名为ECMAScript而非JavaScript的原因是JavaScript被当时的Sun Microsystems公司用作商标(之后的Oracle公司)。
ECMAScript 1-2
ES1于1997年发行,在后一年ES2发行,两者之间差别不大。
ES3
ES3于1999年发行,为JavaScript增加了许多新特性,这些特性在今天变成了JavaScript的标准组成部分。
- 严格相等: 自从ES3开始,严格相等操作符(===)作为等于操作符(==)的补充被添加到语言中。两者的区别是等于操作符不涉及类型只考虑值,严格相等操作符则与其他强类型语言的等于操作符类似(JavaScript是弱类型语言)
const compareEypeAndValue = (num, str) => { return num === str; } console.log(compareTypeAndValue(8, '8')); //false console.log(compareTypeAndValue(8, 8)); //true
- 正则表达式: ES3加入了文字和构造器(literal and constructor)两种正则表达式。
- Literal Expressions:文字表达式在两个反斜杠中定义。(详细百度)
/[^abc]/gim
- Constructor Expressions:构造器表达式是RegExp类的实例。
const regex = new RegExp('[^abc]', 'gim');
- Switch Statement: JavaScript的switch-case-default组合与C语言几乎完全一致。
- Try/Catch Handling:try/catch允许你跳出“快速失败”的哲学,在程序运行异常后可以抛出异常且继续运行。
const isValidKey = (val1) => { try { let obj; return obj.hasOwnProperty(val1); } catch (err) { throw new Error("not valid key"); } } console.log(isValidKey(""))
ES3在发布后的十年内都是标准版本,直到2009年ES5发行
ES4
TC39是ECMA International的免版税任务组,其主要工作是标准化ECMAScript。当需要更新和发布ES4标准时,任务组无法就规范达成共识。结果,ES4作为一个备受推崇的版本,却从未完全发布为实际标准。
ES5的更新细节
ES5和ES6是现在为止最后发布的两个版本,这两个版本做出了很多重大的更新。在ES3发行十年后的2009年,ES5发行了,这个新版本是JavaScript创立以来的最大变化。一些新特性包括
- “use strict”:在ES5之前,未声明变量(未使用var声明的变量)在任何情况下都可用。但自从加入"use strict"特性后,启动该特性会禁止使用未声明变量:
"use strict" x = 5; //ReferenceError: x is not defined
- 新的Array方法:ES5加入了一些新的array(内置类)的方法,这些方法包括
every()
:every方法检查数组中的每个变量是否满足条件var arr = [6, 4, 5, 6, 7, 7]; arr.every(function(element) { return element % 2 === 0; //checks to see if even }); // false
filter()
:显然这个方法返回满足条件的子数组var arr = [6, 4, 5, 6, 7, 7];[' arr.filter(function(element) { return element/2 > 3; })
forEach()
:这个方法与for循环很像。对于数组中的每个元素,forEach方法执行一个回调函数。var arr = [6, 4, 5, 6, 7, 7]; arr.forEach(function(element) { console.log(element * 2); })
indexOf()
andlastIndexOf()
:分别返回所查询元素首个和末个的索引var arr = [6, 4, 5, 6, 7, 7]; console.log(arr.indexOf(4)); // 1 console.log(arr.indexOf(2)); // -1 console.log(arr.indexOf(7)); // 4 console.log(arr.lastIndexOf(7)); // 5
isArray()
:查看对象是否为arrayvar arr = [6, 4, 5, 6, 7, 7]; var str = "Hello Educative.io"; console.log(Array.isArray(arr)); console.log(Array.isArray(str));
map()
:map方法和forEach()
非常相似,区别是map返回一个新array,因此回调函数需要有返回值var arr = 6, 4, 5, 6, 7, 7]; arr.map(function(element) { return element * 2; })
reduce()
andrecuceRight()
:与python的reduce类似,将某个操作连续应用到序列的元素上,累计之前的结果,把一系列值规约成一个值。区别是js的reduce可以指定一个参数作为额外的操作数。var arr = [6, 4, 5, 6, 7, 7]; var reduced = arr.reduce(funciton(curr, next) { return curr + next; }, 0); # 0为额外的操作数 var reducedRight = arr.reducedRight(function(curr, next) { return curr + next; }, 0) console.log(reduced); console.log(redecedRight);
some()
:与python的any相似,与arr.every()
相反,every考虑是否所有元素满足条件而some考虑是否存在满足的元素。
- JSON
解析和字符串化JavaScript Object Notation(JSON)的功能在ES5标准中成为可能。JSON格式用于基本通过网络连接(通常是Web应用程序和API)传输某种结构化数据。当我们从一个应用程序传输数据时,它必须为字符串形式。使用JSON.stringify()将JavaScript对象转换为字符串,然后另一侧使用JSON.parse()将数据传输回JavaScript对象后进行转换。var arr = [6, 4, 5, 6, 7, 7]; var obj = { author: "Christina Kopecky", title: "How to parse JSON objects", published: false } console.log("======== ARR EXAMPLE =========="); console.log("orig arr=====>", arr); console.log("stringified arr=====>", JSON.stringify(arr)); console.log("proof of type=====>", typeof JSON.stringify(arr)); console.log("parsed string=====>", JSON.parse(JSON.stringify(arr))); console.log("proof of type=====>", typeof JSON.parse(JSON.stringify(arr)), "\n\n"); console.log("======== OBJ EXAMPLE =========="); console.log("orig obj=====>", obj); console.log("stringified obj=====>", JSON.stringify(obj)); console.log("proof of type=====>", typeof JSON.stringify(obj)); console.log("parsed string=====>", JSON.parse(JSON.stringify(obj))); console.log("proof of type=====>", typeof JSON.parse(JSON.stringify(obj)), "\n\n");
- 新的Date方法
ES5为Date类新加入两种方法Date.now()
和Date.valueOf()
,它们都返回自1970年1月1日以来的当前时间(以毫秒为单位)。两个方法都返回自1970一月一日的毫秒值,区别是Date.valueOf()
返回值是Date类的实例而Date.now()
只是调用函数返回一个数值。(Date.valueOf()返回值和Date.now()并不同,也许作者意为刚加入时两者类似?) - getters and setters
在ES5的更新中,存取器属性被加入语言(accessor properties)。get和set的唯一角色就是得到、设置值。在设置了get和set后,他们就像标准的类属性。let character = { first_name: "Darth", last_name: "Vader", get fullName() { return `${this.first_name} ${this.last_name}`; }, set fullName(str) { [this.first_name, this.last_name] = str.split(" "); } }; console.log(character.fullName); //Darth Vader character.fullName = "Luke Skywakker" console.log(character.first_name); console.log(character.last_name);
ES5标准使JavaScript代码更具可读性。通过引入新的数组方法,解析和字符串化JSON的能力以及使代码创建更加严格,它使JavaScript更易于理解。
ES6的更新细节
ES5版本发布七年后,于2015年6月ES6成为新的标准。
-
Babel
最大变化之一是ES6 JavaScript无法直接在浏览器中进行编译。我们需要使用一个名为Babel.js的编译器来生成兼容的JavaScript,这样旧的浏览器才可读取这些JavaScript。Babel允许您在项目中使用ES6功能和语法,然后将其转换为ES5,以便可以在生产中使用它。要在构建项目时使用Babel,您需要将package.json添加到您的项目中。这是项目的所有依赖项所在的位置。
如果你安装了Node和npm(yarn可替代npm),那么在项目目录下打开shell键入
npm init
(或者yarn init
),在回答问题后package.json将会自动生成。
使用npm/yarn将babel加入项目依赖:npm install --save-dev babel-cli //or yarn add babel-cli --dev
将babel加入项目依赖后,还需要配置babel,首先安装babel-preset-env到项目:
npm install --save-dev babel-preset-env //or yarn add babel-preset-env --dev
然后创建.babelrc文件,写入以下内容
{ "presets": ["env"] }
现在,您可以通过运行构建命令来运行Babel。现在,目标文件夹应该看起来与原始文件夹完全一样,只是目标文件夹的内容是ES5代码而不是ES6。如果您碰巧使用了JavaScript库或create-react-app之类的框架,那么Babel很有可能已经为您配置了,您无需担心。
-
使用
=>
创建函数
在该功能加入JavaScript前,我们只能使用function关键字来创建函数。现在我们可以使用=>来创建函数,使用=>创建行间函数可能更加优雅(译注:为何不用lambda呢)function add(num1, num2) { return num1 + num2; } // ES6 (implicit return) const addImplicit = (num1, num2) => num1 + num2; console.log(add(3, 4)); console.log(addImplicit(3, 4));
=>具有隐式return。如果函数只有一行,不需要return关键字,这也意味着不需要花括号。如果函数多于一行,则需要花括号和return语句:
//ES6 (explicit return) const addExplicitReturn = (num1, num2) => { let sum = num1 + num2; return sum; }; console.log(addExplicitReturn(3, 4));
还需要注意的是,当使用类时,箭头函数已绑定到“ this”关键字,因此无需实际使用bind()方法将函数绑定至类。
如果使用function关键字,则需要使用bind()方法将该方法绑定到该类。
-
Classes
在JavaScript的原型之上,类充当语法糖。它们代替原型继承,而是将古典继承与extends关键字一起使用。总体而言,它减少了一些代码量。class StarWarsCharacter{ constructor(attributes) { this.name = attributes.name; this.age = attributes.age; this.homePlanet = attributes.homePlanet; } getCharacter = () => `${this.name} is ${this.age} years old and is from ${this.homePlanet}.`; } const luke = new StarWarsCharacter({ name: "Luke Skywalker", age: 23, homePlanet: "Tatooine"}); luke.getCharacter(); class Heroes extends StarWarsCharacter { constructor(attributes) { super(attributes); this.favoriteVehicle = attributes.favoriteVehicle; } getFavoriteVehicle = () => `${this.name} is ${this.age} and their favorite vehicle is the ${this.favoriteVehicle}`; } const hans = new Heroes({ name: "Hans Solo", age: 35, favoriteVehicle: "Millennium Falcon"}); console.log(hans.getFavoriteVehicle());
-
Destructuring(析构)
它允许我们unpack对象并将该解压缩后的值用作我们稍后在代码中引用的变量。const state = { name: "Luke Skywalker", age: 22, dark_side: false } console.log("before destructuring"); console.log(state.name); console.log(state.age); console.log(state.dark_side); const { name, age, dark_side } = state; console.log("after destructuring"); console.log(name); console.log(age); console.log(dark_side);
我们可以通过拉出属性来对其进行结构分解,将其放在花括号中,然后将其设置为对象名称。**确保在花括号前使用const关键字,**它允许我们将这些属性作为变量访问,而不是在实际对象本身上使用点符号。
Array的析构与上面的代码类似,不同的是使用花括号。
const arr_state = [ "Luke Skywalker", 22, false]; console.log("before destructuring"); console.log(arr_state[0]); console.log(arr_state[1]); console.log(arr_state[2]); const [ name, age, dark_side ] = arr_state; console.log("after destructuring"); console.log(name); console.log(age); console.log(dark_side);
-
let
andconst
ES6加入了两个新的声明变量的关键字,他们一定程度上取代了var关键字。在ES6之前,JavaScript仅仅有函数作用域和全局作用域。加入let和const后,我们有了块作用域(有点类似python的闭包,但在if-else嵌套作用域是python没有的)let x = 5; function blockExample() { let x = 2; if (x >= 3) { let x = 10; console.log(x, "inside if block"); } else { let x = 1; console.log(x, "inside else block") } console.log(x, "inside function"); } blockExample(); console.log(x, "global example");
**let关键字暗含作用域,在同一作用域下重新声明相同变量将引发语法错误(使用var看起来不报错)。
var x = 3; var x = 120; // no errors
使用let报错
let x = 5; let x = 120; // syntax error
const在声明不变量时很有用。尝试将const变量赋值将引发错误。
-
Promises
Promises处理异步JavaScript编程更加优雅,在ES6以前,异步调用是通过使用回调函数进行的,这可能会使代码非常复杂并令人费解。注意:Promises将异步逻辑包装在net程序包中以使代码更具可读性。
console.log("before promise") let promise = new Promise((resolve, reject) => { let resolvedFlag = false; //this is just a flag so we can intentionally throw the response to test logic console.log("this is eventually going to be an API call"); resolvedFlag = true;//flip resolved to true once all console logs are done if (resolvedflag) {//if resolved is true invoke the resolve function resolve("Promise resolved THIS IS THE RESPONSE"); } else {// else invoke the reject function with a new Error object with message reject(new Error("Promise failed")); console.log("after promise"); } }); promise.then(resp => { console.log(resp);//promise response });
-
rest和spread操作符(译注:即…操作符,用作unpack)
rest运算符和spread运算符语法相同,但用途不同。在函数参数之前使用rest运算符来指示应将多个参数分配给该参数。(译注:这里类似python的*符号)function restExample(a, ...b) { console.log(a); ocnsole.log(b); } restExample(1, 2, 3, 4, 5, 6);
spread运算符由array使用(译注:与python的*符号类似,拆包),用法见下面的例子:
function spreadExample(arr) { let newArr = [2, 4, 6, 8]; console.log("arr", arr); let combinedArr = [...newArr, ...arr] let arrWithOtherContents = ["a", ...newArr, {b: "c", d: "e"}, true, ...arr]; console.log(arrWithOtherContents); console.log("combined", combinedArr); } console.log(spreadExample([1, 3, 5, 7, 9]))
上面代码的运行结果:
-
模板文字Template Literals
在ES6中,我们不再需要使用+操作符连接文字、空格和变量来构成一个大的字符串,使用template literals来构造表达式将变量嵌入字符串是更优雅的做法(译注:与python的format格式化显示异曲同工)let name = "Jane"; let holiday = "Christmas"; //pre-ES6: console.log(name + "'s favorite holiday is " + holiday); //ES6+: console.log(`${name}'s favorite holiday is ${holiday}`);
进一步了解JavaScript
自ES6发布以来,该标准化每年都在进行更新。您可以随时关注ECMA International的ECMA-262标准,以了解JavaScript的新功能。这些标准具有可免费在线阅读的PDF格式的标准。
学习JavaScript,你还需要了解下面这些概念:
- Map
- Set
- Generators
- async/await