0%

ES6新特性

ES6新特性🥳🥳

let,const新的声明方式🧐🧐

Let

  1. 变量不能重复声明
  2. 块级作用域
  3. 不存在变量提升
  4. 不影响作用域链
  5. 暂时性死区(只要块级作用域内存在let命令,这个区域就不再受外部影响)

for中的var/let

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var arr = [];
for (var i = 0; i < 3; i++) {
arr[i] = function() {
console.log('@' + i);
}
}
arr[0](); //3
arr[1](); //3
arr[2](); //3

for (var i = 0; i < 3; i++) {
arr[i]();
} // 0,1,2
for (let i = 0; i < 3; i++) {
arr[i]();
} // 3 3 3
  • 第一个for循环中,向arr中添加三个函数,但是函数中的i是并不会赋值的,正常人的思维习惯会误以为,函数中的i也会被赋予值,但实际上并没有。

  • 当在外部调用数组中三个函数的时候,每个函数创建不同的AO对象,但是其寻找的i值是全局下的i,就是GO中的i。此时GO中的i已经被上文中的for循环结束导致i赋值成为3,所以执行结果为3,3,3

  • 第二个for循环中的var对i进行重新赋值导致i又变回0,当执行arr[0]()这个函数的时候,他会前往声明函数的所在域,此时函数寻找全局GO中的i,而i变成了0。依次执行下去。所以结果为0,1,2

  • 第三个for循环用let声明,创建了一个一个的块,但是!常规思路会导致误认为调用函数的时候,里面的i也是块里面的i值,会认为i为0,1,2;而函数调用的时候会有一个特殊性,其在被调用的时候会返回函数声明的地方,它的i值查找的就不是for循环let创建的一个一个块里面的i,而是去查找声明函数所在域下的i值。上文中的函数声明是在全局下,它的i值被第二个函数定格在了3,所以函数输出为全3

1
2
3
4
5
6
7
8
9
function fn() {
console.log(a);
}
var a = 10;

{
let a = 99;
fn(); //10
}

const

  1. 一定要赋初始值
  2. 一般常量是用大写(潜规则)
  3. 常量的值不能修改
  4. 块级作用域
  5. 对于数组和对象的元素修改,不算是对常量的修改,不会报错

变量的解构赋值

ES6 允许按照一定模式从数组和对象中提取值,对变量进行赋值这被称为结构赋值

数组的结构:

1
2
3
4
5
6
7
//1.数组的结构
const f4 = ['小沈阳', '刘能', '赵四', '宋小宝'];
let [xiao, liu, zhao, song] = f4;
console.log(xiao);
console.log(liu);
console.log(zhao);
console.log(song);

对象的结构:

1
2
3
4
5
6
7
8
9
let {
name,
age,
xiaopin
} = zhao;
console.log(name);
console.log(age);
console.log(xiaopin);
xiaopin();

相当于 zhao.xiaopin(); 替换成了 xiaopin( );不用重复书写zhao.

模板字符串

不同于es5中的字符串用+拼接,es6的模板字符串可以使用``符号和${变量名}进行拼接

1
2
3
4
5
6
7
8
9
10
let str = `<ul>
<li>沈腾</li>
<li>玛丽</li>
<li>魏翔</li>
</ul>`;

// 3.变量拼接
let lovest = '魏翔';
let out = `将${lovest}替换成lovest`;
console.log(out);

对象的简化写法

ES6 允许在大括号内直接写入变量和函数 作为对象的属性和方法,这样的书写更加简洁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let name = '尚硅谷';
let change = function() {
console.log('我可以改变你');
};
const school = {
name,
change,
//旧的写法:
// import: function() {
// console.log('声明方法');
// }

// es6写法:
import () {
console.log('声明方法');
}
}
console.log(school);

箭头函数以及声明特点🏹🏹

es6 允许使用[箭头] (=>) 定义函数.

声明一个函数:

1
2
3
4
5
6
let fn = (a, b) => {
return a + b;
};
//调用函数
let reslut = fn(1, 2);
console.log(reslut); //3
  1. this是静态的,this始终是指向函数声明时所在作用域下的this的值(其父级作用域的this)
  2. 不能做为构造实例化对象 就是无法构造函数
  3. 不能使用arguments变量

箭头函数的简写

(1)省略小括号,当形参有且只有一个时候

1
2
3
4
let add = n => {
return n + n;
};
console.log(add(1)); //2

(2)省略花括号,当代码只有一条语句时候,此时return必须省略

而且语句的执行结果就是函数的返回值

1
2
let pow = n => n * n;
console.log(pow(9)); //81

箭头函数的this指向问题

this是静态的,this始终是指向函数声明时所在作用域下的this的值(其父级作用域的this)

