JavaScript 是世界上最流行和最广泛的编程语言之一。自 1995 年诞生以来,最终被称为 JavaScript 的语言经历了多次迭代和版本。
JavaScript 由 Brendan Eich 发明,并于 1997 年成为 ECMA 标准。ECMAScript 是官方语言名称。ECMAScript 版本包括 ES1、ES2、ES3、ES5 和 ES6。
临近年底,让我们回顾一下 JavaScript 所经历的所有变化,以更好地了解如何使用这种语言。
我们将回顾:
学习如何编写现代 JavaScript
这是您作为前端开发人员开始旅程的理想场所。您将深入学习 HTML、CSS 和 JavaScript。
JavaScript 之前的网站
在 JavaScript 出现之前,网页是非常静态的。列表、日期和链接都被硬编码到你的 HTML 中,任何类型的动态功能都被编码在 HTML 文档的头部作为标题。
仍然存在的网页设计前 JavaScript 的最著名示例之一是 San Francisco FogCam:
旧金山 FogCam 大约创建于 30 年前,被誉为世界上最古老的网络摄像头之一。这是我们可以在互联网上看到“实时”图片的最早方式。
随着时代的变化,该网站没有。它仍然使用 1990 年代创建的静态 HTML 和 CSS。该页面使用<meta>
文档标题中的信息每 20 秒刷新一次。
在创建 FogCam 时,JavaScript 还没有正式发布,但我们已经见证了对可以动态加载图像的东西的需求。
另一个 JavaScript 之前网页的架构示例是 90 年代后期为参议员鲍勃·多尔 (Bob Dole) 竞选总统而构建的 Dole/Kemp '96 页面。
这个网站仍然是一个静态网站,但它使用 HTML 路由从一个页面到另一个页面。这个网站上的一切都是硬编码的。Netscape Communicator 的工程师看到了这个问题,并决定创建一种脚本语言,允许动画、表单构建和更动态的交互。
这就是 JavaScript 诞生的起点。
JavaScript 开始
JavaScript 的第一次迭代实际上根本不称为 JavaScript。它被称为摩卡。这种语言被创建为设计人员和非程序员等的高级语言。
当 Mocha 与 Netscape Navigator 2.0 一起发布时,它的产品名称变成了 LiveScript,然后在以后的版本中变成了 JavaScript。
JavaScript 的第一个公开版本被集成到 Netscape Navigator 2.0 (1995) 中
Netscape 与 Sun Microsystems 在创建 JavaScript 方面的合作无疑处于领先地位。随着 Netscape 获得越来越多的浏览器份额,其他浏览器需要想出一些办法来跟上 Netscape 的成功。
由于法律原因,微软创建了自己的 JavaScript 版本,称为 JScript。这些“方言”的主要任务是增加网站的用户体验和用户交互。
起初,Netscape 赢得了这些战争,但是随着 JScript 的创建,Microsoft 的 Internet Explorer 正在增加其浏览器份额。
这使得标准化非常困难。由于其与 Java 语法的相似性,JavaScript 在脚本语言战争中逐渐领先。随着 Java 变得越来越流行,JavaScript 也获得了更多的支持。
注意: Java 不等于 JavaScript。Java 是一种编译语言,它使用虚拟机或浏览器来执行代码。
JavaScript 是一种脚本语言,它在生产中的浏览器中大放异彩,并在浏览器之外的 Node.js 中使用。
JavaScript 版本
版本 | 官方名字 | 描述 |
---|---|---|
ES1 | ECMAScript 1 (1997) | 第一版 |
ES2 | ECMAScript 2 (1998) | 编辑更改 |
ES3 | ECMAScript 3 (1999) | 添加了正则表达式 & try/catch |
ES4 | ECMAScript 4 | 未发布 |
ES5 | ECMAScript 5 (2009) | 添加了“严格模式”、JSON 支持、String.trim()、Array.isArray() 和数组迭代方法。 |
ES6 | ECMAScript 2015 | 添加了 let 和 const、默认参数值、Array.find() 和 Array.findIndex() |
ES6 | ECMAScript 2016 | 添加指数运算符 & Array.prototype.includes |
ES6 | ECMAScript 2017 | 添加了字符串填充、Object.entries、Object.values、异步函数和共享内存 |
ES6 | ECMAScript 2018 | 添加了休息/传播属性、异步迭代、Promise.finally() 和 RegExp |
ECMAScript
Netscape Communicator 于 1997 年向 ECMA International 提交了文件,ECMA International 是一家标准化信息和通信系统的公司。
ECMA International 使用 Netscape 的 JavaScript 和 Microsoft 的 JScript 创建了一个称为 ECMAScript 的标准化,这是两种语言都基于的语言规范。ECMA 不能称为 JavaScript,因为 JavaScript 是 Sun Microsystems(后来成为 Oracle)持有的商标。
ECMAScript 1-4
ECMAScript 1 (ES1) 于 1997 年与第二年的 ES2 一起发布。两个版本之间没有太大变化。
ES3
ES3 于 1999 年发布,并增加了对许多新事物的支持,这些事物今天已成为该语言的标准部分:
- **严格相等:**从 ES3 开始,严格相等运算符 (
===
) 成为除相等运算符 (==
)之外的一个选项。两者之间的区别是比较类型和数量的问题。严格相等认为相同但类型不同的值是不相等的。
const compareTypeAndValue = (num, str) => {
return num === str;
}
console.log(compareTypeAndValue(8, '8')); //false
console.log(compareTypeAndValue(8, 8)); //true
-
正则表达式: ES3 中提供了两种类型的正则表达式:文字和构造函数。
-
**文字表达式:**文字正则表达式在两个反斜杠之间表示。实际表达式在斜杠和全局之间,忽略大小写,多行标志可以在最后一个反斜杠之后打开或关闭。
/[^abc]/gim
- **构造函数表达式:**构造函数正则表达式是作为 RegExp 对象实例创建的那些表达式。实际的正则表达式是传递给 RegExp 构造函数的第一个参数。如果需要,第二个是您想要使用的标志。
const regex = new RegExp(‘[^abc]’, ‘gim’);
- Switch 语句: switch 语句是一种控制流语句,它基本上将许多 if 条件链接在一起,而不必使用 else if 语句。switch 语句使用一个参数并将该参数与每个 case 语句进行比较。如果该参数与案例匹配,则执行该块中的逻辑。
const fizzBuzz = (num) => {
switch(num) {
case 1:
console.log(num);
break;
case 2:
console.log(num);
break;
case 3:
console.log("fizz");
break;
case 4:
console.log(num);
break;
case 5:
console.log("buzz");
break;
case 6:
console.log("fizz");
break;
case 7:
console.log(num);
break;
case 8:
console.log(num);
break;
case 9:
console.log("fizz");
break;
case 10:
console.log(num);
break;
}
}
console.log(fizzBuzz(3))
- **Try/Catch 处理:**如果 try 块由于任何原因失败,try/catch 处理程序将抛出错误。下面,尝试失败,因为从未定义 obj。执行 catch 块并抛出一个新的 Error 对象异常。
const isValidKey = (val1) => {
try {
let obj;
return obj.hasOwnProperty(val1);
} catch (err) {
throw new Error("not valid key");
}
}
console.log(isValidKey(""))</pre>
ES3 是近十年来 ECMAScript 规范的最后一次重大更新,直到 2009 年使用 ES5。
ES4
TC39 是 ECMA International 的一个免版税任务组,其主要工作是标准化 ECMAScript。当需要更新和发布 ES4 标准时,任务组确实无法就规范达成一致。结果,ES4 作为一个版本被保留了下来,但从未完全作为实际标准发布。
ECMAScript 5 详细更新
ES5 和 ES6 是最新发布的规范,变化最多。
ES3 发布十年后的 2009 年,ECMAScript 的新版本发布。该标准是 JavaScript 自成立以来最大的变化。一些新功能包括:
严格使用
在 ES5 之前,允许使用未声明的变量(最初引入时不使用 var 关键字的变量)。当“使用严格”功能打开时,会引发引用错误。
"use strict"
x = 5; // ReferenceError: x is not defined
新的数组方法
ES5 中引入了几种新的数组方法,它们使处理数组的工作变得更加轻松。此处按字母顺序显示了新的数组方法:
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()
将过滤器方法视为具有 if 语句的 for 循环。如果元素通过测试,则将该元素推送到新数组。这就是filter()
引擎盖下的工作方式。
像map()
,本节中提到的另一个数组方法,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()
和 lastIndexOf()
如果需要搜索数组中的特定元素,可以使用indexOf()
和 来完成lastIndexOf()
。indexOf()
如果找到,则返回搜索参数的第一个索引,否则返回-1
.
在 中lastIndexOf()
,它为我们提供了数组中搜索元素的最后一个索引。同样,如果没有找到,它将返回-1
。
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()
此方法检查传递给它的对象是否为数组。返回一个布尔值。
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()
在于它返回一个全新的数组。这允许在不影响原始数组的情况下操作数据。
回调函数必须有一个 return 语句。这是将进入新数组的特定索引的新值。
var arr = [6, 4, 5, 6, 7, 7];
arr.map(function(element) {
return element * 2;
})
reduce()
和 reduceRight()
这些reduce 方法中的每一个都将回调函数应用于数组中的每个元素。reduce()
和reduceRight()
方法的特别之处在于它将数组缩减为单个元素。
该reduceRight()
方法就像reduce()
是从右到左而不是从左到右迭代。
var arr = [6, 4, 5, 6, 7, 7];
var reduced = arr.reduce(function(curr, next) {
return curr + next;
}, 0);
var reducedRight = arr.reduceRight(function(curr, next) {
return curr + next;
}, 0)
console.log(reduced);
console.log(reducedRight);
some()
该some()
方法几乎与该every()
方法完全相同,不同之处在于它会检查是否至少有一个元素满足您为其设置的条件。
var arr = [6, 4, 5, 6, 7, 7];
arr.some(function(element) {
return element % 2 === 0; //checks to see if even
}); //true
JSON
解析和字符串化 JavaScript 对象表示法 (JSON) 的能力在 ES5 标准中成为可能。JSON 格式基本上用于通过网络连接传输某种结构化数据,通常是 Web 应用程序和 API。
当我们从一个应用程序传输数据时,它必须采用字符串的形式。我们JSON.stringify()
用来将 JavaScript 对象转换为字符串。
然后我们JSON.parse()
在另一端使用,将传输后的数据转换回 JavaScript 对象,以便我们可以使用它。
console.log("======== ARR 示例 ==========");
console.log("原始 arr====>", arr);
console.log("stringified arr====>", JSON.stringify(arr));
console.log("类型证明======>", typeof JSON.stringify(arr));
console.log("解析后的字符串======>", JSON.parse(JSON.stringify(arr)));
console.log("proof of type ======>", typeof JSON.parse(JSON.stringify(arr)), "\n\n");
console.log("======== 对象示例 ==========");
console.log("原始对象======>", obj);
console.log("stringified obj====>", JSON.stringify(obj));
console.log("类型证明======>", typeof JSON.stringify(obj));
console.log("解析后的字符串======>", JSON.parse(JSON.stringify(obj)));
console.log("proof of type =====>", typeof JSON.parse(JSON.stringify(obj)),
新日期方法
ES5 中引入了两个新的 Date 对象方法,它们在功能上是等效的。它们都以毫秒为单位返回自 1970 年 1 月 1 日以来的当前时间。它们是Date.now()
和 new Date().valueOf()
。
console.log(Date.now());
console.log(new Date().valueOf());
这两种方法最大的区别在valueOf()
于是Date对象实例上的方法,是Date对象Date.now()
的静态函数。
注意: Internet Explorer 可能不支持
Date.now()
,因此如果您担心,您可能需要在代码中处理它。
吸气剂和吸气剂
在 ES5 中,我们引入了访问器属性的概念。这些函数的唯一目的是获取或设置值。当您调用它们时,它们看起来像标准属性:
let character = {
first_name: "Darth",
last_name: "Vader",
获取 fullName() {
return `${this.first_name} ${this.last_name}`;
},
set fullName(str) {
[this.first_name, this.last_name] = str.split(" ");
}
};
console.log(character.fullName); // 达斯维达
character.fullName = "卢克·天行者"
console.log(character.first_name);
console.log(character.last_name);
ES5 标准真正开始为提高 JavaScript 代码的可读性铺平道路。随着新数组方法的引入、解析和字符串化 JSON 的能力以及使代码创建更加严格,它确实有助于使 JavaScript 更易于理解。
继续学习。
无需浏览视频或文档即可学习现代 JavaScript。Educative 的基于文本的学习路径易于浏览并具有实时编码环境,使学习变得快速高效。
ES6 详细更新
从 ES5 的完成版本到ES6的发布,已经过去了七年。它于 2015 年 6 月成为标准。
通天塔
ES5 的最大变化之一是 ES6 JavaScript 不能直接在浏览器中编译。我们需要使用一个被调用的转译器Babel.js
来生成旧浏览器可以读取的兼容 JavaScript。
Babel 允许您在项目中使用 ES6 特性和语法,然后将其转换为 ES5,以便您可以在生产中使用它。
要在构建项目时使用 Babel,您需要将 package.json 添加到您的项目中。这是您项目的所有依赖项的存放位置。
确保您已安装 Node 和 npm(如果您更喜欢使用 Yarn,则安装 Node 和 Yarn),然后在终端中输入npm init
或yarn init
命令。回答出现的问题,然后package.json
将预先填写这些值。
使用 npm/yarn 通过以下命令将 babel 添加到您的依赖项中:
npm install --save-dev babel-cli
// or
yarn add babel-cli --dev
您将使用脚本中的字段package.json
通过 Babel 设置构建命令。实际命令将根据您构建的文件夹和您想要构建的位置而有所不同。
最后,在项目的根文件夹(所在的位置package.json
)中,创建一个.babelrc
文件。这是一个 Babel 配置文件,它会告诉 Babel 将您的代码转换为 ES5。安装预设:
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 关键字来创建函数。现在,我们可以使用大箭头=>
, 来编写函数。它可以使代码看起来更优雅,因为我们可以创建单行粗箭头函数。
//pre ES-6
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));
ES6 one-liner 有一个隐式返回。如果函数只有一行,我们就不需要 return 关键字。这也意味着不需要花括号。如果函数不止一行,我们需要花括号和 return 语句:
//ES6 (explicit return)
const addExplicitReturn = (num1, num2) => {
let sum = num1 + num2;
return sum;
};
console.log(addExplicitReturn(3, 4));
同样重要的是要注意,当您使用类时,箭头函数绑定到“this”关键字,因此无需实际使用该bind()
方法将函数绑定到类。
如果使用 function 关键字,则需要将方法绑定到具有该
bind()
方法的类。
班级
类充当 JavaScript 原型之上的语法糖。他们没有使用Prototypal Inheritance,而是使用带有关键字的Classical Inheritanceextends
。总的来说,它只是减少了代码量,并稍微修饰了一下。
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());
解构
对象解构是减少代码混乱以使其更可口的好方法。它允许我们“解包”一个对象,并将解包后的值用作我们稍后在代码中引用的变量。
const state = {
name: "Luke Skywalker",
age: 22,
dark_side: false
}
console.log("before destructuring");
console.log(state.name); // notice the prefixed object name prior to each property
console.log(state.age); // destructuring gets rid of this
console.log(state.dark_side);
const { name, age, dark_side } = state;
console.log("after destructuring");
console.log(name); // we can access using just the property name now!
console.log(age);
console.log(dark_side);
在“解构之前”部分,除了要访问该属性的属性之外,我们还必须使用对象名称。我们可以通过拉出属性来解构它,将它放在一组花括号中并将其设置为对象名称。
确保在大括号前使用 const 关键字。它允许我们将这些属性作为变量访问,而不是在实际对象本身上使用点符号。
数组解构以非常相似的方式完成,但使用方括号而不是花括号。
const arr_state = [ "Luke Skywalker", 22, false];
console.log("before destructuring");
console.log(arr_state[0]); // notice the index number in bracket notation
console.log(arr_state[1]); // destructuring gets rid of this
console.log(arr_state[2]);
console.log("\n\n\n")
const [ name, age, dark_side ] = arr_state; // assign a variable to each of the indexes in the array
console.log("after destructuring");
console.log(name); // we can access using just the variable name we created now!
console.log(age);
console.log(dark_side);
let
和 const
在 ES6 中,我们有一些新的变量关键字,它们基本上取代了 var 关键字。在 ES6 之前,JavaScript 只有函数作用域和全局作用域。通过添加let
和const
,我们现在有了块作用域。
let x = 5;
function blockExample() {
let x = 2 //this is function scope;
if(x >= 3) {
let x = 10; // this is block scope
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
关键字的改进,您可以在其中使用另一个值重新声明变量。当我们有相同的变量名和不同的值时,这被证明是有问题的,这会产生意外的错误。
// pre-ES6:
var x = 5;
var x = 120; //produces no errors
// ES6:
let x = 5;
let x = 120; // produces a syntax error
const
当您有一个不想重新分配的变量时,该关键字很有用。如果您尝试将 const 变量重新分配给另一个值,它将引发错误。
承诺
Promise 是一种以更好的方式处理异步 JavaScript 编程的方法。以前,异步调用是通过使用回调函数进行的,这会使代码很快变得复杂和混乱。
注意: Promise 将异步逻辑包装在一个 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 运算符本质上是相同的语法,但服务于不同的目的。在函数参数之前使用 rest 运算符来指示应将多个参数分配给该参数。
function restExample(a, ...b) {
console.log(a); // 1
console.log(b); // [2, 3, 4, 5, 6]
}
restExample(1, 2, 3, 4, 5, 6);
展开运算符使用相同的语法,但由数组使用。它本质上获取数组的内容,复制它,以便它可以传播到新结构中。我们可以使用扩展运算符作为向数组添加内容的一种方式,而无需使用push()
或unshift()
。
function spreadExample(arr) {
let newArr = [2, 4, 6, 8];
console.log("arr", arr);
let combinedArr = [...newArr, ...arr]; //this pushes the contents of newArr and the contens of arr into a one-dimensional combined array.
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]))
当您需要处理数组但不想操作数组的实际内容时,展开运算符非常有用。您可以使用扩展运算符基本上创建一个要使用的副本。
模板文字
在 ES6 中,我们不再需要将字符串、空格和变量连接在一起以形成更大的字符串。我们使用模板文字来创建表达式,允许我们在字符串中嵌入变量。
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}`);