let 和 const
解决问题
- var 不能用于定义常量
- var 可以重复声明变量
- var 存在变量提升
- var 不支持块级作用域
var a = 1,
b = 2;
function m1() {
console.log(a); //这里会输出 undefined,因为 var 存在变量提升
var a = 3;
function m2() {
console.log(a, b);
}
m2();
}
m1();
不可以重复声明变量
let site = 'itLike';
let site = 'itLike';
console.log(site);
不存在变量提升
console.log(site);
let site = 'itLike';
const 用来定义常量
const E = 2.718;
const 定义的基本类型不能改变,但是定义的对象是可以通过修改对象属性等方法来改变
块级作用域用一组大括号定义一个块,使用 let 定义的变量在大括号的外部是访问不到的,此外,let 声明的变量不会污染全局作用域。(let 拥有块级作用域)
{let site = 'itLike';}
解构赋值
对象解构允许我们使用变量的名字匹配对象的属性 匹配成功 将对象属性的值赋值给变量
let person = {name: 'lisi', age: 30, sex: '男'};
let {name: myName} = person;
console.log(myName)
let [a, b, c] = [1, 2, 3]
let [json, arr, num, str] = [{ a: 1, b: 2 }, [1, 2, 3], 8, 'str']
let [name1, name2, name3] = nameArr;
let {name: lkName, age: lkAge, sex: lkSex}= {name: '小煤球', age: 1, sex: '公'};
- 解构赋值
- 左右两个边结构必须一样
- 右边必须是个东西
- 声明和赋值赋值不能分开,必须在一句话里
深层解构
const user = {
name: "monica",
age: 17,
addr: {
province: "黑龙江",
city: "哈尔滨",
},
};
// 取出 user 中的 name 和 age
const { name, age } = user;
console.log(name, age); // monica 17
// 取出 user 中的 city
const {
addr: { city },
} = user;
console.log(city); // 哈尔滨
函数解构
// 箭头函数也可以在参数位置进行解构
const method = ({ a, b }) => {
console.log(a, b);
};
const obj = {
a: 1,
b: 2,
c: 3,
};
method(obj); // 1 2
遍历解构
const users = [
{ name: "monica", age: 17 },
{ name: "邓哥", age: 70 },
];
// 在遍历时进行解构
for (const { name, age } of users) {
console.log(name, age);
}
类操作
在之前定义类是通过构造函数来操作的,es6 新增了 class 关键字,此时,我们也可以像其它语言一样来创建各种类了。
class Person{
constructor(name, age){
this.name = name;
this.age = age;
}
print(){
console.log('我叫' + this.name + ',今年' + this.age + '岁');
}
}
类继承
老版本版本继承
function User(name, pass) {
this.name = name
this.pass = pass
}
User.prototype.showName = function () {
console.log(this.name)
}
User.prototype.showPass = function () {
console.log(this.pass)
}
var u1 = new User('able', '1233')
u1.showName()
u1.showPass()
// 老版本继承
function VipUser(name, pass, level) {
User.call(this, name, pass)
this.level = level
}
VipUser.prototype = new User()
VipUser.prototype.constructor = VipUser
VipUser.prototype.showLevel = function () {
console.log(this.level)
}
var v1 = new VipUser('blue', '1234', 3)
v1.showName()
v1.showLevel()
类继承
class Father {
// constructor 里面的this 指向的是 创建的实例对象
constructor(x, y) {
this.x = x; //this.x这样这个x函数体内的所有函数都可以访问了
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
super(x, y); //调用了父类中的构造函数
}
}
var son = new Son(1, 2);
var son1 = new Son(11, 22);
son.sum();
son1.sum();
继承中的属性或者方法查找原则: 就近原则
- 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
- 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
模板字符串
const s1 = `姓名:${user.name},年龄:${user.age} my name is ${user.name}`;
// 等同于
const s2 =
"姓名:" + user.name + ",年龄:" + user.age + "\nmy name is " + user.name;
对象速写
const name = "monica",
age = 17;
const sayHello = function () {
console.log(`my name is ${this.name}`);
};
// 过去的方式
const user = {
name: name,
age: age,
sayHello: sayHello,
};
// 速写
const user = {
name,
age,
sayHello,
};
// 过去的方式
const MyMath = {
sum: function (a, b) {
//...
},
random: function (min, max) {
//...
},
};
// 速写
const MyMath = {
sum(a, b) {
// ...
},
random(min, max) {
// ...
},
};
展开运算符
示例 1:
const arr = [3, 6, 1, 7, 2];
// 对数组的展开
Math.max(...arr); // 相当于:Math.max(3, 6, 1, 7, 2)
示例 2:
const o1 = {
a: 1,
b: 2,
};
const o2 = {
a: 3,
c: 4,
};
// 对对象的展开
const o3 = {
...o1,
...o2,
};
/*
o3:{
a: 3,
b: 2,
c: 4
}
*/
示例 3:
const arr = [2, 3, 4];
const arr2 = [1, ...arr, 5]; // [1,2,3,4,5]
示例 4:
const user = {
name: "monica",
age: 17,
};
const user2 = {
...user,
name: "邓哥",
};
// user2: { name:'邓哥', age: 17 }
展开运算符可以展开可迭代对象
剩余参数和 arguments 对象
剩余参数用法是放在参数的最后一位,用…xx 来表示
function fn1(a,...res){
console.log(a);
console.log(res)
}
fn1(3,4,4,5)
a:3,res:[4, 4, 5]
在上面的例子中,theArgs 将收集该函数的第三个参数(因为第一个参数被映射到 a,而第二个参数映射到 b)和所有后续参数。
arguments
function fn1(a,...res){
console.log(arguments)
}
剩余参数和 arguments 对象的区别
剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
arguments 对象不是一个真正的数组,而剩余参数是真正的 Array 实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach 或 pop。
arguments 对象还有一些附加的属性 (如 callee 属性)。
迭代器
迭代是从一个数据集合中按照一定的顺序,不断取出数据的过程
迭代器是用于访问集合类的标准访问方法,它可以把访问逻辑从不同类型集合中抽象出来,从而避免向外部暴露集合内部的结构。
.
迭代和遍历的区别
迭代强调的是依次取数据,并不保证取多少,也不保证把所有的数据取完(不关心数据的长度,可以无限延伸)
遍历强调的是要把整个数据依次全部取出
迭代器语法
const iterator = {
next() {
return {
value: xx,
done: xx,
};
},
};
通过迭代器,可以使得取数据的过程中,不用到 arr,使得不直接操作数据,等于说是一层封装,在原始数据和操作数据之间架起了桥梁
const arr = [1, 2, 3, 4, 5];
//迭代数组arr
const iterator = {
i: 0, //当前的数组下标
next() {
var result = {
value: arr[this.i],
done: this.i >= arr.length,
};
this.i++;
return result;
},
};
//让迭代器不断的取出下一个数据,直到没有数据为止
let data = iterator.next();
while (!data.done) {
//只要没有迭代完成,则取出数据
console.log(data.value);
//进行下一次迭代
data = iterator.next();
}
在一个无止境的数组中,很难去确定数组的长度,这时候迭代器就可以做到,因为他不关心数据的长度,只是给我规则我不停的去迭代,不停的去计算。
依次得到斐波拉契数列前面 n 位的值
function createFeiboIterator() {
let prev1 = 1,
prev2 = 1, //当前位置的前1位和前2位
n = 1; //当前是第几位
return {
next() {
let value;
if (n <= 2) {
value = 1;
} else {
value = prev1 + prev2;
}
const result = {
value,
done: false,
};
prev2 = prev1;
prev1 = result.value;
n++;
return result;
},
};
}
//生成器函数一调用 拿到的是一个生成器
const iterator = createFeiboIterator();
通过迭代器统一化遍历方法
我们访问一个数组可能使用 for 循环或者 map,foreach,filter 等 for(int i=0; i<array.size(); i++) { … get(i) … }, 但是当我们想要遍历链表(linkedlist)的时候就得使用 while 循环 while((e=e.next())!=null) { … e.data() … }, 以上两种方式我们都必须知道集合的内部结构是怎么样的我们才可以使用对应的循环方式去循环整个集合,那么这样就造成了很大的耦合度,当我们把一个集合的类型从 Arrarlist 变成 Linkedlist 的时候,那么原来客户端的代码必须重写,因为我们集合变了,遍历的方式也必须改成对应的方式。
为解决以上问题,Iterator 模式总是用同一种逻辑来遍历集合: for(Iterator it = c.iterater(); it.hasNext(); ) { … },这样就在一定程度上解决了以上的问题。
特点 1.不用去关心数据的长度,只需要一直迭代和告诉我能否迭代 2.用于访问集合类的标准访问方法 3.可以使得取数据的过程中,不会用到原始数据,在操作数据和原始数据之间增加了一层处理
可迭代协议
ES6 规定,如果一个对象具有知名符号属性Symbol.iterator
,并且属性值是一个迭代器创建函数,则该对象是可迭代的
往往在原型中,比如数组,以及使用 querySelectorAll 获取下来的数据都拥有这个符号属性,说明他们都可以迭代
for-of 循环
for-of 循环是迭代器的语法糖,只要一个对象拥有 Symbol.iterator 都可以使用迭代器
const arr = [5, 7, 2, 3, 6];
//两个代码功能完全一致
const iterator = arr[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
const item = result.value; //取出数据
console.log(item);
//下一次迭代
result = iterator.next();
}
//两个代码功能完全一致
for (const item of arr) {
console.log(item);
}
tip:使用 for in 也可以遍历数组,但是会存在以下问题:
- index 索引为字符串型数字,不能直接进行几何运算
- 遍历顺序有可能不是按照实际数组的内部顺序
- 使用 for in 会遍历数组所有的可枚举属性,包括原型。例如上栗的原型方法 method 和 name 属性 所以 for in 更适合遍历对象,不要使用 for in 遍历数组。
手写 obj 迭代器,使用 obj 可以被迭代
let obj = {
a: 1,
b: 2,
[Symbol.iterator]() {
const keys = Object.keys(this);
let i = 0;
return {
next: () => {
const propName = keys[i];
const propValue = this[propName];
const result = {
value: {
propName,
propValue
},
done: i >= keys.length
}
i++;
return result;
}
}
}
}
这个对象就可以被迭代,因为手写了迭代器,拥有能被迭代的标准语法
for (const item of obj) {
console.log(item)
}
let p={
name:'kevin',
age:2,
sex:'male'
}
//还可以通过属性标识符操作
Object.defineProperty(p,Symbol.iterator,{
enumberable:false,
configurable:false,
writable:false,
value:function(){
var _this=this;
var nowIndex=-1;
var key=Object.keys(_this);
return {
next:function(){
nowIndex++;
return {
value:_this[key[nowIndex]],
done:(nowIndex+1>key.length)
}
}
}
}
})
}
展开运算符与可迭代对象
展开运算符可以作用于可迭代对象,这样,就可以轻松的将可迭代对象转换为数组。
只要这个obj对象可以被迭代,那么就可以用这样的方式实现对象转数组
或者放入函数参数中
const arr = [...obj];
console.log(arr);
function test(a, b) {
console.log(a, b)
}
test(...obj);
生成器 (Generator)
生成器,生成的是迭代器,是生成迭代器函数的语法糖
什么是生成器
生成器是一个通过构造函数 Generator 创建的对象,生成器既是一个迭代器,同时又是一个可迭代对象
如何创建生成器
生成器的创建,必须使用生成器函数(Generator Function)
如何书写一个生成器函数呢?
//这是一个生成器函数,该函数一定返回一个生成器,可以没有return值
function* method() {}
生成器函数内部的执行
生成器函数内部是为了给生成器的每次迭代提供的数据
每次调用生成器的 next 方法,将导致生成器函数运行到下一个 yield 关键字位置
yield 是一个关键字,该关键字只能在生成器函数内部使用,表达“产生”一个迭代数据。
function* test() {
console.log("第1次运行")
yield 1;
console.log("第2次运行")
yield 2;
console.log("第3次运行")
}
const generator = test();
调用generator.next() 拿到yield右边的数据
有哪些需要注意的细节
1). 生成器函数可以有返回值,返回值出现在第一次 done 为 true 时的 value 属性中
function* test() {
console.log("第1次运行");
yield 1;
console.log("第2次运行");
yield 2;
console.log("第3次运行");
return 10;
}
const generator = test();
2). 调用生成器的 next 方法时,可以传递参数,传递的参数会交给 yield 表达式的返回值
function* test() {
console.log("函数开始");
let info = yield 1;
console.log(info);
info = yield 2 + info;
console.log(info);
}
const generator = test();
3). 第一次调用 next 方法时,传参没有任何意义
4). 在生成器函数内部,可以调用其他生成器函数,但是要注意加上*号
function* t1() {
yield "a";
yield "b";
}
function* test() {
yield* t1();
yield 1;
yield 2;
yield 3;
}
const generator = test();
//也可以调用一个普通函数
function fn(num) {
console.log(num);
return num;
}
function* gen() {
yield fn(1);
yield fn(2);
return 3;
}
const g = gen();
console.log(g.next());
// 1
// { value: 1, done: false }
console.log(g.next());
// 2
// { value: 2, done: false }
console.log(g.next());
// { value: 3, done: true }
生成器的其他 API
- return 方法:调用该方法,可以提前结束生成器函数,从而提前让整个迭代过程结束
- throw 方法:调用该方法,可以在生成器中产生一个错误
案例
创建一个斐波拉契数列的迭代器
function* createFeiboIterator() {
let prev1 = 1,
prev2 = 1, //当前位置的前1位和前2位
n = 1; //当前是第几位
while (true) {
if (n <= 2) {
yield 1;
} else {
const newValue = prev1 + prev2;
yield newValue;
prev2 = prev1;
prev1 = newValue;
}
n++;
}
}
const iterator = createFeiboIterator();
创建一个异步函数
function* task() {
const d = yield 1;
console.log(d);
// //d : 1
const resp = yield fetch("http://101.132.72.36:5100/api/local");
const result = yield resp.json();
console.log(result);
}
run(task);
function run(generatorFunc) {
const generator = generatorFunc();
let result = generator.next(); //启动任务(开始迭代), 得到迭代数据
handleResult();
//对result进行处理
function handleResult() {
if (result.done) {
return; //迭代完成,不处理
}
//迭代没有完成,分为两种情况
//1. 迭代的数据是一个Promise
//2. 迭代的数据是其他数据
if (typeof result.value.then === "function") {
//1. 迭代的数据是一个Promise
//等待Promise完成后,再进行下一次迭代
result.value.then((data) => {
result = generator.next(data);
handleResult();
});
} else {
//2. 迭代的数据是其他数据,直接进行下一次迭代
result = generator.next(result.value);
handleResult();
}
}
}
async 写法
const task = async () => {
const d = await 1;
console.log(d)
// //d : 1
const resp = await fetch("http://101.132.72.36:5100/api/local")
const result = await resp.json();
console.log(result);
}
一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
async 函数对 Generator 函数的改进,体现在以下四点:
- 内置执行器。Generator 函数的执行必须依靠执行器,而 async 函数自带执行器,无需手动执行 next() 方法。
- 更好的语义。async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
- 更广的适用性。co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
- 返回值是 Promise。async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用。
async/await 的用处就是:用同步方式,执行异步操作
set 与 map
set
Set 对象是值的合集(collection)。集合(set)中的元素只会出现一次,即集合中的元素是唯一的。
set 的 key 和 value 是相同的
使用 Set 对象
const mySet1 = new Set();
mySet1.add(1); // Set(1) { 1 }
mySet1.add(5); // Set(2) { 1, 5 }
mySet1.add(5); // Set(2) { 1, 5 }
mySet1.add("some text"); // Set(3) { 1, 5, 'some text' }
const o = { a: 1, b: 2 };
mySet1.add(o);
mySet1.add({ a: 1, b: 2 }); // o 是不同对象的引用,所以这是可以的
mySet1.has(1); // true
mySet1.has(3); // false,因为并未将 3 添加到集合中
mySet1.has(5); // true
mySet1.has(Math.sqrt(25)); // true
mySet1.has("Some Text".toLowerCase()); // true
mySet1.has(o); // true
mySet1.size; // 5
mySet1.delete(5); // 从集合中移除 5
mySet1.has(5); // false,5 已从集合中移除
mySet1.size; // 4,因为我们刚刚移除了一个值
mySet1.add(5); // Set(5) { 1, 'some text', {...}, {...}, 5 }——先前删除的元素会作为新的元素被添加,不会保留删除前的原始位置
console.log(mySet1); // Set(5) { 1, "some text", {…}, {…}, 5 }
迭代集合
迭代会按元素的插入顺序访问集合中的元素。
for (const item of mySet1) {
console.log(item);
}
// 1、"some text"、{ "a": 1, "b": 2 }、{ "a": 1, "b": 2 }、5
for (const item of mySet1.keys()) {
console.log(item);
}
// 1、"some text"、{ "a": 1, "b": 2 }、{ "a": 1, "b": 2 }、5
for (const item of mySet1.values()) {
console.log(item);
}
// 1、"some text"、{ "a": 1, "b": 2 }、{ "a": 1, "b": 2 }、5
// 键和值是相同的
for (const [key, value] of mySet1.entries()) {
console.log(key);
}
// 1、"some text"、{ "a": 1, "b": 2 }、{ "a": 1, "b": 2 }、5
// 使用 Array.from 将 Set 对象转换为数组对象
const myArr = Array.from(mySet1); // [1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}, 5]
// 如果在 HTML 文档中使用,也可以:
mySet1.add(document.body);
mySet1.has(document.querySelector("body")); // true
// 在 Set 和 Array 之间转换
const mySet2 = new Set([1, 2, 3, 4]);
console.log(mySet2.size); // 4
console.log([...mySet2]); // [1, 2, 3, 4]
// 可以通过如下代码模拟求交集
const intersection = new Set([...mySet1].filter((x) => mySet2.has(x)));
// 可以通过如下代码模拟求差集
const difference = new Set([...mySet1].filter((x) => !mySet2.has(x)));
// 使用 forEach() 迭代集合中的条目
mySet2.forEach((value) => {
console.log(value);
});
// 1
// 2
// 3
// 4
与数组的关系
const myArray = ["value1", "value2", "value3"];
// 使用常规的 Set 构造函数将 Array 转换为 Set
const mySet = new Set(myArray);
mySet.has("value1"); // 返回 true
// 使用展开语法将 Set 转换为 Array。
console.log([...mySet]); // 将显示与 myArray 完全相同的数组
数组去重
// 用于从数组中删除重复元素
const numbers = [2, 3, 4, 4, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 5, 32, 3, 4, 5];
console.log([...new Set(numbers)]);
// [2, 3, 4, 5, 6, 7, 32]
与字符串的关系
const text = "India";
const mySet = new Set(text); // Set(5) {'I', 'n', 'd', 'i', 'a'}
mySet.size; // 5
// 大小写敏感,且忽略重复项
new Set("Firefox"); // Set(7) { "F", "i", "r", "e", "f", "o", "x" }
new Set("firefox"); // Set(6) { "f", "i", "r", "e", "o", "x" }
map
Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值。
Map 对象是键值对的集合。Map 中的一个键只能出现一次;它在 Map 的集合中是独一无二的。Map 对象按键值对迭代——一个 for…of 循环在每次迭代后会返回一个形式为 [key,value] 的数组。迭代按插入顺序进行,即键值对按 set() 方法首次插入到集合中的顺序(也就是说,当调用 set() 时,map 中没有具有相同值的键)进行迭代。
const map1 = new Map(); map1.set('a', 1); map1.set('b', 2); map1.set('c', 3);
console.log(map1.get('a')); // Expected output: 1 map1.set('a', 97);
console.log(map1.get('a')); // Expected output: 97 console.log(map1.size); //
Expected output: 3 map1.delete('b'); console.log(map1.size); // Expected output:
2
不过 Map 和 Object 有一些重要的区别
Map | Object | |
---|---|---|
意外的键 | Map 默认情况不包含任何键。只包含显式插入的键。 | 一个 Object 有一个原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。 |
备注:虽然可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。 | ||
键的类型 | 一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。 | 一个 Object 的键必须是一个 String |
或是 Symbol
。 |
| 键的顺序 | Map 中的键是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。 | 虽然 Object 的键目前是有序的,但并不总是这样,而且这个顺序是复杂的。因此,最好不要依赖属性的顺序。
自 ECMAScript 2015 规范以来,对象的属性被定义为是有序的;ECMAScript 2020 则额外定义了继承属性的顺序。参见 OrdinaryOwnPropertyKeys 和 EnumerateObjectProperties 抽象规范说明。但是,请注意没有可以迭代对象所有属性的机制,每一种机制只包含了属性的不同子集。(for-in 仅包含了以字符串为键的属性;Object.keys 仅包含了对象自身的、可枚举的、以字符串为键的属性;Object.getOwnPropertyNames 包含了所有以字符串为键的属性,即使是不可枚举的;Object.getOwnPropertySymbols 与前者类似,但其包含的是以 Symbol 为键的属性,等等。) |
| Size | Map 的键值对个数可以轻易地通过 size
属性获取。 | Object 的键值对个数只能手动计算。 |
| 迭代 | Map 是 可迭代的
的,所以可以直接被迭代。 | Object 没有实现 迭代协议,所以使用 JavaSctipt 的 for…of 表达式并不能直接迭代对象。
备注:
- 对象可以实现迭代协议,或者你可以使用 Object.keys 或 Object.entries。
- for…in 表达式允许你迭代一个对象的可枚举属性。
|
| 性能 | 在频繁增删键值对的场景下表现更好。 | 在频繁添加和删除键值对的场景下未作出优化。 |
| 序列化和解析 | 没有元素的序列化和解析的支持。
(但是你可以使用携带 replacer 参数的 JSON.stringify() 创建一个自己的对 Map 的序列化和解析支持。参见 Stack Overflow 上的提问:How do you JSON.stringify an ES6 Map?) | 原生的由 Object 到 JSON 的序列化支持,使用 JSON.stringify()。
原生的由 JSON 到 Object 的解析支持,使用 JSON.parse()。 |
weakSet 和 weakMap
symbol
第七种数据类型
生成唯一的标识符来避免冲突
不能被遍历
可以用于当做私有属性
symbol 是一种基本数据类型(primitive data type)。Symbol() 函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的 symbol 注册
每个从 Symbol() 返回的 symbol 值都是唯一的
//直接使用Symbol()创建新的 symbol 类型,并用一个可选的字符串作为其描述。 const
symbol1 = Symbol(); const symbol2 = Symbol(42); const symbol3 = Symbol('foo');
console.log(typeof symbol1); // Expected output: "symbol" console.log(symbol2
=== 42); // Expected output: false console.log(symbol3.toString()); // Expected
output: "Symbol(foo)" console.log(Symbol('foo') === Symbol('foo')); // Expected
output: false