何为父级作用域的this,一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
setTimeout(() => {
console.log('id', this.id);
}, 500);
// setTimeout(function() {
// console.log('id', this.id);
// }, 500)
};
var id = 10;
foo.call({
id: 50
});
  • 箭头函数的结果是50

  • 普通函数的结果是10

foo.call( )方法改变了foo函数的this指向,定时器中的函数this指向的是window,所以普通函数的this.id为window下的10。而箭头函数因为foo函数的this改变使得其this也改变成了对象**{id:50}**

函数参数的默认值和rest参数

函数参数的默认值

ES6 允许给函数参数赋值初始值

  1. 形参初始值 具有默认值的参数 一般位置要靠后(潜规则)
1
2
3
4
5
6
function add(a, b, c = 2) { //此处给形参设置默认值
return a + b + c;
};
let result = add(1, 2);
console.log(result);
//当没有第三个实参传入时,可以在形参中设置默认值,否则就是NaN
  1. 与结构赋值结合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function connect({
host,
username,
password,
port
}) {
console.log(host);
console.log(username);
console.log(password);
console.log(port);
}
connect({
host: '',
username: 'root',
password: 'root',
port: 3306,
});

rest参数

es6 引入 rest 参数 用于获取函数的实参 用来替代arguments

es5获取函数实参的方法是 arguments

1
2
3
4
5
// rest 参数
function date(...args) {
console.log(args); //filter some every map方法
};
date('黑', '白', '灰'); //数组

rest参数 必须 放在参数末尾

1
2
3
4
5
6
7
//rest参数 必须 放在参数最后
function fn(a, b, ...args) {
console.log(a);
console.log(b);
console.log(args);
};
fn(1, 2, 3, 4, 5, 6);

扩展运算符

[…] 扩展运算符能将[数组]转换为逗号分割的[参数序列]

1
2
3
4
// 声明一个数组 ... 
const colors = ['黑', '白', '灰'];
console.log(colors); // ['黑', '白', '灰']
console.log(...colors); // 黑 白 灰

扩展运算符的运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div></div>
<div></div>
<div></div>
<script>
//1. 数组的合并 黑 白 灰
const anSe = ['黑', '白', '灰'];
const liangSe = ['红', '黄', '绿'];
//老方法 使用concat拼接数组
// const heBing = anSe.concat(liangSe);
const heBing = [...anSe, ...liangSe];
console.log(heBing);

//2.数组的克隆
const q1 = ['E', 'G', 'M'];
const q2 = [...q1];
console.log(q2);

//3.将伪数组转换为真正的数组
const divs = document.querySelectorAll('div');
console.log(divs); //此时还为伪数组
const divArr = [...divs]; //转换
console.log(divArr);
</script>

Symbol的介绍与创建

创建Symbol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//创建Symbol
let s = Symbol();
console.log(s, typeof s);
let s2 = Symbol('尚硅谷');
let s3 = Symbol('尚硅谷');
console.log(s2 === s3); //false

//Symbol.for 创建 这个Symbol是一个函数对象
let s4 = Symbol.for('尚硅谷');
let s5 = Symbol.for('尚硅谷');
console.log(s4 === s5); //true
//不能与其他数据进行运算
// undefined
// srting symbol
// object
// null number
// boolean

Symbol创建对象属性

第一种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 向对象中添加方法 up down 属性name
let game = {};
let methods = {
name: Symbol('name'),
up: Symbol('up'),
down: Symbol('down'),
};
game[methods.up] = function() {
console.log('我可以改变形状');
};
game[methods.down] = function() {
console.log('我可以快速下降');
};
game[methods.name] = 'A神';
console.log(game[methods.name]);
game[methods.up]();
game[methods.down]();

第二种:

1
2
3
4
5
6
7
8
9
10
11
12
13
let youxi = {
[Symbol.for('name')]: '狼人杀',
[Symbol.for('say')]: function() {
console.log('我可以发言');
},
[Symbol.for('zibao')]: function() {
console.log('我可以自爆');
},
};
console.log(youxi[Symbol.for('name')]);
//Symbol.for('say')产生的标识是唯一的,所以可以调用定义在youxi里面的[Symbol.for('say')]方法
youxi[Symbol.for('say')]();
youxi[Symbol.for('zibao')]();

Symbol内置的一些属性

Symbol.hasInstance方法

静态成员

  • 当此类被当作instanceof后值作为参数时触发
  • 传递的param为被检测的对象
1
2
3
4
5
6
7
8
9
10
class Person {
static[Symbol.hasInstance](param) {
console.log(param);
console.log('我被用来检测类型了');
}
};
let o = {
a: '1'
};
console.log(o instanceof Person);

instanceof更详细的解释:

