事件委托(事件代理)
事件委托也可以叫事件代理,是事件冒泡与事件捕获的运用。
- 基本概念
一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时(事件捕获),会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
- 动态绑定事件
在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;
如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;
所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。
<ul id="list">
<li className="class-1">item 1</li>
<li>item 2</li>
<li className="class-1">item 3</li>
......
<li>item n</li>
</ul>
document.getElementById('list').addEventListener('click', function (e) {
// 兼容性处理
var event = e || window.event;
var target = event.target || event.srcElement;
if (target.matches('li.class-1')) {
console.log('the content is: ', target.innerHTML);
}
});
自定义事件
可以代码触发事件执行,同时可以配合代理等功能实现修改数据界面自动更新
- Event()——Event对象的构造函数
<script type="text/javascript">
/* 创建一个事件对象,名字为newEvent,类型为build */
var newEvent = new Event('build', { bubbles:true,cancelable:true,composed:true });
/* 给这个事件对象创建一个属性并赋值 */
newEvent.name = "新的事件!";
/* 将自定义事件绑定在document对象上,这里绑定的事件要和我们创建的事件类型相同,不然无法触发 */
document.addEventListener("build",function(){
alert("你触发了自定义事件!" + newEvent.name);
//true - 事件句柄在捕获阶段执行
// false- false- 默认。事件句柄在冒泡阶段执行
},false)
/* 触发自定义事件 */
document.dispatchEvent(newEvent);
</script>
- event = new Event(typeArg, eventInit);
- typeArg:指定事件类型,传递一个字符串。这里的事件类型指的是像点击事件(click)、提交事件(submit)、加载事件(load)等等。
- eventInit:可选,传递EventInit类型的字典。实际上这个EventInit类型的字典也就是我们使用InitEvent()时需要传递的参数,以键值对的形式传递,不过它可以多选一个参数:
- bubbles:事件是否支持冒泡,传递一个boolean类型的参数,默认值为false。
- cancelable:是否可取消事件的默认行为,传递一个boolean类型的参数,默认值为false。
- composed:事件是否会触发shadow DOM(阴影DOM)根节点之外的事件监听器,传递一个boolean类型的参数,默认值为false。
- CustomEvent()——CustomEvent对象的构造函数
CustomEvent()可以像Event()那样赋值,但它可以在Web Workers中使用(与主线程分离的另一个线程),可以传递跟事件关联的相关值(detail)。
<script type="text/javascript">
/* 创建一个事件对象,名字为newEvent,类型为build */
var newEvent = new CustomEvent('build', { bubbles:true,cancelable:true,composed:true });
/* 给这个事件对象创建一个属性并赋值,这里绑定的事件要和我们创建的事件类型相同,不然无法触发 */
newEvent.name = "新的事件!";
/* 将自定义事件绑定在document对象上 */
document.addEventListener("build",function(){
alert("你触发了使用CustomEvent创建的自定义事件!" + newEvent.name);
},false)
/* 触发自定义事件 */
document.dispatchEvent(newEvent);
</script>
- event = new CustomEvent(typeArg, customEventInit);
- typeArg:指定事件类型,传递一个字符串。这里的事件类型指的是像点击事件(click)、提交事件(submit)、加载事件(load)等等。
- customEventInit:可选。传递一个CustomEventInit字典。实际上这个字典就是我们使用initCustomEvent()时需要的参数,这个参数就是事件相关值(detail):
- detail:可选,默认值为null,类型为any(也就是说可以传递任意类型的参数)。这个值就是和事件相关联的值。
- CustomEventInit字典也可以接受EventInit字典的参数,
<script type="text/javascript">
/* 创建一个事件对象,名字为newEvent,类型为build */
var newEvent = new CustomEvent('build',{
detail: {
dog:"wo",cat:"mio"
}
});
/* 将自定义事件绑定在document对象上 */
document.addEventListener("build",function(){
alert(" event.detail.dog:" + event.detail.dog
+ "\n event.detail.cat:" + event.detail.cat );
},false)
/* 触发自定义事件 */
document.dispatchEvent(newEvent);
</script>
WebComponents
基本使用(复制阮一峰博客)
<user-card></user-card>
这种自定义的 HTML 标签,称为自定义元素(custom element)。根据规范,自定义元素的名称必须包含连词线,用与区别原生的 HTML元素。所以,<user-card>不能写成<usercard>。
class UserCard extends HTMLElement {
constructor() {
super();
}
}
上面代码中,UserCard就是自定义元素的类。注意,这个类的父类是HTMLElement,因此继承了 HTML 元素的特性。接着,使用浏览器原生的customElements.define()方法,告诉浏览器<user-card>元素与这个类关联。
window.customElements.define('user-card', UserCard);
- 自定义元素的内容
自定义元素<user-card>目前还是空的,下面在类里面给出这个元素的内容。
class UserCard extends HTMLElement {
constructor() {
super();
var image = document.createElement('img');
image.src = 'https://semantic-ui.com/images/avatar2/large/kristy.png';
image.classList.add('image');
var container = document.createElement('div');
container.classList.add('container');
var name = document.createElement('p');
name.classList.add('name');
name.innerText = 'User Name';
var email = document.createElement('p');
email.classList.add('email');
email.innerText = 'yourmail@some-email.com';
var button = document.createElement('button');
button.classList.add('button');
button.innerText = 'Follow';
container.append(name, email, button);
this.append(image, container);
}
}
- <template>标签
使用 JavaScript 写上一节的 DOM 结构很麻烦,Web Components API提供了<template>标签,可以在它里面使用 HTML 定义 DOM。
<template id="userCardTemplate">
<img src="https://semantic-ui.com/images/avatar2/large/kristy.png" class="image">
<div class="container">
<p class="name">User Name</p>
<p class="email">yourmail@some-email.com</p>
<button class="button">Follow</button>
</div>
</template>
然后,改写一下自定义元素的类,为自定义元素加载<template>。
class UserCard extends HTMLElement {
constructor() {
super();
var templateElem = document.getElementById('userCardTemplate');
//true深度克隆
var content = templateElem.content.cloneNode(true);
this.appendChild(content);
}
}
上面代码中,获取<template>节点以后,克隆了它的所有子元素,这是因为可能有多个自定义元素的实例,这个模板还要留给其他实例使用,所以不能直接移动它的子元素。
- 完整代码如下
<body>
<user-card></user-card>
<template>...</template>
<script>
class UserCard extends HTMLElement {
constructor() {
super();
var templateElem = document.getElementById('userCardTemplate');
var content = templateElem.content.cloneNode(true);
this.appendChild(content);
}
}
window.customElements.define('user-card', UserCard);
</script>
</body>
- 添加样式
自定义元素还没有样式,可以给它指定全局样式,比如下面这样。
user-card {
/* ... */
}
但是,组件的样式应该与代码封装在一起,只对自定义元素生效,不影响外部的全局样式。所以,可以把样式写在<template>里面。
<template id="userCardTemplate">
<style>
:host {
display: flex;
align-items: center;
width: 450px;
height: 180px;
background-color: #d4d4d4;
border: 1px solid #d5d5d5;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
border-radius: 3px;
overflow: hidden;
padding: 10px;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
.image {
flex: 0 0 auto;
width: 160px;
height: 160px;
vertical-align: middle;
border-radius: 5px;
}
.container {
box-sizing: border-box;
padding: 20px;
height: 160px;
}
.container > .name {
font-size: 20px;
font-weight: 600;
line-height: 1;
margin: 0;
margin-bottom: 5px;
}
.container > .email {
font-size: 12px;
opacity: 0.75;
line-height: 1;
margin: 0;
margin-bottom: 15px;
}
.container > .button {
padding: 10px 25px;
font-size: 12px;
border-radius: 5px;
text-transform: uppercase;
}
</style>
<img src="https://semantic-ui.com/images/avatar2/large/kristy.png" class="image">
<div class="container">
<p class="name">User Name</p>
<p class="email">yourmail@some-email.com</p>
<button class="button">Follow</button>
</div>
</template>
上面代码中,<template>样式里面的:host伪类,指代自定义元素本身。
- 自定义元素的参数
<user-card>
内容现在是在<template>里面设定的,为了方便使用,把它改成参数。
<user-card
image="https://semantic-ui.com/images/avatar2/large/kristy.png"
name="User Name"
email="yourmail@some-email.com"
></user-card>
<template>
代码也相应改造
<template id="userCardTemplate">
<style>...</style>
<img class="image">
<div class="container">
<p class="name"></p>
<p class="email"></p>
<button class="button">Follow John</button>
</div>
</template>
最后,改一下类的代码,把参数加到自定义元素里面。
class UserCard extends HTMLElement {
constructor() {
super();
var templateElem = document.getElementById('userCardTemplate');
var content = templateElem.content.cloneNode(true);
content.querySelector('img').setAttribute('src', this.getAttribute('image'));
content.querySelector('.container>.name').innerText = this.getAttribute('name');
content.querySelector('.container>.email').innerText = this.getAttribute('email');
this.appendChild(content);
}
}
window.customElements.define('user-card', UserCard);
- Shadow DOM
如果不希望用户能够看到<user-card>的内部代码,Web Component 允许内部代码隐藏起来,这叫做 Shadow DOM,即这部分 DOM 默认与外部 DOM隔离,内部任何代码都无法影响外部。
自定义元素的this.attachShadow()方法开启 Shadow DOM
class UserCard extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow( { mode: 'closed' } );
var templateElem = document.getElementById('userCardTemplate');
var content = templateElem.content.cloneNode(true);
content.querySelector('img').setAttribute('src', this.getAttribute('image'));
content.querySelector('.container>.name').innerText = this.getAttribute('name');
content.querySelector('.container>.email').innerText = this.getAttribute('email');
shadow.appendChild(content);
}
}
window.customElements.define('user-card', UserCard);
this.attachShadow()方法的参数{ mode: 'closed' },表示 Shadow DOM 是封闭的,不允许外部访问
- 组件的扩展
用户卡片是一个静态组件,如果要与用户互动,也很简单,就是在类里面监听各种事件。
this.$button = shadow.querySelector('button');
this.$button.addEventListener('click', () => {
// do something
});
上面的例子中,<template>与网页代码放在一起,其实可以用脚本把<template>注入网页。这样的话,JavaScript 脚本跟<template>就能封装成一个 JS 文件,成为独立的组件文件。网页只要加载这个脚本,就能使用<user-card>组件。例如:模板字符串通过HTTP传递,然后动态插入。
扩展原生组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!-- 两秒之后添加图片 -->
<!-- 注意此处的is是关联下面扩展类的关键 -->
<img is="my-img" src="" alt="">
<script>
class myIMG extends HTMLImageElement {
constructor() {
super();
setTimeout(() => {
this.src =
'https://hbimg.huabanimg.com/67bcf019c3aba12abc19624e1afe3418a03fb2021cf00-iDEjac_fw658/format/webp';
}, 2000);
}
}
customElements.define('my-img', myIMG, {
extends: 'img'
});
</script>
</body>
</html>
实际案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<my-com data-test='aosd' title='这里是自定义的标题' content='这里是我们自定义的内容'>
<span>asdaoj</span>
</my-com>
<script src='./dialog.js' type='module'></script>
<script type='module'>
let domcom = document.querySelector('my-com');
//相当于覆盖了dialog.js中的test函数
// domcom.__proto__['test'] = function () {
// console.log('123');
// }
</script>
</body>
</html>
下面是通过函数名,动态注册函数到webcomponents实例的工具类,拿来使用即可
export default class popEvent {
constructor(option) {
/*
* 接收四个参数:
* 1,对象的this
* 2,要监听的元素, 不传则为对象this
* 3,要监听的事件,默认监听点击事件
* 4,是否冒泡, 默认冒泡
* */
this.eventObj = option.obj;
this.target = option.target || this.eventObj;
this.eventType = option.eventType || 'click';
this.popup = option.popup || true;
this.bindEvent();
}
bindEvent() {
let _this = this;
_this.target.addEventListener(_this.eventType, function (ev) {
let target = ev.target;
let dataset, parent, num, b;
popup(target);
function popup(obj) {
if (obj === document) {
return false;
}
dataset = obj.dataset;
num = Object.keys(dataset).length;
parent = obj.parentNode;
if (num < 1) {
popup(parent);
num = 0;
} else {
for (b in dataset) {
if (_this.eventObj.__proto__[b]) {
_this.eventObj.__proto__[b].call(_this.eventObj, {
obj: obj,
ev: ev,
target: dataset[b],
data: _this.eventObj
});
}
}
_this.popup && popup(parent);
}
}
})
}
}
Webcomponents实体类逻辑函数
import popEvent from './event.js';
let temp = document.createElement('template');
document.body.appendChild(temp);
// 此处是把模板用作字符串处理,实际上也可以放在html页面中(template中不会显示在页面上),或者js脚本字符串形式导入,或者http传递
temp.innerHTML = `
<div class="mask" data-hide='true'></div>
<div class="dialog_box bg2">
<div class="dialog_header"></div>
<div class="dialog_title">{{ title}}</div>
<div class="dialog_content">{{content}}</div>
<div class="btn_line">
<div class="btn cancel" data-hide='true' data-cancel='123'>知道了</div>
<div class="btn confirm" data-confirm='javascript'>马上来抢</div>
</div>
</div>`;
export default class myCom extends HTMLElement {
constructor() {
super();
console.log(this.innerHTML);
let attr = this.attributes;
this._data = {
title: attr.title ? attr.title.value : '默认的标题',
content: attr.content ? attr.content.value : '这里是默认的内容,这个人很懒,什么有意义的内容都没有留下',
}
this.render();
this.bindEvent();
this.compileNode(this.obj);
this.observe(this._data);
setTimeout(() => {
this._data.title = '这里是修改的标题'
}, 2000)
}
//响应式,这样注册之后,可以直接修改数据就可以变化显示内容
observe(data) {
let _this = this;
this._data = new Proxy(data, {
set(obj, prop, value) {
let event = new CustomEvent(prop, {
detail: value
});
_this.dispatchEvent(event);
return Reflect.set(...arguments);
}
});
}
//拦截事件,防止传递到下一个Element,直接return false即可;
// if (btn) {
// btn.bind('click',function() {
// return false
// })
// }
render() {
this.btn = document.createElement('button');
this.btn.innerText = '点击显示弹窗';
this.btn.setAttribute('data-open', 'true');
this.obj = document.createElement('div');
//深度克隆
this.obj.append(temp.content.cloneNode(true));
this.append(this.obj, this.btn);
this.hide();
}
compileNode(el) {
let child = el.childNodes;
[...child].forEach((node) => {
if (node.nodeType === 3) {
let text = node.textContent;
//正则表达式用于替换内容{{}}
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
if (reg.test(text)) {
let $1 = RegExp.$1;
this._data[$1] && (node.textContent = text.replace(reg, this._data[$1]));
this.addEventListener($1, (e) => {
node.textContent = text.replace(reg, e.detail)
})
};
} else if (node.nodeType === 1) {
let attrs = node.attributes;
if (attrs.hasOwnProperty('v-model')) {
let keyname = attrs['v-model'].nodeValue;
node.value = this._data[keyname];
node.addEventListener('input', e => {
this._data[keyname] = node.value;
});
// console.log(this._data);
// console.log(attrs['v-model'].nodeValue);
}
if (node.childNodes.length > 0) {
this.compileNode(node);
}
}
})
}
bindEvent() {
this.event = new popEvent({
obj: this
});
}
open() {
this.obj.style.display = 'block';
}
hide() {
console.log('点击了mask');
this.obj.style.display = 'none';
}
cancel() {
console.log('点击了取消');
}
confirm() {
console.log('点击了确定');
}
test() {
console.log('触发了弹窗的点击事件');
}
}
window.customElements.define('my-com', myCom);
let style = document.createElement('style');
document.body.appendChild(style);
style.innerText = `
body {
margin: 0;
}
.dialog_box {
width: 521px;
height: 563px;
border-radius: 8px;
box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, 0.4);
background-color: #fff;
position: absolute;
padding: 0 40px;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
.dialog_box.bg1 {
background-image: url("img/bg_03.png");
background-repeat: no-repeat;
background-size: 100%;
}
.dialog_box.bg2 {
background-image: url("img/bg_06.png");
background-repeat: no-repeat;
background-size: 100%;
}
.dialog_header {
height: 215px;
}
.dialog_title {
height: 57px;
font-size: 30px;
text-align: center;
line-height: 57px;
}
.dialog_content {
height: 130px;
text-align: center;
font-size: 24px;
line-height: 29px;
padding: 30px 52px 0;
}
.btn_line {
height: 110px;
display: flex;
justify-content: center;
}
.btn {
width: 221px;
height: 74px;
border-radius: 4px;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3);
font-size: 30px;
line-height: 74px;
text-align: center;
cursor: pointer;
margin: 0 20px;
}
.btn.cancel {
background: #e7e7e7;
}
.btn.confirm {
background: #e5322e;
color: #fff;
}
.mask {
background: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}`
前端大数据量插入,不卡顿方案
渲染大数据时,合理使用 createDocumentFragment
和 requestAnimationFrame
,将操作切分为一小段一小段执行。
createDOcumentFragment
1 . createDOcumentFragment()方法,是用来创建一个虚拟的节点对象,或者说,是用来创建文档碎片节点。它可以包含各种类型的节点,在创建之初是空的。
2 . DocumentFragment 节点不属于文档树,继承的 ParentNode 属性总是 null 。它有一个很实用的特点,当请求把一个DocumentFragment节点插入文档树时,插入的不是DocumentFragment自身,而是它的所有子孙节点,即插入的是括号里的节点。这个特性使得DocumentFragment成了占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作。
另外当需要添加多个dom元素时,如果先将这些元素添加到DocumentFragment 中,再统一将DocumentFragment添加到页面,会减少页面渲染dom的次数,效率会明显提升。
requestAnimationFrame
requestAnimationFrame 比起 setTimeout 、 setlnterval 的优势主要有两点:
1 、 requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
2 、在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意昧着更少的的 cpu , gpu 和内存使用量。
总结来说:通过requestAnimationFrame可以在每一帧渲染得时候执行插入操作,这样可以避免卡顿,不会因为递归导致光执行插入,而卡顿(连页面都滚动不了)。
而createDOcumentFragment其实就相当于虚拟节点,被插入到它内部得节点,在没有被其他dom使用之前,可以理解成不涉及渲染消耗,只是纯数据。这两者结合不是可以插入N条数据还不卡顿,而是实现插入得过程不卡顿。因为不论再怎么优化,只要数据足够大,内存依然会爆炸
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul></ul>
</body>
</html>
<script>
const total = 100000;
//每次插入20条
const once = 20;
//需要插入的次数
const loopCount = Math.ceil(total / once);
//渲染的次数
let countRender = 0;
const ul = document.querySelector('ul');
//添加数据的方法
function add() {
//创建虚拟节点
const fragment = document.createDocumentFragment();
for (let i = 0; i < once; i++) {
const li=document.createElement('li');
li.innerHTML=Math.floor(Math.random()*100000);
fragment.appendChild(li);
}
ul.appendChild(fragment);
countRender++;
loop();
}
function loop() {
if (countRender<loopCount) {
window.requestAnimationFrame(add)
}
}
loop();
</script>
迭代
- 迭代和遍历的区别
迭代:1. 有序的 2. 连续的
- 为什么Object不能遍历?
下面开始针对这开始说明
<script>
var arr = [1, 2, 3, 4];
var str = '123';
var obj = { a: 1, b: 2 };
console.log(arr);//原型上面有Symbol(Symbol.iterator)
//es6出的获取字符串原型,es5时候还是str__proto__
console.log(Object.getPrototypeOf(str));//原型上面有Symbol(Symbol.iterator)
console.log(obj);//没有Symbol(Symbol.iterator)
//其实就相当于arr上面有Symbol.iterator属性然后是一个函数,可以执行
var inter = arr[Symbol.iterator]();
console.log(inter);//图1
//可知有next函数
console.log(inter.next());//{value: 1, done: false}
console.log(inter.next());
console.log(inter.next());
console.log(inter.next());// {value: 4, done: false}
console.log(inter.next()); //{value: undefined, done: true}
function makeInterator(arr) {
let index = 0;
return {
next() {
if (index < arr.length) {
return {
value: arr[index++], done: false
}
}
return {
value: undefined, done: true
}
}
}
}
var interObj = makeInterator(arr);
console.log(interObj.next());
console.log(interObj.next());
console.log(interObj.next());
console.log(interObj.next());
console.log(interObj.next());
//可以发现上面和默认实现是等效的
//推理可知,实际上只要替object实现迭代器即可即Symbol(Symbol.iterator)
var obj1 = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator]() {
let index = 0;
let map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
return {
next() {
let mapEntries = [...map.entries()];
if (index < map.size) {
return {
value: mapEntries[index++], done: false
}
}
return { value: undefined, done: true }
}
}
}
}
let iter1 = obj1[Symbol.iterator]();
console.log(iter1.next());
console.log(iter1.next());
console.log(iter1.next());
console.log(iter1.next());
for (const iterator of obj1) {
console.log(iterator);//["a",1],["a",2],["a",3]
}
//所以只要有迭代器对象的属性都可以使用for of
//例如:Array Map Set String TypeArray arguments NodeList
//生成器
function* test() {
yield 1
yield 2
yield 3
yield 4
}
let iter2 = test();
// console.log(iter2.next());
// console.log(iter2.next());
// console.log(iter2.next());
// console.log(iter2.next());
for (const iterator of iter2) {
console.log(111,iterator);// 1 2 3 4
}
</script>
- 生成器相关
<script>
//生成器
function* test() {
console.log(1);
yield 1
console.log(2);
yield 2
yield 3
yield 4
}
//返回的是迭代器对象
let iter2 = test();//此时 console.log(1);是不会执行的,只有第一次next()才会执行
console.log(iter2.next());
console.log(iter2.next());//此处调用的时候才会 console.log(2); 也就是说yield实际上有中断的效果
console.log(iter2.next());
console.log(iter2.next());
// for (const iterator of iter2) {
// console.log(111, iterator);// 1 2 3 4
// }
//yield参数和返回值
// 重点:
对于`var foo = yield expression`
// 语句,yield 左侧变量 foo 的值将在下一次调用 next() 方法时获得,并且等于调用时 next() 方法的参数。
</script>
<script>
var obj1 = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator]: function* () {
let index = 0;
let map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
let mapEntries = [...map.entries()];
while (index < map.size) {
yield mapEntries[index++]
}
}
}
for (const i of obj1) {
console.log(i);//["a",1],["a",2],["a",3]
}
</script>
Promise
Promise的状态
- pending - 进行中
- fulfilled - 成功
- rejected - 失败
Promise的特性
- promise状态不受外界影响
- promise固化性:一旦promise状态变化后就不可在更改了,例如:只能从pending->fulfilled或者pending->rejected
案例一:固化
const fs = require('fs');
function readFile(pathname) {
return new Promise((resolve, reject) => {
console.log(1);
false.readFile(pathname, 'utf-8', (err, data) => {
if (err) {
reject(err);
return;
} else {
resolve(data);
}
})
})
}
let promise=readFile('./1.html');
promise.then(function(data) {
console.log(1,data);
promise.then(function(data) {
console.log(2,data);
})
},function(err) {
console.log("失败回调",err);
})
//resovle:then不论是嵌套几层,都是可以立刻拿到data的
//而reject返回也一样,不过都是resovle只能成功回调接收
//而reject返回结果只能err回调接收
//这就是promise的固化
执行流程案例一
<script>
let promise=new Promise(function(resolve,reject) {
console.log('promise');
resolve('resolve');
})
setTimeout(() => {
console.log('timeout');
}, 0);
promise.then(function(data) {
console.log(data);
})
console.log('hi');
/*
* 执行流程:
promise :宏任务
hi 宏任务
resolve 微任务
timeout 异步队列/回调队列
* /
</script>
Promise静态方法
<script>
//promise静态方法
let p1 = Promise.resolve(1);
let p2 = Promise.reject(2);
p1.then(function (data) {
console.log(data);
})
p2.then(function() {
},function (err) {
console.log('error'+err);
})
//优雅方式书写2
p2.catch(function (err) {
console.log('err:' + err);
})
</script>
- thenable对象
// thenable对象:
let obj = {
then(resolve, reject) {
// resolve(11)
reject(22)
}
}
//Promise.resolve 传递进去对象就会被自动调用obj内部的then
//如果then内部是reject就是失败回调,resolve就会走成功回调
let p1 = Promise.resolve(obj);
p1.then(function (data) {
console.log(data);
}, function (err) {
console.log('err' + err); //err22
});
//但是reject时候不会调用then函数
let p11 = Promise.reject(obj);
p11.then(function (data) {
console.log(11, data);
}, function (err) {
console.log('err11', err);//err11 {then;f} //注意此处是对象是特别的
});
执行流程案例二
Promise.resolve().then(function () {
console.log('promise1');
setTimeout(() => {
console.log('timeout2');
}, 0);
})
setTimeout(() => {
console.log('timeout1');
Promise.resolve().then(function () {
console.log('promise2');
})
}, 0);
//下面的一个setTimeout是先注册的,Promise.resolve().then也注册到任务队列
//然后执行then之后其内部的settimeout才注册上
//答案: promise1 timeout1 promise2 timeout1
//其他静态方法:
// Promise.all:let p1=Promise.all([p2,p3,p4])
// Promise.race: let p1=Promise.race([p2,p3,p4])
//all:全部成功才成功,否则就是失败
//race:谁最快返回谁,不论是成功还是失败
Promise链式调用
<script>
//Promise的链式调用
//1. 普通值| promise
let p1 = new Promise((resolve, reject) => {
resolve(1);
});
//then链式调用的时候,到底出发后一个then的成功还是失败
//通过前一个then的返回值确定,如果没有明确指定返回值
//例如:只是一个输出,则默认包装成Promise.resolve(underfined)
//如果返回的是一个普通值也会例如包装成Promise.resolve(1)
// p1.then(res=>res+1)
// .then(res=>res+1)
// .then(res=>{console.log(res+1);}) //输出4
p1.then(res => new Promise((resolve, reject) => {
reject(2);
}))
//案例一
// .then(function(res) {
// },function(err) {
// //因为then第一个直接返回的promise而且内部是rejct
// //所以触发这里的输出,而不是成功输出
// console.log('err:'+2);
// })
//案例二:
//如果有reject或者异常发生然后紧接着的thne没有错误
//捕获,则会类似冒泡一样找下一个then中的异常捕获
//而且如果只是为了捕获异常则成功回调可以设置成null
//而且这种写法等效于catch
.then(function () {
})
.then(null, function (err) {
console.log(err);
})
// .catch(err=>{
// console.log(err);
// })
//区别
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 1000);
})
p2.then(res => { console.log(res + 1); return res + 1 })//11
p2.then(res => { console.log(res + 1); return res + 1 })//11
p2.then(res => { console.log(res + 1); return res + 1 })//11
p2.then(res => res + 1)//11
.then(res => res + 1)//12
.then(res => res + 1)//13
//Promise的状态依赖:该promise存在状态依赖则自身状态会无效,根据被依赖的promise来确定
const p1=new Promise((resolve,reject)=>{
setTimeout(() => {
reject('10000')
}, 3000);
})
const p2=new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(p1);//该resolve会失效转而由p1的状态决定
}, 1000);
})
p2.then(res=>console.log(res))
.catch(err=>console.log(err)) //此处会输出10000
</script>
生成器以及co模块原理和演化过程
//自定义实现promisify
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, data) => {
if (err) {
reject(err);
return
}
resolve(data);
})
})
}
};
let readFile = promisify(fs.readFile);
//方式一
// readFile('./name.txt')
// .then(res=>readFile(res))
// .then(res=>readFile(res))
// .then(res=>console.log(res))
//本意就是:文件连续读取,然后把文件内部文件名传递下去一层层读
//最后打印最后一个文件的内容
//因为then内部返回会被自动包装成Promise所以这就避免了回调地狱
//方式二:演进
// 生成器和执行器
function* read() {
let v1 = yield readFile('./name.txt', 'utf-8');
let v2 = yield readFile(v1, 'utf-8');
let v3 = yield readFile(v2, 'utf-8');
console.log(v3);
}
//执行器逻辑
// let iter = read();
// let {value,done}=iter.next();
// value.then(v1=>{
// let {value,done}=iter.next(v1);
// value.then(v2=>{
// let {value,done}=iter.next(v2);
// value.then(v3=>{
// iter.next(v3);//这个值就返回给了上面的console.log(v3);
// })
// })
// })
//封装的写法:演进
function co(iter) {
return new Promise((resolve, reject) => {
let next = function (data) {
let { value, done } = iter.next(data);
if (done) {
resolve(data);
} else {
value.then(val => {
next(val);
},reject);
}
}
next();
})
}
let promise=co(read());
promise.then(res=>{
console.log(res);
})
//最终方案:async await其实就是上面co的进阶版
async function readSync() {
let v1 = await readFile('./name.txt', 'utf-8');
let v2 = await readFile(v1, 'utf-8');
let v3 = await readFile(v2, 'utf-8');
return v3;
}
let pawait=readSync();
pawait.then(res=>{
console.log(res);
})