Javascript-browser

Browser

arrayToHtmlList

Array.prototype.map() document.querySelector()


const arrayToHtmlList = (arr, listID) => 
        (el => (
        (el = document.querySelector('#' + listID)),
        (el.innerHTML += arr.map(item => `<li>${item}</li>`).join(''))
    ))();
arrayToHtmlList(['item 1', 'item 2'], 'myListID');

bottomVisible


// 页面底部可见返回true,否则false
const bottomVisible = () => 
    document.documentElement.clientHeight + window.scrollY >=
    (document.documentElement.scrollHeight || document.documentElement.clientHeight);
bottomVisible(); // true

Can I use an arrow function as the callback for an event listener in JavaScript?


const toggleElements = document.querySelectorAll('.toggle');
toggleElements.forEach(el => {
  el.addEventListener('click', function() {
    this.classList.toggle('active');
  });
});

const toggleElements = document.querySelectorAll('.toggle');
toggleElements.forEach(el => {
  el.addEventListener('click', () => {
    this.classList.toggle('active'); // `this` refers to `window`
    // Error: Cannot read property 'toggle' of undefined
  });
});

const toggleElements = document.querySelectorAll('.toggle');
toggleElements.forEach(el => {
  el.addEventListener('click', (e) => {
    e.currentTarget.classList.toggle('active'); // works correctly
  });
});

copyToClipboard


const copyToClipboard = str => {
    const el = document.createElement('textarea');
    el.value = str;
    el.setAttribute('readonly', '');
    el.style.position = 'absolute';
    el.style.left = '-9999px';
    document.body.appendChild(el);
    const selected =
        document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false;
    el.select();
    document.execCommand('copy');
    document.body.removeChild(el);
    if (selected) {
        document.getSelection().removeAllRanges();
        document.getSelection().addRange(selected);
    }
};
copyToClipboard('Lorem ipsum'); // 'Lorem ipsum' copied to clipboard.

counter

为指定的选择器创建一个具有指定范围,步长和持续时间的计数器。


const counter = (selector, start, end, step = 1, duration = 2000) => {
    let current = start,
        _step = (end - start) * step < 0 ? -step : step,
        timer = setInterval(() => {
            current += _step;
            document.querySelector(selector).innerHTML = current;
            if (current >= end) document.querySelector(selector).innerHTML = end;
            if (current >= end) clearInterval(timer);
        }, Math.abs(Math.floor(duration / (end - start))));
    return timer;
};
counter('#my-id', 1, 1000, 5, 2000); // Creates a 2-second timer for the element with id="my-id"

createElement


const createElement = str => {
  const el = document.createElement('div');
  el.innerHTML = str;
  return el.firstElementChild;
};

const el = createElement(
  `<div class="container">
    <p>Hello!</p>
  </div>`
);
console.log(el.className); // 'container'

createEventHub

使用emit,on和off方法创建一个发布/订阅(发布-订阅)事件中心。


const createEventHub = () => ({
    hub: Object.create(null),
    emit(event, data) {
        (this.hub[event] || []).forEach(handler => handler(data));
    },
    on(event, handler) {
        if(!this.hub[event]) this.hub[event] = [];
        this.hub[event].push(handler);
    },
    off(event, handler) {
        const i = (this.hub[event] || []).findIndex(h => h === handler);
        if (i > -1) this.hub[event].splice(i, 1);
        if (this.hub[event].length === 0) delete this.hub[event];
    }
});

const handler = data => console.log(data);
const hub = createEventHub();
let increment = 0;
// Subscribe: listen for different types of events
hub.on('message', handler);
hub.on('message', () => console.log('Message event fired'));
hub.on('increment', () => increment++);
// Publish: emit events to invoke all handlers subscribed to them, passing the data to them as an argument
hub.emit('message', 'hello world'); // logs 'hello world' and 'Message event fired'
hub.emit('message', { hello: 'world' }); // logs the object and 'Message event fired'
hub.emit('increment'); // `increment` variable is now 1
// Unsubscribe: stop a specific handler from listening to the 'message' event
hub.off('message', handler);

