ECMAScript
ECMAScript 简介
ECMAScript 是一种由 Ecma 国际(前身为欧洲计算机制造商协会,英文名称是 European Computer Manufacturers Association)通过 ECMA-262 标准化的脚本程序设计语言。ECMAScript 是浏览器脚本语言的规范,而 JavaScript 和 JScript 都是 ECMAScript 规范的实现者。
前端发展历程回顾
Web 1.0 时代
- 最初的网页以 HTML 为主,是纯静态的网页。网页是只读的,信息流只能从服务到客户端单向流通。开发人员只关心页面的样式和内容即可。
Web 2.0 时代
- 1995 年,网景工程师 Brendan Eich 花了 10 天时间设计了 JavaScript 语言。
- 1996 年,微软发布了 JScript,其实是 JavaScript 的逆向工程实现。
- 1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。
- 1997 年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
- JavaScript 和 JScript 都是 ECMAScript 规范的实现者,随后各大浏览器厂商纷纷实现了 ECMAScript 规范。
ES6 快速入门
ES6 简介
ECMAScript 6.0(简称 ES6)是 JavaScript 语言的下一代标准,在 2015 年 6 月正式发布,并且从 ECMAScript 6 开始,开始采用年号来做版本。因此,ECMAScript 2015,也被称为 ECMAScript 6。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。 ECMAScript 每年发布一个新版本,从版本发布的概念上,不同版本的 ECMAScript 可以类比不同版本的 Java JDK(例如 JDK 8、JDK 11、JDK 15)。
变量声明
let 声明变量
let
声明的变量有严格的局部作用域,而 var
声明的变量往往会越域
1 2 3 4 5 6
| { var a = 1; let b = 2; } console.log(a); console.log(b);
|
- 同一个变量,
let
只可以声明一次,而 var
可以声明多次
1 2 3 4 5 6
| var a = 1; var a = 2; let b = 3;
console.log(a); console.log(b);
|
1 2 3 4 5
| console.log(a); var a = 1;
console.log(y); let y = 2;
|
const 声明常量
const
用于声明常量,声明后不允许改变常量的值,一旦声明必须初始化,否则会出现语法错误
解构表达式
数组解构
1 2 3 4 5 6 7 8 9 10 11
| let arr = [1, 2, 3];
let a1 = arr[0]; let a2 = arr[1]; let a3 = arr[2]; console.log(a1, a2, a3);
let [b1, b2, b3] = arr; console.log(b1, b2, b3);
|
对象解构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const person = { name: "jack", age: 23 }
const name1 = person.name; const age1 = person.age; console.log(name1, age1);
const {name, age} = person; console.log(name, age);
const {name:name2, age:age2} = person; console.log(name2, age2);
|
字符串扩展
新 API 函数
includes()
:返回布尔值,表示是否找到了参数字符串startsWith()
:返回布尔值,表示参数字符串是否在原字符串的头部endsWith()
:返回布尔值,表示参数字符串是否在原字符串的尾部
1 2 3 4 5
| let str = "hello world"; console.log(str.startsWith("hello")); console.log(str.endsWith("world")); console.log(str.includes("e")); console.log(str.includes("hello"));
|
字符串模版
模板字符串相当于加强版的字符串,使用反引号 ` 包裹,除了可以作为普通字符串,还可以用来定义多行字符串,也可以在字符串中加入变量和表达式。
1 2 3 4 5 6
| let str = ` <div> <span>Hello World</span> </div> `; console.log(str);
|
1 2 3 4
| let name = "Jack"; let age = 18; let info = `我是${name}, 年龄是${age}岁`; console.log(info);
|
- 字符串中插入表达式,
${}
内可以放入 JavaScript 表达式,例如函数调用
1 2 3 4 5 6 7
| function fun() { return "This is a function." }
let str2 = `Return Message : ${fun()}`; console.log(str2);
|
函数优化
函数参数默认值
ES6 支持给函数参数设置默认值,语法为 参数名称 = 默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function add(a, b) { b = b || 1; return a + b; }
console.log(add(10));
function sub(a, b = 1) { return a - b; }
console.log(sub(3));
|
不定参数
不定参数用来表示不确定参数个数,语法为 ... 变量名
,由 ...
加上一个具名参数标识符组成。特别注意,具名参数只能放在参数列表的最后,并且有且只能有一个不定参数。
1 2 3 4 5
| function fun(... params) { console.log(params.length); } fun("a", "b"); fun("a", "b", "c", "d");
|
箭头函数
声明单个参数的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var print = function (param) { console.log(param); }; print("Hello World");
var echo = (param) => { console.log(param); } echo("Hello World")
var output = param => { console.log(param); } output("Hello World")
var display = param => console.log(param); display("Hello World")
|
声明多个参数的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var sum = function (a, b) { console.log(a + b); } sum(2, 5);
var sub = (a, b) => { console.log(a - b); } sub(5, 2);
var div = (a, b) => console.log(a / b); div(8, 4);
var multi = (a, b) => a * b; console.log(multi(3, 7));
|
箭头函数结合解构表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const person = { name: "jack", age: 23 }
function printPerson(person){ console.log("hello " + person.name); } printPerson(person);
var echoPerson = ({ name }) => { console.log("hello " + name); } echoPerson(person);
|
对象优化
新 API 函数
ES6 给 Object 对象拓展了许多新的 API 函数,如:
keys(obj)
:获取对象的所有 key
形成的数组values(obj)
:获取对象的所有 value
形成的数组entries(obj)
:获取对象的所有 key 和 value 形成的二维数组,格式:[[k1,v1], [k2,v2], ...]
assign(dest, ...src)
:将多个 src
对象的值拷贝到 dest
中(第一层为深拷贝,第二层为浅拷贝)。
1 2 3 4 5 6 7
| const person = { name: "jack", age: 25 } console.log(Object.keys(person)); console.log(Object.values(person)); console.log(Object.entries(person));
|
1 2 3 4 5 6 7
| const dest = {a : 1}; const source1 = {b: 2}; const source2 = {c: 3};
Object.assign(dest, source1, source2); console.log(dest);
|
声明对象简写
1 2 3 4 5 6 7 8 9 10
| const age = 23; const name = "Jack";
const person1 = {age: age, name: name}; console.log(person1);
const person2 = {age, name}; console.log(person2);
|
对象的函数属性简写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| let person = { name: "Jack",
eat: function (food) { console.log(this.name + " eat " + food); },
play1: (toy) => { console.log(person.name + " play " + toy); },
play2 (toy) { console.log(this.name + " play " + toy); } }
person.eat('apple'); person.play1("computer"); person.play2("computer");
|
对象拓展运算符
对象拓展运算符 ...
用于取出参数对象所有可遍历的属性,然后拷贝给当前对象。
1 2 3 4 5 6 7 8 9 10
| let person1 = { name: "Jack", age: 23 }; let someone = { ...person1 }; console.log(someone);
let person2 = { age: 15 }; let person3 = { name: "Jack" }; let person4 = { ...person2, ...person3 }; console.log(person4);
|
map 和 reduce 函数
ES6 在数组中新增了 map
和 reduce
函数。
map 函数
- 语法:
arr.map(callback)
- 描述:
map
函数的参数是一个回调函数,将原数组中的所有元素用这个回调函数处理后放入新数组并返回。
1 2 3 4 5 6 7 8
| let arr = ["1", "23", "5", "16"]; console.log(arr);
arr = arr.map(item => parseInt(item)); console.log(arr);
arr = arr.map(item => item * 2); console.log(arr);
|
reduce 函数
语法:arr.reduce(callback, [initialValue])
描述:reduce
函数会为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素
callback
:处理数组中每个元素的回调函数,包含四个参数
previousValue
:上一次调用回调函数返回的值,或者是提供的初始值(initialValue)currentValue
:数组中当前被处理的元素index
:当前元素在数组中的索引array
:调用 reduce
函数的数组
initialValue
:作为第一次调用 callback
回调函数的初始值(或者上一次回调函数的返回值)
1 2 3 4 5 6 7 8 9
| const arr = [1, 20, -5, 3];
console.log(arr.reduce((a, b) => a + b)); console.log(arr.reduce((a, b) => a * b));
console.log(arr.reduce((a, b) => a + b, 1)); console.log(arr.reduce((a, b) => a * b, 0));
|
Promise
在 JavaScript 的世界中,所有代码都是单线程执行的。由于这个 “缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现。一旦有一连串的 Ajax 请求 a,b,c,d … 后面的请求依赖前面的请求结果,就需要层层嵌套。这种缩进和层层嵌套的方式,非常容易造成上下文代码混乱,开发者不得不非常小心翼翼处理内层函数与外层函数的数据,一旦内层函数使用了上层函数的变量,这种混乱程度就会加剧。总之,这种层叠上下文
的层层嵌套方式,着实增加了开发的难度。
传统 Ajax 回调使用案例
这里将演示传统 Ajax 回调的使用案例,目的是让读者对层层嵌套的代码有一个直观的了解。
需求说明
用户登录后,展示该用户的各科成绩。在页面发送三次请求:
- 1、查询用户,查询成功说明可以登录
- 2、根据用户的查询结果,查询科目信息
- 3、根据科目的查询结果,获取科目成绩
后端接口
此时后台应该提供三个接口,一个提供用户的查询接口,一个提供科目的查询接口,一个提供各科成绩的查询接口。为了渲染方便,建议最好是响应 JSON 数据。在这里就不编写后台接口了,而是提供三个 JSON 文件,通过直接提供 JSON 数据的方式来模拟后台的接口。
1 2 3 4 5
| { "id": 1, "name": "zhangsan", "password": "123456" }
|
1 2 3 4
| { "id": 10, "name": "chinese" }
|
1 2 3 4
| { "id": 100, "score": 90 }
|
调用接口
在传统的 Jquery Ajax 回调方式中,回调函数层层嵌套,也被称为 回调地狱
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| $.ajax({ url: "mock/user.json", success(data) { console.log("查询到用户:", data); $.ajax({ url: `mock/user_corse_${data.id}.json`, success(data) { console.log("查询到课程:", data); $.ajax({ url: `mock/corse_score_${data.id}.json`, success(data) { console.log("查询到分数:", data); }, error(error) { console.log("出现异常了:" + error); } }); }, error(error) { console.log("出现异常了:" + error); } }); }, error(error) { console.log("出现异常了:" + error); } });
|
Promise 语法
1 2 3 4 5 6 7 8 9 10
| const promise = new Promise(function (resolve, reject) { let result = true; if (result) { resolve(value); } else { reject(error); } });
|
1 2 3 4 5 6 7 8 9 10
| const promise = new Promise((resolve, reject) => { let result = true; if (result) { resolve(value); } else { reject(error); } });
|
Prmise 处理执行结果
如果想要等待异步执行完成后做一些事情,可以通过 Promise 的 then
函数来实现。如果想要处理 Promise 异步执行失败的事件,还可以使用 catch
函数。
1 2 3 4 5
| promise.then(function (value) { }).catch(function (error) { })
|
Promise 使用案例
上述的 Jquery Ajax 回调代码,使用 Promise 改造后如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| new Promise((resolve, reject) => { $.ajax({ url: "mock/user.json", success(data) { console.log("查询到用户:", data); resolve(data.id); }, error(error) { console.log("出现异常了:" + error); } }); }).then((userId) => { return new Promise((resolve, reject) => { $.ajax({ url: `mock/user_corse_${userId}.json`, success(data) { console.log("查询到课程:", data); resolve(data.id); }, error(error) { console.log("出现异常了:" + error); } }); }); }).then((corseId) => { $.ajax({ url: `mock/corse_score_${corseId}.json`, success(data) { console.log("查询到分数:", data); }, error(error) { console.log("出现异常了:" + error); } }); });
|
Promise 优化处理
通常在企业开发中,会把 Promise 封装成通用方法,下述的代码封装了一个通用的 get
请求方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| let get = function (url, data) { return new Promise((resolve, reject) => { $.ajax({ url: url, type: "GET", data: data, success(result) { resolve(result); }, error(error) { reject(error); } }); }) }
get("mock/user.json").then((result) => { console.log("查询到用户:", result); return get(`mock/user_corse_${result.id}.json`); }).then((result) => { console.log("查询到课程:", result); return get(`mock/corse_score_${result.id}.json`) }).then((result) => { console.log("查询到分数:", result); }).catch(() => { console.log("出现异常了:" + error); });
|
模块化
模块化介绍
模块化就是把代码进行拆分,方便重复利用。类似 Java 中的导包操作:要使用一个包,必须先导包。而 JavaScript 中没有包的概念,换来的是模块。模块功能主要由两个指令构成: export
和 import
。
export
:用于规定模块的对外接口import
:用于导入其他模块提供的功能
export 指令
比如定义一个 JavaScript 文件 hello.js
,里面有一个对象,可以使用 export
指令将这个对象导出:
1 2 3 4 5 6 7
| const util = { sum (a, b) { return a + b; } }
export {util};
|
当然,也可以简写为:
1 2 3 4 5
| export const util = { sum (a, b) { return a + b; } }
|
export
指定不仅可以导出对象,一切 JavaScript 变量都可以导出,包括基本类型变量、函数、数组、对象。当要导出多个值时,还可以简写,比如定义 user.js
文件:
1 2 3
| var name = "jack" var age = 21 export {name, age}
|
在上面的导出代码中,都明确指定了导出的变量名,这样其它人在导入使用时就必须准确写出变量名,否则就会出错。因此 JavaScript 提供了 default
关键字,可以对导出的变量名进行省略
1 2 3 4 5
| export default { sum (a, b) { return a + b; } }
|
import 指令
使用 export
命令定义了模块的对外接口以后,其他 JavaScript 文件就可以通过 import
指令加载这个模块。
1 2 3 4
| import util from 'hello.js'
util.sum(1, 2);
|
批量导入前面在 user.js
中导出的 name
和 age
变量
1 2 3
| import {name, age} from 'user.js'
console.log(name + " , 今年" + age + "岁了");
|
浏览器支持说明
上面的代码暂时无法直接在浏览器运行,因为浏览器目前还不支持 ES6 的导入和导出功能。除非借助于第三方工具,将 ES6 的语法进行编译降级到 ES5,比如 Babel
工具。值得一提的是,Babel
是一个工具链,主要用于将 ES6+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。