irpas技术客

【面试】前端面试常考手写题 - JavaScript - CSS_YK菌

网络投稿 4169

持续更新中…

文章目录 1. CSS布局1.1 盒子居中对齐1.2 两栏布局1.3 三栏布局双飞翼圣杯 1.4 品字布局1.5 画图三角形圆形筛子 2. JS手写函数2.1 手写原生PromisePromise.allPromise.racePromise.resolve()Promise.reject()callapplybindnewinstanceofObject.createObject.assignsetTimeout模拟setIntervalsetInterval模拟setTimeoutmapreducefilterfindfindIndexeverysome 2.2 自定义函数类型判断数组去重unique数组合并方法 cancat数组切片方法 slice数组扁平化flatten数组分块方法 chunk数组取差集 difference删除数组部分元方法 pull pullAll得到数组部分元方法 drop dropRight深拷贝深比较自定义合并对象mergeObject节流防抖寄生式组合继承ES6继承一次性执行函数柯里化Ajax随机数字符串反转检测回文字符串截取字符串 3. 场景题自定义DOM事件监听自定义事件总线eventBus自定义消息订阅与发布PubSub url转objectobject转url手机号3-3-4分割千分位格式化数字解析url参数实现一个通用函数判断数据类型字符串转驼峰生成随机字符串生成随机hex颜色rgb颜色转成hexhex颜色转成rgb

1. CSS布局 1.1 盒子居中对齐

【CSS】实现盒子居中对齐的七种方法

知道父子盒子宽高的情况

定位:子绝父相,margin 纯计算【不推荐】

.parent { width: 500px; height: 500px; position: relative; } .child { width: 200px; height: 200px; position: absolute; margin-top:150px; margin-left:150px; }

父子盒子宽高不知道的情况

定位 + margin

.parent { position: relative; } .child { position: absolute; top: 0; left: 0; bottom: 0; right: 0; margin: auto; }

定位 + transform

.parent { position: relative; } .child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }

flex

.parent { display: flex; justify-content: center; align-items: center; }

table-cell

.parent { display: table-cell; vertical-align: middle; } .child { margin: 0 auto; }

inline-block

.parent { text-align: center; line-height: 500px; } .child { display: inline-block; } 1.2 两栏布局 1.3 三栏布局

【CSS】看了C站CSDN源码,这次带你彻底搞清楚经典的 双飞翼布局 与 圣杯布局 - margin负值 - 浮动 - padding