currentURL


const currentURL = () => window.location.href;
currentURL(); // 'https://google.com'

detectDeviceType

检测网站是在移动设备上/笔记本上打开。


const detectDeviceType = () =>
  /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
    ? 'Mobile'
    : 'Desktop';
detectDeviceType(); // "Mobile" or "Desktop"

elementContains

如果父元素包含子元素,则返回true,否则返回false。

node.contains( otherNode )

  • node 是否包含otherNode节点.
  • otherNode 是否是node的后代节点.
  • 如果 otherNode 是 node 的后代节点或是 node 节点本身.则返回true , 否则返回 false.

const elementContains = (parent, child) => parent !== child && parent.contains(child);
elementContains(document.querySelector('head'), document.querySelector('title')); // true
elementContains(document.querySelector('body'), document.querySelector('body')); // false

elementIsFocused

如果给定元素已聚焦,则返回true,否则返回false。


const elementIsFocused = el => (el === document.activeElement);
elementIsFocused(el); // true if the element is focused

elementIsVisibleInViewport

如果指定的元素在视口中可见,则返回true,否则返回false。


const elementIsVisibleInViewport = (el, partiallyVisible = false) => {
    const { top, left, bottom, right } = el.getBoundingClientRect();
    const { innerHeight, innerWidth } = window;
    return partiallyVisible
        ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) &&
            ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
        : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
};
// e.g. 100x100 viewport and a 10x10px element at position {top: -1, left: 0, bottom: 9, right: 10}
elementIsVisibleInViewport(el); // false - (not fully visible) 部分可见 false
elementIsVisibleInViewport(el, true); // true - (partially visible) 部分可见 true

formToObject

将表单元素转化为对象


const formToObject = form => 
    Array.from(new FormData(form)).reduce(
        (acc, [key, value]) => {
            acc[key] = value;
            return acc;
        },
        {}
    );
// or
const formToObject = form => 
    Array.from(new FormData(form)).reduce(
        (acc, [key, value]) => ({
            ...acc,
            [key]: value
        }),
        {}
    );
formToObject(document.querySelector('#form')); // { email: 'test@email.com', name: 'Test Name' }

getBaseURL

返回没有任何参数的url


const getBaseURL = url => url.indexOf('?') > 0 ? url.slice(0, url.indexOf('?')) : url;
getBaseURL('http://url.com/page?name=Adam&surname=Smith'); // 'http://url.com/page'

getImages

从元素中获取所有图像并将它们放入一个数组中


const getImages = (el, includeDuplicates = false) => {
    const images = [...el.getElementsByTagName('img')].map(img => img.getAttribute('src'));
    return includeDuplicates ? images : [...new Set(images)];
};
getImages(document, true); // ['image1.jpg', 'image2.png', 'image1.png', '...']
getImages(document, false); // ['image1.jpg', 'image2.png', '...']

getScrollPosition

返回当前页面返回的位置


const getScrollPosition = (el = window) => ({
    x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
    y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
});
getScrollPosition(); // {x: 0, y: 200}

getSelectedText

获取当前选择的文本


const getSelectedText = () => window.getSelection().toString();
getSelectedText(); // 'Lorem ipsum'

getSiblings

返回一个包含给定元素的所有同级元素的数组。


const getSiblings = el => 
    [...el.parentNode.childNodes].filter(node => node !== el);
getSiblings(document.querySelector('head')); // ['body']

getStyle

返回指定元素的CSS值。


const getStyle = (el, ruleName) => getComputedStyle(el)[ruleName];
getStyle(document.querySelector('p'), 'font-size'); // '16px'

getURLParameters

返回一个包含当前URL参数的对象。


const getURLParameters = url =>
    (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce(
        (a, v) => ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a),
        {}
    );
getURLParameters('http://url.com/page?name=Adam&surname=Smith'); // {name: 'Adam', surname: 'Smith'}
getURLParameters('google.com'); // {}

hasClass

如果包含指定的类,则返回true,否则false


