解析 URL Params 为对象
| let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'; |
| parseParam(url) |
| |
| |
| |
| |
| |
| |
| |
| function parseParam(url) { |
| const paramsStr = /.+\?(.+)$/.exec(url)[1]; |
| const paramsArr = paramsStr.split('&'); |
| let paramsObj = {}; |
| |
| paramsArr.forEach(param => { |
| if (/=/.test(param)) { |
| let [key, val] = param.split('='); |
| val = decodeURIComponent(val); |
| val = /^\d+$/.test(val) ? parseFloat(val) : val; |
| |
| if (paramsObj.hasOwnProperty(key)) { |
| paramsObj[key] = [].concat(paramsObj[key], val); |
| } else { |
| paramsObj[key] = val; |
| } |
| } else { |
| paramsObj[param] = true; |
| } |
| }) |
| |
| return paramsObj; |
| } |
异步并发数限制
| |
| |
| |
| |
| |
| |
| |
| |
| function limit(count, array, iterateFunc) { |
| const tasks = [] |
| const doingTasks = [] |
| let i = 0 |
| const enqueue = () => { |
| if (i === array.length) { |
| return Promise.resolve() |
| } |
| const task = Promise.resolve().then(() => iterateFunc(array[i++])) |
| tasks.push(task) |
| const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1)) |
| doingTasks.push(doing) |
| const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve() |
| return res.then(enqueue) |
| }; |
| return enqueue().then(() => Promise.all(tasks)) |
| } |
| |
| |
| const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i)) |
| limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => { |
| console.log(res) |
| }) |
解析 URL Params 为对象
| let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'; |
| parseParam(url) |
| |
| function parseParam(url) { |
| const paramsStr = /.+\?(.+)$/.exec(url)[1]; |
| const paramsArr = paramsStr.split('&'); |
| let paramsObj = {}; |
| |
| paramsArr.forEach(param => { |
| if (/=/.test(param)) { |
| let [key, val] = param.split('='); |
| val = decodeURIComponent(val); |
| val = /^\d+$/.test(val) ? parseFloat(val) : val; |
| if (paramsObj.hasOwnProperty(key)) { |
| paramsObj[key] = [].concat(paramsObj[key], val); |
| } else { |
| paramsObj[key] = val; |
| } |
| } else { |
| paramsObj[param] = true; |
| } |
| }) |
| return paramsObj; |
| } |
Array.prototype.filter()
| Array.prototype.filter = function(callback, thisArg) { |
| if (this == undefined) { |
| throw new TypeError('this is null or not undefined'); |
| } |
| if (typeof callback !== 'function') { |
| throw new TypeError(callback + 'is not a function'); |
| } |
| const res = []; |
| |
| const O = Object(this); |
| |
| const len = O.length >>> 0; |
| for (let i = 0; i < len; i++) { |
| |
| if (i in O) { |
| |
| if (callback.call(thisArg, O[i], i, O)) { |
| res.push(O[i]); |
| } |
| } |
| } |
| return res; |
| } |
手写 new 操作符
在调用 new
的过程中会发生以上四件事情:
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
| function objectFactory() { |
| let newObject = null; |
| let constructor = Array.prototype.shift.call(arguments); |
| let result = null; |
| |
| if (typeof constructor !== "function") { |
| console.error("type error"); |
| return; |
| } |
| |
| newObject = Object.create(constructor.prototype); |
| |
| result = constructor.apply(newObject, arguments); |
| |
| let flag = result && (typeof result === "object" || typeof result === "function"); |
| |
| return flag ? result : newObject; |
| } |
| |
| objectFactory(构造函数, 初始化参数); |
Promise
| |
| |
| |
| |
| |
| |
| |
| const PENDING = 'PENDING'; |
| const FULFILLED = 'FULFILLED'; |
| const REJECTED = 'REJECTED'; |
| |
| class Promise { |
| constructor(exector) { |
| |
| this.status = PENDING; |
| |
| this.value = undefined; |
| this.reason = undefined; |
| |
| this.onFulfilledCallbacks = []; |
| |
| this.onRejectedCallbacks = []; |
| |
| const resolve = value => { |
| |
| if (this.status === PENDING) { |
| this.status = FULFILLED; |
| this.value = value; |
| |
| this.onFulfilledCallbacks.forEach(fn => fn(this.value)); |
| } |
| } |
| const reject = reason => { |
| |
| if (this.status === PENDING) { |
| this.status = REJECTED; |
| this.reason = reason; |
| |
| this.onRejectedCallbacks.forEach(fn => fn(this.reason)) |
| } |
| } |
| try { |
| |
| |
| exector(resolve, reject); |
| } catch(e) { |
| |
| reject(e); |
| } |
| } |
| then(onFulfilled, onRejected) { |
| onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; |
| onRejected = typeof onRejected === 'function'? onRejected : |
| reason => { throw new Error(reason instanceof Error ? reason.message : reason) } |
| |
| const self = this; |
| return new Promise((resolve, reject) => { |
| if (self.status === PENDING) { |
| self.onFulfilledCallbacks.push(() => { |
| |
| try { |
| |
| setTimeout(() => { |
| const result = onFulfilled(self.value); |
| |
| |
| |
| result instanceof Promise ? result.then(resolve, reject) : resolve(result); |
| }) |
| } catch(e) { |
| reject(e); |
| } |
| }); |
| self.onRejectedCallbacks.push(() => { |
| |
| try { |
| setTimeout(() => { |
| const result = onRejected(self.reason); |
| |
| result instanceof Promise ? result.then(resolve, reject) : resolve(result); |
| }) |
| } catch(e) { |
| reject(e); |
| } |
| }) |
| } else if (self.status === FULFILLED) { |
| try { |
| setTimeout(() => { |
| const result = onFulfilled(self.value); |
| result instanceof Promise ? result.then(resolve, reject) : resolve(result); |
| }); |
| } catch(e) { |
| reject(e); |
| } |
| } else if (self.status === REJECTED) { |
| try { |
| setTimeout(() => { |
| const result = onRejected(self.reason); |
| result instanceof Promise ? result.then(resolve, reject) : resolve(result); |
| }) |
| } catch(e) { |
| reject(e); |
| } |
| } |
| }); |
| } |
| catch(onRejected) { |
| return this.then(null, onRejected); |
| } |
| static resolve(value) { |
| if (value instanceof Promise) { |
| |
| return value; |
| } else { |
| |
| return new Promise((resolve, reject) => resolve(value)); |
| } |
| } |
| static reject(reason) { |
| return new Promise((resolve, reject) => { |
| reject(reason); |
| }) |
| } |
| static all(promiseArr) { |
| const len = promiseArr.length; |
| const values = new Array(len); |
| |
| let count = 0; |
| return new Promise((resolve, reject) => { |
| for (let i = 0; i < len; i++) { |
| |
| Promise.resolve(promiseArr[i]).then( |
| val => { |
| values[i] = val; |
| count++; |
| |
| if (count === len) resolve(values); |
| }, |
| err => reject(err), |
| ); |
| } |
| }) |
| } |
| static race(promiseArr) { |
| return new Promise((resolve, reject) => { |
| promiseArr.forEach(p => { |
| Promise.resolve(p).then( |
| val => resolve(val), |
| err => reject(err), |
| ) |
| }) |
| }) |
| } |
| } |
实现防抖函数(debounce)
防抖函数原理:把触发非常频繁的事件合并成一次去执行 在指定时间内只执行一次回调函数,如果在指定的时间内又触发了该事件,则回调函数的执行时间会基于此刻重新开始计算