双飞翼 <header>头部</header> <div class="main "> <div class="center">主区域</div> </div> <div class="left ">左区域</div> <div class="right ">右区域</div> <footer>底部</footer> header { background-color: pink; } .main { width: 100%; float: left; background-color: skyblue; } .center { margin: 0 100px; } .left { width: 100px; background-color: green; float: left; margin-left: -100%; } .right { width: 100px; background-color: red; float: left; margin-left: -100px; } footer { background-color: pink; clear: both } 圣杯 <header>头部</header> <div class="clearfix wrapper"> <div class="center">主区域</div> <div class="left">左区域</div> <div class="right">右区域</div> </div> <footer>底部</footer> header { background-color: pink; } .clearfix::after { content: ''; display: block; clear: both; } .wrapper { padding: 0 100px; } .center { width: 100%; background-color: skyblue; float: left; } .left { background-color: green; float: left; width: 100px; margin-left: -100%; position: relative; left: -100px; } .right { width: 100px; background-color: red; float: left; margin-right: -100px; } footer { background-color: pink; } 1.4 品字布局 1.5 画图 三角形 圆形 筛子 2. JS手写函数 2.1 手写原生 Promise /** * Promise构造函数 * @param {*} executor 执行器函数(同步执行)(resolve, reject) => {} */ function Promise(executor) { const self = this; // 保存当前实例对象的this的值 // 添加属性 self.PromiseState = PENDING // 给promise对象指定status属性,初始值为pending self.PromiseResult = null // 给promise对象指定一个用于存储结果数据的属性 self.callbacks = [] // 存的是对象 每个元素的结构:{onResolved() {}, onRejected() {}} /** * executor内部定义成功时调用的函数 * @param {*} value 成功的值 * @returns */ function resolve(value) { // 如果当前状态不是pending,直接结束 if (self.PromiseState !== PENDING) return // 1. 修改对象的状态(promiseState)为 fulfilled self.PromiseState = RESOLVED // 2. 设置对象结果值(promiseResult)为 value self.PromiseResult = value // 如果有待执行的callback函数,立即【异步】执行回调函数onResolved if (self.callbacks.length > 0) { setTimeout(() => { // 放入队列中执行所有成功的回调 self.callbacks.forEach(callbacksObj => { callbacksObj.onResolved(value) }) }, 0) } } /** * executor内部定义失败时调用的函数 * @param {*} reason 失败的原因 * @returns */ function reject(reason) { // 如果当前状态不是pending,直接结束 if (self.PromiseState !== PENDING) return // 1. 修改对象的状态(promiseState)为 rejected self.PromiseState = REJECTED // 2. 设置对象结果值(promiseResult)为 reason self.PromiseResult = reason // 如果有待执行的callback函数,立即【异步】执行回调函数onRejected if (self.callbacks.length > 0) { setTimeout(() => { // 放入队列中执行所有失败的回调 self.callbacks.forEach(callbacksObj => { callbacksObj.onRejected(reason) }) }, 0) } } // 立即【同步】执行executor函数 try { executor(resolve, reject) } catch(error) { // 如果执行器抛出异常,promise对象变成rejected状态 reject(error) } } Promise.all

接收的是一个promise数组promises 当传入的所有promise都成功,则返回一个成功的promise,值为所有成功promise的值的数组【注意顺序】 当传入的promises中有一个不成功,则返回一个失败的promise,其值为这个失败的promise的值

思路 首先创建一个结果数组,再创建要给计数变量

Promise.all = function(promises){ return new Promise((resolve, reject)=>{ let count = 0 const values = [] for(let i = 0; i < promises.length; i++){ Promise.resolve(promise[i]).then(value => { count++ values[i] = value if(count === promises.length){ resolve(values) } }, reason => { reject(reason) }) } }) } Promise.race

接收的是一个promise数组promises 返回一个promise,状态和值都取决于第一个改变状态的promise

Promise.race = function(promises) { return new Promise((resolve, reject)=>{ for(let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]).then(value => { resolve(value) }, reason => { reject(reason) } } }) } Promise.resolve() Promise.resolve = function(value) { return new Promise((resolve, reject) => { if(value instanceof Promise) { value.then(resolve, reject) } else { resolve(value) } } } Promise.reject() Promise.reject = function(reason) { return new Promise((resolve, reject)=> { reject(reason) }) } call function call(Fn, obj, ...args) { if (obj === undefined || obj === null) { // 表示全局对象(ES11新增特性) obj = globalThis; } // 为 obj 添加临时的方法 obj.temp = Fn; // 调用 temp 方法 let result = obj.temp(...args); // 删除tempfangfa delete obj.temp; // 返回执行结果 return result; } Function.prototype.myCall = function(context,...args){ let cxt = context || window; //将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this) //新建一个唯一的Symbol变量避免重复 let func = Symbol() cxt[func] = this; args = args ? args : [] //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向 const res = args.length > 0 ? cxt[func](...args) : cxt[func](); //删除该方法,不然会对传入对象造成污染(添加该方法) delete cxt[func]; return res; } apply function apply(Fn, obj, arr) { if (obj === undefined || obj === null) { obj = globalThis; } // 为obj添加临时方法 obj.temp = Fn; // 执行方法 let result = obj.temp(...arr); // 删除临时属性 delete obj.temp; // 返回结果 return result; } Function.prototype.myApply = function(context,args = []){ let cxt = context || window; //将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this) //新建一个唯一的Symbol变量避免重复 let func = Symbol() cxt[func] = this; //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向 const res = args.length > 0 ? cxt[func](...args) : cxt[func](); delete cxt[func]; return res; } bind function bind(Fn, obj, ...args) { // 返回一个新的函数 return function (...args2) { // 执行 call 函数、 return call(Fn, obj, ...args, ...args2); }; } Function.prototype.myBind = function (context, ...args) { //新建一个变量赋值为this,表示当前函数 const fn = this //判断有没有传参进来,若为空则赋值[] args = args ? args : [] //返回一个newFn函数,在里面调用fn return function newFn(...newFnArgs) { if (this instanceof newFn) { return new fn(...args, ...newFnArgs) } return fn.apply(context, [...args,...newFnArgs]) } } new function newInstance(Fn, ...args) { // 1. 创建新对象 // 创建空的object实例对象,作为Fn的实例对象 const obj = {}; // 修改新对象的原型对象 // 将Fn的prototype(显式原型)属性赋值给obj的__proto__(隐式原型)属性 obj.__proto__ = Fn.prototype; // 2. 修改函数内部this指向新对象,并执行 // const result = Fn.call(obj, ...args); // 3. 返回新对象 // return obj // 与new保持一直,如果构造函数有返回值,返回值是对象a就返回对象a,否则返回实例对象 return result instanceof Object ? result : obj; } instanceof function myInstanceOf(obj, Fn) { // 得到obj的隐式原型对象 let protoObj = obj.__proto__; // 原型对象存在,就遍历原型链 while (protoObj) { // 实例对象的隐式原型 等于 构造函数的显式原型 就返回true if (protoObj === Fn.prototype) { return true; } // 不相等就根据原型链一直往上找 直到最后为null protoObj = protoObj.__proto__; } return false; } Object.create //简略版 function myCreate(obj){ // 新声明一个函数 function C(){}; // 将函数的原型指向obj C.prototype = obj; // 返回这个函数的实力化对象 return new C() } //官方版Polyfill if (typeof Object.create !== "function") { Object.create = function (proto, propertiesObject) { if (typeof proto !== 'object' && typeof proto !== 'function') { throw new TypeError('Object prototype may only be an Object: ' + proto); } else if (proto === null) { throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument."); } if (typeof propertiesObject !== 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument."); function F() {} F.prototype = proto; return new F(); }; } Object.assign Object.myAssign = function(target, ...source) { if (target == null) { throw new TypeError('Cannot convert undefined or null to object') } let ret = Object(target) source.forEach(function(obj) { if (obj != null) { for (let key in obj) { if (obj.hasOwnProperty(key)) { ret[key] = obj[key] } } } }) return ret } setTimeout模拟setInterval const simulateSetInterval = (func, timeout) => { let timer = null const interval = () => { timer = setTimeout(() => { // timeout时间之后会执行真正的函数func func() // 同时再次调用interval本身,是不是有点setInterval的感觉啦 interval() }, timeout) } // 开始执行 interval() // 返回用于关闭定时器的函数 return () => clearTimeout(timer) } const cancel = simulateSetInterval(() => { console.log(1) }, 300) setTimeout(() => { cancel() console.log('一秒之后关闭定时器') }, 1000) setInterval模拟setTimeout const simulateSetTimeout = (fn, timeout) => { let timer = null timer = setInterval(() => { // 关闭定时器,保证只执行一次fn,也就达到了setTimeout的效果了 clearInterval(timer) fn() }, timeout) // 返回用于关闭定时器的方法 return () => clearInterval(timer) } const cancel = simulateSetTimeout(() => { console.log(1) }, 1000) // 一秒后打印出1 map /** * 封装map函数 * @param {Array} arr * @param {Function} callback * @returns */ export function map(arr, callback) { // 声明一个空的结果数组 let result = []; // 遍历数组 for (let i = 0; i < arr.length; i++) { // 执行回调,将回调函数执行结果添加到结果数组中 result.push(callback(arr[i], i)); } return result; } reduce /** * 封装reduce函数 * @param {Array} arr * @param {Function} callback * @param {number} initValue * @returns */ export function reduce(arr, callback, initValue) { // 声明变量 let result = initValue; for (let i = 0; i < arr.length; i++) { // 执行回调 result = callback(result, arr[i]); } return result; } filter /** * 封装filter函数 * @param {Array} arr * @param {Function} callback * @returns */ export function filter(arr, callback) { // 定义空数组接收 回调返回为true的 元素 let result = []; for (let i = 0; i < arr.length; i++) { let res = callback(arr[i], i); // 如果回调结果为真 就压入结果数组中 if (res) { result.push(arr[i]); } } return result; } find /** * 封装find函数 * @param {Array} arr * @param {Function} callback * @returns */ export function find(arr, callback) { for (let i = 0; i < arr.length; i++){ let res = callback(arr[i], i) if (res) { // 返回 满足条件的第一个元素 return arr[i] } } // 如果没有遇到满足条件的就返回undefined return undefined } findIndex /** * 封装findIndex函数 * @param {Array} arr * @param {Function} callback * @returns */ export function findIndex(arr, callback) { for (let i = 0; i < arr.length; i++){ let res = callback(arr[i], i) if (res) { // 返回 满足条件的第一个元素的小标 return i } } // 如果没有遇到满足条件的就返回 -1 return -1 } every /** * 封装every函数 * @param {Array} arr * @param {Function} callback * @returns */ export function every(arr, callback) { for (let i = 0; i < arr.length; i++){ let res = callback(arr[i]) // 只要有一个不满足就返回false if (!res) { return false } } return true } some /** * 封装some函数 * @param {Array} arr * @param {Function} callback * @returns */ export function some(arr, callback) { for (let i = 0; i < arr.length; i++){ let res = callback(arr[i]) if (res) { // 只要有一个满足就返回 true return true } } return false } 2.2 自定义函数 类型判断 function type(data) { var toString = Object.prototype.toString; var dataType = toString .call(data) .replace(/\[object\s(.+)\]/, "$1") .toLowerCase() return dataType }; 数组去重unique /** * 利用forEach()和indexOf()双重循环效率低 * @param {Array} arr * @returns */ function unique(arr) { const result = []; arr.forEach((item) => { if (result.indexOf(item) === -1) { result.push(item); } }); return result; } /** * 利用forEach()和对象容器 * @param {Array} arr * @returns */ function unique1(arr) { const result = []; const obj = {}; arr.forEach((item) => { if (!obj.hasOwnProperty(item)) { obj[item] = true; result.push(item); } }); return result; } /** * 利用ES6中Set的特性 * @param {Array} arr * @returns */ function unique2(arr) { return [...new Set(arr)]; } function objSort(obj){ let newObj = {} //遍历对象,并将key进行排序 Object.keys(obj).sort().map(key => { newObj[key] = obj[key] }) //将排序好的数组转成字符串 return JSON.stringify(newObj) } function unique(arr){ let set = new Set(); for(let i=0;i<arr.length;i++){ let str = objSort(arr[i]) set.add(str) } //将数组中的字符串转回对象 arr = [...set].map(item => { return JSON.parse(item) }) return arr } 数组合并方法 cancat /** * 封装 concat 方法 * @param {Array} arr * @param {...any} args * @returns */ function concat(arr, ...args) { const result = [...arr]; args.forEach((item) => { // 判断item是否是数组,是数组就要展开入栈 if (Array.isArray(item)) { result.push(...item); } else { result.push(item); } }); return result; } 数组切片方法 slice function slice(arr, begin, end) { if (arr.length === 0) { return []; } begin = begin || 0; if (begin >= arr.length) { return []; } end = end || arr.length; if (end > arr.length) { end = arr.length; } if (end < begin) { return []; } const result = []; for (let i = begin; i < end; i++) { result.push(arr[i]); } return result; } 数组扁平化flatten /** * 取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中 * 递归方式 * @param {*} arr */ function flatten(arr) { let result = []; arr.forEach((item) => { // 判断是不是数组 if (Array.isArray(item)) { result = result.concat(flatten(item)); } else { result = result.concat(item); } }); return result; } function flatten1(arr) { let result = [...arr]; // 判断result里有没有子数组 while (result.some((item) => Array.isArray(item))) { result = [].concat(...result); } return result; } 数组分块方法 chunk /** * 将arr拆分成多个size长度的区块,每个区块组成小数组,整体是一个二维数组 * @param {Array} arr * @param {Number} size * @returns */ function chunk(arr, size = 1) { if (arr.length === 0) { return []; } let result = []; let temp = []; arr.forEach((item) => { // 这里先推入temp再往temp中推入元素 // 判断temp元素长度是否为0 if (temp.length === 0) { result.push(temp); } // 将元素压入到临时数组temp中 temp.push(item); // temp满了就清空 if (temp.length === size) { temp = []; } }); return result; } 数组取差集 difference /** * 数组取差集 在arr1中存在且不在arr2中存在的元素 * @param {Array} arr1 * @param {Array} arr2 * @returns */ function difference(arr1, arr2 = []) { if (arr1.length === 0) { return []; } if (arr2.length === 0) { return arr1.slice(); } const result = arr1.filter((item) => !arr2.includes(item)); return result; } 删除数组部分元方法 pull pullAll /** * 删除原数组中与value相同的元素,返回所有删除元素的数组 * @param {Array} arr * @param {...any} values */ function pull(arr, ...values) { const result = []; for (let i = 0; i < arr.length; i++) { // 判断arr中当前元素是否存在于values数组中 if (values.includes(arr[i])) { // 先将该元素存入result数组中 result.push(arr[i]); // 然后再删除该元素 arr.splice(i, 1); // 因为删除的元素,下标自减 i--; } } return result; } function pullAll(arr, values) { return pull(arr, ...values); } 得到数组部分元方法 drop dropRight /** * 得到arr过滤掉左边size个后剩余元素组成的数组,不改变arr * @param {Array} arr * @param {Number} size * @returns */ function drop(arr, size) { // return arr.filter((valur, index) => { // return index >= size; // }); return arr.filter((value, index) => index >= size); } /** * 得到arr过滤掉右边size个后剩余元素组成的数组,不改变arr * @param {Array} arr * @param {Number} size * @returns */ function dropRight(arr, size) { return arr.filter((value, index) => index < arr.length - size); } 深拷贝 /** * 深拷贝乞丐版 函数(方法)属性会丢失,循环引用会出错 * @param {*} target * @returns */ function deepClone1(target) { // 通过数据创建JSON格式的字符串 let str = JSON.stringify(target); // 将 JSON 字符串创建为JS数据 let data = JSON.parse(str); return data; } /** * 使用递归 * 解决问题1: 函数属性不会丢失 * 循环引用会出错 * @param {*} target */ function deepClone2(target) { if (typeof target === "object" && target !== null) { // 创建一个容器 const result = Array.isArray(target) ? [] : {}; // 遍历target for (let key in target) { // 检测该属性是否为对象本身的属性(不能拷贝原型对象上的属性) if (target.hasOwnProperty(key)) { result[key] = deepClone2(target[key]); } } return result; } else { return target; } } /** * 使用递归 * 解决问题1: 函数属性不会丢失 * 解决问题2: 循环引用不会出错 * @param {*} target */ function deepClone3(target, map = new Map()) { if (typeof target === "object" && target !== null) { // 克隆数据之前进行判断,查看数据之前是否被克隆过 let cache = map.get(target); if (cache) { return cache; } // 创建一个容器 const result = Array.isArray(target) ? [] : {}; // 将新的结果保存到容器中 map.set(target, result); // 遍历target for (let key in target) { // 检测该属性是否为对象本身的属性(不能拷贝原型对象上的属性) if (target.hasOwnProperty(key)) { result[key] = deepClone3(target[key], map); } } return result; } else { return target; } } function deepClone(obj) { function isObject(o) { return (typeof o === 'object' || typeof o === 'function') && o !== null } if (!isObject(obj)) { throw new Error('非对象') } let isArray = Array.isArray(obj) let newObj = isArray ? [...obj] : { ...obj } Reflect.ownKeys(newObj).forEach(key => { newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] }) return newObj } /** * 最终版 优化遍历性能 * 数组: while | for | forEach() 优于 for-in | keys()&forEach() * 对象: for-in 与 keys()&forEach() 差不多 * @param {*} target */ function deepClone4(target, map = new Map()) { if (typeof target === "object" && target !== null) { // 克隆数据之前进行判断,查看数据之前是否被克隆过 let cache = map.get(target); if (cache) { return cache; } // 创建一个容器 const result = Array.isArray(target) ? [] : {}; // 将新的结果保存到容器中 map.set(target, result); // 优化遍历 // 如果目标数据是数组 使用 forEach 循环 if (Array.isArray(target)) { target.forEach((item, index) => { result[index] = deepClone4(item, map); }); } else { // 如果是对象,获取所有的键名, 再 forEach 遍历 Object.keys(target).forEach((key) => { result[key] = deepClone4(target[key], map); }); } // 遍历target for (let key in target) { // 检测该属性是否为对象本身的属性(不能拷贝原型对象上的属性) if (target.hasOwnProperty(key)) { result[key] = deepClone4(target[key], map); } } return result; } else { return target; } } 深比较 function isObject(obj) { return typeof obj === object && obj !== null; } // 深度比较 function isEqual(obj1, obj2) { if (!isObject(obj1) || !isObject(obj2)) { // 值类型,直接判断【一般不会传函数,不考虑函数】 return obj1 === obj2; } if (obj1 === obj2) { return true; } // 两个都是对象或数组,而且不相等 // 1. 先判断键的个数是否相等,不相等一定返回false const obj1Keys = Object.keys(obj1); const obj2Keys = Objext.keys(obj2); if (obj1Keys.length !== obj2Keys.length) { return false; } // 2. 以obj1为基准,和obj2依次递归比较 for (let key in obj1) { // 递归比较 const res = isEqual(obj1[key], obj2[key]); if (!res) { return false; } } // 3. 全相等 return true; } 自定义合并对象mergeObject /** * 合并多个对象,返回一个合并后的对象,不改变原对象 * @param {...any} objs * @returns */ function mergeObject(...objs) { const result = {}; // 遍历objs得到一个个obj objs.forEach((obj) => { // 遍历obj的键得到一个个key Object.keys(obj).forEach((key) => { // 判断result对象中有没有key值属性 if (!result.hasOwnProperty(key)) { // 如果没有,就将obj中的key值属性添加到result中 result[key] = obj[key]; } else { // 如果result有了,就合并属性 result[key] = [].concat(result[key], obj[key]); } }); }); return result; } 节流

使用定时器版本

function throttle(callback, wait=100){ let flag = true return function(){ if(flag){ flag = false setTimeout(()=>{ flag = true callback(...arguments) }, wait) } } }

不使用定时器版本

function throttle(callback, wait=100){ let start = 0 return function(){ let now = Date.now() if(now - start >= wait) { callback(...arguments) start = now } } } 防抖

连续触发事件,只有最后一次成功

function debounce(callback, wait=100) { // 定时器变量 let timeId = undefined; // 返回一个函数 return function () { // 有定时器,就清空,取消定时器,阻止回调执行 if (timeId !== undefined) { // 清空定时器 clearTimeout(timeId); } // 启动定时器 timeId = setTimeout(() => { // 执行回调 callback(...arguments); // 执行完了重置id timeId = undefined; }, wait); }; } 寄生式组合继承 function Person(obj) { this.name = obj.name this.age = obj.age } Person.prototype.add = function(value){ console.log(value) } var p1 = new Person({name:"番茄", age: 18}) function Person1(obj) { Person.call(this, obj) this.sex = obj.sex } // 这一步是继承的关键 Person1.prototype = Object.create(Person.prototype); Person1.prototype.constructor = Person1; Person1.prototype.play = function(value){ console.log(value) } var p2 = new Person1({name:"鸡蛋", age: 118, sex: "男"}) ES6继承 class People{ constructor(name='wang',age='27'){ this.name = name; this.age = age; } eat(){ console.log(`${this.name} ${this.age} eat food`) } } //继承父类 class Woman extends People{ constructor(name = 'ren',age = '27'){ //继承父类属性 super(name, age); } eat(){ //继承父类方法 super.eat() } } let wonmanObj=new Woman('xiaoxiami'); wonmanObj.eat(); 一次性执行函数 function once(fn) { return fucntion (...args) { if(fn) { const ret = fn.apply(this, args) fn = null return ret } } } 柯里化 function curry(fn,...args){ let fnLen = fn.length, argsLen = args.length; //对比函数的参数和当前传入参数 //若参数不够就继续递归返回curry //若参数够就调用函数返回相应的值 if(fnLen > argsLen){ return function(...arg2s){ return curry(fn,...args,...arg2s) } }else{ return fn(...args) } } Ajax function ajax(url) { const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open('GET', url, true) xhr.onreadystatechange = function () { if(xhr.readyState === 4) { if(xhr.status === 200) { resolve( JSON.parse(xhr.responseText) ) } else if (xhr.status === 404) { reject(new Error('404 not found')) } } } xhr.send(null) }) return p } 随机数 function getRandom(min, max) { return Math.floor(Math.random() * (max - min)) + min } 字符串反转 function reverseString(str) { // 将字符串转换成数组 let arr = str.split(""); // 使用数组的翻转方法 arr.reverse(); // 将数组拼接成字符串 let s = arr.join(""); return s; } 检测回文字符串 function palindrome(str) { return reverseString(str) === str; } 截取字符串 function truncate(str, size) { return str.slice(0, size) + "..."; } 3. 场景题 自定义DOM事件监听 /** * 自定义事件监听 * @param {String} el 父元素选择器 * @param {String} type 事件类型 * @param {Function} fn 回调函数 * @param {String} selector 子元素选择器 */ function myAddEventListener(el, type, fn, selector) { // 判断el的类型,获取元素 if (typeof el === "string") { el = document.querySelector(el); } // 事件判定 // 若没有传第四个参数(子元素选择器),则给el元素绑定事件 if (!selector) { el.addEventListener(type, fn); } else { el.addEventListener(type, function(e) { // 获取点击的目标事件源 const target = e.target; // 判断选择器与目标元素是否相符 if (target.matches(selector)) { // 相符就执行回调,否则就什么都不做 fn.call(target,e); } }); } } 自定义事件总线eventBus // 定义事件总线 const eventBus = { // 保存eventName类型与回调callback的容器 callbackObj: {}, }; /** * 绑定事件监听,保存回调函数 * @param {*} eventName 事件名称 * @param {*} callback 回调函数 */ eventBus.on = function (eventName, callback) { if (this.callbackObj[eventName]) { // 如果 callbackObj 属性中存在该类型事件,就压入 this.callbackObj[eventName].push(callback); } else { // 如果 callbackObj 属性中不存在该类型事件,就添加成数组 this.callbackObj[eventName] = [callback]; } }; /** * 分发事件,触发容器中的该属性eventName的回调函数 * @param {*} eventName 事件名称 * @param {*} data 数据 */ eventBus.emit = function (eventName, data) { // 判断callbackObj中有没有回调函数 if (this.callbackObj[eventName] && this.callbackObj[eventName].length > 0) { // 遍历数组 this.callbackObj[eventName].forEach((callback) => { // 执行回调函数 callback(data); }); } }; /** * 移除事件监听 * @param {*} eventName 事件名称 */ eventBus.off = function (eventName) { // 若传入了 eventName 事件类型 if (eventName) { // 只是删除 eventName 对应的事件回调 delete this.callbackObj[eventName]; } else { // 否则全部删除 this.callbacksObj = {}; } }; export default eventBus; 自定义消息订阅与发布PubSub const PubSub = { // 订阅编号 id: 1, // 频道与回调保存的容器 callbacks: { // 例子 // pay: { // token_1: fn, // token_2: fn2, // }, }, }; /** * 订阅频道 * @param {*} channel 频道 * @param {*} callback 回调 */ PubSub.subscribe = function (channel, callback) { // 为每一个订阅创建唯一的编号 let token = "token_" + this.id++; if (this.callbacks[channel]) { // 如果callbacks中有这个频道就直接压入 this.callbacks[channel][token] = callback; } else { // 没有就创建一个 this.callbacks[channel] = { [token]: callback, }; } // 返回频道订阅的ID return token; }; /** * 异步执行 发布消息 * @param {*} channel 频道 * @param {*} data 数据 */ PubSub.publish = function (channel, data) { // 获取当前频道所有回调 if (this.callbacks[channel]) { // 启动定时器异步执行任务 setTimeout(() => { // Object.values() 返回对象值的数组 Object.values(this.callbacks[channel]).forEach((callback) => { callback(data); }); }, 0); } }; /** * 同步执行 发布消息 * @param {*} channel * @param {*} data */ PubSub.publishSync = function (channel, data) { // 获取当前频道所有回调 if (this.callbacks[channel]) { // Object.values() 返回对象值的数组 Object.values(this.callbacks[channel]).forEach((callback) => { callback(data); }); } }; /** * 取消订阅 * 三种情况 * 1. 没有传值 全删 * 2. 传的是id 删id对应的频道 * 3. 传的是频道名 删该频道 * @param {*} flag */ PubSub.unsubscribe = function (flag) { if (flag === undefined) { // 清空所有订阅 this.callbacks = {}; } else if (typeof flag === "string") { // 判断是否是 token_ 开头的字符串 if (flag.indexOf("token_") === 0) { // 是订阅id // 遍历callbacks 找到具体的token 对象值的数组用find方法找到有flag的对象 let callbackObj = Object.values(this.callbacks).find((obj) => obj.hasOwnProperty(flag) ); // 判断是不是存在 if (callbackObj) { // 有这个对象 delete callbackObj[flag]; } } else { // 是频道名称 delete this.callbacks[flag]; } } else { throw new Error("如果传入参数, 必须是字符串类型"); } }; url转object // url参数转object(?后面的参数) function paramStringToObject (paramString) { if (!paramString || paramString.length === 0) { return {} } let paramList = [] let paramObj = {} if (paramString.indexOf('&') >= 0) { paramList = paramString.split('&') } else { paramList[0] = paramString } paramList.forEach(value => { paramObj[value.split('=')[0]] = decodeURIComponent(value.split('=')[1]) }) return paramObj } object转url // object转为url参数(?后面的参数) function objectToParamString (paramObj) { if (!paramObj) { return '' } let paramList = [] Object.keys(paramObj) && Object.keys(paramObj).forEach(key => { let val = paramObj[key] if (val.constructor === Array) { val.forEach(_val => { paramList.push(key + '=' + _val) }) } else { paramList.push(key + '=' + val) } }) return paramList.join('&') } 手机号3-3-4分割 // 适合纯11位手机 const splitMobile = (mobile, format = '-') => { return String(mobile).replace(/(?=(\d{4})+$)/g, format) } // 适合11位以内的分割 const splitMobile2 = (mobile, format = '-') => { return String(mobile).replace(/(?<=(\d{3}))/, format).replace(/(?<=([\d\-]{8}))/, format) } console.log(splitMobile(18379802267)) // 183-7980-2267 console.log(splitMobile2(18379876545)) // 183-7987-6545 千分位格式化数字 // 金额转千分位 const formatPrice = (number) => { number = '' + number const [ integer, decimal = '' ] = number.split('.') return integer.replace(/\B(?=(\d{3})+$)/g, ',') + (decimal ? '.' + decimal : '') } console.log(formatPrice(123456789.3343)) // 123,456,789.3343 解析url参数 const getQueryByName = (name) => { const queryNameRegex = new RegExp(`[?&]${name}=([^&]*)(&|$)`) const queryNameMatch = window.location.search.match(queryNameRegex) // 一般都会通过decodeURIComponent解码处理 return queryNameMatch ? decodeURIComponent(queryNameMatch[1]) : '' } 实现一个通用函数判断数据类型 const getType = (s) => { const r = Object.prototype.toString.call(s) return r.replace(/\[object (.*?)\]/, '$1').toLowerCase() } // 测试 console.log(getType()) // undefined console.log(getType(null)) // null console.log(getType(1)) // number console.log(getType('YK菌')) // string console.log(getType(true)) // boolean console.log(getType(Symbol('YK菌'))) // symbol console.log(getType({})) // object console.log(getType([])) // array 字符串转驼峰 const camelCase = (string) => { const camelCaseRegex = /[-_\s]+(.)?/g return string.replace(camelCaseRegex, (match, char) => { return char ? char.toUpperCase() : '' }) } // 测试 console.log(camelCase('foo Bar')) // fooBar console.log(camelCase('foo-bar--')) // fooBar console.log(camelCase('foo_bar__')) // fooBar 生成随机字符串 function randomString(len = 32) { var chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789"; var length = chars.length; var pwd = ""; for (i = 0; i < len; i++) { pwd += chars.charAt(Math.floor(Math.random() * length)); } return pwd; } 生成随机hex颜色 const randomHexColorCode = () => { let n = ((Math.random() * 0xfffff) | 0).toString(16); return ( "#" + (n.length !== 6 ? ((Math.random() * 0xf) | 0).toString(16) + n : n) ); }; rgb颜色转成hex // 将rgb颜色转成hex 输入(24,12,255) function colorRGB2Hex(r,g,b) { let hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); return hex; } hex颜色转成rgb // 将hex颜色转成rgb function hexToRgba(hex, opacity) { let RGBA = "rgba(" + parseInt("0x" + hex.slice(1, 3)) + "," + parseInt("0x" + hex.slice(3, 5)) + "," + parseInt( "0x" + hex.slice(5, 7)) + "," + opacity + ")"; return { red: parseInt("0x" + hex.slice(1, 3)), green: parseInt("0x" + hex.slice(3, 5)), blue: parseInt("0x" + hex.slice(5, 7)), rgba: RGBA } }


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #面试前端面试常考手写题 #JavaScript #CSS #初版还在不断完善中