const hasClass = (el, className) => el.classList.contains(className);
hasClass(document.querySelector('p.special'), 'special'); // true

hashBrowser

使用SHA-256算法为值创建哈希。 返回Promise。

SubtleCrypto.digest()


const hashBrowser = val => 
    crypto.subtle.digest('SHA-256', new TextEncoder('utf-8').encode(val)).then(h => {
        let hexes = [],
            view = new DataView(h);
        for (let i = 0; i < view.byteLength; i += 4)
            hexes.push(('00000000' + view.getUint32(i).toString(16)).slice(-8));
        return hexes.join('');
    });
hashBrowser(JSON.stringify({ a: 'a', b: [1, 2, 3, 4], foo: { c: 'bar' } })).then(console.log); // '04aa106279f5977f59f9067fa9712afc4aedc6f5862a8defc34552d8c7206393'

// or
const hashBrowser = val => {
    const msgUint8 = new TextEncoder().encode(val);                           // encode as (utf-8) Uint8Array
    return crypto.subtle.digest('SHA-256', msgUint8);           // hash the message 
};
hashBrowser(JSON.stringify({ a: 'a', b: [1, 2, 3, 4], foo: { c: 'bar' } })).then(h => {
    const hashArray = Array.from(new Uint8Array(h));                     // convert buffer to byte array
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
    return hashHex;
}).then(console.log); 

// or
async function hashBrowser(message) {
  const msgUint8 = new TextEncoder().encode(message);                           // encode as (utf-8) Uint8Array
  const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8);           // hash the message
  const hashArray = Array.from(new Uint8Array(hashBuffer));                     // convert buffer to byte array
  const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
  return hashHex;
}
const digestHex = await hashBrowser(JSON.stringify({ a: 'a', b: [1, 2, 3, 4], foo: { c: 'bar' } }));
console.log(digestHex); 

hide

NodeList.prototype.forEach()

隐藏所有指定的元素。


const hide = (...el) => [...el].forEach(e => (e.style.display = 'none'));
hide(document.querySelectorAll('img')); // Hides all <img> elements on the page

Javascriot如何复制文本

一个普遍的需求是单击复制文本,Javascript可以通过五个步骤做到这点。

  • 创建一个<textarea>,设置为要复制到剪切板的字符串
  • 将<textarea>添加到document
  • 使用HTMLInputElement.select()选择<textarea>内容
  • 使用Document.execCommand('copy')将<textarea>内容复制到剪切板
  • 删除<textarea>

const copyToClipboard = str => {
    const el = document.createElement('textarea');
    el.value = str;
    document.body.appendChild(el);
    el.select();
    document.execCommand('copy');
    document.body.removeChild(el);
};


// 前面已经有过的例子
const copyToClipboard = str => {
    const el = document.createElement('textarea');
    el.value = str;
    el.setAttribute('readonly', '');
    el.style.position = 'absolute';
    el.style.left = '-9999px';
    document.body.appendChild(el);
    el.select();
    document.execCommand('copy');
    document.body.removeChild(el);
};

如何使用Javascript修改url时不重新加载页面?

使用History API


const nextURL = 'https://my-website.com/page_b';
const nextTitle = 'My new page title';
const nextState = { additionalInformation: 'Updated the URL with JS' };

// This will create a new entry in the browser's history, without reloading
window.history.pushState(nextState, nextTitle, nextURL);

// This will replace the current entry in the browser's history, without reloading
window.history.replaceState(nextState, nextTitle, nextURL);

使用Location API


// Current URL: https://my-website.com/page_a
const nextURL = 'https://my-website.com/page_b';

// This will create a new entry in the browser's history, reloading afterwards
window.location.href = nextURL;

// This will replace the current entry in the browser's history, reloading afterwards
window.location.assign(nextURL);

// This will replace the current entry in the browser's history, reloading afterwards
window.location.replace(nextURL);

httpDelete

对传递的URL进行DELETE请求。