用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上

  • instanceof可以对不同的对象实例进行判断,判断方法是根据对象的原型链依次向下查询,如果obj2的原型属性存在obj1的原型链上,obj1 instanceof obj2值为true。
  • obj1是否是obj2的实例

Symbol.isConcatSpreadable方法

  • arr2[Symbol.isConcatSpreadable] = false;
  • arr2[Symbol.isConcatSpreadable] = true;
  • 为false表示不展开合并
1
2
3
4
5
6
7
   const arr = [1, 2, 3];
const arr2 = [4, 5, 6];
arr2[Symbol.isConcatSpreadable] = false;
// arr2[Symbol.isConcatSpreadable] = true;
// 为false表示不展开合并
console.log(arr.concat(arr2)); //[1, 2, 3, Array(3)]
//console.log(arr.concat(arr2)); //[1,2,3,4,5,6] 为true时

Set集合和Map对象

set集合

没有重复值的一堆数据

即便是传入的参数有重复值他也会自动将重复值削减为1个

1
2
3
4
5
6
// 声明一个set
let s = new Set();
// 可以传入初始参数
let s2 = new Set(['红', '黑', '白', '灰', '红']);
console.log(s2 instanceof Object); //true
console.log(s2); //Set(4) {'红', '黑', '白', '灰'}

内置的方法:

  • 获取元素个数 .size

    • console.log(s2.size); // 4
  • 添加元素 .add

    • s2.add('蓝');
    • console.log(s2); //Set(5) {'红', '黑', '白', '灰', '蓝'}
  • 删除元素 .delete

    • s2.delete('黑');
    • console.log(s2); //Set(4) {'红', '白', '灰', '蓝'}
  • 检测元素是否存在 .has

    • console.log(s2.has('橙')); //false
  • 清空集合 .clear

    • s2.clear();
    • console.log(s2); //Set(0) {size: 0}

集合实践:

