ECMAScript-6(ES2015)
编程之家-ES6 教程:https://www.jb51.cc/es6-tutorial/
阮一峰的开源书籍《ES6 入门教程》,这本书定义 ES6 之后的语法统称 ES6:https://es6.ruanyifeng.com/
ES6
1.为什么要学习 ES6
⚫ ES6 的版本变动内容最多,具有里程碑意义
⚫ ES6 加入许多新的语法特性,编程实现更简单、高效
⚫ ES6 是前端发展趋势,就业必备技能
2.开始 (聚焦 ES6)
这里引用 阮一峰
老师的 ES6标准入门
一书中的总结:ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版本以后的 JavaScript
的下一代标准,涵盖了 ES2015、ES2016、ES2017
等,而 ES2015
则是正式名称,特指当年发布的正式版本的语言标准 市面上提到的 ES6 一般是指 ES2015
标准,但有时也是泛指 下一代 JavaScript
本文主要讲解以下内容:
- 块级作用域(Block scoping,ES2015)
- 解构(Destructuring,ES2015)
- 箭头函数(Arrow Functions,ES2015)
- 模板字符串(template string,ES2015)
- 剩余参数 / 展开语法(Rest and spread parameters,ES2015)
- 对象字面量简写语法(Object shorthand,ES2015)
- 数组实例的 includes() (ES2016)
- Async/await 异步语法 (ES2017)
2.ES6 级其他ES版本兼容性
查看兼容性
语法兼容性查询网站can i use
:https://caniuse.com
ES6+版本的代码 --> 通过 webpack的Babel插件 --> ES5 版本支持的代码
- 兼容性更好
提案
01.let 和 const
let 声明变量
1.let 作用域只局限于当前代码块{ }
块级作用域的任意嵌套
{
{
{
{
{
let insane = "Hello World";
}
console.log(insane); // 报错
}
}
}
}
上面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。 第四层作用域无法读取第五层作用域的内部变量
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函数有两个代码块,都声明了变量 n,运行后输出 5。这表示外层代码块不受内层代码块的影响。 如果两次都使用 var 定义变量 n,最后输出的值才是 10。
内层作用域可以定义外层作用域的同名变量
{
{
{
{
let insane = "Hello World";
{
let insane = "Hello World";
}
}
}
}
}
作用
块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。
- js
// IIFE 写法 (function () { var tmp = ...; ... }()); // 块级作用域写法 { let tmp = ...; ... }
原因看面试题
2.let 声明的变量同层作用域下不能重复声明
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
内层作用域可以定义外层作用域的同名变量
// 不能在函数内部重新声明参数
function func(arg) {
let arg;
}
func(); // 报错
// 不同作用域则可以
// 内层作用域可以定义外层作用域的同名变量
function func(arg) {
{
let arg;
}
}
func(); // 不报错
3.使用 let 声明的变量作用域不会提升
var 命令会发生“变量提升”现象,即变量可以在声明之前使用,值为 undefined
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
脚本开始运行时,变量 foo 已经存在了,但是没有值,所以会输出 undefined
let 所声明的变量一定要在声明后使用,否则报错
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
4.暂时性死区特性 (变量在 let || const 声明之前使用)
var tmp = 123;
if (true) {
tmp = "abc"; // ReferenceError
let tmp;
}
上面代码中,存在全局变量 tmp,但是块级作用域内 let 又声明了一个局部变量 tmp,导致后者绑定这个块级作用域,所以在 let 声明变量前,对 tmp 赋值会报错
解释
ES6 明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的
跟作用域提升一样的报错信息
ReferenceError
ReferenceError: xxx is not defined
const 声明常量
1.只在当前的代码块{}中有效
if (true) {
const MAX = 5;
}
MAX; // 作用域外使用报错:Uncaught ReferenceError: MAX is not defined
2.作用域不会提升
3.暂时性死区
const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
上面代码在常量 MAX 声明之前就调用,结果报错。
4.const 声明的常量不能重复声明
var message = "Hello!";
let age = 25;
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;
5.基本数据类型常量在声明之后值不可更改
const PI = 3.1415;
PI; // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
6.声明的常量必须赋值
const 声明的变量不得改变值,这意味着,const 一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo;
// SyntaxError: Missing initializer in const declaration
7.复合数据类型常量在声明之后值可更改,但不可重新赋值
复合数据类型常量在声明之后值可更改(数据结构内部值可更改),但不可重新赋值(内存地址不可更改)
文档:https://es6.ruanyifeng.com/?search=Reflect&x=0&y=0#docs/let#本质
const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个 内存地址 所保存的数据不得改动
对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量
但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop; // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
代码中,常量 foo 储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把 foo 指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
const array = [100, 200];
array[0] = 123; // 改变属性,成功
// 将数组指向另一个数组,就会报错
array = [];
2
const a = [];
a.push("Hello"); // 可执行
a.length = 0; // 可执行
a = ["Dave"]; // 报错
代码中,常量 a 是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给 a,就会报错。
const a = [];
a.push("Hello"); // 可执行
a.length = 0; // 可执行
a = ["Dave"]; // 报错
对象冻结
1.真的想将对象冻结,应该使用 Object.freeze 方法
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
代码中,常量 foo 指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。
2.对象的属性也应该冻结
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key, i) => {
if (typeof obj[key] === "object") {
constantize(obj[key]);
}
});
};
let、const、var 的区别
var 定义的变量
1.没有块级作用域的概念,可以跨块访问, 不能跨函数访问
2.存在变量提升:允许你在变量声明前使用变量
var 变量提升的问题引起的报错 (for 循环 var 声明变量,会变成全局变量)
for 循环用 var 声明的 i 变量报错
遍历点击改变颜色代码例子
正常运行代码
- js
<h2 class="page-header">点击切换颜色 //获取div元素对象 let items = document.getElementsByClassName('item'); //遍历并绑定事件 for(let i = 0;i<items.length;i++){ items[i].onclick = function(){ //修改当前元素的背景颜色 // this.style.background = 'pink'; items[i].style.background = 'pink'; } }
错误代码
- js
//遍历并绑定事件 for (var i = 0; i < items.length; i++) { items[i].onclick = function () { //修改当前元素的背景颜色 // this.style.background = 'pink'; items[i].style.background = "pink"; }; }
let 定义的变量
1.只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
2.不存在变量提升:不允许你在变量声明前使用变量
3.暂时性死区特性
- 函数或方法外部和内部可以声明同名变量
在 for 循环 let 定义变量
- js
for (let i = 0; i < 2; i++) {} console.log(i); // 报错未定义
这个 let 声明的 i 变量没有变量提升 for 循环外面访问不到,会报错(防止循环变量,变成全局变量)
const 用来定义常量
使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
例子代码
// 块作用域
{
var a = 1;
let b = 2;
const c = 3;
// c = 4; // 报错
var aa;
let bb;
// const cc; // 报错
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(aa); // undefined
console.log(bb); // undefined
}
console.log(a); // 1
// console.log(b); // 报错
// console.log(c); // 报错
// 函数作用域
(function A() {
var d = 5;
let e = 6;
const f = 7;
console.log(d); // 5
console.log(e); // 6
console.log(f); // 7
})();
// console.log(d); // 报错
// console.log(e); // 报错
// console.log(f); // 报错
面试题
var 变量和 let 变量计数循环对比
var arr = [];
for (var i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
};
}
arr[0]();
arr[1]();
此题的关键点在于变量 i 是全局的,函数执行时输出的都是全局作用域下的 i 值。
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
};
}
arr[0]();
arr[1]();
此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的 i 值.
var 变量:内层变量可能会覆盖外层变量
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = "hello world";
}
}
f(); // undefined
上面代码的原意是,if 代码块的外部使用外层的 tmp 变量,内部使用内层的 tmp 变量。 但是,函数 f 执行后,输出结果为 undefined,原因在于变量提升,导致内层的 tmp 变量覆盖了外层的 tmp 变量。
var 变量:用来计数的循环变量泄露为全局变量
var s = "hello";
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面代码中,变量 i 只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
块级作用域可以替换立即执行函数作用域的原因
立即执行函数
(function () {
var b = 123;
});
console.log(b); //error
- 立即执行函数里面的 var 变量,是这个函数的局部变量
- 可以借 立即执行函数 使 var 声明的变量,变成函数的局部变量
let 的块级作用域
- 替代了立即执行函数的局部变量的功能
块级作用域与函数声明
https://es6.ruanyifeng.com/?search=Reflect&x=0&y=0#docs/let#块级作用域与函数声明
函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。
02.变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值, 对变量进行赋值,这被称为解构(Destructuring)
声明变量的六种方法
ES5 只有两种声明变量的方法:
var 命令
function 命令
ES6 添加了四种命令
let 命令
const 命令
import 命令
class 命令
1.解构基本用法
let [name, age, sex] = ["张三", 18, "男"];
console.log(name);
console.log(age);
console.log(sex);
相当于
let name = '张三',age = 18,sex = '男';
2.对象的解构赋值
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo; // "aaa"
bar; // "bbb"
1.对象的属性没有次序,变量必须与属性同名,才能取到正确的值
- js
// 等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响 let { bar, foo } = { foo: "aaa", bar: "bbb" }; foo; // "aaa" bar; // "bbb" // 变量没有对应的同名属性,导致取不到值,最后等于undefined let { baz } = { foo: "aaa", bar: "bbb" }; baz; // undefined
解构失败
- js
// 如果解构失败,变量foo的值等于undefined let { foo } = { bar: "baz" }; foo; // undefined
2.将现有对象的方法,赋值到某个变量
例子一
// 例一:将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上
let { log, sin, cos } = Math;
例子二
// 例二:将console.log赋值到log变量
const { log } = console;
log("hello"); // hello
3.要修改变量名字,得指定名字赋值: let { 原变量名: 要改成的变量名 }
实际上原来的是简写
// 原来的
let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };
// 可以自动简写成
let { foo:, bar } = { foo: 'aaa', bar: 'bbb' };
foo 是匹配的模式,baz 才是变量
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz; // "aaa"
s
let obj = { first: "hello", last: "world" };
let { first: f, last: l } = obj;
f; // 'hello'
l; // 'world'
4.嵌套结构的对象
这时 p 是模式,不是变量,因此不会被赋值
let obj = {
p: ["Hello", { y: "World" }],
};
let {
p: [x, { y }],
} = obj;
x; // "Hello"
y; // "World"
p 也要作为变量赋值
let obj = {
p: ["Hello", { y: "World" }],
};
let {
p,
p: [x, { y }],
} = obj;
x; // "Hello"
y; // "World"
p; // ["Hello", {y: "World"}]
三次解构赋值,分别是对 loc、start、line 三个属性的解构赋值
const node = {
loc: {
start: {
line: 1,
column: 5,
},
},
};
let {
loc,
loc: { start },
loc: {
start: { line },
},
} = node;
line; // 1
loc; // Object {start: Object}
start; // Object {line: 1, column: 5}
5.对象嵌套赋值
let obj = {};
let arr = [];
({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });
obj; // {prop:123}
arr; // [true]
解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错
// 报错
let {
foo: { bar },
} = { baz: "baz" };
6.对象的解构赋值可以取到继承的属性
const obj1 = {};
const obj2 = { foo: "bar" };
Object.setPrototypeOf(obj1, obj2);
const { foo } = obj1;
foo; // "bar"
对象 obj1 的原型对象是 obj2。foo 属性不是 obj1 自身的属性,而是继承自 obj2 的属性,解构赋值可以取到这个属性
Object.setPrototypeOf(obj, proto)
参数
obj:要设置原型对象的对象。
proto:该对象的新原型对象或 null,否则抛出 TypeError 异常。
返回值
- 设置了新的原型对象的对象
7.对象的解构指定默认值
使用例子
var { x = 3 } = {};
x; // 3
var { x, y = 5 } = { x: 1 };
x; // 1
y; // 5
var { x: y = 3 } = {};
y; // 3
var { x: y = 3 } = { x: 5 };
y; // 5
var { message: msg = "Something went wrong" } = {};
msg; // "Something went wrong"
默认值生效的条件是,对象的属性值严格等于 undefined
// 默认值生效
var { x = 3 } = { x: undefined };
x; // 3
// 默认值不生效
var { x = 3 } = { x: null };
x; // null
3.数组的解构赋值
模式匹配
let [a, b, c] = [1, 2, 3];
// 等价于
let a = 1;
let b = 2;
let c = 3;
1.嵌套数组进行解构
let [x, , y] = [1, 2, 3];
x; // 1
y; // 3
let [, , third] = ["foo", "bar", "baz"];
third; // "baz"
let [arr1, [arr2, arr3, [arr4, arr5]]] = [1, [2, 3, [4, 5]]];
arr1; // 1
arr2; // 2
arr3; // 3
arr4; // 4
arr5; // 5
let [head, ...tail] = [1, 2, 3, 4];
head; // 1
tail; // [2, 3, 4]
let [x, y, ...z] = ["a"];
x; // "a"
y; // undefined
z; // []
2.解构不成功
let [foo] = [];
let [bar, foo] = [1];
两种情况都属于解构不成功,foo 的值都会等于 undefined。
3.不完全解构
- 即等号左边的模式,只匹配一部分的等号右边的数组
let [x, y] = [1, 2, 3];
x; // 1
y; // 2
let [a, [b], d] = [1, [2, 3], 4];
a; // 1
b; // 2
d; // 4
4.报错解构
如果等号的右边不是数组
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
上面的语句都会报错,因为等号右边的值,要么转为对象以后不具备 Iterator 接口(前五个表达式),要么本身就不具备 Iterator 接口(最后一个表达式)
5.Set 结构给 数组的解构赋值
let [x, y, z] = new Set(["a", "b", "c"]);
x; // "a"
6.只要某种数据结构具有 Iterator 接口 都可以采用数组形式的解构赋值
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let [first, second, third, fourth, fifth, sixth] = fibs();
sixth; // 5
上面代码中,fibs 是一个 Generator 生成器函数(参见《11.迭代器和生成器》一章),原生具有 Iterator 接口。解构赋值会依次从这个接口获取值
7.数据解构的默认值
解构赋值允许指定默认值
let [foo = true] = [];
foo; // true
let [x, y = "b"] = ["a"]; // x='a', y='b'
let [x, y = "b"] = ["a", undefined]; // x='a', y='b'
只有当一个数组成员严格等于(===) undefined,默认值才会生效
let [x = 1] = [undefined]; // 默认值生效
x; // 1
let [x = 1] = [null];
x; // null
解构赋值表达式结合
- js
function f() { console.log("aaa"); } let [x = f()] = [1];
上面代码中,因为 x 能取到值,所以函数 f 根本不会执行。
上面的代码其实等价于下面的代码。
let x;
if ([1][0] === undefined) {
x = f();
} else {
x = [1][0];
}
默认值可以引用解构赋值的其他变量, 但该变量必须已经声明
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined
上面最后一个表达式之所以会报错,是因为 x 用 y 做默认值时,y 还没有声明
数组的解构赋值注意点
(1)如果要将一个已经声明的变量用于解构赋值,必须非常小心
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
// 正确的写法
let x;
({ x } = { x: 1 });
上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。
关于圆括号与解构赋值的关系:
(2)解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式
({} = [true, false]);
({} = "abc");
({} = []);
上面的表达式虽然毫无意义,但是语法是合法的,可以执行。
(3)由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构
let arr = [1, 2, 3];
let { 0: first, [arr.length - 1]: last } = arr;
first; // 1
last; // 3
对数组进行对象解构
数组 arr 的 0 键对应的值是 1,[arr.length - 1]就是 2 键,对应的值是 3
方括号这种写法,属于“属性名表达式”
4.字符串的解构赋值
const [a, b, c, d, e] = "hello";
a; // "h"
b; // "e"
c; // "l"
d; // "l"
e; // "o"
字符串被转换成了一个类似数组的对象
let { length: len } = "hello";
len; // 5
类似数组的对象都有一个 length 属性,因此还可以对这个属性字符串的长度解构赋值
5.数值和布尔值的解构赋值
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象
// 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象
let { toString: s } = 123;
s === Number.prototype.toString; // true
let { toString: s } = true;
s === Boolean.prototype.toString; // true
数值和布尔值的包装对象都有 toString 属性,因此变量 s 都能取到值
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
由于 undefined 和 null 无法转为对象,所以对它们进行解构赋值,都会报错
6.函数参数的解构赋值
function add([x, y]) {
return x + y;
}
add([1, 2]); // 3
函数 add 的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量 x 和 y
js 内置方法解构赋值
[
[1, 2],
[3, 4],
].map(([a, b]) => a + b);
// [ 3, 7 ]
函数参数的解构 使用默认值
// undefined就会触发函数参数的默认值
[1, undefined, 3].map((x = "yes") => x);
// [ 1, 'yes', 3 ]
2
function move({ x = 0, y = 0 } = {}) {
return [x, y];
}
move({ x: 3, y: 8 }); // [3, 8]
move({ x: 3 }); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
上面代码中,函数 move 的参数是一个对象,通过对这个对象进行解构,得到变量 x 和 y 的值。如果解构失败,x 和 y 等于默认值
function move({ x, y } = { x: 0, y: 0 }) {
return [x, y];
}
move({ x: 3, y: 8 }); // [3, 8]
move({ x: 3 }); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
上面代码是为函数 move 的参数指定默认值,而不是为变量 x 和 y 指定默认值,所以会得到与前一种写法不同的结果
7.圆括号问题
文档:https://es6.ruanyifeng.com/?search=Reflect&x=0&y=0#docs/destructuring#圆括号问题
变量的解构赋值用途
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
(2)从函数返回多个值
返回一个数组
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
返回一个对象
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2,
};
}
let { foo, bar } = example();
(3)函数参数的定义
参数是一组有次序的值
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
参数是一组无次序的值
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
(4)提取 JSON 数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309],
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
(5)函数参数的默认值
// 定义参数
var param = {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}
// 函数
jQuery.ajax = function (url, param = {}) {
// ... do stuff
};
指定参数的默认值,就避免了在函数体内部再写 var foo = config.foo || 'default foo';这样的语句
(6)遍历 Map 结构获取键值对
任何部署了 Iterator 接口的对象,都可以用 for...of 循环遍历。
Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
const map = new Map();
map.set("first", "hello");
map.set("second", "world");
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
如果只想获取键名,或者只想获取键值
获取键名
// 获取键名
for (let [key] of map) {
// ...
}
获取键值
// 获取键值
for (let [, value] of map) {
// ...
}
(7)输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰
const { SourceMapConsumer, SourceNode } = require("source-map");
小结
解构赋值就是把数据结构分解,然后给变量进行赋值
如果结构不成功,变量跟数值个数不匹配的时候,变量的值为 undefined
数组解构用中括号包裹,多个变量用逗号隔开,对象解构用花括号包裹,多个变量用逗号隔开
利用解构赋值能够让我们方便的去取对象中的属性跟方法
03.字符串的扩展
1.字符的 Unicode 表示法
ES6 加强了对 Unicode 的支持
允许采用\uxxxx 形式表示一个字符,其中 xxxx 表示字符的 Unicode 码点
// "a"的 Unicode 编码 "\u0061"
但是,这种表示法只限于码点在\u0000~\uFFFF 之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。
"\uD842\uDFB7";
// "𠮷"
"\u20BB7";
// " 7"
如果直接在\u 后面跟上超过 0xFFFF 的数值(比如\u20BB7),JavaScript 会理解成\u20BB+7。由于\u20BB 是一个不可打印字符,所以只会显示一个空格,后面跟着一个 7
只要将码点放入大括号,就能正确解读该字符
"\u{20BB7}";
// "𠮷"
"\u{41}\u{42}\u{43}";
// "ABC"
let hello = 123;
hello; // 123
// 大括号表示法与四字节的 UTF-16 编码是等价的
"\u{1F680}" === "\uD83D\uDE80";
// true
有了这种表示法之后,JavaScript 共有 6 种方法可以表示一个字符
"z" === "z"; // true
"\172" === "z"; // true
"\x7A" === "z"; // true
"\u007A" === "z"; // true
"\u{7A}" === "z"; // true
2.字符串的遍历器接口 for...of
ES6 为字符串添加了遍历器接口(详见迭代器(Iterator)),使得字符串可以被 for...of 循环遍历
for (let codePoint of "foo") {
console.log(codePoint);
}
// "f"
// "o"
// "o"
识别大于 0xFFFF 的码点
let text = String.fromCodePoint(0x20bb7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
for (let i of text) {
console.log(i);
}
// "𠮷"
上面代码中,字符串 text 只有一个字符,但是 for 循环会认为它包含两个字符(都不可打印),而 for...of 循环会正确识别出这一个字符。
3.直接输入 U+2028 和 U+2029
JSON.stringify() 的改造
03.模板字符串
ES6 新增的创建字符串的方式,使用反引号定义
1.基本定义
let name = `zhangsan`;
2.模板字符串中可以解析变量
let name = "张三";
let sayHello = `hello,my name is ${name}`;
console.log(sayHello);
3.模板字符串中可以换行
let result = {
name: "zhangsan",
age: 20,
sex: "男",
};
let html = `
${result.name}
${result.age}
${result.sex}
`;
4.在模板字符串中可以调用函数
const sayHello = function () {
return "哈哈 追不到我吧";
};
let greet = `${sayHello()} 啦啦啦`;
console.log(greet); // 哈哈 追不到我吧啦啦啦
04.对象简化写法
对象声明简化
let name = ‘张三’
let age = 18
let info = {
name:name,
age:age
}
// 变成
let name = ‘张三’
let age = 18
let info = {
name,
age
}
对象方法简化写法
const school = {
func: function () {},
};
// 变成
const school = {
func() {},
};
05.箭头函数
使用
() => {}
- ():代表是函数; =>:必须要的符号,指向哪一个代码块;{}:函数体
const fn = () => {}
- 把一个函数赋值给 fn 常量,便于调用
const fn = (n1,n2) => n1 + n2
- 返回值可以省略{},直接写代码 n1+n2
const fn = n1 => n1
- 形参只有一个可以省略(),和只有一条语句时,可以省略{}
剩余参数
使用
const sum = (...args) => {
let total = 0;
args.forEach((item) => (total += item));
return total;
};
sum(10, 20);
sum(10, 20, 30);
- ...args
- 剩余的参数全部给它
剩余参数配合解构使用
箭头函数的 this 指向
1.箭头函数不绑定 this 关键字,箭头函数中的 this,指向的是函数定义位置的上下文 this (函数声明时所在作用域下的 this 的值)
const obj = { name: "张三" };
function fn() {
console.log(this); //this 指向 是obj对象
return () => {
console.log(this); //this 指向 的是箭头函数定义的位置,那么这个箭头函数定义在fn里面,而这个fn指向是的obj对象,所以这个this也指向是obj对象
};
}
const resFn = fn.call(obj);
resFn();
2.如果在箭头函数中使用 this,this 关键字将指向 箭头函数定义位置中的 this
定义箭头函数中的作用域的 this 指向谁,它就指向谁
对象不能产生作用域,如果调用的是对象里面的方法,this.指向的是全局变量
3.箭头函数不适合与 this 有关的回调. 事件回调, 对象的方法
箭头函数使用注意
1.箭头函数的 this 是静态的
无论用什么方式想改变 this 的值,this 的指向始终都是函数声明时所在的作用域下
2.箭头函数 不能作为构造函数实例化对象
let person = (name, age) => {
this.name = name;
this.age = age;
};
let me = new Person("xiao0", 30);
console.log(me); // 报错
3.不能使用 argumnets 变量
let fn = () => {
console.log(arguments);
};
fn(1, 2, 3); // 报错
要使用剩余参数...args
const sum = (...args) => {
let total = 0;
args.forEach((item) => (total += item));
return total;
};
sum(10, 20);
sum(10, 20, 30);
...args:剩余的参数全部给它
箭头函数应用场景
箭头函数适用于与 this 无关的回调:定时器、数组的方法回调
定时变色案例
//需求: 点击 div 2s 后颜色变成『粉色』
//获取元素
let ad = document.getElementById("ad");
//绑定事件
ad.addEventListener("click", function () {
//保存 this 的值
// let _this = this;
//定时器
setTimeout(() => {
//修改背景颜色 this
// console.log(this);
// _this.style.background = 'pink';
this.style.background = "pink";
}, 2000);
});
数组中返回偶数的元素案例
//需求-2 从数组中返回偶数的元素
const arr = [1, 6, 9, 10, 100, 25];
// const result = arr.filter(function(item){
// if(item % 2 === 0){
// return true;
// }else{
// return false;
// }
// });
const result = arr.filter((item) => item % 2 === 0);
console.log(result);
箭头函数面试题
1
var age = 100;
var obj = {
age: 20,
say: () => {
alert(this.age);
},
};
obj.say(); //箭头函数this指向的是被声明的作用域里面,而对象没有作用域的,所以箭头函数虽然在对象中被定义,但是this指向的是全局作用域
06.函数参数默认值
1.形参初始值 具有默认值的参数, 一般位置要靠后(潜规则)
function add(a, c = 10, b) {
return a + b + c;
}
let result = add(1, 2);
console.log(result);
2.与解构赋值结合
function connect({ host = "127.0.0.1", username, password, port }) {
console.log(host);
console.log(username);
console.log(password);
console.log(port);
}
connect({
host: "atguigu.com",
username: "root",
password: "root",
port: 3306,
});
07.rest 剩余参数
ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments
ES5 剩余参数变量 arguments
function date() {
// arguments代表剩余参数
console.log(arguments);
}
date("阿娇", "柏芝", "思慧");
函数 rest 剩余参数
function date(...args) {
console.log(args);
}
date("阿娇", "柏芝", "思慧");
函数 rest 剩余参数 必须要放到参数最后
function fn(a, b, ...args) {
console.log(a);
console.log(b);
console.log(args);
}
fn(1, 2, 3, 4, 5, 6);
剩余参数和解构配合使用
let students = ["wangwu", "zhangsan", "lisi"];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
08.spread 拓展运算符
扩展运算符(spread)也是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。
扩展例子
数组扩展
let arr = ["a", "b", "c", "d"];
console.log(...ary);
数组参数扩展
const arr = ["1", "2", "3"];
function chunwan() {
console.log(arguments);
}
chunwan(...arr); // chunwan('易烊千玺','王源','王俊凯')
拓展运算符应用
1.数组合并
const kuaizi = ["王太利", "肖央"];
const fenghuang = ["曾毅", "玲花"];
const zuixuanxiaopingguo = kuaizi.concat(fenghuang);
const zuixuanxiaopingguo = [...kuaizi, ...fenghuang];
console.log(zuixuanxiaopingguo);
push 方法合并数组
fenghuang.push(...kuaizi);
2.一维数组的或对象克隆 (一层数组或对象的深拷贝)
const sanzhihua = ["E", "G", "M"];
// ['E','G','M']
const sanyecao = [...sanzhihua];
console.log(sanyecao);
3.将伪数组转为真正的数组
// 伪数组
const divs = document.querySelectorAll("div");
// 转成真数组
const divArr = [...divs];
console.log(divArr);
// arguments参数也可以转,但rest参数足以
使用构造函数 Array.from()方法 也能将伪数组转成真正的数组
// 定义一个集合
let arrayLike = {
0: "a",
1: "b",
2: "c",
length: 3,
};
// 转成数组
let arr2 = Array.from(arrayLike);
// ['a', 'b', 'c']
方法还可以接受第二个参数-函数,作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组
let arrayLike = {
0: 1,
1: 2,
length: 2,
};
let newAry = Array.from(arrayLike, (item) => item * 2); // [2,4]
09.Symbol 类型
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。
Symbol 特点
1.Symbol 的值是唯一的,用来解决命名冲突的问题
2.Symbol 值不能与其他数据进行运算
3.Symbol 定义的对象属性不能使用 for…in 循环遍历 ,但是可以使用 Reflect.ownKeys 来获取对象的所有键名
使用场景
ES5 的对象属性名都是字符串,容易造成属性名冲突
Symbol 类型用于解决命名冲突
注: 遇到唯一性的场景时要想到 Symbol
创建方法
正常创建
// 创建 Symbol
let s1 = Symbol();
console.log(s1, typeof s1);
添加标识创建
//添加标识的 Symbol
let s2 = Symbol("尚硅谷");
let s2_2 = Symbol("尚硅谷");
console.log(s2 === s2_2);
使用 Symbol for 定义
// 使用 Symbol for 定义 值是相等的
let s3 = Symbol.for("尚硅谷");
let s3_2 = Symbol.for("尚硅谷");
console.log(s3 === s3_2);
Symbol 类型应用
向对象中添加属性或属性方法
//向对象中添加方法 up down
let game = {
name: "俄罗斯方块",
up: function () {},
down: function () {},
};
//声明一个对象,添加属性
// let methods = {
// up: Symbol(),
// down: Symbol()
// };
// game[methods.up] = function(){
// console.log("我可以改变形状");
// }
// game[methods.down] = function(){
// console.log("我可以快速下降!!");
// }
// console.log(game);
//
let youxi = {
name: "狼人杀",
// 添加属性方法
[Symbol("say")]: function () {
console.log("我可以发言");
},
[Symbol("zibao")]: function () {
console.log("我可以自爆");
},
};
console.log(youxi);
对象的属性名 可以有两种类型
字符串
- let s4 = Symbol.for('哈哈哈'); let s5 = Symbol.for('哈哈哈')
Symbol 类型
- 独一无二,不会与其他属性名冲突
不能与其他数据进行运算
使用注意
不能与其他数据进行运算和比较
//不能与其他数据进行运算
// let result = s + 100;
// let result = s > 100;
// let result = s + s;
Symbol 内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场 景下自动执行。
文档:https://www.cnblogs.com/zygll/p/14262826.html
内置值
Symbol.hasInstance
- 当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法
Symbol.isConcatSpreadable
- 对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于 Array.prototype.concat()时,是否可以展开。
Symbol.species
- 创建衍生对象时,会使用该属性
Symbol.match
- 当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。
Symbol.replace
- 当该对象被 str.replace(myObject)方法调用时,会返回该方法的返回值。
Symbol.search
- 当该对象被 str. search (myObject)方法调用时,会返回该方法的返回值。
Symbol.split
- 当该对象被 str. split (myObject)方法调用时,会返回该方法的返回值。
Symbol.iterator
- 对象进行 for…of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器
Symbol.toPrimitive
- 该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
Symbol.toStringTag
- 在该对象上面调用 toString 方法时,返回该方法的返回值
Symbol.unscopables
- 该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。
使用例子
class Person {
static [Symbol.hasInstance](param) {
console.log(param);
console.log("我被用来检测类型了");
return false;
}
}
let o = {};
console.log(o instanceof Person);
const arr = [1, 2, 3];
const arr2 = [4, 5, 6];
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr.concat(arr2));
10.内置对象方法扩展
数组方法的扩展
ary1 = [1, 2, 3];
ary2 = [4, 5, 6];
let ary3 = [...ary1, ...ary2];
...ary1
- 拆分成 1,2,3
分解后再合并成一个数组 arry3
将伪数组转成真数组
// 将伪数组转成真数组
Array.from(伪数组名, 函数);
零散的数统一转成数组
// 零散的数统一转成数组
Array.of(1, 2, 3);
找出第一个符合条件的数组成员,如果没有找到返回 undefined
Array.find(查找条件); // 查找条件可以是函数,或者其他
找出第一个符合条件的数组成员的位置,如果没有找到返回-1
Array.findIndex(value, index);
某个数组是否包含给定的 1 值,返回布尔值
参数
可以是字符串
可以是数字
可以是其他
Array.includes(1);
复制宽度
Array.copywithin(3, 7, 9);
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
Array.copywithin(3, 7, 9);
alert(arr); // 1,2,3,8,9,6,7,8
第一个参数
- 从哪个下标开始
第二和第三个参数
- 范围[start,end]
数值方法的扩展
0.Number.EPSILON
是 JavaScript 表示的最小精度
// EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16
function equal(a, b) {
if (Math.abs(a - b) < Number.EPSILON) {
return true;
} else {
return false;
}
}
// 浮点数运算和对比
console.log(0.1 + 0.2 === 0.3);
console.log(equal(0.1 + 0.2, 0.3));
1.二进制和八进制的表示方式
let b = 0b1010;
let o = 0o777;
let d = 100;
let x = 0xff;
console.log(x);
2.Number.isFinite
检测一个数值是否为有限数,返回布尔值
console.log(Number.isFinite(100));
console.log(Number.isFinite(100 / 0));
console.log(Number.isFinite(Infinity));
3.Number.isNaN
检测一个数值是否为 NaN ,返回布尔值
console.log(Number.isNaN(123));
4.Number.parseInt | Number.parseFloat
字符串转整数
console.log(Number.parseInt("5211314love"));
console.log(Number.parseFloat("3.1415926神奇"));
5.Number.isInteger
判断一个数是否为整数
console.log(Number.isInteger(5));
console.log(Number.isInteger(2.5));
6.Math.trunc
将数字的小数部分抹掉
console.log(Math.trunc(3.5));
7.Math.sign
判断一个数到底为正数 负数 还是零
console.log(Math.sign(100));
console.log(Math.sign(0));
console.log(Math.sign(-20000));
字符串方法的扩展
模板字符串
ES6 新增的创建字符串的方式,使用反引号定义
1.基本定义
let name = `zhangsan`;
2.模板字符串中可以解析变量
let name = "张三";
let sayHello = `hello,my name is ${name}`;
console.log(sayHello);
3.模板字符串中可以换行
let result = {
name: "zhangsan",
age: 20,
sex: "男",
};
let html = `
${result.name}
${result.age}
${result.sex}
`;
4.在模板字符串中可以调用函数
const sayHello = function () {
return "哈哈 追不到我吧";
};
let greet = `${sayHello()} 啦啦啦`;
console.log(greet); // 哈哈 追不到我吧啦啦啦
startsWith() 和 endsWith()
let str = "Hello world!";
str.startsWith("Hello"); // true
str.endsWith("!"); // true
startsWith()
- 表示参数字符串是否在原字符串的头部,返回布尔值
endsWith()
- 表示参数字符串是否在原字符串的尾部,返回布尔值
str.repeat()
将原字符串重复 n 次,返回一个新字符串
"x".repeat(3); // "xxx"
"hello".repeat(2); // "hellohello"
对象方法的扩展
Object.is(obj1,obj2)
判断两个值是否完全相等,返回布尔值
console.log(Object.is(NaN, NaN)); // true为真,是相等的
console.log(NaN === NaN); // false 为假,不相等
Object.assign(obj1,obj2,obj3)
多个对象合并到 第一个对象 里面去:适合用于 配置的合并
// 配置项的合并例子
const config1 = {
host: "localhost",
port: 3306,
name: "root",
pass: "root",
test: "test",
};
const config2 = {
host: "http://atguigu.com",
port: 33060,
name: "atguigu.com",
pass: "iloveyou",
test2: "test2",
};
console.log(Object.assign(config1, config2));
Object.setPrototypeOf() Object.getPrototypeOf()
获取和设置原型对象
// 获取和设置原型对象
const school = {
name: "尚硅谷",
};
const cities = {
xiaoqu: ["北京", "上海", "深圳"],
};
Object.setPrototypeOf(school, cities);
console.log(Object.getPrototypeOf(school));
console.log(school);
延展操作符
- 去除重复数据
数据集合方法
1.数据集合-new Set()
new Set()方法简介
ES6 提供了新的数据结构 Set(集合)。 它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历
let se = new Set(["大事", "好事", "小事", "坏事"]);
for (let v of se) {
console.log(v);
}
new Set()方法概况
1.特点
01.类似于数组,没有重复的元素(唯一的)
02.开发中用于去除重复数据
03.key 和 value 都是相等的
2.四个方法
构造函数创建:
const set = new Set();
add(value)
set.add('张三');
添加某个值,返回 Set 结构本身
delete(value)
set.delete('张三');
删除某个值,返回一个布尔值,表示删除是否成功
has(value)
set.has('张三');
返回一个布尔值,表示该值是否为 Set 的成员
clear()
set.clear();
清除所有成员,没有返回值
3.一个属性
- set.size
- 获取过滤重复值后的数组长度
从数据结构中取值遍历
Set 结构的实例与数组一样,拥有 forEach 方法,用于对每个成员执行某种操作,没有返回值
相当于:
const set = new Set(['a','b','c','d']) set.forEach(value => console.log(value))
new Set()方法的使用
const newSet = new Set();
- Set 本身是一个构造函数,用来生成 Set 数据结构
// Set函数可以接受一个数组作为参数, 用来初始化。
const set = new Set([1, 2, 3, 4, 4]);
console.log(set);
// 值转为{1, 2, 3, 4} , 去除了重复的数据 4
new Set()方法可以做数组去重:常用于用户搜索记录功能
new Set()方法应用
数组去重
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let result = [...new Set(arr)];
console.log(result);
交集
数组
- js
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1]; let arr2 = [4, 5, 6, 5, 6];
全部
- js
let result = [...new Set(arr)].filter((item) => { let s2 = new Set(arr2); // 4 5 6 if (s2.has(item)) { return true; } else { return false; } });
简化
- js
let result = [...new Set(arr)].filter((item) => new Set(arr2).has(item)); console.log(result);
并集
数组
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let arr2 = [4, 5, 6, 5, 6];
let union = [...new Set([...arr, ...arr2])];
console.log(union);
差集
数组
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let arr2 = [4, 5, 6, 5, 6];
let diff = [...new Set(arr)].filter((item) => !new Set(arr2).has(item));
console.log(diff);
2.数据集合-new Map()
new Map()方法简介
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。
但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历
new Map()方法概况
1.特点
01.类似于对象,本质上是键值对的集合
- Object 解构提供了"字符串-值"的对应。Map 结构提供了"值-值"的对应, 是一种 的 Hash 结构的实现
2.属性
- map.size
- 长度
- map.size
3.常用方法
1.set 和 get:设置和获取
map.set(age,['gg','ll']).set(['dog'],'ggg')
map.get(age)
2.delete
- map.delete(obj);
3.has
- map.has(obj);
4.clear
- map.clear();
5.keys(), values(), entries()
map.keys();
map.values();
map.entries();
4.遍历
- map.forEach();
new Map()方法的使用
1
//创建一个空 map
let m = new Map();
//创建一个非空 map
let m2 = new Map([
["name", "尚硅谷"],
["slogon", "不断提高行业标准"],
]);
//属性和方法
//获取映射元素的个数
console.log(m2.size);
//添加映射值
console.log(m2.set("age", 6));
//获取映射值
console.log(m2.get("age"));
//检测是否有该映射
console.log(m2.has("age"));
//清除
console.log(m2.clear());
//遍历
for (let v of m) {
console.log(v);
}
2
// 声明 Map
let m = new Map();
// 添加元素
m.set("name", "尚硅谷");
m.set("change", function () {
console.log("我们可以改变你!!");
});
let key = {
school: "ATGUIGU",
};
m.set(key, ["北京", "上海", "深圳"]);
//size
// console.log(m.size);
//删除
// m.delete('name');
//获取
// console.log(m.get('change'));
// console.log(m.get(key));
//清空
// m.clear();
//遍历
for (let v of m) {
console.log(v);
}
// console.log(m);
new Map()方法应用
11.迭代器和生成器
迭代器(Iterator)
迭代器简介
ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费
迭代器也称遍历器(Iterator)就是一种机制。
它是一种接口,为各种不同的数据结构提供统一的访问机制。
任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。使得集合的操作变得更容易
两个核心
1)迭代器能快捷的访问数据,通过 Symbol.iterator 创建迭代器,通过迭代器的 next()方法获取结果
2)迭代器是用于遍历数据结构的指针(数据库的游标)
原生具备 iterator 接口的数据 (可用 for of 遍历)
a) Array
b) Arguments
c) Set
d) Map
e) String
f) TypedArray
g) NodeList
工作原理和例子
迭代器是一种特殊对象,他具有一些专门为迭代过程设计的专有接口,所有的迭代器对象都有一个 next()方法,每次调用都返回一个结果对象。
结果对象有两个属性
一个是 value,表示下一个将要返回的值;
另一个是 done,他是一个布尔类型的值,在当没有可返回的数据时返回 true,预示着迭代结束
使用步骤
a) 创建一个指针对象,指向当前数据结构的起始位置
b) 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
c) 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
d) 每调用 next 方法返回一个包含 value 和 done 属性的对象
代码例子
//声明一个数组
const xiyou = ["唐僧", "孙悟空", "猪八戒", "沙僧"];
//使用 for...of 遍历数组
// for(let v of xiyou){
// console.log(v);
// }
let iterator = xiyou[Symbol.iterator]();
//调用对象的next方法
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
2
let arr = [1, 2, 3];
console.log(arr);
//输出arr 打开arr的原型 会发现上面有Symbol方法
//通过Symbol.iterator创建迭代器
const iter = arr[Symbol.iterator]();
console.log(iter); //done为是否迭代完成,false是未完成,true为迭代完成
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
注: 需要自定义遍历数据的时候,想到使用迭代器。
自定义遍历数据例子
//声明一个对象
const banji = {
name: "终极一班",
stus: ["xiaoming", "xiaoning", "xiaotian", "knight"],
[Symbol.iterator]() {
//索引变量
let index = 0;
//
let _this = this;
return {
next: function () {
if (index < _this.stus.length) {
const result = { value: _this.stus[index], done: false };
//下标自增
index++;
//返回结果
return result;
} else {
return { value: undefined, done: true };
}
},
};
},
};
//遍历这个对象
for (let v of banji) {
console.log(v);
}
ES5 语法构建迭代器
function createIterator() {
var i = 0;
return {
next: function () {
var done = i >= items.length;
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value,
};
},
};
}
var iterator = createInterator([1, 2, 3]);
console.log(iterator.next()); // "{value:1,done:false}"
console.log(iterator.next()); // "{value:2,done:false}"
console.log(iterator.next()); // "{value:3,done:false}"
console.log(iterator.next()); // "{value:undefined,done:true}"
// 当调用次数超过实际值后,不管再调用多少次结果都会是如下值
console.log(iterator.next()); // "{value:undefined,done:true}"
createIterator()方法会返回一个对象,该对象拥有一个 next()方法,每次调用 next()方法,都会判断 i 是否大于等于 当前实参的长度;大于或等于时 done 即为 true,反之亦然。然后将 done 取反;当 done 为 true 时 value 赋值为 undefined;否则将 i+1,再取 item 对应下标的值赋值于 value。ES6 迭代器实现原理和案例也就类似。
迭代器一般需要配合生成器使用
生成器(Generator)
生成器(Generator)简介
生成器函数是 ES6 提供的一种异步编程解决方案
简单代码例子和说明
简单代码例子
function* gen() {
yield "一只没有耳朵";
yield "一只没有尾巴";
return "真奇怪";
}
let iterator = gen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
代码说明
1.表示该函数是一个生成器, * 的位置没有限制
- 星号可以紧挨着 function,也可以在中间添加一个空格。
2.生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值
3.yield 相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次 next 方法,执行一段代码
4.next 方法可以传递实参,作为 yield 语句的返回值
generator 函数 可以通过 yield 关键字 将函数挂起,为改变执行流提供了可能,同时为了做异步编程提供了方案。
生成器函数参数
function* gen(arg) {
console.log(arg);
let one = yield 111;
console.log(one);
let two = yield 222;
console.log(two);
let three = yield 333;
console.log(three);
}
//执行获取迭代器对象
let iterator = gen("AAA");
console.log(iterator.next());
//next方法可以传入实参:调用都是下一个的返回结果
console.log(iterator.next("BBB"));
console.log(iterator.next("CCC"));
console.log(iterator.next("DDD"));
生成器函数实例
异步编程实例:文件操作/网络操作(ajax, request)/数据库操作
// 异步编程 文件操作 网络操作(ajax, request) 数据库操作
// 1s 后控制台输出 111 2s后输出 222 3s后输出 333
// 回调地狱
// setTimeout(() => {
// console.log(111);
// setTimeout(() => {
// console.log(222);
// setTimeout(() => {
// console.log(333);
// }, 3000);
// }, 2000);
// }, 1000);
function one() {
setTimeout(() => {
console.log(111);
iterator.next();
}, 1000);
}
function two() {
setTimeout(() => {
console.log(222);
iterator.next();
}, 2000);
}
function three() {
setTimeout(() => {
console.log(333);
iterator.next();
}, 3000);
}
function* gen() {
yield one();
yield two();
yield three();
}
//调用生成器函数
let iterator = gen();
iterator.next();
模拟获取数据实例: 用户数据 /订单数据 /商品数据
//模拟获取 用户数据 订单数据 商品数据
function getUsers() {
setTimeout(() => {
let data = "用户数据";
//调用 next 方法, 并且将数据传入
iterator.next(data);
}, 1000);
}
function getOrders() {
setTimeout(() => {
let data = "订单数据";
iterator.next(data);
}, 1000);
}
function getGoods() {
setTimeout(() => {
let data = "商品数据";
iterator.next(data);
}, 1000);
}
// 生成器
function* gen() {
let users = yield getUsers();
let orders = yield getOrders();
let goods = yield getGoods();
}
//调用生成器函数
let iterator = gen();
iterator.next();
高效处理数据的关键
迭代器,数组方法,新增的集合类型(set,map)结合使用
12.class 类的基本运用
方法字段说明
1.class 声明类
2.constructor 定义构造函数初始化
3.extends 继承父类
4.super 调用父级构造方法
5.static 定义静态方法和属性
6.父类方法可以重写
- 子类对父类的方法 :子类声明和父类相同的命名的方法 ,用来 对父类方法的重写
7.get 和 set
- js
get price(){ console.log("价格被读取了") return '价格呢?' }
- js
set price(newVal){ console.log("价格该修改了") }
class 类的使用
简介
面向对象介绍
- 面向对象把事务分解成一个个对象,然后由对象之间分工与合作
面向对象优点
- 代码灵活、可复用、容易维护和开发,适合多人合作的大型软件项目
面向对象特性
封装性
我们平时所用的方法和类都是一种封装,当我们在项目开发中,遇到一段功能的代码在好多地方重复使用的时候,我们可以把他单独封装成一个功能的方法,这样在我们需要使用的地方直接调用就可以了。
封装的优势在于定义只可以在类内部进行对属性的操作,外部无法对这些属性指手画脚,要想修改,也只能通过你定义的封装方法;
继承性
继承在我们的项目开发中主要使用为子类继承父类
继承减少了代码的冗余,省略了很多重复代码,开发者可以从父类底层定义所有子类必须有的属性和方法,以达到耦合的目的;
特别提醒:继承会继承父类的实例属性和实例方法,并不会继承静态属性和静态方法,并且静态方法只能通过类名去调用。
多态性
多态的具体表现为方法重载和方法重写:
方法重载:重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数
方法重写:重写(也叫覆盖)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样
多态实现了方法的个性化,不同的子类根据具体状况可以实现不同的方法,光有父类定义的方法不够灵活,遇见特殊状况就捉襟见肘了
面向对象和面向过程概念总结比较
总结
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
比较
面向过程性能比面向对象高,适合跟硬件联系很紧密的东西,但是不易维护、不易复用、不易扩展。
面向对象易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护,但是性能比面向过程低。
类和对象
对象
在 JavaScript 中,对象是一组无序的相关属性的方法的集合,所有的事物都是对象
- 例如:字符串、数组、函数、数值
对象是由属性和方法组成
属性
- 事物的特征,在对象中用属性来表示
方法
- 事物的行为,在对象中用方法来表示
类 class
ES6 中增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类实例化对象
类抽象了对象的公共部分,它泛指某一大类(class)对象特指某一个,通过类实例化一个具体的对象
类的实例化
class Name {
constructor(uname) {
this.uname;
}
sing() {
console.log(this.uname);
}
}
// 实例化
var zs = new Name("张三");
// 调用方法
zs.sing();
类的继承
class Father {
constructor(uname) {
this.uname;
}
say() {
return "111";
}
}
// 继承
class Son extends Father {
constructor(x) {
super(x);
}
say() {
super.say();
}
}
super(x)
先调用父类的构造函数
子类调用必须写在子类 this 之前调用
然后才可以调用父类的方法
super.say()
- 调用父类的普通函数
继承中的属性或者方法查找原则
就近原则
1.继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类
2.继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
注意
1.在 ES6 中类没有变量提升,所以必须先定义类,才能通过实例化对象
2.公有的属性和方法调用要在构造函数里加 this.
3.类里面的 this 指向问题
4.constructor 里面的 this 指向实例化对象, 方法里面的 this 指向这个方法的调用者
类的使用
基本使用
class Name {
// 在类中使用constructor来放置属性
construtor(name, age) {
this.name = name;
this.age = age;
}
// 在类中添加方法
sing(song) {
console.log(this.name + song);
}
}
// 实例化类对象
let zs = new Name("张三", 18);
// 实例化后调用类的方法
zs.sing("我爱你你却爱着他");
类的继承 2
子类继承父类基本示例:extends 关键字可以实现子类继承父类
class Father {
constructor() {}
money() {
console.log("钱钱钱");
}
}
class Son extends Father {}
let son = new Son();
son.money();
super 关键字的使用
super 关键字 用于访问和调用对象父类上的函数 可以调用父类的构造函数,也可以调用父类的普通函数
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
say() {
return "我是父类";
}
}
class Son extends Father {
constructor(x, y) {
// 调用了父类中的构造函数
super(x, y);
}
say() {
// super.say()调用了父类的say方法
console.log(super.say() + "的子类");
}
}
let son = new Son(1, 2);
son.sum();
son.say();
子类继承父类方法同时扩展方法
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
// 利用super调用父类的构造函数
// 必须写在子类this之前调用操作
super(x, y);
this.x = x;
this.y = y;
}
substrcact(x, y) {
console.log(this.x + this.y);
}
}
let son = new Son(5, 2);
son.substrcact();
son.sum();
类和对象的三个注意点
1.在 ES6 中类没有变量提升,必须先定义类,才能通过类实例化对象
2.类里面共有的属性和方法一定要加 this 调用
3.类里面的 this 指向问题
this 指向示例说明
class Star {
constructor(name, age) {
const that = this;
// constructor里面的this指向的是 创建的实例对象
this.name = name;
this.age = age;
this.btn = document.querySelector("button");
this.btn.onclick = this.sing;
}
sing() {
// 这个方法的this 指向的是 btn 这个按钮
// 因为这个按钮调用了这个函数
console.log(this);
// that是constructor的this是要打印出名字
console.log(that.name);
}
dance() {
// 这个方法里面的this指向的是实例对象 ldh
console.log(this);
}
}
let ldh = new Star("刘德华");
- 面向对象案例
- tab 栏切换
13.新增 API:Proxy 与 Reflect
Proxy
Reflect
15.Promise
Promise 是 ES6 引入的异步编程的新解决方案
- 语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
语法
const promiseObj = new Promise((resolve, reject) => {})
- 实例化 Promise 对象
promiseObj.then() 方法
promiseObj.catch() 方法
Promise (excutor) {}
- Promise 构造函数:
基本使用语法
//实例化 Promise 对象
const p = new Promise(function (resolve, reject) {
setTimeout(function () {
//
// let data = '数据库中的用户数据';
// resolve
// resolve(data);
let err = "数据读取失败";
reject(err);
}, 1000);
});
// 调用 promise 对象的 then 方法
p.then(
function (value) {
console.log(value);
},
function (reason) {
console.error(reason);
}
);
应用实例
Promise 封装读取文件
//1. 引入 fs 模块
const fs = require("fs");
//2. 调用方法读取文件
// fs.readFile('./resources/为学.md', (err, data)=>{
// //如果失败, 则抛出错误
// if(err) throw err;
// //如果没有出错, 则输出内容
// console.log(data.toString());
// });
//3. 使用 Promise 封装 读取多个文件时使用
const p = new Promise(function (resolve, reject) {
fs.readFile("./resources/为学.mda", (err, data) => {
//判断如果失败
if (err) reject(err);
//如果成功
resolve(data);
});
});
p.then(
function (value) {
console.log(value.toString());
},
function (reason) {
console.log("读取失败!!");
}
);
Promise 封装 AJAX
// 接口地址: https://api.apiopen.top/getJoke
const p = new Promise((resolve, reject) => {
//1. 创建对象
const xhr = new XMLHttpRequest();
//2. 初始化
xhr.open("GET", "https://api.apiopen.top/getJ");
//3. 发送
xhr.send();
//4. 绑定事件, 处理响应结果
xhr.onreadystatechange = function () {
//判断
if (xhr.readyState === 4) {
//判断响应状态码 200-299
if (xhr.status >= 200 && xhr.status < 300) {
//表示成功
resolve(xhr.response);
} else {
//如果失败
reject(xhr.status);
}
}
};
});
//指定回调
p.then(
function (value) {
console.log(value);
},
function (reason) {
console.error(reason);
}
);
Promise-then 方法 (成功和失败的回调)
//创建 promise 对象
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("用户数据");
// reject('出错啦');
}, 1000);
});
//调用 then 方法 then方法的返回结果是 Promise 对象, 对象状态由回调函数的执行结果决定
//1. 如果回调函数中返回的结果是 非 promise 类型的属性, 状态为成功, 返回值为对象的成功的值
// const result = p.then(value => {
// console.log(value);
// //1. 非 promise 类型的属性
// // return 'iloveyou';
// //2. 是 promise 对象
// // return new Promise((resolve, reject)=>{
// // // resolve('ok');
// // reject('error');
// // });
// //3. 抛出错误
// // throw new Error('出错啦!');
// throw '出错啦!';
// }, reason=>{
// console.warn(reason);
// });
//链式调用
p.then((value) => {}).then((value) => {});
Promise-catch 方法 (不指定参数:失败的回调)
const p = new Promise((resolve, reject) => {
setTimeout(() => {
//设置 p 对象的状态为失败, 并设置失败的值
reject("出错啦!");
}, 1000);
});
// p.then(function(value){}, function(reason){
// console.error(reason);
// });
p.catch(function (reason) {
console.warn(reason);
});
Promise 连续读取文件 (回调地狱演示)
/* 连续读取多个文件:回调地狱演示 */
//引入 fs 模块
const fs = require("fs");
// fs.readFile('./resources/为学.md', (err, data1)=>{
// fs.readFile('./resources/插秧诗.md', (err, data2)=>{
// fs.readFile('./resources/观书有感.md', (err, data3)=>{
// let result = data1 + '\r\n' +data2 +'\r\n'+ data3;
// console.log(result);
// });
// });
// });
//使用 promise 实现
const p = new Promise((resolve, reject) => {
fs.readFile("./resources/为学.md", (err, data) => {
resolve(data);
});
});
p.then((value) => {
return new Promise((resolve, reject) => {
fs.readFile("./resources/插秧诗.md", (err, data) => {
resolve([value, data]);
});
});
})
.then((value) => {
return new Promise((resolve, reject) => {
fs.readFile("./resources/观书有感.md", (err, data) => {
//压入
value.push(data);
resolve(value);
});
});
})
.then((value) => {
console.log(value.join("\r\n"));
});
16.ES6 模块化
概念
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。
模块化的好处
1.防止命名冲突
2.代码复用
3.高维护性
ES6 之前的模块化规范
1.CommonJS => NodeJS、Browserify
2.AMD => requireJS
3.CMD => seaJS
ES6 模块化语法
export 暴露数据语法
export
- 用于规定模块的对外接口
1.分别暴露
export function test() {
console.log("哈哈哈");
}
2.统一暴露
function test() {
console.log("哈哈哈");
}
function test2() {
console.log("呵呵呵");
}
export { test, test2 };
3.默认暴露
export default {
school: "蓝翔挖掘机学校",
chang: function () {
console.log("来了啊");
},
};
import 引入模块
- import
- 用于输入其他模块提供的功能
1.通用导入模块方法
import * as mi from ‘./src/js/m1.js’
mi.test()
2.解构赋值形式
基本使用
import { test,test2 } from ‘./src/js/m1.js’
test()
命名冲突使用别名
import { test as test111,test2 } from ‘./src/js/m1.js’
默认暴露方法 的 导入
import { default as m3} from ‘./src/js/m1.js’
简便形式:默认暴露才能使用这个形式
import m3 from ‘./src/js/m1.js’
m3.chang()
3.script 标签形式引入
应用 babel 代码转换
npm install 安装工具
babel-cli
babel-preset-env
browserify(webpack)
命令转换和打包(转成 ES5 语法)
引入 npm 包
import $ from ‘jquery’;
$(''body).css('background','pink')