const httpDelete = (url, callback, err = console.error) => {
    const request = new XMLHttpRequest();
    request.open('DELETE', url, true);
    request.onload = () => callback(request);
    request.onerror = () => err(request);
    request.send();
};
httpDelete('https://jsonplaceholder.typicode.com/posts/1', request => {
  console.log(request.responseText);
}); /*
Logs: {}
*/

httpGet

向传递的URL发出GET请求。


const httpGet = (url, callback, err = console.error) => {
    const request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.onload = () => callback(request.responseText);
    request.onerror = () => err(request);
    request.send();
};
httpGet(
    'https://jsonplaceholder.typicode.com/posts/1',
    console.log
); /*
Logs: {
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
*/

httpPost

向传递的URL发出POST请求。


const httpPost = (url, data, callback, err = console.error) => {
    const request = new XMLHttpRequest();
    request.open('POST', url, true);
    request.setRequestHeader('Content-type', 'application/json; charset=utf-8');
    request.onload = () => callback(request.responseText);
    request.onerror = () => err(request);
    request.send(data);
};
const newPost = {
    userId: 1,
    id: 1337,
    title: 'Foo',
    body: 'bar bar bar'
};
const data = JSON.stringify(newPost);
httpPost(
    'https://jsonplaceholder.typicode.com/posts',
    data,
    console.log
); /*
Logs: {
  "userId": 1,
  "id": 1337,
  "title": "Foo",
  "body": "bar bar bar"
}
*/
httpPost(
    'https://jsonplaceholder.typicode.com/posts',
    null, // does not send a body
    console.log
); /*
Logs: {
  "id": 101
}
*/

httpPut

向传递的URL发出PUT请求。


const httpPut = (url, data, callback, err = console.error) => {
    const request = new XMLHttpRequest();
    request.open('PUT', url, true);
    request.setRequestHeader('Content-type', 'application/json; charset=utf-8');
    request.onload = () => callback(request);
    request.onerror = () => err(request);
    request.send(data);
};
// const password = 'fooBaz';
const data = JSON.stringify({
    id: 1,
    title: 'foo',
    body: 'bar',
    userId: 1
});
httpPut('https://jsonplaceholder.typicode.com/posts/1', data, request => {
    console.log(request.responseText);
}); /*
Logs: {
  id: 1,
  title: 'foo',
  body: 'bar',
  userId: 1
}
*/

httpsRedirect

如果页面当前位于HTTP中,则将其重定向到HTTPS。同时,按返回按钮不会将其带回到HTTP页面,因为历史记录已被替换。


const httpsRedirect = () => {
    if (location.protocol !== 'https:') location.replace('https://' + location.href.split('//')[1]);
};
httpsRedirect(); // If you are on http://mydomain.com, you are redirected to https://mydomain.com

insertAfter

在指定元素后面插入html

insertAdjacentHTML


<!-- beforebegin -->
<p>
  <!-- afterbegin -->
  foo
  <!-- beforeend -->
</p>
<!-- afterend -->


const insertAfter = (el, htmlString) => el.insertAdjacentHTML('afterend', htmlString);
insertAfter(document.getElementById('myId'), '<p>after</p>');
// <div id="myId">...</div> <p>after</p>

isBrowser

确定当前运行时环境是否是浏览器,以便前端模块可以在服务器(节点)上运行而不会引发错误。


const isBrowser = () => ![typeof window, typeof document].includes('undefined');
isBrowser(); // true (browser)
isBrowser(); // false (Node)

isBrowserTabFocused

如果页面的浏览器tab具有焦点,则返回true,否则返回false。


const isBrowserTabFocused = () => !document.hidden;
isBrowserTabFocused(); // true

listenOnce

将事件侦听器添加到仅在首次触发事件时才运行回调的元素。


const listenOnce = (el, evt, fn) => el.addEventListener(evt, fn, { once: true });
listenOnce(
    document.getElementById('my-id'),
    'click',
    () => console.log('Hello world')
); // 'Hello world' will only be logged on the first click

nodeListToArray

将NodeList转换为数组。


