Giter Club home page Giter Club logo

blog's Introduction

  • I’m @shulandmimi
  • I'm currently learning Rust, Typescript and what interests me

blog's People

Contributors

shulandmimi avatar

Watchers

 avatar

blog's Issues

copy dom时,如何copy事件

前言

偶然的,从网上看到一个这个面试题,从而引发了后面的一系列思考,在思考过程中产生出一些小想法,然后逐一解决。



一. 查询相关API、深入JQ源码

首先,查询各种API,比较差异。

从中找出差异,然后查看能copy事件的API是如何完成的,当然,原生API除外


原生cloneNode

const newEl = el.cloneNode(bool);
bool 控制是否深度(克隆子级)clone

原生的copy后返回的newEl并不携带任何事件,只是有el的各种属性描述


jq clone

const newEl = $(el).clone(bool)
bool 控制是否深入clone并且clone事件

jq的copy后返回一个jq包裹的元素,元素的dom存在事件

那么我们可以深入一下jq的源码操作,以下是查询源码后获得的消息

  • 在使用jq的绑定事件操作时,会缓存绑定的详细信息(类型、回调、配置)
  • 如果clone的第一位参数是true,那么会在cloneNode返回newEl后,从缓存中获取到详细配置,然后在对newEl进行事件绑定



二.实现一个jq式的copy事件

首先,写一个缓存存取

function cache() {
    const map = new Map();
    return {
        add(k, v){
            let arr = map.get(k);
            
            Array.isArray(arr) ? arr.push(v) : arr = [v];
            return map.set(k, b);
        },
        del(k){
            return map.delete(k);
        },
        get(k) {
            return map.get(k);
        }
    }
}

clone函数和on函数

const EventCache = cache();
function clone(DeepAndEventClone) {
    const newEl = this.el;
    if(DeepAndEventClone) {
        const allEvent = EventCache.get(this.el);
        if(allEvent && Array.isArray(allEvent)) {
            for(let i = 0; i < allEvent.length; i ++>) {
                const event = allEvent[i];
                on(newEl, event.type, event.handler, event.options);
            }
        }
    }
}

// 绑定事件,绑定事件时,根据DOM缓存元素
function on(target, type, handler, options) {
    target.addEventListener(type, handler, options);
    EventCache.add(target, {
        type,
        handler,
        options
    });
}



三. 原生实现

WeakMap做缓存

const cache = new WeakMap();

拦截addEventListener 和 removeEventListener
addEventListener 在拦截函数中保存事件
removeEventListener 在拦截函数中清除事件

// addEventListener
const addEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, handler, options){
    const target = this;
    const event = cache.get(target) || cache.set(target, {}).get(target);
    const data = {
        type,
        handler,
        options
    }
    if(typeof event === 'object' && event !== null && !Array.isArray(event)) {
        const targetEvent = event[type] || (event[type] = []);
        targetEvent.push(data);
    }
    return addEventListener.call(target, type, handler, options);
}

// removeEventListener
const removeEventListener = EventTarget.prototype.removeEventListener;
EventTarget.prototype.removeEventListener = function(type, handler, options) {
    const target = this;
    cache.delete(target);
    return removeEventListener.call(target, type, handler, options);
}

PS: 一定要拦截EventTarget.prototype上addEventListener 和 removeEventListener, 其他地方的都是继承这里的


拦截cloneNode
拦截cloneNode并且在其中将句柄和缓存中的数据给copy出来的node

const EventList = `oncopy oncut onpaste onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus onformdata oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmousedown onmouseenter onmouseleave onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange onreset onresize onscroll onseeked onseeking onselect onstalled onsubmit onsuspend ontimeupdate ontoggle onvolumechange onwaiting onwheel onauxclick ongotpointercapture onlostpointercapture onpointerdown onpointermove onpointerup onpointercancel onpointerover onpointerout onpointerenter onpointerleave onselectstart onselectionchange onanimationend onanimationiteration onanimationstart ontransitionend onpointerrawupdate onbeforecopy onbeforecut onbeforepaste onsearch onfullscreenchange onfullscreenerror onwebkitfullscreenchange onwebkitfullscreenerror`.split(' ');