let arr = [1, 2, 3, 4, 4, 5, 5, 6, 6, 9, 8, 7];

  1. 数组去重

    •         let result = [...new Set(arr)];
              console.log(result);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      2. 交集 `let arr2 = [2, 4, 5, 9, 2, 8, 9];`

      - ```js
      let result = [...new Set(arr)].filter(item => {
      let s2 = new Set(arr2);
      //将s2去重
      //利用has方法检测arr中的在s2中是否也存在
      if (s2.has(item)) {
      return true;
      } else {
      return false;
      }
      });
    • ```js
      // 简化写法:
      let result = […new Set(arr)].filter(item =>
      new Set(arr2).has(item)
      );
      console.log(result);

      1
      2
      3
      4
      5
      6

      3. 并集

      - ```js
      let union = [...new Set([...arr, ...arr2])];
      console.log(union);
  2. 差集

    • ```js
      let result = […new Set(arr)].filter(item =>
      !(new Set(arr2).has(item))
      );
      console.log(result);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43

      ### map对象

      map其实就是升级版对象,可以自定义key值和属性值

      - 声明map`let m = new Map();`
      - .set方法添加元素`("key值",“属性值”)`
      - .size获取长度`.size`
      - .delete删除对应属性`.delete('属性')`
      - .get获取方法`.get('change')`
      - .clear清空`.clear()`

      ```js
      // 声明 Map
      // 就是升级版对象 可以自定义key值和属性值
      let m = new Map();
      // .set方法添加元素 ('key值','属性值')
      m.set('name', '123');
      m.set('change', function() {
      console.log('456');
      });
      console.log(m);

      var abc = {
      name: '789'
      };
      m.set(abc, [7, 8, 9]);
      console.log(m);

      // .size方法获取长度
      console.log(m.size);

      // 删除方法
      m.delete('name');
      console.log(m);

      // 获取方法
      console.log(m.get('change'));
      console.log(m.get(abc)); //[7,8,9]

      // 清空
      // m.clear();
      // console.log(m);

Generator 生成器函数

Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

生成器其实就是一个特殊的函数 必须要有 * 分割

  • yield 相当于函数代码的分隔符

  • 必须调用next方法才会执行

  • 借助 iterator.next( ) 才可以执行函数内代码

1
2
3
4
5
6
7
8
9
10
function* gen() {
console.log('hello generator');
yield '一直没有耳朵';
console.log(222);
yield '一直没有尾巴';
console.log(333);
yield '真奇怪';
console.log(444);
}
let iterator = gen();
1
2
3
 console.log(iterator.next());
// hello generator
// {value: '一直没有耳朵', done: false}
1
2
3
4
5
6
console.log(iterator.next());
console.log(iterator.next());
// hello generator
// {value: '一直没有耳朵', done: false}
// 222
// {value: '一直没有尾巴', done: false}
1
2
3
4
5
6
7
8
9
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// hello generator
// {value: '一直没有耳朵', done: false}
// 222
// {value: '一直没有尾巴', done: false}
// 333
// {value: '真奇怪', done: false}
1
2
3
4
5
6
7
8
9
10
11
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// hello generator
// {value: '一直没有耳朵', done: false}
// 222
// {value: '一直没有尾巴', done: false}
// 333
// {value: '真奇怪', done: false}
// 444
// {value: undefined, done: true}

可以看出iterator.next()的值是yield后面的值,并且是以对象形式返回。而没next()触发一次,都会执行一次yield和yield之间的代码块

next()、throw()、return() 的共同点

next()throw()return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。

next()函数参数:

next方法可以传入实参 相当于将BBB赋值给yield 111;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* gen(arg) {
console.log(arg);
let one = yield 111;
console.log(one);
let two = yield 222;
console.log(two);
let three = yield 333;
console.log(three);
}

//执行获取迭代器对象
let iterator = gen('AAA');
console.log(iterator.next());
// next方法可以传入实参 相当于将BBB赋值给yield 111;
console.log(iterator.next('BBB'));
console.log(iterator.next('CCC'));
console.log(iterator.next('DDD'));

throw()将yield表达式替换成throw语句抛出错误

throw()是将yield表达式替换成一个throw语句。

生成器函数解决回调地狱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 异步编程
// 1s后控制台输出111 2s后输出222 3s后输出333
// 回调地狱演示
// setTimeout(() => {
// console.log(111);
// setTimeout(() => {
// console.log(222);
// setTimeout(() => {
// console.log(333);
// }, 3000);
// }, 2000);
// }, 1000);

// 生成器函数制作
function one() {
setTimeout(() => {
console.log(111);
iterator.next();
}, 1000);
};

function two() {
setTimeout(() => {
console.log(222);
iterator.next();
}, 2000);
};

function three() {
setTimeout(() => {
console.log(333);
iterator.next();
}, 3000);
};

function* gen() {
yield one();
yield two();
yield three();
};
//调用生成器函数
let iterator = gen();
iterator.next();

Promise对象

  • Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
  • Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。
  • Promise的状态有 初始化(pending) 成功(fulfilled) 失败(rejected)

单纯的new Promise或者有变量接收他,也会自执行。Promise中传入一个函数,函数的两个参数分别是成功执行形参和失败执行形参

  • resolve(date);成功执行函数
  • reject(err);失败执行函数
  • 另外:如果两个函数都存在则谁在上边就先执行谁,前提是调用过then方法并传参
1
2
3
4
5
6
7
8
9
const p = new Promise(function(resolve, reject) {
// 封装异步操作
setTimeout(function() {
let date = '数据库中用户数数据';
resolve(date);
// let err = '数据调用失败';
// reject(err);
}, 1000);
});

Promise内置的方法

Promise.all()

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

传入的p1,p2,p3都是Promise实例,只有三个状态都变成已成功,才会执行promiseAll的已成功

但是当有一个失败,就会触发catch错误执行

1
2
3
4
5
6
7
8
var promiseAll = Promise.all([p1, p2, p3]);
promiseAll.then((data) => {
console.log(data);
console.log('圆满结束');
}).catch((error) => {
console.error('失败请求');
console.log(error);
});

image-20220728171606233

Promise.race()

原型上的方法

then方法

承接上文中的resolve(date)reject(err),then中可以传入两个参数(函数),第一个函数为成功执行所触发的函数resolve(date),第二个函数为失败执行所触发的reject(err)

1
2
3
4
5
6
7
p.then(function(value) {
//成功执行
console.log(value);
}, function(reason) {
//失败执行
console.error(reason);
});

catch方法

catch方法用于捕获失败的单独方法,then方法可以传入两个函数来捕获成功和失败,而catch是单独的一个专门捕获失败的方法

1
2
3
4
5
6
7
8
9
10
11
12
const p = new Promise((resolve, reject) => {
setTimeout(() => {
// 设置p对象的状态为失败,并设置失败的值
// reject("出错了!");
// resolve("出错了!");
reject("出错了!");
}, 1000);
});
// catch方法用于捕获失败的单独方法
p.catch(function(reason) {
console.error(reason);
})

finally方法

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

例题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
console.log("start");
setTimeout(() => {
console.log("children2")
Promise.resolve().then(() => {
console.log("children3")
})
}, 0)

new Promise(function(resolve, reject) {
console.log("children4")
setTimeout(function() {
console.log("children5")
resolve("children6")
}, 0)
}).then(res => {
console.log("children7")
setTimeout(() => {
console.log(res)
}, 0)
});
// start
// children4
// children2
// children3
// children5
// children7
// children6
  • 先将同步任务start执行
  • settimeout会被放到消息队列,也可以称它为宏任务
  • new Promise的机制是当new时就立即执行,所以输出children4,而后面的settimeout会再次放到消息队列进行排队
  • 此时检查执行栈中有无同步任务,微任务队列有无任务,前往消息队列依次执行。
  • 队列中第一个settimeout输出children2,而下面的Promise属于成功执行立即调用后面的.then,输出children3
  • 执行第二个settimeout中的children5,下面的resolve(“children6”)被调用,紧跟后面的.then中的任务被放到微任务队列,而此时消息队列已经为空。
  • 执行微任务队列中的then,输出children7,settimeout被放到消息队列,此时微任务队列空。
  • 最后微任务队列为空,执行消息队列中的console.log(res)输出children6

Async/Await

Async

  • async/await是写异步代码的新方式,以前的方法有回调函数Promise
  • async/await是基于Promise实现的,它不能用于普通的回调函数。
  • async/await与Promise一样,是非阻塞的。
  • async/await使得异步代码看起来像同步代码,这正是它的魔力所在。

async/await实际上是Generator的语法糖。顾名思义,async关键字代表后面的函数中有异步操作,await表示等待一个异步方法执行完成。声明异步函数只需在普通函数前面加一个关键字async即可,如:

1
async function funcA() {}

async 函数返回一个Promise对象(如果指定的返回值不是Promise对象,也返回一个Promise,只不过立即 resolve ,处理方式同 then 方法),因此 async 函数通过 return 返回的值,会成为 then 方法中回调函数的参数:

async返回的Promise函数实际上是Promise.resolve()成功回调

1
2
3
4
5
6
7
async function funcA() {
return 'hello!';
}
funcA().then(value => {
console.log(value);
})
// hello!

Await

顾名思义, await 就是异步等待,它等待的是一个Promise,因此 await 后面应该写一个Promise对象,如果不是Promise对象,那么会被转成一个立即 resolve 的Promise。 async 函数被调用后就立即执行,但是一旦遇到 await 就会先返回,等到异步操作执行完成,再接着执行函数体内后面的语句。总结一下就是:async函数调用不会造成代码的阻塞,但是await会引起async函数内部代码的阻塞。看看下面这个例子:

  1. await 必须写在async函数中
  2. await 右侧的表达式一般为promise对象
  3. await 返回的是promise成功的值
  4. await 的promise失败了,就会抛出异常,需要通过try…catch捕获处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const p = new Promise((resolve, reject) => {
resolve('成功的值!');
// reject('失败了!');
})
async function main() {
try {
let result = await p;
console.log(result);
} catch (e) {
// 捕获失败
console.log(e);
}
}
main();

例题:

01:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
async function async1() {
console.log("async1 start")
await async2();
//await async2(), 这里的代码相当于new Promise(()=>{async2()})
//而将 await 后面的全部代码放到.then()中去
console.log("async1 end")
};
async function async2() {
console.log("async2")
};
console.log("script start");
setTimeout(function() {
console.log("setTimeout")
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1")
resolve()
}).then(function() {
console.log("promise2")
});
console.log("script end");
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
  1. 定时器的事件会被放置在消息队列
  2. promise实例对象的then方法会被放置在微任务队列
  3. 等待调用栈为空时再执行微任务队列和消息队列
  4. 先执行微任务队列再执行消息队列

02:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
console.log("start");
setTimeout(() => {
console.log("children2")
Promise.resolve().then(() => {
console.log("children3")
})
}, 0)

new Promise(function(resolve, reject) {
console.log("children4")
setTimeout(function() {
console.log("children5")
resolve("children6")
}, 0)
}).then(res => {
console.log("children7")
setTimeout(() => {
console.log(res)
}, 0)
});
// start
// children4
// children2
// children3
// children5
// children7
// children6

03:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var p = new Promise(resolve => {
console.log(4);
resolve(5);
}).then(resolve => {
console.log(resolve);
});

function func1() {
console.log(1);
};

function fun2() {
setTimeout(() => {
console.log(2);
});
func1();
console.log(3);

new Promise(resolve => {
resolve();
}).then(resolve => {
console.log('新的resolve');
})

p.then(resolve => {
console.log(7);
}).then(resolve => {
console.log(6);
});
}
fun2()

// 4
// 1
// 3
// 5
// 新的resolve
// 7
// 6
// 2

04:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
async function async1() {
console.log('async1 start');
const result = await async2();
console.log(result);
// 会先执行async2函数, 然后跳出async1, 同时将返回的promise放入微队列
console.log('async1 end');
}
async function async2() {
console.log('async');
return "testAwait";
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
new Promise(function(resolve) {
console.log('promise3');
resolve();
}).then(function() {
console.log('promise4');
});
console.log('script end');

iterator迭代器(遍历器)

有iterator接口 即可实现迭代器

工作原理

  1. 创建一个指针对象,指向当前数据结构的起始位置
  2. 第一次调用对象的next方法,指针自动指向数据结构的第一个成员
  3. 接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
  4. 每调用next方法返回一个包含value和done属性的对象

注:需要自定义遍历数据的时候,要想到迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//具有iterator接口 即可实现迭代器
// 声明一个数组
const xiyou = ['唐僧', '孙悟空', '猪八戒', '沙僧'];
//使用for of遍历数组
for (let v of xiyou) {
console.log(v);
}
let iterator = xiyou[Symbol.iterator]();
// 调用对象的next方法
console.log(iterator);
console.log(iterator.next()); //唐僧
console.log(iterator.next()); //孙悟空
console.log(iterator.next()); //猪八戒
console.log(iterator.next()); //沙僧
console.log(iterator.next()); //undefined

ES6-class类

es5的构造函数创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
// es5构造函数创建对象-------------------------
// // 利用构造函数创建对象
function Phone(name, price) {
this.name = name;
this.price = price;
};
// 在原型上添加方法
Phone.prototype.call = function() {
console.log('我可以打电话!');
};
//实例化对象
let HuaWei = new Phone('华为', 1999);
HuaWei.call();

继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//手机
function Phone(brand, price) {
this.brand = brand;
this.price = price;
}
//在原型上添加call方法
Phone.prototype.call = () => {
console.log('我可以打电话');
};

//智能手机
function SmartPhone(brand, price, color, size) {
//利用call方法改变this指向
Phone.call(this, brand, price);
this.color = color;
this.size = size;
}
//设置子级构造函数的原型
SmartPhone.prototype = new Phone();
// 第二种写法
// SmartPhone.prototype = Object.create(Phone.prototype);

// 矫正
SmartPhone.prototype.constructor = SmartPhone;

//声明子类方法
SmartPhone.prototype.photo = () => {
console.log('拍照功能');
};
SmartPhone.prototype.playGame = () => {
console.log('玩游戏');
}

// 实例化一个对象
const chuizi = new SmartPhone('锤子', 1999, '黑色', '123');
console.log(chuizi);
chuizi.photo();
chuizi.call();

es6使用class类创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Phone2 {
//构造方法 名字不能修改
constructor(name, price) {
this.name = name;
this.price = price;
}
//方法必须使用该语法,不能使用ES5的对象完整形式
call() {
console.log('我可以打电话!');
}
}
let onePlus = new Phone2('一加', 1999);
onePlus.call();
console.log(onePlus);

继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//创建一个Phone类
class Phone {
//构造方法
constructor(brand, price) {
this.brand = brand;
this.price = price;
};
//父类成员属性和方法
call() {
console.log('打电话');
}
}

//创建一个子类继承Phone类
// extends 关键词
class SmartPhone extends Phone {
constructor(brand, price, color, size) {
// super方法类似于Phone.call(this,brand,price)
super(brand, price);
// super子类调用父类的构造方法
this.color = color;
this.size = size;
}
photo() {
console.log('拍照');
}
playGame() {
console.log('玩游戏');
}
call() {
super.call(); //通过super方法可以调用被继承父级的call方法
console.log('视频通话');
}
}
const xiaomi = new SmartPhone('小米', 1999, '黑色', '123');

console.log(xiaomi);

xiaomi.call();

静态成员

  • 静态成员不能被实例化的对象所调用,静态成员在谁身上,谁才可以调用

什么是静态成员,静态成员就是你所创建的构造函数不在内部添加属性或者方法,而是在外部通过调用的方式添加的属性或者方法称为静态成员,例如:

函数其实又叫函数对象,将属性或者方法打点调添加的属性或方法就叫静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Phone() {
};
// ---------------------------------
// 此处添加的属性或方法是添加在了Phone(函数对象)上
// 其实例化对象不会存在这些属性或者方法
Phone.nam = '手机';
Phone.call = () => {
console.log('我可以改变世界');
};
// 这样的属性称之为静态成员
// ---------------------------------
let pho = new Phone();
//console.log(pho.nam); //underfined
//pho.call(); //报错:call方法不是实例对象的方法,不是pho的方法
console.log(pho); //空
// 在Phone函数原型上添加方法
Phone.prototype.size = '123cm';
console.log(pho.size); //123cm
Phone.call(); //我可以改变世界
console.log(Phone.nam); //手机

class中添静态成员的方法

使用static fun(){}在这个类身上添加静态成员,实例对象无法使用

1
2
3
4
5
6
7
8
9
10
class Phone2 {
// 静态属性static标注的属性或者方法是属于类的不会属于实例化对象pho2
static nam = '手机';
static change() {
console.log('我可以改变世界');
}
}
let pho2 = new Phone2();
console.log(pho2.nam); //underfined
console.log(Phone2.nam); //手机

Proxy代理器

Proxy介绍

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

Proxy 是一个构造函数可以通过var proxy = new Proxy(target, handler);实例化对象

  • target参数表示所要拦截的目标对象
  • handler参数也是一个对象,用来定制拦截行为。
1
2
3
4
5
6
7
8
9
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

可以看到当外界想要获取target对象中的属性时,就会触发get拦截

class类中的getter和setter

与 ES5 一样, 在 Class 内部可以使用get和set关键字, 对某个属性设置存值函数和取值函数, 拦截该属性的存取行为

  • get:当外界读取触发

    • get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
  • set:当外界修改时触发

    • set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

被检测的属性不必要再放入到constructor再重新改变指向,其属性值是get 后面函数return的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//get和set
class Phone {
constructor(age) {
this.age = age;
}
get name() {
console.log('名字被读取');
return 'A神'
}
get price() {
console.log('价格属性被读取');
return '123';
//return 返回值给了s.price
}
set price(newVal) {
console.log('价格属性被修改成' + newVal);
}
}
//实例化对象
let s = new Phone('18');
console.log(s);
console.log(s.price); //价格属性被读取
// 123
s.price = 'abc';

Reflect

Reflect介绍

Reflect反射

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。

静态方法

Reflect对象一共有 13 个静态方法。

Reflect.get(target,name,receiver)

Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined

1
2
3
4
5
6
7
```