const nodeListToArray = nodeList => [...nodeList];
nodeListToArray(document.childNodes); // [ <!DOCTYPE html>, html ]

observeMutations

创建并返回一个新的MutationObserver它会在指定的DOM发生变化时被调用。

MutationObserver


const observeMutations = (element, callback, options) => {
    const observer = new MutationObserver(mutations => mutations.forEach(m => callback(m)));
    observer.observe(
        element,
        Object.assign(
        {
            childList: true,
            attributes: true,
            attributeOldValue: true,
            characterData: true,
            characterDataOldValue: true,
            subtree: true
        },
        options
        )
    );
    return observer;
};
const obs = observeMutations(document, console.log); // Logs all mutations that happen on the page
obs.disconnect(); // Disconnects the observer and stops logging mutations on the page

off

从元素中删除事件侦听器。


const off = (el, evt, fn, opts = false) => el.removeEventListener(evt, fn, opts);
const fn = () => console.log('!');
document.body.addEventListener('click', fn);
off(document.body, 'click', fn); // no longer logs '!' upon clicking on the page

on

将事件侦听器添加到具有使用事件委托功能的元素。


const on = (el, evt, fn, opts = {}) => {
    const delegatorFn = e => e.target.matches(opts.target) && fn.call(e.target, e);
    el.addEventListener(evt, opts.target ? delegatorFn : fn, opts.options || false);
    if (opts.target) return delegatorFn;
};
const fn = () => console.log('!');
on(document.body, 'click', fn); // logs '!' upon clicking the body
on(document.body, 'click', fn, { target: 'p' }); // logs '!' upon clicking a `p` element child of the body
on(document.body, 'click', fn, { options: true }); // use capturing instead of bubbling

// or

const on = (el, evt, fn, opts = {}) => {
    const delegatorFn = e => e.target.matches(opts.target) && fn.call(e.target, e);
    el.addEventListener(evt, opts.target ? e => {
        return e.target.matches(opts.target) && fn.call(e.target, e)
    } : fn, opts.options || false);
};
var fn = () => console.log('!');
on(document.body, 'click', fn); // logs '!' upon clicking the body
on(document.body, 'click', fn, { target: '.tt' }); // logs '!' upon clicking a `p` element child of the body
on(document.body, 'click', fn, { options: true }); // use capturing instead of bubbling

onUserInputChange

每当用户输入类型更改(鼠标或触摸)时,运行回调。 根据输入设备来启用/禁用代码。 此过程是动态的,并且适用于混合设备(例如触摸屏笔记本电脑)


const onUserInputChange = callback => {
    let type = 'mouse',
        lastTime = 0;
    const mousemoveHandler = () => {
        const now = performance.now();
        if (now - lastTime < 20)
        (type = 'mouse'), callback(type), document.removeEventListener('mousemove', mousemoveHandler);
        lastTime = now;
    };
    document.addEventListener('touchstart', () => {
        if (type === 'touch') return;
        (type = 'touch'), callback(type), document.addEventListener('mousemove', mousemoveHandler);
    });
};
onUserInputChange(type => {
  console.log('The user is now using', type, 'as an input method.');
});

parseCookie

解析HTTP Cookie标头字符串,并返回所有cookie名称-值对的对象。


const parseCookie = str =>
  str
    .split(';')
    .map(v => v.split('='))
    .reduce((acc, v) => {
      acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim());
      return acc;
    }, {});
parseCookie('foo=bar; equation=E%3Dmc%5E2'); // { foo: 'bar', equation: 'E=mc^2' }

prefersDarkColorScheme

matchMedia

如果用户配色方案偏好设置为深色,则返回true,否则返回false。


window.matchMedia('(max-width: 600px)'); // 视口宽度大于600px,返回false,否则true

const prefersDarkColorScheme = () =>
  window && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
prefersDarkColorScheme(); // true

prefersLightColorScheme

如果用户配色方案偏好设置为浅色,则返回true,否则返回false。


const prefersLightColorScheme = () =>
  window && window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches;
prefersLightColorScheme(); // true

prefix