const cloneNode = Node.prototype.cloneNode;
Node.prototype.cloneNode = function(copyDeepAndEvent) {
    const node = cloneNode.call(this, copyDeepAndEvent);
    if(copyDeepAndEvent) {
        const event = cache.get(this) || {};
        // addEventListener 绑定的
        for(let prop in event) {
        const targetEvent = event[prop];
        if(Array.isArray(targetEvent)) {
            for(let i = 0; i < targetEvent.length; i ++) {
                    node.addEventListener(targetEvent[i].type, targetEvent[i].handler, targetEvent[i].options);
                }
            }
        }

        // 句柄
        for(let i = 0; i < EventList.length; i ++ ){
            if(typeof this[EventList[i]] === 'function') node[EventList[i]] = this[EventList[i]];
        }
    }
    return node;
}

测试

    
const div1 = document.createElement('div');

const handler = function() {
    console.log('event', '234');
}
div1.onclick = () => {
    console.log('123');
};
div1.addEventListener('click', handler);

div1.style.cssText = `
    width: 100px;
    height: 100px;
    background: red;
`;

document.body.appendChild(div1);

const div2 = div1.cloneNode(true);

document.body.appendChild(div2);

div2.removeEventListener('click', handler);



四. 全部代码

PS: 重写的代码必须放置在第一个addEventListener使用之前, 可以用一个立即执行函数使外界访问不到

const EventList = `oncopy oncut onpaste onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus onformdata oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmousedown onmouseenter onmouseleave onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange onreset onresize onscroll onseeked onseeking onselect onstalled onsubmit onsuspend ontimeupdate ontoggle onvolumechange onwaiting onwheel onauxclick ongotpointercapture onlostpointercapture onpointerdown onpointermove onpointerup onpointercancel onpointerover onpointerout onpointerenter onpointerleave onselectstart onselectionchange onanimationend onanimationiteration onanimationstart ontransitionend onpointerrawupdate onbeforecopy onbeforecut onbeforepaste onsearch onfullscreenchange onfullscreenerror onwebkitfullscreenchange onwebkitfullscreenerror`.split(' ');
const cache = new WeakMap();
const addEventListener = EventTarget.prototype.addEventListener;



EventTarget.prototype.addEventListener = function(type, handler, options){
    const target = this;
    const event = cache.get(target) || cache.set(target, {}).get(target);
    const data = {
        type,
        handler,
        options
    }
    if(typeof event === 'object' && event !== null && !Array.isArray(event)) {
        const targetEvent = event[type] || (event[type] = []);
        targetEvent.push(data);
    }
    return addEventListener.call(target, type, handler, options);
}




const removeEventListener = EventTarget.prototype.removeEventListener;
EventTarget.prototype.removeEventListener = function(type, handler, options) {
    const target = this;
    cache.delete(target);
    return removeEventListener.call(target, type, handler, options);
}




const cloneNode = Node.prototype.cloneNode;
Node.prototype.cloneNode = function(copyDeepAndEvent) {
    const node = cloneNode.call(this, copyDeepAndEvent);
    if(copyDeepAndEvent) {
        const event = cache.get(this) || {};
        // addEventListener 绑定的
        for(let prop in event) {
        const targetEvent = event[prop];
        if(Array.isArray(targetEvent)) {
            for(let i = 0; i < targetEvent.length; i ++) {
                    node.addEventListener(targetEvent[i].type, targetEvent[i].handler, targetEvent[i].options);
                }
            }
        }

        // 句柄
        for(let i = 0; i < EventList.length; i ++ ){
            if(typeof this[EventList[i]] === 'function') node[EventList[i]] = this[EventList[i]];
        }
    }
    return node;
}




const div1 = document.createElement('div');

const handler = function() {
    console.log('event', this);
}
div1.onclick = () => {
    console.log('123');
};
div1.style.cssText = `
    width: 100px;
    height: 100px;
    background: red;
`;

document.body.appendChild(div1);

const div2 = div1.cloneNode(true);
console.log(div2)

document.body.appendChild(div2);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.