字符串常用方法
charAt(); // 返回指定索引位置的字符
indexOf(); // 返回字符串中检索指定字符第一次出现的位置
lastIndexOf(); // 返回字符串中检索指定字符最后一次出现的位置
slice(); // 提取字符串的片断,并在新的字符串中返回被提取的部分
split() // 把字符串分割为子字符串数组
toLowerCase() // 把字符串转换为小写
toUpperCase() // 把字符串转换为大写
substr() // 从起始索引号提取字符串中指定数目的字符
substring() // 提取字符串中两个指定的索引号之间的字符
ES6 扩展
includes(); // 返回布尔值,表示是否找到了参数字符串
startsWith(); // 返回布尔值,表示参数字符串是否在原字符串的头部
endsWith(); // 返回布尔值,表示参数字符串是否在原字符串的尾部
repeat(); // 返回一个新字符串,表示将原字符串重复n次
padStart(); // 用于头部补全
padEnd(); // 用于尾部补全
函数默认参数
// ES5
function fun(x,y){
y = y || '10';
console.log(x,y);
}
fun(123);
// ES6
function fun(x,y ="10"){
console.log(x,y);
}
fun(123);
rest参数
ES6 引入 rest 参数(形式为 …变量名 ),用于获取函数的多余参数,这样就不需要使用 arguments 对
象了。
// rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中
function fun(...values){
console.log(values); // [1, 2, 3, 4, 5]
}
fun(1, 2, 3, 4, 5);
// 获取具体参数值可以通过values[2]
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
function fun(a,...b){
console.log(a); //1
console.log(b); // [2, 3, 4]
}
fun(1,2,3,4)
// 报错
function fun(a,...b,c){}
fun(1,2,3,4)
基本用法
ES6 允许使用“箭头”( => )定义函数。
var f = v => v; // 等同于
var f = function (v) {
return v;
};
参数为空
let f = () => '123';
多个参数
let f = (n1,n2) => n1+n2;
返回对象
let f = (n1,n2) => ({name:n1,age:n2})
定义默认值
var f = (x,y ="10")=>{
console.log(x,y);
}
f(123);
解构中的运用
let f = ({x = 0, y = 0} = {}) => {
return [x, y];
}
f({x:3,y:3})
let f = ({x = 0, y = 0} = {}) => {
return [x, y];
}
f()
rest参数
const fun = (...item) => [item];
fun(1, 2, 3, 4, 5);
箭头函数有几个使用注意点
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
1、箭头函数不能当作构造函数, 不可以使用new 命令
// 箭头函数中,this指向固定化,本身是没有自己的this,所以不能用作构造函数
var Fun = ()=>{
this.name = '1'
};
var f1 = new Fun() //Fun is not a constructor
2、箭头函数没有原型对象
function fun(){}
fun.prototype;
var fun2 =()=>{}
fun2.prototype;
3、不可以使用arguments对象该对象在函数体内不存在替代 rest
var fun = ()=>{
console.log(arguments[1]) //arguments is not defined
};
fun(1,2,3);
// 替代rest
var fun = (...item)=>{
console.log(item[1])
}
fun(1,2,3);
4、this指向由于箭头函数不绑定this,它会捕获其所在上下文的this的值,作为自己的this值
var str = 'global';
var obj = {
str:'private',
getStr:function(){
console.log(this.str)
}
}
obj.getStr(); // 运行者(调用者)为obj,this指向obj
var a = obj.getStr;
a();
var str = 'global';
var obj = {
str:'private',
getStr:()=>{
console.log(this.str)
}
}
obj.getStr();
// 绑定的是定义者,箭头函数的表现为getStr的value,它在obj对象中,obj的执行上下文就是 window,this.str 实际就是 window.str 输出‘global’
5、call() apply() bind() 对于this毫无影响
var n = 10;
function f(){
return this.n
}
var obj ={
n:8,
f2:f
}
var obj2 = {
n:99
}
console.log(obj.f2.call(obj2)); // 99
// 箭头函数
var n = 10;
var f=()=> this.n;
var obj ={
n:8,
f2:f
}
var obj2 = {
n:99
}
console.log(obj.f2.call(obj2)); // 10
扩展运算符(spread)是三个点(…)。
它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
该运算符主要用于函数调用。
数组的合并
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
let arr3 = [...arr1,...arr2]
// 或者直接
arr1.push(...arr2);
参数
function add(x, y) {
return x + y;
}
const numbers = [4, 5];
add(...numbers)
扩展运算符与正常的函数参数可以结合使用,非常灵活
function f(...items) {
console.log(items);
console.log(...items);
}
const args = [0, 1];
f(-1, ...args, 2, ...[3]);
扩展运算符可以与解构赋值结合起来,用于生成数组
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
const [...butLast, last] = [1, 2, 3, 4, 5]; // 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 报错
在字符串中的应用
扩展运算符还可以将字符串转为真正的数
// ES5
var s1 = "hello";
console.log(s1.split(''));
// ES6的用法
[...'hello']
对象中的运用
let obj1 = {id:1};
let obj2 = {name:2};
let obj3 = {...obj1,...obj2}
// 对象中合并函数
let fun = () => 123;
let obj4 = {...obj3,fun};
parseInt() // 函数可解析一个字符串,并返回一个整数
parseFloat() // 函数可解析一个字符串,并返回一个浮点数
Number.isInteger() // 用来判断一个数值是否为整数
Math.ceil() // 返回大于或等于一个给定数字的最小整数
Math.floor() // 返回小于或等于一个给定数字的最大整数
Math.round() // 返回一个数字四舍五入后最接近的整数
Math.trunc() // 用于去除一个数的小数部分,返回整数部分
Math.sign() // 方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值
Array.of()
定义:用于将一组值,转换为数组
Array.of(); // []
Array.of(1); // [1]
Array.of(1,2,3); //[1, 2, 3]
Array.of(4,5).length; //2
copyWithin()
定义:将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组(会修改当前数组)
接受三个参数copyWithin(target, start , end)
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
[1, 2, 3, 4, 5].copyWithin(0, 3) ; // [4, 5, 3, 4, 5]
// 将从3号位直到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]
// 将从3号位开始4号位结束的成员,复制到从0号位开始的位置,结果覆盖了原来的1。
[1, 2, 3, 4, 5, 6, 7].copyWithin(0, 3, 5) // [4, 5, 3, 4, 5, 6, 7]
// 将从3号位开始5号位结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。
[1, 2, 3, 4, 5].copyWithin(0, -2, -1) // [4, 2, 3, 4, 5]
// -2相当于3号位,-1相当于4号位,代码等同于[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
find()
find()用于找出第一个符合条件的数组成员。它的参数是一个回调函数,,所有数组成员依次执行该回调
函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回
undefined。
find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组
var arr = [3,4,7,9];
var a = arr.find(function(item,index,arr){
// 用于找出第一个符合条件的数组成员
return item >5
})
console.log(a);
// 另一种写法
[1, 11, 12, 10].find(n => n > 5) // 11
findIndex()
findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不
符合条件,则返回-1
var arr = [3,4,7,9];
var a = arr.find(function(item,index,arr){
// 返回第一个符合条件的数组成员的位置
return item >5
})
console.log(a); //2
fill()
定义:用于将一个固定值替换数组的元素。
array.fill(value, start, end)
value(必需):填充的值。
start(可选):开始填充位置。
end(可选):停止填充位置 (默认为 array.length)。
var arr = ["blue", "Orange", "red", "green"];
arr.fill("abc", 2, 4); // ["blue", "Orange", "abc", "abc"]
includes()
定义:方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
var arr = ["blue", "Orange", "red", "green"];
arr.includes('blue'); //true
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
flat()
定义:用于将嵌套的数组“拉平”,变成一维的数组。
[1, 2, [3, 4, 6],[7, 8]].flat(); // [1, 2, 3, 4, 6, 7, 8]
该方法返回一个新数组,对原数据没有影响。
var arr = [1, 2, [3, 4, 6],[7, 8]];
var a = arr.flat();
console.log(arr); // [1, 2, [3, 4, 6],[7, 8]];
console.log(a); // [1, 2, 3, 4, 6, 7, 8]
默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。
flat()的参数为2,表示要“拉平”两层的嵌套数组
[1, 2, [3, [4, 6]],[7, 8]].flat(2); // [1, 2, 3, 4, 6, 7, 8]
[1, 2, [3, [4, [6]]],[7, 8]].flat(2); // [1, 2, 3, 4, [6], 7, 8]
如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。
[1, 2, [3, [4, [6]]],[7, 8]].flat(Infinity); // [1, 2, 3, 4, 6, 7, 8]
属性名表达式
JavaScript 定义对象的属性,有两种方法。
var obj = {};
obj.id = 1;
obj['name'] = 'abc';
obj['get'+'name'] = 'xyz';
// 另一种写法
var a = 'name';
let obj2 = { [a]: 'abc', ['get' + a]: 'xyz' };
链判断运算符
编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如要读取message.body.user.firstName,安全的写法是写成下面这样。
// 错误的写法
const firstName = message.body.user.firstName;
// 正确的写法
const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default';
这样的层层判断非常麻烦,引入了“链判断运算符” ?.,简化上面的写法。
const firstName = message?.body?.user?.firstName || 'default';
?.运算符常见形式,以及不使用该运算符时的等价形式。
a?.b // 等同于
a == null ? undefined : a.b a?.[x] // 等同于
a == null ? undefined : a[x] a?.b() // 等同于
a == null ? undefined : a.b() a?.() // 等同于
a == null ? undefined : a()
set
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成 Set 数据结构。
const set = new Set();
set.add(1);
set.add(2);
set.add(1).add(2).add(3).add(3);
另一种方式
const s2 = new Set([1,2,4,2,4,23,4,'2',2,3,5])
Set 实例的属性和方法
1、add(value) 添加某个值,返回 Set 结构本身。
const set = new Set();
set.add(1).add(2).add(3).add(3);
2、delete(value) 删除某个值,返回一个布尔值,表示删除是否成功。
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.delete(2)
3、clear() 清除所有成员,没有返回值。
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.clear()
4、has(value); 返回一个布尔值,表示该值是否为 Set 的成员。
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.has(3);
5、size属性:返回 set结构的成员总数
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.size; //4
与数组结构类型转换
const set = new Set([1, 2, 3, 4, 5]);
let arr = Array.from(set);
// 扩展运算符
let arr2 = [...new Set(set)];
遍历方法
const set = new Set([2,3,52,3,4,43,3]);
for(let v of set){
console.log(v)
}
set.forEach((v,i)=>{
console.log(v)
});
map
JavaScript 的对象(Object),本质上是键值对的集合,但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
const m = new Map();
m.set('name','value').set('age',18).set('a','a');
var a = {id:2};
m.set(a,'value');
Map实例的属性和方法
1、size属性
返回 Map 结构的成员总数
const m = new Map();
m.set('a','a').set('b','b');
m.size //2
2、set(key, value)
const m = new Map();
m.set('qq', '2429462491') // 键是字符串
m.set(100, 'jindu') // 键是数值
m.set(undefined, 'value') // 键是undefined
3、get(key) 读取 key 对应的键值,如果找不到 key ,返回 undefined
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.get(a); // 'value' 通过变量a来获取
m.get('name'); //jindu 通过'name'属性获取
4、has(key) 返回一个布尔值,表示某个键是否在当前 Map 对象之中
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.has('name') //true
m.has('a') //false
m.has(a) //true
5、delete(key) 删除某个键,返回 true 。如果删除失败,返回 false
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.delete('name')
m.has('name') //false
6、clear() 清除所有成员,没有返回值
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.clear();
Map遍历方法
const m1 = new Map([['a',1],['b',2],['c',3]]);
for(let k of m1.keys()){
console.log(k) // a,b,c
}
for(let v of m1.values()){
console.log(v) // 1,2,3
}
for(let [k,v] of m1){
console.log(k,v)
};
与其它结构类型转换
const m1 = new Map([['a',1],['b',2],['c',3]]);
1、map类型转为数组
[...m1.keys()];
[...m1.values()];
[...m1];
2、map类型转为object
var o = {};
for (let [k,v] of m1){
o[k]=v;
};
3、object转为map
let obj = {"name":"jindu", "qq":"2429462491"};
const m1 = new Map(Object.entries(obj));
// 另一种方式
const m2 = new Map();
for(let k in obj){
m2.set(k,obj[k])
}
for…of循环
ES6 借鉴 C++、Java、C# 和 Python 语言,引入了 for…of 循环,作为遍历所有数据结构的统一的方法。
for…of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v);
}
for…of循环可以代替数组实例的forEach方法
arr.forEach(function (item,index) {
console.log(item); // red green blue
console.log(index); // 0 1 2
});
set中的运用
var set = new Set([1,2,3,4,5]);
for(let v of set){
console.log(v) //1,2,3,4,5
};
map中的运用
var m = new Map();
m.set('name','value').set('age',18).set('a','a');
for(let [k,v] of m){
console.log(k) // name,age,a
console.log(v) //value,18,'a'
};
对象中的运用
对于普通的对象,for…of结构不能直接使用,会报错
var obj = {
id:1,
name:'abc'
};
for (let k in obj) {
console.log(k);
console.log(obj[k]);
}
for (let k of obj) {
console.log(k);
}
// TypeError: es6[Symbol.iterator] is not a function
// 解决的办法
for (let [k,v] of Object.entries(obj)) {
console.log(k); // 获取键
console.log(v); // 获取值
}
与其它遍历的比较
1、最原始的写法就是for循环
var arr = [1,2,3,4,5];
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
2、forEach方法
这种写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能奏效。
arr.forEach(function (item,index) {
console.log(item);
});
3、for…in
for (let i in arr) {
console.log(i); // 0 1 2 3 获取索引
console.log(arr[i]) // 'a', 'b', 'c', 'd' 获取键名
}
4、for…of
for (let v of arr) {
if(v == 3){
break; //用于跳出循环,会继续执行该循环之后的代码(如果有的话)
//continue;
// 如果指定了条件,中断当前的迭代,然后继续循环中的下一个迭代。
}
console.log(v);
}
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值
解决问题:如果添加的属性名出现重复,如何保证属性名的唯一性,解决属性名的冲突
凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突
var obj = {id:1,id:3,name:'abc',name:'jindu'};
console.log(obj) // 获取结果:{id: 3, name: "jindu"}
声明的方式
let s = Symbol();
独一无二
let s1 = Symbol();
let s2 = Symbol();
console.log(s1==s2) // false
查看类型
let s = Symbol();
typeof s; // "symbol"
Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述
主要是为了在控制台显示,或者转为字符串时,比较容易区分
let s1 = Symbol('abc');
let s2 = Symbol('xyz'); // 注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
let s3 = Symbol('jindu');
let s4 = Symbol('jindu'); s3 === s4 // false
类型转换
// 1、转成布尔值
let s = Symbol();
Boolean(s) //true
// 2、不能转成数值
Number(s) //Cannot convert a Symbol value to a number
获取描述信息
let s = Symbol('jindu');
s.description // jindu
实际的运用
注意 symbol作为对象属性名,不能用点运算符
var id = Symbol();
var o = {
[id] :'hello world',
id:'abc'
}
o.id // 'abc'
o['id'] // 'abc'
o[id] // 'hello world'
注意:果用symbol作用属性名后,Object.keys()、 for in 不能输出symbol类型的属性名
Object.keys(o) // ["id"]
for(let v in o){
console.log(v) //id
}
Object.getOwnPropertyNames(o) // ["id"]
Symbol.for()
由于symbol()每次调用都会返回一个不同的值,希望重新使用同一个 Symbol 值, Symbol.for() 方法可以做到这一点
let s1 = Symbol.for('jindu');
let s2 = Symbol.for('jindu'); s1 === s2 // true
s1和s2都是 Symbol 值,但是它们都是由同样参数的 Symbol.for 方法生成的,所以实际上是同一个值。
Symbol.for()与Symbol()的区别:
Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for(“cat”)30 次,每次都会返回同一个 Symbol 值,但是调用Symbol(“cat”)`30 次,会返回 30 个不同的 Symbol 值。
var o = {};
var a = Symbol('a');
o[a] = 123;
var a = Symbol('a');
o[a] = 456;
console.log(o);
// 返回的结果:{Symbol(a): 123, Symbol(a): 456}
var o = {};
var a = Symbol.for('a');
o[a] = 123;
var a = Symbol.for('a');
o[a] = 456;
console.log(o);
// 返回的结果:{Symbol(a): 456}
Symbol.keyFor()
定义:返回一个已登记的 Symbol 类型值的key
var s = Symbol.for('abc');
Symbol.keyFor(s); //abc
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
所谓 Promise ,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
解决 JS 中多个异步回调难以维护和控制的问题
回调陷阱
$.ajax({
url:'',
data:{},
success:function(data){
//......
$.ajax({
url:'',
data:{},
success:function(data){
//......
$.ajax({
url:'',
data:{},
success:function(data){
//......
}
});
}
});
}
});
Promise 对象有以下两个特点:
(1)对象的状态不受外界影响。 Promise 对象代表一个异步操作,有三种状态: pending (进行中)、 fulfilled (已成功)和 rejected (已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是“承 诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。 Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected 。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
基本用法
Promise 对象是一个构造函数,用来生成 Promise 实例。
var p = new Promise(function(resolve,reject){
if(true){
//请求成功
resolve('ok')
} else {
reject('error')
}
});
p.then(function(res){
console.log(res)
},function(res){
console.log(res)
})
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject 。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve 函数的作用是,将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; reject 函数的作用是,将 Promise 对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise 实例生成以后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数。
then 方法可以接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 resolved 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受 Promise 对象传出的值作为参数。
解决回调陷阱
let p = new Promise(function(resolve, reject){
if (true){
resolve("成功");
} else {
reject("失败");
}
})
p.then(function(v){
return new Promise(function(resolve, reject){
if (true){
resolve("成功");
} else {
reject("失败");
}
})
}, function(v){
console.log(v)
}).then(function(){
console.log(4)
}, function(){
console.log(5)
})
用Promise对象实现的 Ajax
const getJSON = function(url,type, data) {
const promise = new Promise(function(resolve, reject){
const xmlHttp = new XMLHttpRequest();
xmlHttp.open(type, url);
if(type =='get'){
xmlHttp.send();
} else {
xmlHttp.setRequestHeader("Content-Type", "application/json");
xmlHttp.send(JSON.stringify(data));
};
xmlHttp.responseType = "json";
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState !== 4)
return;
if (xmlHttp.status === 200) {
resolve(xmlHttp.response);
} else {
reject(new Error(xmlHttp.statusText));
}
};
});
return promise;
};
Promise.all()
Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
Promise.all() 方法接受一个数组作为参数, p1 、 p2 、 p3 都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all() 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是Promise 实例。
p 的状态由 p1 、 p2 、 p3 决定,分成两种情况。
(1)只有 p1 、 p2 、 p3 的状态都变成 fulfilled , p 的状态才会变成 fulfilled ,此时 p1 、 p2 、 p3 的返回值组成一个数组,传递给 p 的回调函数。
(2)只要 p1 、 p2 、 p3 之中有一个被 rejected , p 的状态就变成 rejected ,此时第一个被 reject
的实例的返回值,会传递给 p 的回调函数。
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('1111')
},1000)
});
var p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('22222')
},4000)
});
Promise.all([p1,p2]).then(([d1,d2])=>{
console.log(d1,d2);
},err=>{
});
Promise.race()
const p = Promise.race([p1, p2, p3]);
只要 p1 、 p2 、 p3 之中有一个实例率先改变状态, p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。
Promise.race() 方法的参数与 Promise.all() 方法一样,如果不是 Promise 实例,就会先调用下面讲到的 Promise.resolve() 方法,将参数转为 Promise 实例,再进一步处理。
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('1111')
},1000)
});
var p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('22222')
},4000)
});
Promise.race([p1,p2]).then(d =>{
console.log(d);
},err=>{
});
async await优于promise的几个特点。async-await是promise和generator的语法糖。只是为了让我们书写代码时更加流畅,当然也增强了代码的可读性。
简单来说:async-await 是建立在 promise机制之上的,并不能取代其地位。
具体理解:
async
async function timeout() {
return 'hello world'
}
timeout(); // 为什么没有执行'hello world'
async function timeout() {
return 'hello world'
}
console.log(timeout()); // Promise {: "hello world"}
async 函数返回的是一个promise 对象,如果要获取到promise 返回值,应该用then 方法
async function fn(){
return 100;
}
console.log(fn()); //Promise {: 100}
let f = fn();
f.then(function(d){
console.log(d) //100
})
await
await操作符用于等待一个Promise对象,它只能在异步函数async function内部使用。
返回值:
返回promise对象的处理结果,如果待等的不是promise对象,则返回该值本身,如果一个promise被传递给一个await操作符,await将等待promise正常处理完成并返回其处理结果
function f2(){
console.log(4)
}
async function f(){
await f2();
console.log(2)
}
f();
console.log(3);
正常情况下,await命令后面是一个promise对象,它也可以是其它值,如字符串,布尔值,数值以及普通函数。
console.log(2)
async function fn(){
console.log(3)
await 100;
console.log(1)
}
fn()
console.log(4)
await命令后面是一个promise对象
function p(){
return new Promise(resolve=>{
resolve();
console.log(1)
});
};
async function fn(){
console.log(2)
await p();
console.log(3)
}
fn()
微任务与宏任务的区别
就像去银行办业务一样,先要取号进行排号。
一般上边都会印着类似:“您的号码为XX,前边还有XX人。”之类的字样。
由于柜员只能处理一个来办理业务的客户,这时每一个来办理业务的人就可以认为是银行柜员的一个宏任务来存在的;
当柜员处理完当前客户的问题以后,选择接待下一位,广播报号,也就是下一个宏任务的开始。
所以多个宏任务合在一起就可以认为有一个任务队列在这,里边是当前银行中所有排号的客户。
如果叫到你的时候你不在,那么你当前的号牌就作废了,柜员会选择直接跳过进行下一个客户的业务处理,等你回来以后还需要重新取号而且一个宏任务在执行的过程中,是可以添加一些微任务的,就像在柜台办理业务,你前边的一位老大爷可能在存款,结果在存款这个业务办理完以后,柜员会问老大爷还有没有其他需要办理的业务,这时老大爷想了一下:“想选择稳一些的理财”,这时候柜员肯定不能告诉老大爷说:“您再上后边取个号去,重新排队”。
所以本来快轮到你来办理业务,会因为老大爷临时添加的“理财业务”而往后推。
也许老大爷在办完理财以后还想 再办一个信用卡?或者 再买点儿纪念币?
无论是什么需求,只要是柜员能够帮她办理的,都会在处理你的业务之前来做这些事情,这些都可以认为是微任务。
在当前的微任务没有执行完成时,是不会执行下一个宏任务的。
promise是同步还是异步?
Promise是ES6提出的解决异步编程导致陷入回调地狱问题的,那么Promise是同步的还是异步的?可以
确定的是,Promise本身是同步的
console.log(1)
let a = new Promise((res,rej) => {
console.log(2);
});
console.log(3);
let b = new Promise((res,rej) => {
console.log(4);
});
console.log(5);
// 拿到的结果是 1 2 3 4 5
Promise本身是同步的,他的then方法和catch方法是异步的
let a = new Promise((res, rej) => {
// 同步
console.log(1);
res(3)
});
a.then((res) => {
// 微任务
console.log(res);
});
console.log(2); // 同步
// 拿到的结果是 1 2 3
扩展实例
console.log(1)
let a = new Promise((res,rej) => {
res();
console.log(2);
});
a.then(() => {
console.log(3)
})
console.log(4);
let b = new Promise((res,rej) => {
res();
console.log(5);
});
b.then(() => {
console.log(6)
})
console.log(7);
proxy 在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截
var proxy = new Proxy(target, handler);
基本用法
new Proxy() 表示生成一个 Proxy 实例, target 参数表示所要拦截的目标对象, handler 参数也是一个对象,用来定制拦截行为
var target = {
name: 'jindu'
};
var handler = {
get: function(target, key) {
console.log(`${key} 被读取`);
return target[key];
},
set: function(target, key, value) {
console.log(`${key} 被设置为 ${value}`);
target[key] = value;
}
}
var a = new Proxy(target, handler);
a.name; // 控制台输出:name 被读取
a.name = 'abc'; // 控制台输出:name 被设置为 abc
console.log(target.name); // 控制台输出: abc
a读取属性的值时,实际上执行的是 handler.get:在控制台输出信息,并且读取被代理对象 target 的属性。
在 a 设置属性值时,实际上执行的是 handler.set:在控制台输出信息,并且设置被代理对象 target的属性的值
Proxy的作用
对于代理模式 Proxy 的作用主要体现在三个方面
Proxy所能代理的范围–handler
实际上 handler 本身就是 ES6 所新设计的一个对象,它的作用就是用来自定义代理对象的各种可代理操作。它本身一共有 13 中方法,每种方法都可以代理一种操作,其 13 种方法如下
get(target, propKey, receiver):
拦截对象属性的读取,比如proxy.foo和proxy['foo']。
set(target, propKey, value, receiver):
拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
has(target, propKey):
拦截propKey in proxy的操作,返回一个布尔值。
deleteProperty(target, propKey):
拦截delete proxy[propKey]的操作,返回一个布尔值。
ownKeys(target):
拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、 Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys()的返回结果仅包括目标对象自身的可遍历属性。 getOwnPropertyDescriptor(target, propKey):
拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
defineProperty(target, propKey, propDesc):
拦截Object.defineProperty(proxy, propKey, propDesc)、 Object.defineProperties(proxy, propDescs),返回一个布尔值。 preventExtensions(target):
拦截Object.preventExtensions(proxy),返回一个布尔值。
getPrototypeOf(target):
拦截Object.getPrototypeOf(proxy),返回一个对象。
isExtensible(target):
拦截Object.isExtensible(proxy),返回一个布尔值。
setPrototypeOf(target, proto):
拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种 额外操作可以拦截。
apply(target, object, args):
拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、 proxy.apply(...)。
construct(target, args):
拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
Proxy场景
1)实现私有变量,想获取angelababy,由于添加了拦截获取的只能是18
var target = {
name: 'angelababy',
_age: 30
}
var handler = {
get: function(target,key){
if(key.startsWith('_')){
console.log('私有变量age不能被访问')
return 18
}
return target[key];
},
set: function(target, key, value) {
if(key.startsWith('_')){
console.log('私有变量age不能被修改')
return 18
}
target[key] = value;
}
}
var a = new Proxy(target, handler);
a._age // 18 私有变量age不能被访问
a._age = 20 // 私有变量age不能被修改
2)抽离校验模块
var num = {
count: 0,
id: 1234,
total: 14
};
var handler = {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("只能输入数字");
}
return Reflect.set(target, key, value, proxy);
}
};
var p = new Proxy(num, handler);
// 抛出错误,因为 "123" 不是数值型
p.count = "123";
// 赋值成功
p.count = 333;
Object.defineProperty()与Proxy的异同
Object.defineProperty()
优点:可以更好的拦截
缺点:无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应(虽说对常用
的方法进行了处理,但然存在局限性);只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍
历。
proxy
优点:可以劫持整个对象,并返回一个新对象;有13种劫持操作
缺点:兼容性不好
var Obj = {}
Object.defineProperty(Obj, 'a', {
get: function () {
console.log('get');
return v
},
set: function (val) {
console.log('set');
v = val
}
});
Obj.a = [] // set
Obj.a.push('1') // get
Obj.a[0] = 1 // get
Obj.a.pop(1) // get
Obj.a = [1, 2, 3] //
var arr = [];
var p = new Proxy(arr, {
get: (target, key) => {
console.log('get')
return key in target ? target[key] : undefined
},
set: (target, key, value) => {
console.log('set')
target[key] = value return true
}
})
p.push(1); get // 获取数组arr的push方法
get // 获取数组arr的length属性
set // 设置arr[0] = 1
set // 设置数组arr长度为1
class的基本用法
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到
ES5中定义类
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return this.x
};
var p = new Point(2,4);
ES6
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return this.x;
}
}
var p = new Point(3,5);
上面代码定义了一个“类”,可以看到里面有一个 constructor 方法,这就是构造方法,而 this 关键字则代表实例对象。也就是说,ES5 的构造函数 Point ,对应 ES6 的 Point 类的构造方法。
Point 类除了构造方法,还定义了一个 toString 方法。注意,定义“类”的方法的时候,前面不需要加上 function 这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
ES6 的类,完全可以看作构造函数的另一种写法
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
constructor方法
constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法。一个类必须有
constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加。
class Point { }
// 等同于
class Point {
constructor() {}
}
类的实例
生成类的实例的写法,与 ES5 完全一样,也是使用 new 命令。
class Point {
// ...
}
// 报错
var point = Point(2, 3);
// 正确
var point = new Point(2, 3);
class的继承
Class 可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
class Point {
constructor() {
this.x = 'x';
this.y = 'y';
}
toString() {
return this.x;
}
}
class ColorPoint extends Point {
constructor(){
super();
}
}
var c = new ColorPoint();
c.x //'x'
c.toString() //'x'
上面代码定义了一个 ColorPoint 类,该类通过 extends 关键字,继承了 Point 类的所有属性和方法constructor 方法和 toString 方法之中,都出现了 super 关键字,它在这里表示父类的构造函数,用来新建父类的 this 对象子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类自己的 this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。
super关键字
super 这个关键字,既可以当作函数使用,也可以当作对象使用
super 作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次 super 函数。
class A {} class B extends A {
constructor() {
super();
}
}
// 子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错
注意, super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B 的实例,因此 super() 在这里相当于 A.prototype.constructor.call(this) 。 super 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();