返回浏览器支持的CSS属性的前缀版本(如有必要)。


const prefix = prop => {
    const capitalizedProp = prop.charAt(0).toUpperCase() + prop.slice(1);
    const prefixes = ['', 'webkit', 'moz', 'ms', 'o'];
    const i = prefixes.findIndex(
        prefix => typeof document.body.style[prefix ? prefix + capitalizedProp : prop] !== 'undefined'
    );
    return i !== -1 ? (i === 0 ? prop : prefixes[i] + capitalizedProp) : null;
};
prefix('appearance'); // 'appearance' on a supported browser, otherwise 'webkitAppearance', 'mozAppearance', 'msAppearance' or 'oAppearance'

recordAnimationFrames

在每个动画帧上调用提供的回调。


const recordAnimationFrames = (callback, autoStart = true) => {
    let running = true,
        raf;
    const stop = () => {
        running = false;
        cancelAnimationFrame(raf);
    };
    const start = () => {
        running = true;
        run();
    };
    const run = () => {
        raf = requestAnimationFrame(() => {
        callback();
        if (running) run();
        });
    };
    if (autoStart) start();
    return { start, stop };
};
const cb = () => console.log('Animation frame fired');
const recorder = recordAnimationFrames(cb); // logs 'Animation frame fired' on each animation frame
recorder.stop(); // stops logging
recorder.start(); // starts again
const recorder2 = recordAnimationFrames(cb, false); // `start` needs to be explicitly called to begin recording frames

redirect

重定向到指定的URL。


const redirect = (url, asLink = true) =>
    asLink ? (window.location.href = url) : window.location.replace(url);
redirect('https://google.com');

renderElement

在指定的DOM元素中渲染DOM树。


// test createElement
var myElement = {
    type: 'button',
    props: {
        type: 'button',
        className: 'btn',
        onClick: () => alert('Clicked'),
        children: [
            { props: { nodeValue: 'Click me' } }
        ]
    }
};
var element = document.createElement(myElement.type);
element['type'] = myElement.props.type;
element['className'] = myElement.props.className; 
element // <button type="button" class="btn"></button>


const renderElement = ({ type, props = {} }, container) => {
    const isTextElement = !type;
    const element = isTextElement
        ? document.createTextNode('')
        : document.createElement(type);

    //  是否为事件 是:true 否:false
    const isListener = p => p.startsWith('on');
    //  是否是属性 是:true 否:false
    const isAttribute = p => !isListener(p) && p !== 'children';

    Object.keys(props).forEach(p => {
        // 属性赋值
        if(isAttribute(p)) element[p] = props[p];
        // 事件添加
        if(!isTextElement && isListener(p))
            element.addEventListener(p.toLowerCase().slice(2), props[p]);
    });

    // 获取children后进行递归
    if(!isTextElement && props.children && props.children.length)
        props.children.forEach(childElement => renderElement(childElement, element));

    container.appendChild(element);
}

const myElement = {
    type: 'button',
    props: {
        type: 'button',
        className: 'btn',
        onClick: () => alert('Clicked'),
        children: [
        { props: { nodeValue: 'Click me' } }
        ]
    }
};
renderElement(
    myElement,
    document.body
);

runAsync

Web Workers API Blob

使用Web Worker在单独的线程中运行一个函数,从而允许长时间运行的函数不会阻塞UI。


// test Blob
var typedArray = GetTheTypedArraySomehow();
var blob = new Blob([typedArray.buffer], {type: 'application/octet-stream'}); // 传入一个合适的 MIME 类型
var url = URL.createObjectURL(blob);
// 会产生一个类似 blob:d3958f5c-0777-0845-9dcf-2cb28783acaf 这样的URL字符串
// 你可以像使用普通 URL 那样使用它,比如用在 img.src 上。


const runAsync = fn => {
    const worker = new Worker(
        URL.createObjectURL(new Blob([`postMessage((${fn})());`]), {
            type: 'application/javascript; charset=utf-8'
        }) // MIME类型
    );
    return new Promise((res, rej) => {
        worker.onmessage = ({ data }) => {
            res(data), worker.terminate();
        };
        worker.onerror = err => {
            rej(err), worker.terminate();
        };
    });
};