**Reflect.set(target,name,value,receiver)**

> `Reflect.set`方法设置`target`对象的`name`属性等于`value`。

```js

Reflect.deleteProperty(obj,name)

Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性。

1
2
3
4
5
6
7
const myObj = { foo: 'bar' };

// 旧写法
delete myObj.foo;

// 新写法
Reflect.deleteProperty(myObj, 'foo');

Reflect.defineProperty(target, propertyKey, attributes)

Reflect.defineProperty方法基本等同于Object.defineProperty,用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用Reflect.defineProperty代替它。

1
2
3
4
5
6
7
8
9
10
11
12
13
function MyDate() {
/*…*/
}

// 旧写法
Object.defineProperty(MyDate, 'now', {
value: () => Date.now()
});

// 新写法
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
});

如果Reflect.defineProperty的第一个参数不是对象,就会抛出错误,比如Reflect.defineProperty(1, 'foo')

Object对象方法扩展

1.Object.is 判断两个值是否完全相等

1
2
3
4
console.log(Object.is(120, 120)); //true
console.log(Object.is(120, '120')); //false
console.log(Object.is(NaN, NaN)); //true
console.log(NaN === NaN); //false

2.Object.assign对象的替换/合并

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

  • Object.assign(‘被替换对象’,’替换对象’) 对象的合并

    • Object.assign(target,…sources)

    • target:新对象,用来接受的对象

      sources:源对象(可多个)

  • 如果第一个对象有第二个没有的属性,替换之后也同样保留没有的属性

  • 将多个源对象的可枚举属性复制到自己的目标对象中,只会复制一层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const config1 = {
host: 'abcdefg',
prot: 3306,
name: 'root',
pass: 'root',
test: 'text'
};
const config2 = {
host: 'http://',
prot: 33006,
name: 'xxx.com',
pass: 'abc',
test2: 'text'
};
console.log(Object.assign(config1, config2));

3.设置获取原型对象

  • Object.setPrototypeOf 设置原型对象
    • Object.setPrototypeOf(obj, prototype)
  • Object.getPrototypeOf 获取原型对象
1
2
3
4
5
6
7
8
9
10
const school = {
name: 'abc'
};
const cities = {
shuzu: [1, 2, 3]
}
Object.setPrototypeOf(school, cities)
console.log(school); //{name: 'abc'} 此时他的原型对象已经指向了cities

console.log(Object.getPrototypeOf(school));

4.Object.keys()

ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

1
2
3
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

5.Object.defineProperty方法

参数

  1. 属性所在的对象
  2. 属性的名字(被监听的属性)
  3. 一个描述符对象

属性描述符:

  • configurable:是否可配置,取值为true时,该属性能够从对象删除,可以修改该属性的描述符
  • enumerable:是否可枚举,取值为true可枚举,可以通过for-in遍历
  • value:值,数据描述符有value,存取描述符没有value
  • witable:是否可修改,取值true,该属性取值可以修改

存取描述符:

  • get():在读取属性时调用的函数,默认值是undefined
  • set():在写入属性的时候调用的函数,默认值是undefined

模拟Vue中v-module数据双向绑定原理

通过对数据的监听,当数据改变进行拦截,触发set() 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
#myInput {
width: 400px;
height: 50px;
font-size: 40px;
color: red;
}
#contain {
margin-top: 20px;
width: 400px;
height: 200px;
border: 1px solid salmon;
}
</style>
</head>
<body>
<input id="myInput" type="text" />
<div id="contain"></div>
<script>
var text;
window.data = {};
var oIn = document.getElementById("myInput");
var oDiv = document.getElementById("contain");

oIn.addEventListener("input", function (e) {
text = e.target.value;
console.log(text);
window.data.value = text;
});
Object.defineProperty(window.data, "value", {
get() {
return "";
},
set(v) {
oDiv.innerHTML = v;
},
});
</script>
</body>
</html>

数值的扩展

1.Number.EPSILON

  • EPSILON属性的值接近于2.2204460492503130808472633361816E-16

Number.EPSILON是JavaScript 表示的最小精度

由于js的计算机制,0.1+0.2=0.30000000000000004

封装一个判断结果的函数

1
2
3
4
5
6
7
8
9
function equal(a, b) {
// Math.abs(x) 函数返回指定数字 “x“ 的绝对值
if (Math.abs(a - b) < Number.EPSILON) {
return true;
} else {
return false;
}
}
console.log(equal(0.1 + 0.2, 0.3)); //true

2.Number.isFinite

Number.isFinite 检测一个数值 是否为有限数

1
2
3
console.log(Number.isFinite(100)); //true
console.log(Number.isFinite(100 / 0)); //false
console.log(Number.isFinite(Infinity)); //false

3.Number.isNaN

Number.isNaN 检测一个数值是否为NaN

1
console.log(Number.isNaN(123)); //false

4.Number.parseInt()/Number.parseFloat()

Number.parseInt() Number.parseFloat()字符串转整数

1
2
console.log(Number.parseInt('521.145abx')); //521
console.log(Number.parseFloat('521.145abx')); //521.145

5.Number.isInteger()

Number.isInteger()判断一个属是否为整数

1
2
console.log(Number.isInteger(5)); //true
console.log(Number.isInteger(2.5)); //false

6.Math.trunc()

Math.trunc()将数字的小数部分抹掉

1
console.log(Math.trunc(2.5)); //2

7.Math.sign()

Math.sign() 判断一个数到底为正数 负数 零

1
2
3
console.log(Math.sign(100)); //1
console.log(Math.sign(-100)); //-1
console.log(Math.sign(0)); //0

模块化

暴露数据

1.分别暴露

1
2
3
4
5
6
7
// 使用export命令暴露数据
// 分别暴露
export let school = 'abc';

export function call() {
console.log('123456789');
}

2.统一暴露

1
2
3
4
5
6
7
//统一暴露
let school = 'def';

function call() {
console.log('987654321');
}
export { school, call };

3.默认暴露

1
2
3
4
5
6
7
8
//默认暴露
export default {
school: '默认暴露',
change() {
console.log('默认暴露');
}
}
//此时如果要调用需要加.default见下文默认暴露的接收

引入数据

1.通用的引入方式

通用引入是对导入的文件进行赋值,当你需要调用的时候直接通过js1.调用你想要的属性或者方法

1
2
3
4
5
6
7
8
9
<!-- // 1.通用的引入方法--------------------------------- -->
<script type="module">
import * as js1 from "./36-js模块.js";
console.log(js1.school);
console.log(js1);
import * as js2 from "./36-js模块2.js";
js2.call();
console.log(js2);
</script>

2.解构赋值形式导入或重命名

对接收的数据进行重命名,这时候你只需要使用你as后的名字即可使用导入的变量或方法

1
2
3
4
5
6
7
8
9
<script type="module">
import {school,call} from "./36-js模块.js";
import {school as school2,call as call2} from "./36-js模块2.js";
//利用as为重复的数据重命名
console.log(school);
console.log(school2);
console.log(call);
console.log(call2);
</script>

3.默认暴露的接收

也可以不用对其重命名,那这样的话就相当于是第四个方法接收

1
2
3
4
5
6
7
8
9
<script type="module">
// 为默认暴露,所以调用需要在进入default层级
// js2.default.change();
// 默认暴露解构赋值条件
import {default as def} from "./36-js模块3.js";
// 如果是默认暴露,则需要对default进行as别命名
console.log(def.school);
console.log(def.change);
</script>

4.简便形式接收

简便形式 只针对默认暴露

1
2
3
4
<script>  
import m3 from "./36-js模块3.js";
console.log(m3);
</script>