在过去几年里,JavaScript有不少的更新。若是你想提高写代码的能力,这些更新将会对你有很是大的帮助。javascript
对于程序员来讲,了解这门语言的最新发展是很是重要的。它能使你跟上最新趋势,提升代码质量,在工做中出类拔萃,从而进一步提高你的薪资待遇。css
特别地,若是你想学习像React、 Angular或Vue这样的框架,你必须掌握这些最新的特性。html
最近,JavaScript增长了许多有用的功能,好比Nullish coalescing operator, optional chaining, Promises, async/await, ES6 destructuring,等等。vue
那么如今,咱们将探讨每一个JavaScript开发者都应该知道的概念。java
在ES6以前,JavaScript使用var关键字来声明变量,var只有全局做用域和函数做用域,所谓全局做用域就是在代码的任何位置都能访问var声明的变量,而函数做用域在变量声明的当前函数内部访问变量。此时是没有块级做用域的。webpack
随着let和const这两个关键字的添加,JS增长了块级做用域的概念。程序员
当咱们在用let声明变量时,用于声明一次以后就不能再以相同的名称从新声明它。web
// ES5 Code var value = 10; console.log(value); // 10 var value = "hello"; console.log(value); // hello var value = 30; console.log(value); // 30
如上所示,咱们屡次使用var关键字从新声明了变量值。vue-cli
在ES6以前,咱们可使用var从新声明以前已经声明过的变量,这就会致使了一个问题:若是咱们在不知情的状况下,在其余地方从新声明了该变量,颇有可能会覆盖原先变量的值,形成一些难以调试的问题。数据库
因此,Let解决很好地解决此问题。当你使用let从新声明变量值时,将会报错。
// ES6 Code let value = 10; console.log(value); // 10 let value = "hello"; // Uncaught SyntaxError: Identifier 'value' has already been declared
可是,如下代码是合法的:
// ES6 Code let value = 10; console.log(value); // 10 value = "hello"; console.log(value); // hello
咱们发现上面的代码看起来没什么问题,是由于咱们从新给value变量赋了一个新值,可是并无从新声明。
咱们来看下下面的代码:
// ES5 Code var isValid = true; if(isValid) { var number = 10; console.log('inside:', number); // inside: 10 } console.log('outside:', number); // outside: 10
如上所示,在使用var声明变量时,能够在if块以外访问该变量。
而使用let声明的number变量只能在if块内访问,若是在if块外访问将会报错。
咱们来看下接下来的代码
// ES6 Code let isValid = true; if(isValid) { let number = 10; console.log('inside:', number); // inside: 10 } console.log('outside:', number); // Uncaught ReferenceError: number is not defined
如上述代码所示,使用let分别在if块内、if块外声明了number变量。在if块外,number没法被访问,所以会出现引用错误。
可是,若是变量number在if块外已经声明,将会出现下面的结果。
// ES6 Code let isValid = true; let number = 20; if(isValid) { let number = 10; console.log('inside:', number); // inside: 10 } console.log('outside:', number); // outside: 20
如今在单独的范围内有两个number变量。在if块外,number的值为20。
// ES5 Code for(var i = 0; i < 10; i++){ console.log(i); } console.log('outside:', i); // 10
当使用var关键字时,i在 for循环以外也能够访问到。
// ES6 Code for(let i = 0; i < 10; i++){ console.log(i); } console.log('outside:', i); // Uncaught ReferenceError: i is not defined
而使用let关键字时,在for循环外部是不可访问的。
所以,正如上述示例代码所示,let声明的变量只能在块内部可用,而在块外部不可访问。
咱们可使用一对大括号建立一个块,以下:
let i = 10; { let i = 20; console.log('inside:', i); // inside: 20 i = 30; console.log('i again:', i); // i again: 30 } console.log('outside:', i); // outside: 10
前面有提到,let在同一个块中不能从新声明变量,不过能够在另外一个块中从新声明。如上代码所示,咱们在块内从新声明了i,并赋值20,该变量仅可在该块中使用。
在块外,当咱们打印变量时,咱们获得的是10而不是以前分配的值,这是由于块外,内部变变量i是不存在的。
若是在块外未声明变量,那么将会报错:
{ let i = 20; console.log('inside:', i); // inside: 20 i = 30; console.log('i again:', i); // i again: 30 } console.log('outside:', i); // Uncaught ReferenceError: i is not defined
const关键字在块级做用域中的工做方式与let关键字彻底相同。所以,咱们来看下他们的区别。
const声明的变量为常量,其值是不能改变的。而let声明的变量,能够为其赋一个新值,以下所示:
let number = 10; number = 20; console.log(number); // 20
可是如下状况,咱们不能这样使用const。
const number = 10; number = 20; // Uncaught TypeError: Assignment to constant variable.
咱们甚至不能使用const像下面同样从新声明。
const number = 20; console.log(number); // 20 const number = 10; // Uncaught SyntaxError: Identifier 'number' has already been declared
如今,看下面的代码:
const arr = [1, 2, 3, 4]; arr.push(5); console.log(arr); // [1, 2, 3, 4, 5]
咱们说过const声明的常量,它的值永远不会改变——可是咱们改变了上面的常量数组并无报错。这是为何呢?
注意:数组是引用类型,而不是JavaScript的基本类型
实际存储在arr中的不是数组,而是数组存储的内存位置的引用(地址)。执行arr.push(5),并无改变arr指向的引用,而是改变了存储在那个引用上的值。
对象也是如此:
const obj = { name: 'David', age: 30 }; obj.age = 40; console.log(obj); // { name: 'David', age: 40 }
这里,咱们也没有改变obj指向的引用,而是改变了存储在引用的值。
所以,上述的代码将会起做用,但下面的代码是无效的。
const obj = { name: 'David', age: 30 }; const obj1 = { name: 'Mike', age: 40 }; obj = obj1; // Uncaught TypeError: Assignment to constant variable.
这样写会抛出异常,由于咱们试图更改const变量指向的引用。
所以,在使用const时要记住一点:使用const声明常量时,不能从新声明,也不能从新赋值。若是声明的常量是引用类型,咱们能够更改存储在引用的值。
同理,下面的代码也是无效的。
const arr = [1, 2, 3, 4]; arr = [10, 20, 30]; // Uncaught TypeError: Assignment to constant variable.
好了,咱们继续下一个话题: promises。
对于不少新开发者来讲,promises是JavaScript中较难理解的部分。ES6中原生提供了Promise对象,那么Promise到底是什么呢?
Promise 对象表明了将来将要发生的事件,用来传递异步操做的消息。
如何创造一个 promise
使用promise构造函数建立一个promise,以下所示:
const promise = new Promise(function(resolve, reject) { });
Promise的构造函数接收一个函数做为参数,而且在内部接收两个参数:resolve,reject。 resolve和reject参数其实是咱们能够调用的函数,具体取决于异步操做的结果。
Promise 有三种状态:
当咱们建立Promise时,它处于等待的状态。当咱们调用resolve函数时,它将进入已完成状态。若是调用reject,他将进入被拒绝状态。
在下面的代码中,咱们执行了一个异步操做,也就是setTimeout,2秒后,调用resolve方法。
const promise = new Promise(function(resolve, reject) { setTimeout(function() { const sum = 4 + 5; resolve(sum); }, 2000); });
咱们须要使用如下方法注册一个回调.then得到1promise执行成功的结果,以下所示:
const promise = new Promise(function(resolve, reject) { setTimeout(function() { const sum = 4 + 5; resolve(sum); }, 2000); }); promise.then(function(result) { console.log(result); // 9 });
then接收一个参数,是函数,而且会拿到咱们在promise中调用resolve时传的的参数。
若是操做不成功,则调用reject函数:
const promise = new Promise(function(resolve, reject) { setTimeout(function() { const sum = 4 + 5 + 'a'; if(isNaN(sum)) { reject('Error while calculating sum.'); } else { resolve(sum); } }, 2000); }); promise.then(function(result) { console.log(result); });
若是sum不是一个数字,那么咱们调用带有错误信息的reject函数,不然咱们调用resolve函数。
执行上述代码,输出以下:
调用reject函数会抛出一个错误,可是咱们没有添加用于捕获错误的代码。
须要调用catch方法指定的回调函数来捕获并处理这个错误。
promise.then(function(result) { console.log(result); }).catch(function(error) { console.log(error); });
输出以下:
因此建议你们在使用promise时加上catch方法,以此来避免程序因错误而中止运行。
链式操做
咱们能够向单个promise添加多个then方法,以下所示:
promise.then(function(result) { console.log('first .then handler'); return result; }).then(function(result) { console.log('second .then handler'); console.log(result); }).catch(function(error) { console.log(error); });
当添加多个then方法时,前一个then方法的返回值将自动传递给下一个then方法。
如上图所示,咱们在第一个then方法中输出字符串,并将接收的参数result(sum)返回给下一个result。
在下一个then方法中,输出字符串,并输出上一个then方法传递给它的result。
不少时候,咱们不但愿当即建立promise,而是但愿在某个操做完成后再建立。
咱们能够将promise封装在一个函数中,而后从函数中返回promise,以下所示:
function createPromise() { return new Promise(function(resolve, reject) { setTimeout(function() { const sum = 4 + 5; if(isNaN(sum)) { reject('Error while calculating sum.'); } else { resolve(sum); } }, 2000); }); }
这样,咱们就能够经过函数将参数传递给promise,达到动态的目的。
function createPromise(a, b) { return new Promise(function(resolve, reject) { setTimeout(function() { const sum = a + b; if(isNaN(sum)) { reject('Error while calculating sum.'); } else { resolve(sum); } }, 2000); }); } createPromise(1,8) .then(function(output) { console.log(output); // 9 }); // OR createPromise(10,24) .then(function(output) { console.log(output); // 34 });
此外,咱们只能向resolve或reject函数传递一个值。若是你想传递多个值到resolve函数,能够将它做为一个对象传递,以下:
const promise = new Promise(function(resolve, reject) { setTimeout(function() { const sum = 4 + 5; resolve({ a: 4, b: 5, sum }); }, 2000); }); promise.then(function(result) { console.log(result); }).catch(function(error) { console.log(error); });
上述示例代码中,咱们使用常规的ES5语法建立了promise。可是,一般使用箭头函数代替ES5语法,以下:
const promise = new Promise((resolve, reject) => { setTimeout(() => { const sum = 4 + 5 + 'a'; if(isNaN(sum)) { reject('Error while calculating sum.'); } else { resolve(sum); } }, 2000); }); promise.then((result) => { console.log(result); });
你能够根据本身须要使用ES5或ES6语法。
在ES6以前,咱们在一个HTML文件中可使用多个script标签来引用不一样的JavaScript文件,以下所示:
<script type="text/javascript" src="home.js"></script> <script type="text/javascript" src="profile.js"></script> <script type="text/javascript" src="user.js"></script>
可是若是咱们在不一样的JavaScript文件中有一个同名的变量,将会出现命名冲突,你实际获得的可能并非你指望的值。
ES6增长了模块的概念来解决这个问题。
在ES6中,咱们编写的每个JavaScript文件都被称为模块。咱们在每一个文件中声明的变量和函数不能用于其余文件,除非咱们将它们从该文件中导出并、在另外一个文件中获得引用。
所以,在文件中定义的函数和变量是每一个文件私有的,在导出它们以前,不能在文件外部访问它们。
export有两种类型:
以下所示,将单个变量命名导出:
export const temp = "This is some dummy text";
若是想导出多个变量,可使用大括号指定要输出的一组变量。
const temp1 = "This is some dummy text1"; const temp2 = "This is some dummy text2"; export { temp1, temp2 };
须要注意的是,导出语法不是对象语法。所以,在ES6中,不能使用键值对的形式导出。</pre>
// This is invalid syntax of export in ES6 export { key1: value1, key2: value2 }
import命令接受一对大括号,里面指定要从其余模块导入的变量名。
import { temp1, temp2 } from './filename';
注意,不须要在文件名中添加.js扩展名,由于默认状况下会考虑该拓展名。
// import from functions.js file from current directory import { temp1, temp2 } from './functions'; // import from functions.js file from parent of current directory import { temp1 } from '../functions';
提示一点,导入的变量名必须与被导入模块对外接口的名称相同。
所以,导出应使用:
// constants.js export const PI = 3.14159;
那么在导入的时候,必须使用与导出时相同的名称:
import { PI } from './constants'; // This will throw an error import { PiValue } from './constants';
若是想为输入的变量从新命名,可使用as关键字,语法以下:
import { PI as PIValue } from './constants';
咱们觉得PI重命名为PIValue,所以不能再使用PI变量名。
导出时也可以使用下面的重命名语法:
// constants.js const PI = 3.14159; export { PI as PIValue };
而后在导入是,必须使用PIValue。
import { PIValue } from './constants';
命名导出某些内容以前必须先声明它。
export 'hello'; // this will result in error export const greeting = 'hello'; // this will work export { name: 'David' }; // This will result in error export const object = { name: 'David' }; // This will work
咱们来看下面的validations.js 文件:
// utils/validations.js const isValidEmail = function(email) { if (/^[^@ ]+@[^@ ]+\.[^@ \.]{2,}$/.test(email)) { return "email is valid"; } else { return "email is invalid"; } }; const isValidPhone = function(phone) { if (/^[\\(]\d{3}[\\)]\s\d{3}-\d{4}$/.test(phone)) { return "phone number is valid"; } else { return "phone number is invalid"; } }; function isEmpty(value) { if (/^\s*$/.test(value)) { return "string is empty or contains only spaces"; } else { return "string is not empty and does not contain spaces"; } } export { isValidEmail, isValidPhone, isEmpty };
在index.js中,咱们可使用以下函数:
// index.js import { isEmpty, isValidEmail } from "./utils/validations"; console.log("isEmpty:", isEmpty("abcd")); // isEmpty: string is not empty and does not contain spaces console.log("isValidEmail:", isValidEmail("abc@11gmail.com")); // isValidEmail: email is valid console.log("isValidEmail:", isValidEmail("ab@c@11gmail.com")); // isValidEmail: email is invalid
如上所述,单个文件中最多只能有一个默认导出。可是,你能够在一个文件中使用多个命名导出和一个默认导出。
要声明一个默认导出,咱们须要使用如下语法:
//constants.js const name = 'David'; export default name;
在导入时就不须要再使用花括号了。
import name from './constants';
以下,咱们有多个命名导出和一个默认导出:
// constants.js export const PI = 3.14159; export const AGE = 30; const NAME = "David"; export default NAME;
此时咱们使用import导入时,只须要在大括号以前指定默认导出的变量名。
// NAME is default export and PI and AGE are named exports here import NAME, { PI, AGE } from './constants';
使用 export default 导出的内容,在导入的时候,import后面的名称能够是任意的。
// constants.js const AGE = 30; export default AGE; import myAge from ‘./constants’; console.log(myAge); // 30
另外, export default的变量名称从Age到myAge之因此可行,是由于只能存在一个export default。所以你能够随意命名。还需注意的是,关键字不能在声明变量以前。
// constants.js export default const AGE = 30; // This is an error and will not work
所以,咱们须要在单独的一行使用关键字。
// constants.js const AGE = 30; export default AGE;
不过如下形式是容许的:
//constants.js export default { name: "Billy", age: 40 };
而且须要在另外一个文件中使用它
import user from './constants'; console.log(user.name); // Billy console.log(user.age); // 40
还有,可使用如下语法来导入constants.js文件中导出的全部变量:
// test.js import * as constants from './constants';
下面,咱们将导入全部咱们constants.js存储在constants变量中的命名和export default。所以,constants如今将成为对象。
// constants.js export const USERNAME = "David"; export default { name: "Billy", age: 40 };
在另外一个文件中,咱们按一下方式使用。
// test.js import * as constants from './constants'; console.log(constants.USERNAME); // David console.log(constants.default); // { name: "Billy", age: 40 } console.log(constants.default.age); // 40
也可使用如下方式组合使用命名导出和默认导出:
// constants.js const PI = 3.14159; const AGE = 30; const USERNAME = "David"; const USER = { name: "Billy", age: 40 }; export { PI, AGE, USERNAME, USER as default }; import USER, { PI, AGE, USERNAME } from "./constants";
总而言之:
ES6中,一个模块就是一个独立的文件,该文件内部的全部变量,外部都没法获取。若是想从外部读取模块内的某个变量,必须使用export关键字导出该变量,使用import关键字导入该变量。
ES6增长了一个很是有用的特性,即在定义函数时提供默认参数。
假设咱们有一个应用程序,一旦用户登陆系统,咱们将向他们显示一条欢迎消息,以下所示:
function showMessage(firstName) { return "Welcome back, " + firstName; } console.log(showMessage('John')); // Welcome back, John
可是,若是数据库中没有用户名,那该怎么办呢?因此,咱们首先须要检查是否提供了firstName,而后再显示相应的信息。
在ES6以前,咱们必须写这样的代码:
function showMessage(firstName) { if(firstName) { return "Welcome back, " + firstName; } else { return "Welcome back, Guest"; } } console.log(showMessage('John')); // Welcome back, John console.log(showMessage()); // Welcome back, Guest
但如今使用ES6提供的默认参数,咱们能够这样写:
function showMessage(firstName = 'Guest') { return "Welcome back, " + firstName; } console.log(showMessage('John')); // Welcome back, John console.log(showMessage()); // Welcome back, Guest
函数的默认参数能够为任意值。
function display(a = 10, b = 20, c = b) { console.log(a, b, c); } display(); // 10 20 20 display(40); // 40 20 20 display(1, 70); // 1 70 70 display(1, 30, 70); // 1 30 70
在上面的代码中,咱们没有提供函数的全部参数,实际代码等同于:
display(); // 等同于display(undefined, undefined, undefined) display(40); 等同于display(40, undefined, undefined) display(1, 70); 等同于display(1, 70, undefined)
所以,若是传递的参数是undefined,则对应的参数将使用默认值。
咱们还能够将对象或计算值指定为默认值,以下:
const defaultUser = { name: 'Jane', location: 'NY', job: 'Software Developer' }; const display = (user = defaultUser, age = 60 / 2 ) => { console.log(user, age); }; display(); /* output { name: 'Jane', location: 'NY', job: 'Software Developer' } 30 */
ES5代码以下:
// ES5 Code function getUsers(page, results, gender, nationality) { var params = ""; if(page === 0 || page) { params += `page=${page}&`; } if(results) { params += `results=${results}&`; } if(gender) { params += `gender=${gender}&`; } if(nationality) { params += `nationality=${nationality}`; } fetch('https://randomuser.me/api/?' + params) .then(function(response) { return response.json(); }) .then(function(result) { console.log(result); }) .catch(function(error) { console.log('error', error); }); } getUsers(0, 10, 'male', 'us');
在这段代码中,咱们经过在getUsers函数中传递各类可选参数来进行API调用。在进行API调用以前,咱们添加了各类if条件来检查是否添加了参数,并基于此构造查询字符串,以下所示:
https://randomuser.me/api/? page=0&results=10&gender=male&nationality=us
使用ES6的默认参数则没必要添这么多if条件,以下所示:
function getUsers(page = 0, results = 10, gender = 'male',nationality = 'us') { fetch(`https://randomuser.me/api/?page=${page}&results=${results}&gender=${gender}&nationality=${nationality}`) .then(function(response) { return response.json(); }) .then(function(result) { console.log(result); }) .catch(function(error) { console.log('error', error); }); } getUsers();
这样一来,代码获得了大量的简化,即使咱们不为getUsers函数提供任何参数时,它也能采用默认值。固然,咱们也能够传递本身的参数:
getUsers(1, 20, 'female', 'gb');
它将覆盖函数的默认参数。
注意: 定义默认参数时,null和undefined是不一样的。
咱们来看下面的代码:
function display(name = 'David', age = 35, location = 'NY'){ console.log(name, age, location); } display('David', 35); // David 35 NY display('David', 35, undefined); // David 35 NY // OR display('David', 35, undefined); // David 35 NY display('David', 35, null); // David 35 null
当咱们传递null做为参数时,它实际是给location参数赋一个空值,与undefined不同。因此它不会取默认值“NY”。</pre>
ES7增长了数组的includes方法,用来判断一个数组是否包含一个指定的值,若是是返回 true,不然false。
// ES5 Code const numbers = ["one", "two", "three", "four"]; console.log(numbers.indexOf("one") > -1); // true console.log(numbers.indexOf("five") > -1); // false
数组可使用includes方法:
// ES7 Code const numbers = ["one", "two", "three", "four"]; console.log(numbers.includes("one")); // true console.log(numbers.includes("five")); // false
includes方法可使代码简短且易于理解,它也可用于比较不一样的值。
<pre style="line-height:18.0pt; vertical-align:baseline">const day = "monday";</pre> if(day === "monday" || day === "tuesday" || day === "wednesday") { // do something } // 以上代码使用include方法能够简化以下: const day = "monday"; if(["monday", "tuesday", "wednesday"].includes(day)) { // do something }
所以,在检查数组中的值时,使用includes方法将会很是的方便。
除了语法更新,开发者还需掌握一些第三方组件,以便更好的完成项目交付。
例如,使用 SpreadJS表格组件,能够向 JavaScript 应用程序添加高级电子表格功能,包括支持 450 多种计算公式、在线导入导出 Excel 文档、数据透视表和可视化分析,以建立财务、分析、预算/预测、数据收集、科学和许多其余相似的应用程序。
SpreadJS能够经过如下两种方式与Vue一块儿使用:
打开命令提示符窗口并键入如下命令,使用vue init webpack建立一个简单的Vue项目:
$ npm install --global vue-cli # create a new project using the "webpack" template $ vue init webpack my-project # install dependencies and go! $ cd my-project $ npm run dev
在项目中导入SpreadJS Vue模块:
$ npm install @grapecity/spread-sheets-vue
在Vue应用程序中使用SpreadJS:
<template> <div> <gc-spread-sheets :hostClass='hostClass' > <gc-worksheet :dataSource="dataTable" :autoGenerateColumns = 'autoGenerateColumns' > <gc-column :width="width" :dataField="'price'" :visible = 'visible' :formatter = 'formatter' :resizable = 'resizable' ></gc-column> </gc-worksheet> </gc-spread-sheets> </div> </template> <script> import '@grapecity/spread-sheets/styles/gc.spread.sheets.excel2016colorful.css' import '@grapecity/spread-sheets-vue' export default { data(){ return { hostClass:'spread-host', autoGenerateColumns:true, width:300, visible:true, resizable:true, formatter:"$ #.00" } }, computed:{ dataTable(){ let dataTable = []; for (let i = 0; i < 42; i++) { dataTable.push({price: i + 0.56}) } return dataTable } } } </script> <style scoped> .spread-host { width: 500px; height: 600px; } </style>
建立HTML页面,将SpreadJS和Vue-SpreadJS添加到HTML模板:添加引用 gc.spread.sheets.all....min.js, gc.SpreadJS....css 和 gc.spread.sheets.vue...*.js 文件到 HTML 模板 (i.e. 你的 index.html 文件)便可。
与 Vue 相似,SpreadJS也能够经过使用Node包管理器和传统HTML 两种方式与React一块儿使用,点击此处,了解使用方式。
从ES6开始,JavaScript中发生许多变动。对于JavaScript,Angular,React或Vue开发人员都应该知道它们。
了解这些变动可使你成为更棒的开发者,甚至能够帮助您得到更高的薪水。并且,若是你只是在学习React之类的库以及Angular和Vue之类的框架,那么您必定要掌握这些新特性。