前端知识二

事件委托(事件代理)

事件委托也可以叫事件代理,是事件冒泡与事件捕获的运用。

  • 基本概念

一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时(事件捕获),会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。

  • 动态绑定事件

在很多时候,我们需要通过 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;
}`
1.png
2.png

前端大数据量插入,不卡顿方案

渲染大数据时,合理使用 createDocumentFragmentrequestAnimationFrame ,将操作切分为一小段一小段执行。

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. 迭代和遍历的区别

迭代:1. 有序的 2. 连续的

  1. 为什么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>
1.png
  • 生成器相关
<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的特性

  1. promise状态不受外界影响
  2. 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);
})
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容