const longRunningFunction = () => {
  let result = 0;
  for (let i = 0; i < 1000; i++)
    for (let j = 0; j < 700; j++) for (let k = 0; k < 300; k++) result = result + i + j + k;

  return result;
};
/*
  NOTE: Since the function is running in a different context, closures are not supported.
  The function supplied to `runAsync` gets stringified, so everything becomes literal.
  All variables and functions must be defined inside.
*/
runAsync(longRunningFunction).then(console.log); // 209685000000
runAsync(() => 10 ** 3).then(console.log); // 1000
let outsideVariable = 50;
runAsync(() => typeof outsideVariable).then(console.log); // 'undefined'

scrollToTop

平滑滚动到页面顶部。


// test requestAnimationFrame
var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';

function step(timestamp) {
  if (!start) start = timestamp;
  var progress = timestamp - start;
  element.style.left = Math.min(progress / 10, 200) + 'px';
  if (progress < 2000) {
    window.requestAnimationFrame(step);
  }
}

window.requestAnimationFrame(step);


const scrollToTop = () => {
  const c = document.documentElement.scrollTop || document.body.scrollTop;
  if (c > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, c - c / 8);
  }
};
scrollToTop();

serializeCookie

将Cookie名/值对序列化为Set-Cookie标头字符串。


const serializeCookie = (name, val) => `${encodeURIComponent(name)}=${encodeURIComponent(val)}`;
serializeCookie('foo', 'bar'); // 'foo=bar'

serializeForm

将一组表单元素编码为查询字符串。


// test
Array.from([1, 2, 3], x => x + x);
// [2, 4, 6]
Array.from({length: 5}, (v, i) => i);
// [0, 1, 2, 3, 4]


const serializeForm = form =>
  Array.from(new FormData(form), field => field.map(encodeURIComponent).join('=')).join('&');
serializeForm(document.querySelector('#form')); // email=test%40email.com&name=Test%20Name

setStyle

为指定的HTML元素设置CSS值。


const setStyle = (el, ruleName, val) => (el.style[ruleName] = val);
setStyle(document.querySelector('p'), 'font-size', '20px');
// The first <p> element on the page will have a font-size of 20px

show

显示所有指定的元素。


const show = (...el) => [...el].forEach(e => (e.style.display = ''));
show(...document.querySelectorAll('img')); // Shows all <img> elements on the page

smoothScroll

将元素平滑滚动到浏览器窗口的可见区域。


const smoothScroll = element =>
  document.querySelector(element).scrollIntoView({
    behavior: 'smooth'
  });
smoothScroll('#fooBar'); // scrolls smoothly to the element with the id fooBar
smoothScroll('.fooBar'); // scrolls smoothly to the first element with a class of fooBar

supportsTouchEvents

如果支持触摸事件,则返回true,否则返回false。


