1. Proxy与Reflect
1. Proxy监听对象属性的操作
在ES6中, 新增了一个Proxy类, 用来帮助我们创建一个代理. 也就是说, 如果我们希望监听一个对象的相关操作, 那么我们可以先创建一个代理对象, 之后对该对象的所有操作, 都通过代理对象完成, 代理对象可以监听我们想要对原对象进行哪些操作.
const obj = {
name: "zgc",
age: 18,
height: 1.88,
};
// 需求:监听对象属性的所有操作
// 方式一: Object.defineProperty
const keys = Object.keys(obj);
for (let key of keys) {
// console.log(key);
let value = obj[key];
Object.defineProperty(obj, key, {
get: function () {
// console.log(`监听: 获取${key}的值`);
return value;
},
set(newVal) {
// console.log(`监听: 设置${key}的值`, newVal);
value = newVal;
},
});
}
// console.log(obj.name); // zgc
// obj.name = "wf";
// console.log(obj.name); // wf
// 方式二: Proxy代理对象
// 首先, 我们需要new Proxy对象 并且传入需要侦听的对象(target)以及一个处理对象(handler)
// const p = new Proxy(target, handler)
// 其次, 我们之后的操作都是对Proxy的操作, 而不是原有的对象
var info = {
name: "wlc",
age: 24,
height: 1.88,
};
// 如果我们想要侦听某些具体的操作, 那么就可以在handler中添加对应的捕捉器(Trap):
const infoProxy = new Proxy(info, {
// 获取对象属性:
get: function (target, property, receiver) {
// target: 侦听的对象, property: 被获取的属性key, receiver: 调用的代理对象
console.log(target, property, receiver);
return target[property];
},
// 设置对象属性值/添加对象属性
set: function (target, property, value, receiver) {
// target: 侦听的对象, property: 被设置的属性key, value: 新属性值, receiver: 调用的代理对象
console.log(target, property, value, receiver);
target[property] = value;
},
// 删除对象属性
deleteProperty: function (target, property) {
// target: 侦听的对象, property: 被获取的属性key
console.log(target, property);
delete info.height;
},
// 判断属性是否在对象中
has: function (target, property) {
// target: 侦听的对象, property: 被获取的属性key
console.log(target, property);
return property in target;
},
});
console.log(infoProxy.age); // 24
infoProxy.age = 20;
console.log(infoProxy.age); // 20
// 添加新属性
infoProxy.address = "北京";
console.log(info, infoProxy);
// {name: 'wlc', age: 20, height: 1.88, address: '北京'} {name: 'wlc', age: 20, height: 1.88, address: '北京'}
// 删除对象属性
delete infoProxy.height;
console.log(info, infoProxy);
// {name: 'wlc', age: 20, address: '北京'} {name: 'wlc', age: 20, address: '北京'}
// 判断属性是否在对象中
console.log("age" in infoProxy); // true
console.log("height" in infoProxy); // false
2. Reflect
Reflect也是ES6新增的API, 它是一个 对象 , 字面意思是反射.
Reflect并非一个构造函数,所以不能通过new运算符对其进行调用,或者将Reflect对象作为一个函数来调用,就像Math对象一样,Reflect对象的所有属性和方法都是静态的.
实际上Reflect对象是ES6为操作对象而提供的新API,而这个API设计的目的主要有:
- 将
Object对象的一些属于语言内部的方法放到Reflect对象上,从Reflect上能拿到语言内部的方法,例如Object.defineProperty方法。 - 修改某些
Object方法返回的结果,例如Object.defineProperty(obj, name, desc)方法在无法定义属性的时候会抛出异常,而Reflect.defineProperty(obj, name, desc)方法在操作失败时则会返回false。 - 让
Object的操作都变成函数行为,例如Object的命令式name in obj和delete obj[name]与Reflect.has(obj, name)、Reflect.deleteProperty(obj, name)相等。 - 使
Reflect对象的方法与Proxy对象的方法一一对应,只要Proxy对象上能够定义的handlers方法Reflect也能找到。
Reflect和Proxy共同完成代理
// Reflect也是ES6新增的API, 它是一个 对象 , 字面意思是反射
// Reflect提供了很多操作JS对象的方法, 有点像Object中操作对象的方法
// 如获取对象原型: Reflect.getPrototypeOf() 和 Object.getPrototypeOf()
// 如设置对象属性: Object.defineProperty() 和 Reflect.defineProperty();
const obj = {
name: "zgc",
age: 18,
m: function () {
console.log(this === objProxy, this === obj);
},
};
const objProxy = new Proxy(obj, {});
objProxy.m(); // true false
obj.m(); // false true
// Reflect和Proxy共同完成代理
var info = {
_name: "wlc",
set name(newVal) {
console.log("set this", this);
this._name = newVal;
},
get name() {
console.log("get this", this);
return this._name;
},
age: 18,
};
// 如果我们想要侦听某些具体的操作, 那么就可以在handler中添加对应的捕捉器(Trap):
const infoProxy = new Proxy(info, {
// 获取对象属性:
get: function (target, property, receiver) {
// target: 侦听的对象, property: 被获取的属性key, receiver: 调用的代理对象
// console.log(target, property, receiver);
// return target[property];
return Reflect.get(target, property, receiver);
},
// 设置对象属性值/添加对象属性
set: function (target, property, value, receiver) {
// target: 侦听的对象, property: 被设置的属性key, value: 新属性值, receiver: 调用的代理对象
// console.log(target, property, value, receiver);
// console.log(receiver === infoProxy); // true
// 好处一: 代理对象的目的: 不再直接操作原对象
// 好处二: Reflect.set方法返回一个布尔值, 可以进行边界判断
/*
好处三:
> receiver就是外面的Proxy对象
> Reflect.set/get方法最后一个参数可以决定对象访问器getter,setter的this指向
> 如果target对象中指定了getter,setter,receiver可以作为则为getter,setter调用时的this值
*/
// target[property] = value;
// const isSuccess = Reflect.set(target, property, value); // this 指向 info对象
const isSuccess = Reflect.set(target, property, value, receiver); // this 指向 infoProxy对象
if (!isSuccess) {
throw new Error(`set ${property} failure`);
}
},
});
// infoProxy.age = 24;
// console.log(info, infoProxy); // {_name: 'wlc', age: 24} Proxy {_name: 'wlc', age: 24}
// info.name = 'wf' // this是info对象
// console.log(info.name); // this是info对象
infoProxy.name = "zgc"; // this指向看是否传入receiver属性
console.log(infoProxy.name);// this指向看是否传入receiver属性
Reflect.apply()
Reflect.apply(target, thisArgument, argumentsList)
静态方法Reflect.apply()通过指定的参数列表发起对目标target函数的调用,和Function.prototype.apply()功能类似。
target: 目标函数。thisArgument:target函数调用时绑定的this对象。argumentsList:target函数调用时传入的实参列表,该参数应该是一个类数组的对象。return: 返回值是调用完带着指定参数和this值的给定的函数后返回的结果。
var obj = {name: "11"};
function target(a, b){
console.log(this.name);
console.log(a, b);
}
Reflect.apply(target, obj, [1, 2]); // 11 // 1 2
Reflect.construct()
Reflect.construct(target, argumentsList[, newTarget])
Reflect.construct()方法的行为像new操作符构造函数,相当于运行new target(...args)。
target: 被运行的目标构造函数。argumentsList: 类数组对象,目标构造函数调用时的参数。newTarget: 可选,作为新创建对象的原型对象的constructor属性,默认值为target。
// 创建一个Student类型的对象
// 方式一: 借用构造函数
// function Person(name, age) {
// this.name = name;
// this.age = age;
// }
// function Student(name, age) {
// Person.apply(this, [name, age]);
// }
// const stu = new Student("zgc", 18);
// console.log(stu);
// 方式二: 反射
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student() {}
const stu = Reflect.construct(Person, ["wf", 22], Student);
console.log(stu); // Student {name: 'wf', age: 22}
console.log(stu.__proto__ === Student.prototype); // true
Reflect.defineProperty()
Reflect.defineProperty(target, propertyKey, attributes)
方法Reflect.defineProperty()基本等同于Object.defineProperty()方法,唯一不同是返回Boolean值。
target: 目标对象。propertyKey: 要定义或修改的属性的名称。attributes: 要定义或修改的属性的描述。return: 返回Boolean值指示了属性是否被成功定义。
var obj = {_name: 11};
var success = Reflect.defineProperty(obj, "name", {
get:function(){
console.log("getter");
return obj._name;
}
})
console.log(success); // true
console.log(obj.name); // getter // 11
Reflect.deleteProperty()
Reflect.deleteProperty(target, propertyKey)
方法Reflect.deleteProperty()允许用于删除属性,类似于delete operator,但它是一个函数。
target: 删除属性的目标对象。propertyKey: 需要删除的属性的名称。return: 返回Boolean值表明该属性是否被成功删除。
var obj = {name: 11};
var success = Reflect.deleteProperty(obj, "name");
console.log(success); // true
console.log(obj.name); // undefined
Reflect.get()
Reflect.get(target, propertyKey[, receiver])
Reflect.get()方法与从对象target[propertyKey]中读取属性类似,但它是通过一个函数执行来操作的。
target: 需要取值的目标对象propertyKey: 需要获取的值的键值receiver: 如果target对象中指定了getter,receiver则为getter调用时的this值。return: 返回属性的值。
var obj = {name: 11};
var name = Reflect.get(obj, "name");
console.log(name); // 11
Reflect.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor(target, propertyKey)
方法Reflect.getOwnPropertyDescriptor()与Object.getOwnPropertyDescriptor()方法相似,如果在对象中存在,则返回给定的属性的属性描述符,否则返回undefined。
target: 需要寻找属性的目标对象。propertyKey: 获取自己的属性描述符的属性的名称。
var obj = {name: 11};
var des = Reflect.getOwnPropertyDescriptor(obj, "name");
console.log(des); // {value: 11, writable: true, enumerable: true, configurable: true}
Reflect.getPrototypeOf()
Reflect.getPrototypeOf(target)
方法Reflect.getPrototypeOf()与Object.getPrototypeOf()方法几乎相同,都是返回指定对象的原型,即内部的[[Prototype]]属性的值。
target: 获取原型的目标对象。return: 给定对象的原型,如果给定对象没有继承的属性,则返回null。
var obj = {name: 11};
var proto = Reflect.getPrototypeOf(obj);
console.log(proto === Object.prototype); // true
Reflect.has()
Reflect.has(target, propertyKey)
方法Reflect.has()作用与in操作符类似,但它是通过一个函数执行来操作的。
target: 目标对象.propertyKey: 属性名,需要检查目标对象是否存在此属性。return: 返回一个Boolean类型的对象指示是否存在此属性。
var obj = {name: 11};
var exist = Reflect.has(obj, "name");
console.log(exist); // true
Reflect.isExtensible()
Reflect.isExtensible(target)
方法Reflect.isExtensible()判断一个对象是否可扩展,即是否能够添加新的属性,与Object.isExtensible()方法相似。
target: 检查是否可扩展的目标对象。return: 返回一个Boolean值表明该对象是否可扩展。
var obj = {name: 11};
var extensible = Reflect.isExtensible(obj);
console.log(extensible); // true
Reflect.ownKeys()
Reflect.ownKeys(target)
方法Reflect.ownKeys()返回一个由目标对象自身的属性键组成的数组。
target: 获取自身属性键的目标对象。return: 返回由目标对象的自身属性键组成的Array。
var obj = {name: 11};
var keys = Reflect.ownKeys(obj);
console.log(keys); // ["name"]
Reflect.preventExtensions()
Reflect.preventExtensions(target)
方法Reflect.preventExtensions()方法阻止新属性添加到对象,防止将来对对象的扩展被添加到对象中,该方法与Object.preventExtensions()相似。
target: 阻止扩展的目标对象。return: 返回一个Boolean值表明目标对象是否成功被设置为不可扩展。
var obj = {name: 11};
Reflect.preventExtensions(obj);
console.log(Reflect.isExtensible(obj)); // false
Reflect.set()
Reflect.set(target, propertyKey, value[, receiver])
方法Reflect.set()是在一个对象上设置一个属性。
target: 设置属性的目标对象。propertyKey: 设置的属性的名称。value: 设置的值。receiver: 如果遇到setter,receiver则为setter调用时的this值。return: 返回一个Boolean值表明是否成功设置属性。
var obj = {name: 1};
Reflect.set(obj, "name", 11);
console.log(Reflect.get(obj, "name")); // 11
Reflect.setPrototypeOf()
Reflect.setPrototypeOf(target, prototype)
除了返回类型以外,方法Reflect.setPrototypeOf()与Object.setPrototypeOf()方法是一样的,它可设置对象的原型即内部的[[Prototype]]属性,为另一个对象或null,如果操作成功返回true,否则返回false。
target: 设置原型的目标对象。prototype: 对象的新原型,为一个对象或null。return: 返回一个Boolean值表明是否原型已经成功设置。
var obj = {};
var proto = {name: 11};
Reflect.setPrototypeOf(obj, proto);
console.log(proto === Reflect.getPrototypeOf(obj)); // true
对比
Method Name | Object | Reflect |
|---|---|---|
defineProperty() | Object.defineProperty()返回传递给函数的对象,如果未在对象上成功定义属性,则返回TypeError。 | 如果在对象上定义了属性,则Reflect.defineProperty()返回true,否则返回false。 |
defineProperties() | Object.defineProperties() 返回传递给函数的对象。如果未在对象上成功定义属性,则返回TypeError。 | N/A |
set() | N/A | 如果在对象上成功设置了属性,则Reflect.set()返回true,否则返回false。如果目标不是Object,则抛出TypeError |
get() | N/A | Reflect.get()返回属性的值。如果目标不是Object,则抛出TypeError。 |
deleteProperty() | N/A | 如果属性从对象中删除,则Reflect.deleteProperty()返回true,否则返回false。 |
getOwnPropertyDescriptor() | 如果传入的对象参数上存在Object.getOwnPropertyDescriptor() ,则会返回给定属性的属性描述符,如果不存在,则返回undefined。 | 如果给定属性存在于对象上,则Reflect.getOwnPropertyDescriptor()返回给定属性的属性描述符。如果不存在则返回undefined,如果传入除对象(原始值)以外的任何东西作为第一个参数,则返回TypeError |
getOwnPropertyDescriptors() | Object.getOwnPropertyDescriptors()返回一个对象,其中包含每个传入对象的属性描述符。如果传入的对象没有拥有的属性描述符,则返回一个空对象。 | N/A |
getPrototypeOf() | Object.getPrototypeOf()返回给定对象的原型。如果没有继承的原型,则返回null。在ES5中为非对象抛出TypeError。 | Reflect.getPrototypeOf()返回给定对象的原型。如果没有继承的原型,则返回null,并为非对象抛出TypeError。 |
setPrototypeOf() | 如果对象的原型设置成功,则Object.setPrototypeOf()返回对象本身。如果设置的原型不是Object或null,或者被修改的对象的原型不可扩展,则抛出TypeError。 | 如果在对象上成功设置了原型,则Reflect.setPrototypeOf()返回true,否则返回false(包括原型是否不可扩展)。如果传入的目标不是Object,或者设置的原型不是Object或null,则抛出TypeError。 |
isExtensible() | 如果对象是可扩展的,则Object.isExtensible()返回true,否则返回false,如果第一个参数不是对象,则在ES5中抛出TypeError,在ES2015中,它将被强制为不可扩展的普通对象并返回false。 | 如果对象是可扩展的,则Reflect.isExtensible()返回true,否则返回false。如果第一个参数不是对象,则抛出TypeError。 |
preventExtensions() | Object.preventExtensions()返回被设为不可扩展的对象,如果参数不是对象,则在ES5中抛出TypeError,在ES2015中,参数如为不可扩展的普通对象,然后返回对象本身。 | 如果对象已变得不可扩展,则Reflect.preventExtensions() 返回true,否则返回false。如果参数不是对象,则抛出TypeError。 |
keys() | Object.keys()返回一个字符串数组,该字符串映射到目标对象自己的(可枚举)属性键。如果目标不是对象,则在ES5中抛出TypeError,但将非对象目标强制为ES2015中的对象 | N/A |
ownKeys() | N/A | Reflect.ownKeys()返回一个属性名称数组,该属性名称映射到目标对象自己的属性键,如果目标不是Object,则抛出TypeError。 |
2. Promise
1. Promise的基本使用
// 创建 promise 对象(pending 状态), 指定执行器(executor)函数
const promise = new Promise((resolve, reject) => {
// 接受executor(执行器)函数作为new Promise()的参数, 这个执行器函数会被立即执行
console.log("立即执行");
setTimeout(() => {
const time = Date.now();
// 如果成功了, 调用 resolve(), 执行then传递过来的回调函数, 指定成功的 value值, 且promise变为 resolved 状态
if (time % 2 === 1) {
// promise的状态一旦确定下来, 就不会再更改, 即多次调用resolve/reject无效, 只执行第一个
resolve("成功的值 " + time);
} else {
// 如果失败了, 调用 reject(), 执行then传递过来的回调函数, 指定失败的 reason值, 且promise变为rejected 状态
reject("失败的值" + time);
}
}, 2000);
});
promise.then(
(value) => { // 传入的回调函数, 当Promise的状态为resolved/fullfilled的时候调用resolve方法, 执行这个回调
console.log("成功的回调:", value);
},
(reason) => { // 传入的回调函数, 当Promise的状态为rejected的时候调用reject方法, 执行这个回调
console.log("失败的回调:", reason);
}
);
console.log(".then方法是立即执行的, 但then方法内的回调是异步执行的");
// 立即执行
// .then方法是立即执行的, 但then方法内的回调是异步执行的
// 失败的回调: 失败的值1677679498530
2. Promise的resolve值
var obj = {
name: "zgc",
then: function (resolve) {
resolve("哈哈哈");
},
};
const promise = new Promise((resolve, reject) => {
// 1. 如果传入的参数为非Promise类型的值, 返回的结果为成功promise对象(掌握)
// resolve传入的普通值,就是then函数中res参数接收的数据
resolve([
{ name: "zgc", age: 18 },
{ name: "wf", age: 24 },
]);
// 2. 如果传入的参数为 Promise 对象, 则返回的 promise 对象的状态由传入的 Promise 决定(理解)
// new Promise中resolve/reject传入的普通值,就是then/catch函数中参数接收的数据
// resolve(
// new Promise((resolve, reject) => {
// // resolve("yes");
// reject("no");
// })
// );
// 3. 如果传入的是一个对象, 且对象内部有实现then方法, 那么就会执行该then方法, 并且根据then方法的结果来决定Promise的状态(了解)
// resolve(obj)
});
console.log("promise", promise);
promise
.then((res) => {
console.log("res:", res);
})
.catch((err) => {
console.log("err:", err);
});
3. then&catch方法的返回值
const promise = new Promise((resolve, reject) => {
resolve([
{ name: "zgc", age: 18 },
{ name: "wf", age: 24 },
]);
});
console.log("promise", promise);
// then方法返回的也是一个promise对象
// 所以我们可以定义promise对象进行链式调用
/*
> 当then方法的回调函数本身在执行时, 那么返回的promise对象处在pending状态
> 当then方法的回调函数return一个结果时, 那么它处在fulfilled/resolved状态, 并且会将结果作为新Promise的resolve的参数(默认是undefined)
> 正常数据
> promise对象
> 当then方法抛出一个异常时, 那么它处于reject状态
*/
const result = promise
.then((res) => {
console.log("res1:", res);
return res[0];
})
.then((res) => {
console.log("res2:", res);
return res.name;
})
.then((res) => {
console.log("res3:", res);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("success");
}, 1000);
});
})
.then((res) => {
console.log("res4:", res);
return new Promise((resolve, reject) => {
reject("fail");
});
})
.catch((err) => {
console.log("err1:", err);
return "catch";
})
.then((res) => {
console.log("res5:", res);
// 抛出异常
throw new Error("Error");
})
.then((res) => {
console.log("res6:", res);
})
.catch((err) => {
// Promise 异常穿透(传透)
// 当使用 promise 的 then 链式调用时,可以在最后指定失败的回调
// 前面任何操作出了异常,都会传到最后失败的回调中处理
console.log("err2:", err);
// 中断Promise链
return new Promise(() => {});
})
.then((res) => {
console.log("res7:", res);
})
.then((res) => {
console.log("res8:", res);
});
console.log("result", result);
4. finally方法
// finally表示Promise对象无论状态变为fulfilled/resolved还是rejected, 都会被执行
const promise = new Promise((resolve, reject) => {
resolve([
{ name: "zgc", age: 18 },
{ name: "wf", age: 24 },
]);
});
promise
.then((res) => {
console.log("res", res);
})
.catch((err) => {
console.log("err", err);
})
.finally(() => {
console.log('finall在哪种状态都会被调用');
});
5. Promise类方法
// 1. Promise.resolve
// Promise.resolve("Hello World");
// 相当于
// new Promise((resolve, reject) => {
// resolve("Hello World");
// });
// 2. Promise.reject
// Promise.reject("reject err")
// 相当于
// new Promise((resolve, reject) => {
// reject("reject err");
// });
// 3. Promise.all: 返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败了就直接失败
/*
当所有的Promise状态变成fulfilled时, 新Promise的状态为fulfilled, 并且会将所有Promise的返回值组成一个数组,
数组结果的顺序与Promise.all([数组])的顺序一致。,当有一个Promise的状态为reject时,
新Promise的状态为reject, 并且会将第一个reject的返回值作为结果
*/
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: "zgc", age: 18 });
// reject({ name: "zgc", age: 18 });
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: "wf", age: 24 });
// reject({ name: "wf", age: 24 });
}, 2000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("success");
reject("fail");
}, 3000);
});
const promise = Promise.all([p1, p2, p3]);
console.log("promise", promise);
promise
.then((res) => {
console.log("res1", res);
// [{ name: "zgc", age: 18 }, { name: "wf", age: 24 }, success ];
})
.catch((err) => {
console.log("err", err); // fail
});
// 4. Promise.race: 返回一个新的 promise,第一个完成的 promise 的结果状态就是最终的结果状态
// Promise.race()只会得到(打印)第一个返回的结果,但其余结果也会返回(不打印)。 谁先完成就输出谁(不管是成功还是失败)
const result = Promise.race([p1, p2, p3]);
console.log("result", result);
result
.then((res) => {
console.log("res2", res); // { name: "zgc", age: 18 }
})
.catch((err) => {
console.log("err", err);
});
// 5. Promise.allSettled: 解决all方法的缺陷
/*
all方法有一个缺陷, 当有一个Promise状态变为reject时, 新Promise就会立即变成对应的reject状态,
那么对于resolve的, 以及依然处于pending状态的promise, 我们是获取不到对应结果的;
allSettled方法会在所有的promise都有结果(无论是fulfilled还是rejected)才会有最终的状态,
并且这个状态一定是fulfilled的;
*/
const allSettled = Promise.allSettled([p1, p2, p3]);
console.log("allSettled", allSettled);
allSettled.then((res) => {
console.log("res3", res);
});
// 6. any: 和race方法类似, 但是会等到第一个成功的结果
// 会等到一个fulfilled状态, 才会决定新Promise的状态
// 如果所有的Promise都是reject的, 那么也会等到所有的Promise都变成rejected状态
const any = Promise.any([p1, p2, p3]);
console.log("any", any);
any
.then((res) => {
console.log("res4", res);
})
.catch((err) => {
// 当所有的Promise都是reject时, 返回的结果不是任何一个reject的参数, 而是一条错误信息
console.log("err", err); // err AggregateError: All promises were rejected
});
4. 迭代器(Iterator)与生成器(Generator)
JavaScript提供了四种数据集合,分别是array、object、map和set。这四种数据集合的数据结构各不相同,但是都可以被循环遍历,这一切的背后都离不开iteration(迭代器)的支撑。迭代器(Iterator)是一种机制,也可以说是一种接口,它为各种不同的数据结构提供了统一的访问机制。任何数据结构只要配置了 Iterator 接口(可以称之为迭代器对象),就可以完成遍历操作(配置了迭代器的数据结构称之为可迭代对象).
1. 迭代器对象
- 在 Javascript 中,迭代器是一个对象(也可称作为迭代器对象),它提供了一个 next() 方法,用来返回迭代序列中的下一项;
- next 方法的定义,next 方法是一个无参数或者一个参数的函数,执行后返回一个对象, 返回的包含两个属性:
{ done: [boolean], value: [any] }, 当done为true时value可省略;
// Iterator: 迭代器
const names = ["zgc", "wf", "cx", "wlc"];
// 给数组names模拟一个迭代器对象
function mockIterator(arr) {
let index = 0;
return {
// next方法: 一个无参数或者一个参数的函数, 返回一个带有done/value的对象
next: function () {
// done: boolean值, 为false时表示尚未迭代完成, value: 具体值/undefined, 当done为turue时可以省略
return index < arr.length
? { done: false, value: arr[index++] }
: { done: true };
},
};
}
const NIterator = mockIterator(names)
console.log(NIterator.next()); // {done: false, value: 'zgc'}
console.log(NIterator.next()); // {done: false, value: 'wf'}
console.log(NIterator.next()); // {done: false, value: 'cx'}
console.log(NIterator.next()); // {done: false, value: 'wlc'}
console.log(NIterator.next()); // {done: true}
for (let i = 0; i < names.length; i++) {
console.log(NIterator.next());
}
2. 可迭代对象
ES6中为迭代器提供了统一的访问机制for..of..,ES6中原生的迭代器有
Array、Set、Map、 String、argumrnts,for..of能够遍历它们是因为它们具有Symbol.iterator属性,该属性指向该数据结构的默认迭代器方法,当使用for...of..迭代该数据结构时,js引擎就会调用其Symbol.iterator方法,从而返回相应的默认迭代器。
// 1. 数组/字符串/Set/Map内部有默认的迭代器, 本身就是可迭代的
// 可迭代对象必然有一个名为[Symbol.iterator]的方法
var arr = [10, 2, 3, 4, 5]; //数组是一个可迭代对象
console.log(arr);
// 使用for..of..来遍历迭代器
for (var v of arr) {
console.log(v); // 10 2 3 4 5
}
//也可以使用ES6提供的next()方法手工遍历
var it = arr[Symbol.iterator](); // 调用可迭代对象的Symbol.iterator方法可以获取默认迭代器,将迭代器引用赋给it变量
console.log(it.next().value); // 10
console.log(it.next().value); // 2
console.log(it.next().value); // 3
// 2. 将info变成一个可迭代对象(迭代info中的friends)
/*
> 1. 必须实现一个特定的函数: [Symbol.iterator]
> 2. 这个函数必须返回一个迭代器对象(用于迭代当前的对象)
*/
const info = {
friends: ["wf", "cx", "wlc"],
// 计算属性名: []
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
return index < this.friends.length
? { done: false, value: this.friends[index++] }
: { done: true };
},
};
},
};
const FIterator = info[Symbol.iterator]();
console.log(FIterator.next()); // {done: false, value: 'wf'}
console.log(FIterator.next()); // {done: false, value: 'wf'}
console.log(FIterator.next()); // {done: false, value: 'WLC'}
console.log(FIterator.next()); // {done: true}
console.log(FIterator.next()); // {done: true}
// 3. 将infos变成一个可迭代对象(迭代infos中的key/value)
const infos = {
name: "zgc",
age: 18,
friends: ["wf", "cx", "wlc"],
[Symbol.iterator]: function () {
// const keys = Object.keys(this); // 得到key
const values = Object.values(this); // 得到value
// const entries = Object.entries(this) // 得到key/value数组
let index = 0;
const iterator = {
next: function () {
return index < values.length
? { done: false, value: values[index++] }
: { done: true };
},
};
return iterator;
},
};
// 4. 改造的对象支持for...of遍历(对象原本是不支持的)
for (const item of infos) {
console.log(item); // zgc 18 ["wf", "cx", "wlc"]
}
const obj = {
friends: ["wf", "cx", "wlc"],
};
// for (const item of obj) {
// console.log(item); // obj is not iterable
// }
3. 自定义类的迭代
class Person {
constructor(name, age, height) {
this.name = name;
this.age = age;
this.height = height;
}
// 实例方法是放在Person.prototype上面的
[Symbol.iterator]() {
const values = Object.values(this);
let index = 0;
return {
next: function () {
return index < values.length
? { done: false, value: values[index++] }
: { done: true };
},
// 监听迭代器是否进行了中断的方法, 当迭代进行中断的情况下会触发此方法
return: function () {
console.log("监听到迭代器函数进行中断了");
return { done: true };
},
};
}
}
const p1 = new Person("zgc", "18", "1.88");
const p2 = new Person("wf", "20", "2.88");
console.log(p1);
// 默认对象是不可以进行迭代的
// 要求: 使通过自定义类Person创建出来的对象都是可迭代的
/*
实现方法:
> 1. 必须实现一个特定的实例方法: [Symbol.iterator]
> 2. 这个函数必须返回一个迭代器对象(用于迭代当前的对象)
*/
for (const value of p1) {
console.log(value); // zgc 18 1.88
}
// 迭代器某些情况下会在没有迭代完全都是时候进行中断
for (const value of p2) {
if (value === "20") break; // break return throw
console.log(value); // wf
}
4. 生成器函数的基本使用
生成器是ES6中新增的一种函数控制, 使用的方案, 它可以让我们更加灵活的控制函数什么时候继续执行或者暂停执行.
- 生成器函数需要在
function的后面加一个符号:* - 生成器函数可以通过
yield关键字来控制函数的执行流程 - 生成器函数的
返回值是一个Generaor(生成器), 不会执行函数内部的代码- 生成器事实上是一种特殊的迭代器
- 要想执行函数内部的代码, 需要返回的生成器对象(Generaor)调用它的next方法
- 当遇到yield时中断执行, 想要继续执行需要再次调用next方法
// 1. 定义生成器函数
function* foo() {
console.log(1);
console.log(2);
yield;
console.log(3);
console.log(4);
yield;
console.log(5);
}
// 2. 调用生成器函数, 得到生成器对象
const generator = foo();
// 3. 调用生成器对象的next方法执行函数内部代码
generator.next(); // 1 2
generator.next(); // 3 4
generator.next(); // 5
5. 生成器函数的参数和返回值
// 1. 定义生成器函数
function* foo(text) {
const age = 18;
console.log("执行内部代码1", text);
const next2 = yield;
console.log("执行内部代码2", next2);
const next3 = yield "zgc";
// return "zgc";
console.log("执行内部代码3", next3);
yield age;
console.log("执行内部代码4");
}
// 2. 调用生成器函数, 得到生成器对象
const generator = foo("第一次执行yield");
// 3. 生成器函数的内部next是有返回值的
// 当yield全部执行完成之后的下一次调用next, done值变为true, value为undefined
// console.log(generator.next()); // {value: undefined, done: false}
// console.log(generator.next()); // {value: 'zgc', done: false}
// console.log(generator.next()); // {value: 18, done: false}
// console.log(generator.next()); // {value: undefined, done: true}
// 4. 在中间直接进行return, 表示迭代已经完成, 后续代码无任何意义
// 返回结果后done值变为true, 后续所有的done值也都变为true且value为undefined
// console.log(generator.next()); // {value: undefined, done: false}
// console.log(generator.next()); // {value: 'zgc', done: true}
// console.log(generator.next()); // {value: undefined, done: true}
// 5. 给函数每次执行传入参数
// // 1. 定义生成器函数
function* foo(text) {
const age = 18;
console.log("执行内部代码1", text);
const next2 = yield;
console.log("执行内部代码2", next2);
const next3 = yield "zgc";
// return "zgc";
console.log("执行内部代码3", next3);
yield age;
console.log("执行内部代码4");
}
// 2. 调用生成器函数, 得到生成器对象
const generator = foo("第一次执行yield");
// 3. 生成器函数的内部next是有返回值的
// 当yield全部执行完成之后的下一次调用next, done值变为true, value为undefined
// console.log(generator.next()); // {value: undefined, done: false}
// console.log(generator.next()); // {value: 'zgc', done: false}
// console.log(generator.next()); // {value: 18, done: false}
// console.log(generator.next()); // {value: undefined, done: true}
// 4. 在中间直接进行return, 表示迭代已经完成, 后续代码无任何意义
// 返回结果后done值变为true, 后续所有的done值也都变为true且value为undefined
// console.log(generator.next()); // {value: undefined, done: false}
// console.log(generator.next()); // {value: 'zgc', done: true}
// console.log(generator.next()); // {value: undefined, done: true}
// 5. 给函数每次执行传入参数
// 每个next传入的参数可以再上一个yield拿变量进行接收
// 但第一个yield比较特殊, 无法从next传入参数, 因为第一个yield没有上一个yield接收它的参数
// 所有第一个yield想要传入的参数往往在函数调用时传入
console.log(generator.next());
console.log(generator.next("第二次执行yield"));
console.log(generator.next("第三次执行yield"));
console.log(generator.next());
// 但第一个yield比较特殊, 无法从next传入参数, 因为第一个yield没有上一个yield接收它的参数
// 所有第一个yield想要传入的参数往往在函数调用时传入
console.log(generator.next());
console.log(generator.next("第二次执行yield"));
console.log(generator.next("第三次执行yield"));
console.log(generator.next());
6. 使用生成器来代替迭代器
const names = ["zgc", "wf", "cx", "wlc"];
// 1. 数组自带的迭代器
const defaultIterator = names[Symbol.iterator]();
// console.log(defaultIterator.next());
// console.log(defaultIterator.next());
// console.log(defaultIterator.next());
// console.log(defaultIterator.next());
// console.log(defaultIterator.next());
// 2. 给数组names模拟一个迭代器对象
function mockIterator(arr) {
let index = 0;
return {
// next方法: 一个无参数或者一个参数的函数, 返回一个带有done/value的对象
next: function () {
// done: boolean值, 为false时表示尚未迭代完成, value: 具体值/undefined, 当done为turue时可以省略
return index < arr.length
? { done: false, value: arr[index++] }
: { done: true };
},
};
}
const NIterator = mockIterator(names);
// console.log(NIterator.next());
// console.log(NIterator.next());
// console.log(NIterator.next());
// console.log(NIterator.next());
// console.log(NIterator.next());
// 3. 使用生成器来代替迭代器
// 生成器函数的返回值是一个特殊的迭代器
function* Gfoo(arr) {
for (let i = 0; i < arr.length; i++) {
yield arr[i];
}
}
const GIterator = Gfoo(names);
console.log(GIterator.next()); // {value: 'zgc', done: false}
console.log(GIterator.next()); // {value: 'wf', done: false}
console.log(GIterator.next()); // {value: 'cx', done: false}
console.log(GIterator.next()); // {value: 'wlc', done: false}
console.log(GIterator.next()); // {value: undefined, done: false}
// 4. 我们可以通过 yield*简化上面写法
// yield*是一种yield的语法糖, 只不过会依次迭代一个可迭代对象, 每次迭代其中的一个值
function* Gbar(arr) {
yield* arr;
}
const GIterator2 = Gbar(names);
console.log(GIterator2.next());
console.log(GIterator2.next());
console.log(GIterator2.next());
console.log(GIterator2.next());
console.log(GIterator2.next());
7. 生成器自定义类的迭代
class Person {
constructor(name, age, height) {
this.name = name;
this.age = age;
this.height = height;
}
*[Symbol.iterator]() {
const values = Object.values(this);
// const values = Object.keys(this);
yield* values;
// yield* values 是下方循环的语法糖
// for (let i = 0; i < values.length; i++) {
// yield values[i];
// }
}
// 迭代器写法
// [Symbol.iterator]() {
// const values = Object.values(this);
// let index = 0;
// return {
// next: function () {
// return index < values.length
// ? { done: false, value: values[index++] }
// : { done: true };
// },
// // 监听迭代器是否进行了中断的方法, 当迭代进行中断的情况下会触发此方法
// return: function () {
// console.log("监听到迭代器函数进行中断了");
// return { done: true };
// },
// };
// }
}
const p1 = new Person("zgc", "18", "1.88");
// 默认对象是不可以进行迭代的
// 要求: 使通过自定义类Person创建出来的对象都是可迭代的
// 使用生成器来简化迭代器写法
for (const value of p1) {
console.log(value); // zgc 18 1.88
}
5. async & await
1. 回调函数与Promise处理异步请求
// 1. 回调函数写法:
// 当面临回调函数嵌套调用(外部回调函数异步执行的结果是嵌套的回调执行的条件)时,容易形成回调地狱
function request1(url, callback) {
setTimeout(() => {
callback(url);
}, 2000);
}
function getData1() {
request1("zgc", (res1) => {
console.log("res1", res1);
request1(res1 + "wf", (res2) => {
console.log("res2", res2);
request1(res2 + "cx", (res3) => {
console.log("res3", res3);
});
});
});
}
getData1();
// 2. promise 链式调用
// 解决回调地狱的问题
function request2(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url);
}, 2000);
});
}
function getData2() {
request2("zgc")
.then((res1) => {
console.log("res1", res1);
return request2(res1 + "wf");
})
.then((res2) => {
console.log("res2", res2);
return request2(res2 + "wlc");
})
.then((res3) => {
console.log("res3", res3);
});
}
getData2();
2. 生成器处理异步请求
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url);
}, 2000);
});
}
function* getData() {
const res1 = yield request("zgc");
console.log("res1", res1);
const res2 = yield request(res1 + "wf");
console.log("res2", res2);
const res3 = yield request(res2 + "wlc");
console.log("res3", res3);
}
// const Generator = getData();
// console.log(Generator.next().value);
// 1. 初始写法
// Generator.next().value.then((res1) => {
// Generator.next(res1).value.then((res2) => {
// Generator.next(res2).value.then((res3) => {
// Generator.next(res3);
// });
// });
// });
// 2. 封装优化
function execGenFn(fn) {
// 调用传入的生成器函数, 得到生成器
const generator = fn();
recursiveCall(generator);
}
function recursiveCall(generator, data) {
// 拿到生成器返回的结果
const result = generator.next(data);
if (result.done) return;
result.value.then((res) => {
recursiveCall(generator, res);
});
}
execGenFn(getData);
3. async&await的基本使用
- await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
- 如果表达式是 promise 对象, await 返回的是 promise 成功的值
- 如果表达式是其它值, 直接将此值作为 await 的返回值
- await 必须写在 async 函数中, 但 async 函数中可以没有 await
- 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理
- await会等待Promise得到结果之后才会继续执行后续代码
// 1. 声明异步函数: async
async function foo() {
console.log(1);
}
foo();
// 2. 异步函数的内部代码执行顺序与普通函数是一致的 默认情况下也会同步执行
// 3. 异步函数的返回值:
// 函数的返回值为 promise 对象
// promise 对象的结果由 async 函数return的返回值决定
const bar = async function () {
// 1. 如果return的是非promise的任意值, 那么得到一个新的状态为fulfilled的promise, value为返回值
// return 520;
// 2. 如果return是一个Promise对象,那么return的Promise对象的结果状态决定了新的Promise的结果状态
return new Promise((resolve, reject) => {
resolve("YES");
});
// 3. 如果抛出异常, 新 promise 变为 rejected
// 在异步函数里面如果抛出了异常(产生了错误), 这个异常不会如普通函数一样立即被浏览器处理 ,
// 而是会在异步函数中进行处理: Promise.reject(err)
// "abc".filter();
// throw new Error("asycn fun err");
};
bar()
.then((res) => {
console.log("res", res);
})
.catch((err) => {
console.log("异步函数捕获异常:", err);
});
// 4. await关键字
const baz = async () => {
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("ok");
}, 2000);
});
let p2 = new Promise((resolve, reject) => {
reject("throw error");
});
//1. 右侧为promise的情况
let baz1 = await p1;
console.log(baz1);
//2. 右侧为其他类型的数据
let baz2 = await 20;
console.log(baz2);
//3. 如果promise是失败的状态
try {
let baz3 = await p2;
} catch (err) {
console.log(err);
}
};
baz();
// 5. async & await处理异步请求
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url);
}, 2000);
});
}
async function getData() {
const res1 = await request("zgc");
console.log("res1", res1);
const res2 = await request(res1 + "wf");
console.log("res2", res2);
const res3 = await request(res2 + "wlc");
console.log("res3", res3);
}
getData()
4. 异常处理与捕获
// 1. 函数遇到throw之后, 后面的代码都不会执行
// 2. throw 抛出一个具体的错误信息
function foo() {
console.log("foo");
// 1. 字符串
// throw 'error'
// 2. 对象
// throw {
// errMessage: "我是错误信息",
// errCode: 101101,
// };
// 3. 系统提供的类
// 一般只传入第一个参数就可以了
throw new Error("我是错误信息");
console.log("foo结束");
}
// 当函数出现异常时, 会将异常抛给自己的调用者
// 如果调用者没有进行处理, 那么异常会继续向外抛
// 如果抛到全局还是没有进行处理, 异常就会抛给浏览器, 控制台报错, 后续代码终止执行
// 捕获异常: try{ //正常代码 } catch(err){ //处理异常 }
function bar() {
try {
foo();
console.log('try执行');
} catch (err) { // ES10中, 如果不打算使用参数err, 则小括号可以省略
// new Error()抛出的异常, 有三个属性
console.log('catch执行');
console.log("err", err);
console.log(err.message, err.name);
console.log(err.stack);
} finally {
console.log('不论有没有异常都会执行');
}
console.log("bar");
}
function test() {
bar();
console.log("test");
}
test();
console.log("-------");
6. 宏任务与微任务
1. 进程与线程
- 进程: 进程可以简单的理解为一个可以独立运行的程序单位,进程就是由一个或多个线程构成的,简单来说你的电脑每运行一个程序就是一个进程,是 CPU 资源分配的最小单位;
- 线程: 而线程是进程中的实际运行单位,是操作系统进行运算调度的最小单位。可理解为线程是进程中的一个最小运行单元,线程是 CPU 调度的最小单位;
- 一个程序只至少有一个进程,一个进程至少有一个线程
- 操作系统如何做到同时让多个进程(便听歌, 边写代码, 边查阅资料)同时工作呢?
- 因为CPU(可直接运行操作系统)的运算速度非常快, 它可以迅速的在多个进程之间相互切换
- 当进程中的线程获取到时间片时, 就可以快速编写我们的代码
- 对于用户来说是感受不到这种快速的切换的
2. 单线程与多线程
- JS是单线程的, 但JS的线程有自己的进程容器:浏览器或者Node;
- 多数浏览器是多进程的, 每开启一个tab页就会开启一个新的进程(防止一个页面卡死而造成所有页面无法响应), 每个进程中有很多的线程, 其中包括执JS代码的线程;
- JS的代码执行是在一个单独的线程中执行的, 这就意味着JS的代码在同一时刻只能干一件事, 如果一件事非常耗时, 那么当前的线程就会被阻塞;
- 但真正耗时的操作, 实际上并不是由JS线程在执行的
- 浏览器的每个进程都是多线程的, 那么其他线程可以完成这个耗时的操作
- 比如网络请求, 定时器等
3. 任务队列与事件轮询(Event Loop)
JS是一门单线程的非阻塞脚本语言,Event Loop就是JS异步编程的一种解决方案。 代码执行开启一个全局调用栈(主栈)提供代码运行的环境,在执行过程中同步任务的代码立即执行,遇到异步任务将异步的回调注册到任务队列中,等待同步代码执行完毕查看异步是否完成,如果完成将当前异步任务的回调拿到主栈中执行,得到其执行的结果。
- 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。 js引擎执行异步代码而不用等待,是因有为有任务队列和事件轮询。
- 任务队列:任务队列是一个先进先出的队列,它里面存放着各种异步任务回调。
- 事件轮询:事件轮询是指主线程重复从任务队列中取任务、执行任务的过程。
- JS 主线程不断的循环往复的从任务队列中读取任务,执行任务,这种运行机制称为事件循环(event loop)。
4. 宏任务与微任务
- 浏览器的事件循环中分成宏任务和微任务, JS 中任务分成同步任务和异步任务。
- 定时器,事件绑定,ajax,回调函数等就是宏任务
- promise中的then回调, async/await, process.nextTick等就是微任务。
- 执行顺序: 主栈全局任务(宏任务) > 宏任务中的微任务 > 下一个宏任务。
5. 简述同步和异步的区别
同步是阻塞模式,异步是非阻塞模式。众所周知,javascript是单线程的、同步的语言;
- 同步就是所有的任务都处在同一队列中,一个任务执行完接着开始执行下一个,相对于浏览器而言,同步的效率过低,一些耗费时间比较长的任务应该用异步来执行。
- 异步任务就是将异步的回调注册到任务队列中,等待同步代码执行完毕查看异步是否完成,如果完成将当前异步任务的回调拿到主栈中执行,得到其执行的结果。
7. webStorage
- 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
- 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
1. 相关API
1. ```xxxxxStorage.setItem('key', 'value');```
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
值为字符串,如果存入的值为对象,则将其转换为字符串后存入
2. ```xxxxxStorage.getItem('person');```
该方法接受一个键名作为参数,返回键名对应的值。对象转字符串后存入,取出后需将其重新转化为对象
3. ```xxxxxStorage.removeItem('key');```
该方法接受一个键名作为参数,并把该键名从存储中删除。
4. ``` xxxxxStorage.clear()```
该方法会清空存储中的所有数据。
备注:
SessionStorage(会话存储): 存储的内容会随着浏览器/浏览器窗口关闭而消失。LocalStorage(本地存储): 存储的内容,需要手动清除才会消失, 即关闭浏览器后仍然存在。xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。JSON.parse(null)的结果依然是null。- 这两者的API用法一致
2. localStorage的基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>
<script type="text/javascript">
let p = { name: "张三", age: 18 };
function saveData() {
localStorage.setItem("msg", "hello!!!");
localStorage.setItem("msg2", 666);
localStorage.setItem("person", JSON.stringify(p));
}
function readData() {
console.log(localStorage.getItem("msg"));
console.log(localStorage.getItem("msg2"));
const result = localStorage.getItem("person");
JSON.parse(result);
// console.log(localStorage.getItem('msg3'))
}
function deleteData() {
localStorage.removeItem("msg2");
}
function deleteAllData() {
localStorage.clear();
}
</script>
</body>
</html>
3. sessionStorage的基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>sessionStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>
<script type="text/javascript">
let p = { name: "张三", age: 18 };
function saveData() {
sessionStorage.setItem("msg", "hello!!!");
sessionStorage.setItem("msg2", 666);
sessionStorage.setItem("person", JSON.stringify(p));
}
function readData() {
console.log(sessionStorage.getItem("msg"));
console.log(sessionStorage.getItem("msg2"));
const result = sessionStorage.getItem("person");
console.log(JSON.parse(result));
// console.log(sessionStorage.getItem('msg3'))
}
function deleteData() {
sessionStorage.removeItem("msg2");
}
function deleteAllData() {
sessionStorage.clear();
}
</script>
</body>
</html>
4. local与session的区别
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<a href="./test.html">页面内跳转</a>
<a href="./test.html" target="_blank">页面外跳转</a>
<script>
// 验证一: 关闭网页(复制当前网页地址, 注释掉 1.1, 1.2的代码, 在新的网页打开)
// 1.1 localStorage的存储保持
// localStorage.setItem("name", "localStorage");
// 1.2 sessionStorage的存储会消失
// sessionStorage.setItem("name", "sessionStorage");
// 结论一: 关闭当前网页后重写打开, localStorage的存储保持, sessionStorage的存储会消失
// 验证二: 在页面内实现跳转
// 1.1 localStorage的存储保持
// localStorage.setItem("info", "localStorage");
// 1.2 sessionStorage的存储也会保持
// sessionStorage.setItem("info", "sessionStorage");
// 结论二: 在页面内实现跳转, localStorage的存储保持, sessionStorage的存储也会保持
// 验证三: 在页面外跳转
// 1.1 localStorage的存储保持
localStorage.setItem("key", "localStorage");
// 1.2 sessionStorage的存储不会保留
sessionStorage.setItem("key", "sessionStorage");
// 结论三: 在页面外实现跳转, localStorage的存储保持, sessionStorage的存储不会保留
</script>
</body>
</html>
test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>我是测试网页</h1>
<script>
console.log(sessionStorage.getItem("info"));
</script>
</body>
</html>
5. catch工具封装
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!-- 若不动态地加载或是显式地添加“defer”或“async”属性,script标签将按照在页面上的出现顺序进行加载, 无论是外部脚本还是内联脚本都将这样进行处理。 -->
<script src="./cache.js"></script>
<script>
const user = {
name: "zgc",
age: 18,
};
const info = "My name is wf";
localCache.setCache("user", user);
console.log(localCache.getCache("user"));
sessionCache.setCache("info", info);
console.log(sessionCache.getCache("info"));
</script>
</body>
</html>
cache.js
class Cache {
constructor(storage) {
if (storage === "local") {
this.storage = localStorage;
} else if (storage === "session") {
this.storage = sessionStorage;
} else {
confirm("请输入 'local' 或 'session' 作为参数");
}
}
setCache(key, value) {
if (!value) return confirm("请检查要缓存的数据");
if (key) this.storage.setItem(key, JSON.stringify(value));
}
getCache(key) {
if (key) return JSON.parse(this.storage.getItem(key));
}
removeCache(key) {
if (key) this.storage.removeItem(key);
}
clearCache() {
this.storage.clear();
}
}
const localCache = new Cache("local");
const sessionCache = new Cache("session");
8. 正则表达式
正则匹配规则工具大全: c.runoob.com/front-end/8…
1. 正则表达式初体验
// 正则匹配规则工具大全: https://c.runoob.com/front-end/854/
// 1. 正则表达式是一种字符串匹配机制, 而已用来搜索, 获取, 代替字符串
// 2. 在JS中, 正则表达式使用RegExp类来创建, 也可以使用字面量创建
// 2.1 RegExp类: new RegExp(匹配规则, 修饰符)
const re1 = new RegExp("hello", "i");
// 2.2 字面量: /匹配规则/修饰符
const re2 = /hello/i;
// 3. 正则表达式的初体验
// 需求1: 将所有的abc替换成cba(忽略大小写)
const msg = "abcd00 1Abcd ABc23d ABC456d 789ABCD";
// 传统字符串方法
const msg1 = msg.replaceAll("abc", "cba");
console.log("msg1", msg1); // 失败, 只能替换第一个, 无法忽视大小写
// 正则表达式
const msg2 = msg.replaceAll(/abc/gi, "cba");
console.log("msg2", msg2); // 成功, /abc/:查找abc字符串, i: 忽略大小写, g: 就是匹配全部可匹配结果
// 需求2: 将所有的数字删除
// 正则表达式
const msg3 = msg.replace(/\d+/g, "");
console.log("msg3", msg3); // 成功, \d 匹配数字类型, +: 一个或者多个, g: 就是匹配全部可匹配结果
2. 正则表达式的基本使用
// JS中的正则表达式被用于 RegExp 的 exec, test方法
// 也包括 字符串 的 match, matchAll, replace, replaceAll, search, split方法
// i: 忽略大小写, g: 就是匹配全部可匹配结果
// 如果你不带g,在正则过程中,字符串是从左至右匹配的,如果匹配成功就不再继续向右匹配了,如果你带g,它会重头到尾的把正确匹配的字符串挑选出来
// 1. 使用正则对象上的实例方法
// 1.1 test: 检测某一个字符串是否符合正则的规则, 返回值是布尔类型
/*
如果正则表达式设置了 g,test() 的执行会改变正则表达式 lastIndex属性。连续的执行test()方法,后续的执行将会从 lastIndex 处开始匹配字符串
即在相同的全局正则表达式实例上多次调用test将会越过之前的匹配
但如果只执行一次, 加上g与不加g是一样的
*/
const re1 = /abc/gi;
const msg1 = "abcd00 1Abcd ABc23d ABC456d 789ABCD";
const re2 = /^abxc$/gi;
const msg2 = "aBxc";
if (re1.test(msg1)) {
console.log("msg字符串中包含abc, 符合规则");
}
if (re2.test(msg2)) {
console.log("msg字符串以a开头, 以c结尾, 中间包含bx, 符合规则");
}
// 1.2 exec: 使用正则执行字符串, 匹配成功返回一个数组, 没有匹配成功则返回null
// 不加入g,则只返回第一个匹配,无论执行多少次均是如此,如果加入g,则第一次执行也返回第一个匹配,再执行返回第二个匹配,依次类推。
const res1 = re1.exec(msg1);
console.log("res1", res1);
// 2. 使用字符串方法, 传入一个正则表达式
// 2.1 match: 匹配字符串中符合正则条件的子字符串, 并以数组形式返回, 没有匹配成功则返回null
// 不加入g,也只是返回第一个匹配,一直执行match方法也总是返回第一个匹配,加入g,则一次返回所有的匹配
const res2 = msg1.match(re1);
console.log("res2", res2);
// 2.2 matchAll: 得到一个迭代器, 里面包含匹配到的字符串的详情
// 传入的正则修饰符必须加g, 否则报错
const res3 = msg1.matchAll(re1);
console.log("res3", res3);
for (const item of res3) {
console.log("item", item);
}
// 2.3 replace: 替换
// 表达式不加入g,则只替换第一个匹配,如果加入g,则替换所有匹配
const res4 = msg1.replace(/abc/gi, "cba");
console.log("res4", res4);
// 2.4 replaceAll: 全部替换
// 传入的正则修饰符必须加g, 否则报错
const res5 = msg1.replaceAll(/\d+/g, "");
console.log("res5", res5);
// 2.5 split: 以正则为分隔符切割字符串, 返回一个数组
// 加上g与不加g是一样的
const res6 = msg1.split(re1);
console.log("res6", res6, msg1);
// 2.6 search 搜索字符串, 返回匹配到的位置索引, 失败时返回-1
// 加不加g也是一样的
const res7 = msg1.search(re1);
console.log("res7", res7);
3. 字符类和反向类
// 1. 字符类: 是一种特殊的符号, 匹配特定集中的任何符号
const msg = "abc 1234dfg567u io890MM";
// 1.1 \d(digit) 匹配数字类型(匹配一个从 0 到 9 的字符)
const re1 = /\d\d/gi;
const res1 = msg.match(re1);
console.log("res1", res1);
// 1.2 \s(space) 匹配空格字符, 包括 空格, 制表符\t, 换行符\n和其他少数稀有字符\v, \f, \r等
const re2 = /\s/gi;
const res2 = msg.match(re2);
console.log("res2", res2);
// 1.3 \w(word) 匹配单字字符: 数字或下划线_或拉丁字母(a-z,A-Z),
const re3 = /\w/gi;
const res3 = msg.match(re3);
console.log("res3", res3);
// 1.4 .字符: 与除换行符之外的任何 **一个** 字符匹配
const re4 = /./gi;
const res4 = msg.match(re4);
console.log("res4", res4);
// 2. 反向类
// 2.1 \D: 匹配除了 \d 之外的任意字符
// 2.2 \S 匹配除了 \s 之外的任意字符
// 2.3 \W 匹配除了 \w 之外的任意字符
4. 锚点和词边界
// 1. ^ 和 $ 在正则表达式中被称为锚点
// ^: 匹配文本开头, $: 匹配文本末尾
const msg = "My Name Is zgc.";
// 字符串方法(区分大小写)
if (msg.startsWith("my")) {
console.log("以my开头"); // false
}
if (msg.endsWith("zgc.")) {
console.log("以zgc.结尾"); // true
}
// 正则匹配
if (/^my/gi.test(msg)) {
console.log("以my开头"); // true
}
if (/zgc\.$/gi.test(msg)) {
// \. 使用了转义
console.log("以zgc.结尾"); // true
}
const re1 = /^coder$/; // 必须一个不差
const re2 = /^cod.*er$/; // 想要在中间插入其他字符
const info1 = "coder";
const info2 = "codxxer";
console.log(re1.test(info1)); // true
console.log(re1.test(info2)); //false
console.log(re2.test(info2)); // true
// 2. 词边界 \b: 检查某一字符串是否处于词边界
// 即检测某一字符串的一侧是否匹配 \w
const txt = "My Username Is zgc";
console.log(/name/gi.test(txt)); // true
console.log(/\bname\b/gi.test(txt)); // false, txt字符串的name左边是User, 匹配 \w, 不处于词边界
console.log(/\bname\b/gi.test(msg)); // true, msg字符串的name左右是空格, 不匹配 \w 处于词边界
// 3. 词边界应用: 匹配所有的时间
const time = "now time is 11:56, 12:00 eat food, number is 123:456";
const timeRe = /\b\d\d:\d\d\b/gi;
console.log(time.match(timeRe));
5. 转义字符
// 如果要把特殊的字符作为常规的字符使用, 需要对其进行转义: 在前面加上反斜杠 \
const re1 = /./gi; // .字符: 与除换行符之外的任何字符匹配
const re2 = /\./gi; // 进行转义: 匹配 字符串中的 .
const msg = "abc.def";
console.log(msg.match(re1)); // ['a', 'b', 'c', '.', 'd', 'e', 'f']
console.log(msg.match(re2)); // ['.']
console.log(re1.test(msg)); // true
console.log(re2.test(msg)); // false
// 常见需要转义的字符: [],\, ^, $, ., /, |, ?, *, +, ()
// 应用: 获取所有的js/jsx后缀的文件名
const fileNames = ["abc.html", "abc,css", "abc.js", "xyz.jsx", "def.js"];
const re = /\.jsx?$/gi; // 使用变量承载时不要轻易使用 g, 同一实例多次调用lastIndex会改变
const arr1 = fileNames.filter((item) => re.test(item));
const arr2 = fileNames.filter((item) => /\.jsx?$/i.test(item));
console.log(arr1, arr2);
6. 集合与范围
// 1. 集合
// 有时我们只需要选择多个匹配字符其中之一就可以:
// 在方括号[...]中的几个字符或者字符类 意味着'搜索给定的字符中的任意一个'
const str = "13166781999";
const re = /^1[34567]\d/; // 以 1 开头, 第二位在数字 34567 中选择, 第三位是数字
console.log(re.test(str)); // true
// 应用: 手机号匹配正则
const phoneRe = /^1[3456789]\d{9}$/; // 以 1 开头, 第二位在数字 3456780 中选择, 后面9位都是数字
// 或者: /^1(3|4|5|6|7|8|9)\d{9}$/
console.log(phoneRe.test(str)); // true
/*
注意:
1. 正则里面的中括号[]只能匹配其中一个,如果要匹配特定几组字符串的话,那就必须使用小括号 () 与 |
2. [3456]匹配3或者4或者5或者6,而(3456)只匹配3456,若要跟前面一样可以加或(3|4|5|6)。
3. [34|56]匹配3或者4或者|或者5或者6.而(34|56)能匹配34或者56。
*/
// 2. 范围: []也可以包含字符范围
// [0-5] 匹配从0到5的数字, [a-z]匹配从a到z的拉丁字母
// [0-9A-F]: 搜索一个字符, 妈妈在数字0-9或者字母A-F
// \d: 表示所有数字
// \w: 与[a-zA-Z0-9]相同
// 3. 排除范围 [^...]
// \d : [0-9] 匹配任意一个数字
// \D: [^0-9] 匹配除了 \d 之外的任意字符
7. 量词
// 量词: 描述字符串匹配的数量 {n}
// 确切的数量: {5}, 匹配5个字符
// 描述范围:{3,7}, 匹配3到7个字符
// 注意: 这里的,后面千万不要加空格
// 匹配字符串中3到5个a
const msg = "abdaajsjaaasaaaahdfaaaaajkgdfaaaaaa";
console.log(msg.match(/a{3,5}/gi));
// +: 代表 1个 或者 多个, 相当于 {1,}
// ?: 代表 0个 或者 1个, 相当于{0,1}, 它使得符号变得可选
// *: 代表 0个 或者 多个, 相当于{0,}, 这个字符可以不出现或者多次出现
console.log(msg.match(/a+/gi))
// 案例: 匹配开始结束标签
const el = "<div><i>哈哈哈</i></div><h1>呵呵呵</h1>";
const tagRe = /<\/?[a-z][a-z0-9]*>/gi;
const res = el.match(tagRe);
console.log(res);
8. 贪婪和惰性模式
const msg = "我最喜欢的三个游戏: <仁王2> 与 <文明VI> 以及 <泰拉瑞亚>";
// 1. 贪婪:
/*
^ 正则表达式通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符, 这被称为贪婪匹配。
^ 比如这个表达式:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。
*/
// 正常使用量词默认是贪婪模式:
// * 重复任意次,但尽可能少重复
// + 重复1次或更多次,但尽可能少重复
// ? 重复0次或1次,但尽可能少重复
// {n,m} 重复n到m次,但尽可能少重复
// {n,} 重复n次以上,但尽可能少重复
const re1 = /<.+>/gi;
const res1 = msg.match(re1);
console.log("res1", res1);
// 2. 惰性:
/*
^ 懒惰匹配,也就是匹配尽可能少的字符, 在能使整个匹配成功的前提下使用最少的重复;
^ a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符);
^ 为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?
^ 简单地说,因为正则表达式有一条比懒惰/贪婪规则优先级更高的规则,就是:最先开始的匹配拥有最高的优先权;
*/
// 在量词后加?启用惰性:
//*? 重复任意次,但尽可能少重复
// +? 重复1次或更多次,但尽可能少重复
// ?? 重复0次或1次,但尽可能少重复
// {n,m}? 重复n到m次,但尽可能少重复
// {n,}? 重复n次以上,但尽可能少重复
const re2 = /<.+?>/gi;
const res2 = msg.match(re2);
console.log("res2", res2);
9. 捕获组与或操作
// 1. 捕获组: 模式的一部分可以用 () 括起来, 这称为捕获组
// 1.1 它允许将匹配的一部分作为结果数组中的单独项
const msg = "我最喜欢的三个游戏: <仁王2> 与 <文明VI> 以及 <泰拉瑞亚>";
const re1 = /(<)(.+?)>/gi;
const iterator1 = msg.matchAll(re1);
for (const item of iterator1) console.log(item);
// 1.2 如果我们将量词放在括号后,则它将括号视为一个整体
// 不带括号,模式 go+ 表示 g 字符,其后 o 重复一次或多次。例如 goooo 或 gooooooooo。
// 括号将字符组合,所以 (go)+ 匹配 go,gogo,gogogo等。
console.log("Gogoogooo now!".match(/go+/gi));
console.log("Gogoogooo now!".match(/(go)+/gi));
// 1.3 捕获组命名(?<name>规则): groups
const re2 = /(<)(?<zgc>.+?)>/gi;
const iterator2 = msg.matchAll(re2);
for (const item of iterator2) console.log(item);
// 1.4 非捕获组: 使用 ?: 排除组
// 有时我们需要括号才能正确应用量词, 但我们不希望它们的内容出现在结果中
const re3 = /(?:<)(.+?)>/gi;
const iterator3 = msg.matchAll(re3);
for (const item of iterator3) console.log(item);
// 2. 或(or) :在正则表达式中使用 | 表示
// 通常会和捕获组一起使用, 在其中表示多个值
const info = "abcabcdefcbanhbcaddwewwe";
const re4 = /(abc|cba|bca){1,}/gi;
console.log(info.match(re4));
10. 歌词解析
// 进行歌词格式转化
// line: [00:31.160]如果场景里出现一架钢琴 ==>{time: 16280, content: 'あんなに愛した君がいない'}
const lyricString = "[00:00.000] 作词 : 许嵩\n[00:01.001] 作曲 : 许嵩\n[00:03.010]天青色等烟雨\n[00:05.100]而我在等你\n[00:07.11]这其实不是许嵩的歌\n";
// 用正则表达式匹配前面的时间[00:31.160], 毫秒可能是两位 00:00:12 = 00:00:120
const parseExp = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/;
function parseLyric(lyricStr) {
const lyricArr = lyricStr.split("\n");
console.log("将字符串切割成数组:", lyricArr);
const lyrics = [];
for (const line of lyricArr) {
if (line) {
const result = parseExp.exec(line);
console.log("result", result);
// 如果这一次没有匹配到,则跳过这一次进行下一次匹配
if (!result) continue;
const minuteTime = result[1] * 60 * 1000;
const secondTime = result[2] * 1000;
const msTime = result[3].length === 3 ? result[3] * 1 : result[3] * 10;
const time = minuteTime + secondTime + msTime;
// replace方法:用后面的值取代前面的值,trim:去掉空格
const content = line.replace(parseExp, "").trim();
lyrics.push({ time: time, content: content });
}
}
return lyrics;
}
const lyricInfo = parseLyric(lyricString)
console.log(lyricInfo);
11. 时间格式化
// 得到时间戳
const time = new Date().getTime();
console.log("time", time);
// 商品信息
const productInfo = {
name: "iPhone",
newPrice: 7999,
oldPrice: 8999,
endTime: time,
};
// 进行时间格式化
// yyyy/MM/dd hh:mm:ss
// yyyy-MM-dd hh:mm:ss
// ...
function formatDate(time, formatStr) {
let date = new Date(time);
// console.log("date", date);
const obj = {
"y+": date.getFullYear(),
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"h+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
};
for (const key in obj) {
if (new RegExp(key).test(formatStr)) {
const value = (obj[key] + "").padStart(2, "0");
formatStr = formatStr.replace(new RegExp(key), value);
}
}
return formatStr;
}
console.log(formatDate(productInfo.endTime, "yyyy-MM-dd hh:mm:ss"));
console.log(formatDate(productInfo.endTime, "yyyy年-MM月-dd日 hh:mm:ss"));
console.log(formatDate(productInfo.endTime, "hh:mm:ss yyyy/MM/dd "));
9. HTTP与网络请求
1. 客户端渲染与服务器端渲染
cloud.tencent.com/developer/a…
初识:
服务器端渲染:
客户端渲染(前后端分离):
2. XHR发送网络请求
1. AJAX的理解
Ajax 即异步 JavaScript 和 XML; 通俗的将讲:在网页中利用 XMLHttpRequest 对象和服务器进行数据交互的方式,就是Ajax。
2. XHR请求的过程
- 创建XMLHttpRequest对象;
- 监听onreadystatechange事件,当状态发生改变时浏览器触发事件, 回调绑定的方法调用
- 调用open方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false);
- 调用send方法传递参数。
3. XHR的状态
XMLHttpRequest的状态(state): xhr.readyState
事实上,我们在一次网络请求中看到状态发生了很多次变化,这是因为对于一次请求来说包括如下的状态:
| 标值 | 状态 | 描述 |
|---|---|---|
| 0 | UNSENT | 代理已被创建, 但尚未调用open方法, 一般不进行监听 |
| 1 | OPENED | open方法已经被调用 |
| 2 | HEADERS_RECEIVED | send方法已被调用, 并且头部和状态已经可获得 |
| 3 | LOADING | 数据接收中, responseText已包含部分数据 |
| 4 | DONE | AJAX请求完成 |
tips:
这个状态并非是HTTP的响应状态 ,而是记录的XMLHttpRequest对象的状态变化, http响应状态依旧需要通过响应行的status进行获取;
4. XHR的其他事件监听与响应数据类型
| 事件 | 描述 |
|---|---|
| onloadstart | 请求开始, 当调用 send() 时,触发单个 loadstart 事件 |
| onprogress | 监听进度, xhr对象会发生 progress 事件,通常每隔50ms左右触发一次,所以可以使用这个事件给用户反馈请求的进度。如果请求快速完成,他可能不会触发 progress 事件。注意这里的 progress 是下载的进度,xhr2 额外的定义了上传 upload 属性,用来定义上传的相关事件。 |
| onload | 当事件完成时,触发 load 事件 |
| ontimeout | 如果请求超时,会触发 timeout 事件(在设置了timeout的情况下) |
| onerror | 发生错误, 这些错误(如重定向--域名错误)发生时会触发 error 事件 |
| onabort | 如果请求中止,会触发 abort 事件 |
| onloadend | 对于任何具体的请求,浏览器将只会触发 load/abort/timeout/error 事件中的一个, 一旦这些事件触发以后,浏览器将会触发 loadend 事件 |
5. 响应数据与响应类型的设置
- 在发送了请求之后, 我们需要获取对应的结果:
response属性xhr的response属性内部有响应的正文内容- 返回的类型取决于
xhr.responseType的设置
- 我们可以使用
xhr.responseType属性来设置响应格式"": 与默认类型"text"相同, 响应格式为字符串"text": 响应格式为字符串,response是DOMString对象中的文本"json": response 是一个 JavaScript 对象
6. HTTP响应状态status
xhr.readyState是用于记录xhr对象本身的状态变化, 而非HTTP的网络请求状态- 如果希望获取
HTTP响应的网络状态, 可以通过xhr.status或xhr.statusText来获取
//使用Promise封装一个AJAX
function sendAJAX(url) {
return new Promise((resolve, reject) => {
// 1. 创建XMLHttpRequest对象;
const xhr = new XMLHttpRequest();
// responseType: 用于告诉浏览器,如何解析服务端返回的数据
// 将值设置为 json, 则response 是一个 JavaScript 对象。这个对象是通过将接收到的数据类型视为 JSON 解析得到的。
xhr.responseType = "json";
// 2. 监听状态的改变(浏览器将回调加入到宏任务之中)
xhr.onreadystatechange = function () {
// XMLHttpRequest的状态: console.log("xhr.readyState", xhr);
if (xhr.readyState === 4) {
// 标值 0 - 4 对应 XMLHttpRequest的状态 UNSENT - DONE
// if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
};
// 3. 配置请求open, xhr.open(method, url, async); 设置请求的方式,请求的路径,同步(false)/异步(true:默认);
// get请求参数(query)紧跟url: http://jsonplaceholder.typicode.com/posts?name=zgc&age=18
xhr.open("GET", url, true);
// 4. 发送请求(浏览器帮助我们发送请求)
xhr.send();
// post请求参数放到send内:
// xhr.setRequestHeader("Content-type", "application/json");
// xhr.send(JSON.stringify({ name: "zgc", age: 18 }));
// 同步必须xhr.send有结果后才能继续执行下方代码, 异步则不必等待
// 所以实际开发中我们使用异步请求(默认)
// console.log("++++++++++++++++++");
});
}
sendAJAX("http://jsonplaceholder.typicode.com/posts").then(
(value) => {
console.log("value", typeof value, value);
},
(reason) => {
console.warn("reason", reason);
}
);
3. AJAX网络请求封装
function ajax({
url,
method = "GET",
data = {},
params,
timeout = 10000,
success,
failure,
} = {}) {
const xhr = new XMLHttpRequest();
const promise = new Promise((resolve, reject) => {
xhr.responseType = "json";
xhr.timeout = timeout;
// xhr.onabort = function () {
// console.log("abort");
// };
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// success && success(xhr.response); // 回调函数方法得到结果
resolve(xhr.response);
} else {
// failure && failure({ status: xhr.status, message: xhr.statusText }); // 回调函数方法得到结果
reject({ status: xhr.status, message: xhr.statusText });
}
}
};
if (method.toUpperCase() === "GET") {
if (params) {
// 当传入params类型参数时, 将其拼接成query参数
const queryArr = [];
for (const key in params) {
queryArr.push(`${key}=${params[key]}`);
}
url = url + "?" + queryArr.join("&");
}
// console.log("url", url);
xhr.open(method, url);
xhr.send();
}
if (method.toUpperCase() === "POST") {
xhr.open(method, url);
xhr.setRequestHeader("Content-type", "application/json");
xhr.send(JSON.stringify(data));
}
});
// 可以将xhr对象返回给外界进行操作, 如终止(abort)请求等
promise.xhr = xhr;
return promise;
}
const result = ajax({
// get请求:
method: "GET",
// url: "http://jsonplaceholder.typicode.com/posts?userId=5", // query参数
url: "http://jsonplaceholder.typicode.com/posts", // params参数
params: {
userId: 5,
},
timeout: 10000,
// post请求:
// method: "POST",
// url: "http://jsonplaceholder.typicode.com/posts", // 得到一个id
// data: {
// name: "wf",
// age: 18,
// },
// 成功或者失败的回调, 在不使用Promise的情况下得到结果
// success: function (data) {
// console.log("data", data);
// },
// failure: function (err) {
// console.log("err", err);
// },
});
result
.then((res) => {
console.log("res", res);
})
.catch((err) => {
console.log("err", err); // {status: 404, message: 'Not Found'}
});
// 终止请求执行
// result.xhr.abort();
4. fetch函数的使用
// 1. fetch的基本使用
fetch("http://jsonplaceholder.typicode.com/posts?userId=5")
.then((res) => {
console.log(res); // 返回的响应信息
// text/json()方法属于fetchApi的一部分,它返回一个Promise实例对象 ,再用.then()才可以获取需要数据
return res.json();
})
.then((data) => {
// 注意这里data得到的才是最终数据
console.log("data", data);
})
.catch((err) => {});
// 2. POST请求 & 搭配await
const params = {
name: "zgc",
age: 18,
};
async function getData() {
const res = await fetch("http://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});
const data = await res.json();
console.log("data", data);
}
// async function getData() {
// const res = await fetch("http://jsonplaceholder.typicode.com/posts", {
// method: "POST",
// headers: {
// "Content-Type": "application/x-www-form-urlencoded",
// },
// body: "name=zgc&age=18",
// });
// const data = await res.json();
// console.log("data", data);
// }
getData();
5. XHR上传文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input type="file" class="file" />
<button class="upload">上传文件</button>
<script>
const uploadBtn = document.querySelector(".upload");
uploadBtn.onclick = function (e) {
// console.log("e", e);
const xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
console.log("xhr", xhr.response);
} else {
console.log({ status: xhr.status, message: xhr.statusText });
}
};
xhr.open("post", "http://123.207.32.32:1888/02_param/upload");
const fileEL = document.querySelector(".file");
const file = fileEL.files[0];
const formData = new FormData();
formData.append("avatar", file);
xhr.send(formData);
};
</script>
</body>
</html>
6. fetch上传文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input type="file" class="file" />
<button class="upload">上传文件</button>
<script>
const uploadBtn = document.querySelector(".upload");
uploadBtn.onclick = async function (e) {
const fileEL = document.querySelector(".file");
const file = fileEL.files[0];
const formData = new FormData();
formData.append("avatar", file);
const res = await fetch("http://123.207.32.32:1888/02_param/upload", {
method: "post",
body: formData,
});
const data = await res.json();
console.log("data", data);
};
</script>
</body>
</html>