防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行
,节流是将多次执行变成每隔一段时间执行
eg. 像百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求。
手写简化版:
| |
| |
| const debounce = (func, wait = 50) => { |
| |
| let timer = 0 |
| |
| |
| |
| return function(...args) { |
| if (timer) clearTimeout(timer) |
| timer = setTimeout(() => { |
| func.apply(this, args) |
| }, wait) |
| } |
| } |
适用场景:
- 文本输入的验证,连续输入文字后发送 AJAX 请求进行验证,验证一次就好
- 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
- 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
实现发布-订阅模式
| class EventCenter{ |
| |
| let handlers = {} |
| |
| |
| addEventListener(type, handler) { |
| |
| if (!this.handlers[type]) { |
| this.handlers[type] = [] |
| } |
| |
| this.handlers[type].push(handler) |
| } |
| |
| |
| dispatchEvent(type, params) { |
| |
| if (!this.handlers[type]) { |
| return new Error('该事件未注册') |
| } |
| |
| this.handlers[type].forEach(handler => { |
| handler(...params) |
| }) |
| } |
| |
| |
| removeEventListener(type, handler) { |
| if (!this.handlers[type]) { |
| return new Error('事件无效') |
| } |
| if (!handler) { |
| |
| delete this.handlers[type] |
| } else { |
| const index = this.handlers[type].findIndex(el => el === handler) |
| if (index === -1) { |
| return new Error('无该绑定事件') |
| } |
| |
| this.handlers[type].splice(index, 1) |
| if (this.handlers[type].length === 0) { |
| delete this.handlers[type] |
| } |
| } |
| } |
| } |
Object.assign
Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象(请注意这个操作是浅拷贝)
| Object.defineProperty(Object, 'assign', { |
| value: function(target, ...args) { |
| if (target == null) { |
| return new TypeError('Cannot convert undefined or null to object'); |
| } |
| |
| |
| const to = Object(target); |
| |
| for (let i = 0; i < args.length; i++) { |
| |
| const nextSource = args[i]; |
| if (nextSource !== null) { |
| |
| for (const nextKey in nextSource) { |
| if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { |
| to[nextKey] = nextSource[nextKey]; |
| } |
| } |
| } |
| } |
| return to; |
| }, |
| |
| enumerable: false, |
| writable: true, |
| configurable: true, |
| }) |
数组扁平化
数组扁平化是指将一个多维数组变为一个一维数组
| const arr = [1, [2, [3, [4, 5]]], 6]; |
| |
方法一:使用flat()
const res1 = arr.flat(Infinity);
方法二:利用正则
const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');
但数据类型都会变为字符串
方法三:正则改良版本
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
方法四:使用reduce
| const flatten = arr => { |
| return arr.reduce((pre, cur) => { |
| return pre.concat(Array.isArray(cur) ? flatten(cur) : cur); |
| }, []) |
| } |
| const res4 = flatten(arr); |
方法五:函数递归
| const res5 = []; |
| const fn = arr => { |
| for (let i = 0; i < arr.length; i++) { |
| if (Array.isArray(arr[i])) { |
| fn(arr[i]); |
| } else { |
| res5.push(arr[i]); |
| } |
| } |
| } |
| fn(arr); |
实现数组去重
给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。
ES6方法(使用数据结构集合):
| const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8]; |
| |
| Array.from(new Set(array)); |
ES5方法:使用map存储不重复的数字
| const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8]; |
| |
| uniqueArray(array); |
| |
| function uniqueArray(array) { |
| let map = {}; |
| let res = []; |
| for(var i = 0; i < array.length; i++) { |
| if(!map.hasOwnProperty([array[i]])) { |
| map[array[i]] = 1; |
| res.push(array[i]); |
| } |
| } |
| return res; |
| } |
实现双向数据绑定
| let obj = {} |
| let input = document.getElementById('input') |
| let span = document.getElementById('span') |
| |
| Object.defineProperty(obj, 'text', { |
| configurable: true, |
| enumerable: true, |
| get() { |
| console.log('获取数据了') |
| }, |
| set(newVal) { |
| console.log('数据更新了') |
| input.value = newVal |
| span.innerHTML = newVal |
| } |
| }) |
| |
| input.addEventListener('keyup', function(e) { |
| obj.text = e.target.value |
| }) |
深拷贝
递归的完整版本(考虑到了Symbol属性):
| const cloneDeep1 = (target, hash = new WeakMap()) => { |
| |
| if (typeof target !== 'object' || target === null) { |
| return target; |
| } |
| |
| if (hash.has(target)) return hash.get(target); |
| |
| const cloneTarget = Array.isArray(target) ? [] : {}; |
| hash.set(target, cloneTarget); |
| |
| |
| const symKeys = Object.getOwnPropertySymbols(target); |
| if (symKeys.length) { |
| symKeys.forEach(symKey => { |
| if (typeof target[symKey] === 'object' && target[symKey] !== null) { |
| cloneTarget[symKey] = cloneDeep1(target[symKey]); |
| } else { |
| cloneTarget[symKey] = target[symKey]; |
| } |
| }) |
| } |
| |
| for (const i in target) { |
| if (Object.prototype.hasOwnProperty.call(target, i)) { |
| cloneTarget[i] = |
| typeof target[i] === 'object' && target[i] !== null |
| ? cloneDeep1(target[i], hash) |
| : target[i]; |
| } |
| } |
| return cloneTarget; |
| } |
实现简单路由
| |
| class Route{ |
| constructor(){ |
| |
| this.routes = {} |
| |
| this.currentHash = '' |
| |
| this.freshRoute = this.freshRoute.bind(this) |
| |
| window.addEventListener('load', this.freshRoute, false) |
| window.addEventListener('hashchange', this.freshRoute, false) |
| } |
| |
| storeRoute (path, cb) { |
| this.routes[path] = cb || function () {} |
| } |
| |
| freshRoute () { |
| this.currentHash = location.hash.slice(1) || '/' |
| this.routes[this.currentHash]() |
| } |
| } |
实现千位分隔符
| // 保留三位小数 |
| parseToMoney(1234.56); // return '1,234.56' |
| parseToMoney(123456789); // return '123,456,789' |
| parseToMoney(1087654.321); // return '1,087,654.321' |
| function parseToMoney(num) { |
| num = parseFloat(num.toFixed(3)); |
| let [integer, decimal] = String.prototype.split.call(num, '.'); |
| integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,'); |
| return integer + '.' + (decimal ? decimal : ''); |
| } |
正则表达式(运用了正则的前向声明和反前向声明):
| function parseToMoney(str){ |
| |
| let re = /(?=(?!\b)(\d{3})+$)/g; |
| return str.replace(re,','); |
| } |
图片懒加载
可以给img标签统一自定义属性data-src='default.png'
,当检测到图片出现在窗口之后再补充src属性,此时才会进行图片资源加载。
| function lazyload() { |
| const imgs = document.getElementsByTagName('img'); |
| const len = imgs.length; |
| |
| const viewHeight = document.documentElement.clientHeight; |
| |
| const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop; |
| for (let i = 0; i < len; i++) { |
| const offsetHeight = imgs[i].offsetTop; |
| if (offsetHeight < viewHeight + scrollHeight) { |
| const src = imgs[i].dataset.src; |
| imgs[i].src = src; |
| } |
| } |
| } |
| |
| |
| window.addEventListener('scroll', lazyload); |
模拟new
new操作符做了这些事:
- 它创建了一个全新的对象
- 它会被执行[Prototype](也就是proto)链接
- 它使this指向新创建的对象
- 通过new创建的每个对象将最终被[Prototype]链接到这个函数的prototype对象上
- 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用
| |
| function objectFactory() { |
| const obj = new Object(); |
| const Constructor = [].shift.call(arguments); |
| |
| obj.__proto__ = Constructor.prototype; |
| |
| const ret = Constructor.apply(obj, arguments); |
| |
| return typeof ret === "object" ? ret : obj; |
| } |
实现prototype继承
所谓的原型链继承就是让新实例的原型等于父类的实例:
| |
| function SupperFunction(flag1){ |
| this.flag1 = flag1; |
| } |
| |
| |
| function SubFunction(flag2){ |
| this.flag2 = flag2; |
| } |
| |
| |
| var superInstance = new SupperFunction(true); |
| |
| |
| SubFunction.prototype = superInstance; |
| |
| |
| var subInstance = new SubFunction(false); |
| |
| subInstance.flag1; |
| subInstance.flag2; |
渲染几万条数据不卡住页面
渲染大数据时,合理使用createDocumentFragment和requestAnimationFrame,将操作切分为一小段一小段执行。
| setTimeout(() => { |
| |
| const total = 100000; |
| |
| const once = 20; |
| |
| const loopCount = Math.ceil(total / once); |
| let countOfRender = 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.innerText = Math.floor(Math.random() * total); |
| fragment.appendChild(li); |
| } |
| ul.appendChild(fragment); |
| countOfRender += 1; |
| loop(); |
| } |
| function loop() { |
| if(countOfRender < loopCount) { |
| window.requestAnimationFrame(add); |
| } |
| } |
| loop(); |
| }, 0) |
实现apply方法
apply原理与call很相似,不多赘述
| |
| Function.prototype.myapply = function(context, arr) { |
| var context = Object(context) || window; |
| context.fn = this; |
| |
| var result; |
| if (!arr) { |
| result = context.fn(); |
| } else { |
| var args = []; |
| for (var i = 0, len = arr.length; i < len; i++) { |
| args.push("arr[" + i + "]"); |
| } |
| result = eval("context.fn(" + args + ")"); |
| } |
| |
| delete context.fn; |
| return result; |
| }; |