const supportsTouchEvents = () =>
  window &&
  ('ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch);
supportsTouchEvents(); // true

toggleClass

切换HTML元素的class。


const toggleClass = (el, className) => el.classList.toggle(className);
toggleClass(document.querySelector('p.special'), 'special');
// The paragraph will not have the 'special' class anymore

triggerEvent

创建自定义事件

在给定元素上触发特定事件,可以选择传递自定义数据。


// test 创建自定义事件
var event = new Event('build');
// Listen for the event.
elem.addEventListener('build', function (e) { ... }, false);
// Dispatch the event.
elem.dispatchEvent(event);

// 添加自定义数据 – CustomEvent()
// 要向事件对象添加更多数据,可以使用 CustomEvent 接口,detail 属性可用于传递自定义数据。
var event = new CustomEvent('build', { 'detail': elem.dataset.time });
function eventHandler(e) {
  log('The time is: ' + e.detail);
}


const triggerEvent = (el, eventType, detail) =>
    el.dispatchEvent(new CustomEvent(eventType, { detail }));
triggerEvent(document.getElementById('myId'), 'click');
triggerEvent(document.getElementById('myId'), 'click', { username: 'bob' });

了解JavaScript中的事件冒泡,捕获和委派

事件冒泡

冒泡是指事件从目标元素(即用户单击的按钮)一直传播到其根树,从最近的树开始传播。 默认情况下,所有事件都会冒泡。


<html>
  <body>
    <div id="btn-container">
      <button class="btn">Click me</button>
    </div>
  </body>
</html>


const ancestors = [
    window, document, document.documentElement,
    document.body, document.getElementById('btn-container')
];

// Target phase
document.querySelector('.btn').addEventListener('click', e => {
    console.log(`Hello from ${e.target}`);
});
// Bubble phase
ancestors.forEach(a => {
    a.addEventListener('click', e => {
        console.log(`Hello from ${e.currentTarget}`);
    });
});

// Hello from [object HTMLButtonElement]
// Hello from [object HTMLDivElement]
// Hello from [object HTMLBodyElement]
// Hello from [object HTMLHtmlElement]
// Hello from [object HTMLDocument]
// Hello from [object Window]

如果将事件侦听器添加到树中的每个元素,如上所示,我们将首先看到一个由按钮触发的侦听器,然后其他每个侦听器从最近的祖先一直触发到窗口。

事件捕获

捕获与冒泡完全相反,这意味着外部事件处理程序在最具体的处理程序(即按钮上的那个)之前触发。 请注意,首先运行所有捕获事件处理程序,然后运行所有冒泡事件处理程序。

可以通过将EventTarget.addEventListener的第三个参数设置为true来使用事件捕获。 例如:


ancestors.forEach(a => {
    a.addEventListener('click', event => {
        console.log(`Hello from ${e.currentTarget}`);
    }, true);
});

// Hello from [object Window]
// Hello from [object HTMLDocument]
// Hello from [object HTMLHtmlElement]
// Hello from [object HTMLBodyElement]
// Hello from [object HTMLDivElement]
// Hello from [object HTMLButtonElement]

可以看到首先为该按钮的每个祖先触发事件,然后将触发该按钮的事件。

事件传播

在解释了事件冒泡和捕获之后,我们现在可以解释事件传播的三个阶段:

  • 在捕获阶段,事件从window开始,向下移动到document,父元素。

  • 在目标阶段,事件会在事件目标上触发(例如,用户单击的按钮)。

  • 在冒泡阶段,事件冒泡通过目标元素的父元素,documentwindow

事件委托

事件委托是指将事件侦听委托给父元素的想法,而不是将事件侦听器直接添加到事件目标。 使用此技术,父级可以根据需要捕获并处理冒泡事件。


window.addEventListener('click', e => {
    if (e.target.className === 'btn') console.log('Hello there!');
});

在上面的示例中,我们将事件处理从按钮委托到窗口,并使用event.target获取原始事件的目标。

  • 通过使用事件委托,我们可以侦听大量元素上的事件,而不必分别附加事件侦听器,这可以提供性能上的好处。

  • 通过使用事件委托,动态元素(即随着时间的推移从DOM中添加或删除)可以捕获和处理其事件,而无需注册或删除侦听器。

UUIDGeneratorBrowser

在浏览器中生成UUID。


// [1e7] 转化为字符串
const UUIDGeneratorBrowser = () =>
    ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
        (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
    );
UUIDGeneratorBrowser(); // '7982fcfe-5721-4632-bede-6000885be57d'

Browser environment sniffing


// 复制尤大
export const inBrowser = typeof window !== 'undefined'
export const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform
export const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase()
export const UA = inBrowser && window.navigator.userAgent.toLowerCase()
export const isIE = UA && /msie|trident/.test(UA)
export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
export const isEdge = UA && UA.indexOf('edge/') > 0
export const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android')
export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios')
export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
export const isPhantomJS = UA && /phantomjs/.test(UA)
export const isFF = UA && UA.match(/firefox\/(\d+)/)

文档信息

Search

    Table of Contents