文章目录
- JavaScript版本变迁史
- JavaScript之前的网站
- JavaScript的开端
- JavaScript的各种版本
- ECMAScript
- ECMAScript 1-2
- ES3
- ES4
- ES5的更新细节
- ES6的更新细节
- 进一步了解JavaScript
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 Navigator 2.0发布而首次公开发布的JavaScript
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()
:查看对象是否为array
var 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