手写深拷贝
function deepClone(obj) { let newObj = {}; if(typeof obj !== "object" || obj == null) { return obj; } for(let key in obj) { if(typeof obj[key] !== "object") { // 基本类型加function newObj[key] = obj[key]; }else { if(isType(obj[key]) == "Array") { // 判断数组 let newArray = []; for(let index in obj[key]) { // 递归调用,然后需要返回当前非对象的值,然后加入 newArray[index] = deepClone(obj[key][index]) } newObj[key] = newArray; }else { // 对象类型 let newObject = {}; for(let newKey in obj[key]) { newObject[newKey] = deepClone(obj[newKey]); } } } } return newObj; } function isType(value) { const type = Object.prototype.toString.call(value); switch (type) { case "[object Array]": return "Array" case "[object Object]": return "Object" default: return typeof value; } }
手写节流和防抖
手写call / apply / bind
简单的实现
- 思路就是获取当前调用的函数,然后将其作为当前指定的this属性值。为了避免覆盖当前this对象的已有属性,那么将设置symbol值作为属性名。其他的都是一些边界判断。
const myCall = function (context, ...args) { let func = this context = context || window if (typeof func !== 'function') throw new TypeError('this is not a function') let caller = Symbol('caller') context[caller] = func let res = context[caller](...args) delete context[caller] return res } Function.prototype.myCall = myCall
function myApply(context, arr) { let func = this; context = context || window; if (typeof func !== 'function') throw new TypeError('this is not a function') //唯一的键值 let caller = Symbol('caller') context[caller] = func //函数返回值 let res; if (!arr) { res = context[caller]() } else { res = context[caller](...arr) } //删除该函数 delete context[caller] return res } Function.prototype.myApply = myApply;
function myBind(context, ...args) { const fn = this; if(typeof fn !== "function") throw new TypeError("this is not function"); context = context || window; // 因为bind需要返回一个函数,然后利用call、apply改变this即可。并传入参数。 return function(...args2){ fn.call(context, ...args,...args2) }; } Function.prototype.myBind = myBind
手写Promise.all / Promise.race / Promise.allSettled
实现它,我们必须的知道他们的作用是啥?
- all:如果传入的promises全部都变成fulfilled,那么promise的状态将变为fulfilled。如果有一个promises状态变为rejected,那么promsie状态将变为rejected。
function all(promises) { return new Promise((resolve, reject) => { let resArr = []; for(let promise of promises) { promise.then(res => { resArr.push(res); if(resArr.length === promises.length) { // 每次成功都需要判断 resolve(resArr) } }) promise.catch((err) => { reject(err) }) } }) }
- race: 传入的promises最先改变状态的promise决定当前的promise状态。
function race(promises) { return new Promise((resolve, reject) => { for(let promise of promises) { promise.then(res => { resolve(res) }) promise.catch((err) => { reject(err) }) } }) }
- allSettled: 当传入的promises的状态全部都变化后,promise的状态变为fulfilled。
function allSettled(promises) { return new Promise((resolve) => { let resArr = [] for(let promise of promises) { promise.then(res => { resArr.push({ status: "fulfilled", value: res }) if(resArr.length === promises.length) { resolve(resArr) } }) promise.catch((err) => { resArr.push({ status: "rejected", value: err }) if(resArr.length === promises.length) { // 可能promise有异步任务,所以每一次状态改变都需要判断 resolve(resArr) } }) } }) }
手写括号匹配
不知道是不是LeetCode-20。如果是的话,这一题使用栈的思想最好理解。
数组去重
- 通过set特点实现。
// set数据结构 Array.from(new Set(arr)) [...new Set(arr)]
- 通过对象键名不能重复实现。
// 利用对象的键不能重复的特点 function removeDup(arr) { const obj = {} arr.forEach((item) => { if(!obj[item]) { obj[item] = true } }) return Object.keys(obj) }
将奇数排在前面,偶数排在后面。要求时间复杂度O(n)。空间复杂度O(1)(不能用splice)
不知道这一题是不是这个意思。
function oddEven(arr) { // 先排序,这个排序算法可以自己去查询时间复杂度为o(n)的算法。 arr = arr.sort((a, b) => { return a - b; }) const odd = []; // 奇数 const even = []; // 偶数 for(let item of arr) { if(item % 2 === 0) { // 偶数 even.push(item) }else { // 奇数 odd.push(item) } } return [...odd, ...even] }
解析出URL中所有的部分
通过URL类解析出来的是这个样子,我们只需要处理一下searchParams和protocol即可
网络异常,图片无法展示
|
function parseUrl(url) { const resUrlObj = new URL(url); // console.log(resUrlObj) const params = {} const resObj = {}; // 处理查询参数 for(let [key, value] of resUrlObj.searchParams) { params[key] = value } for(let key in resUrlObj) { if(key == "protocol") { // 处理协议 resObj[key] = resUrlObj[key].slice(0, (resUrlObj[key].length - 1)) }else { resObj[key] = resUrlObj[key] } } return {...resObj, searchParams: params} }
实现一个compare函数,比较两个对象是否相同
这里的比较应该不是值广义的地址,而是指对象的属性和值。
function compare(obj1, obj2) { const obj1Keys = Object.keys(obj1); const obj2Keys = Object.keys(obj2); if(obj1Keys.length !== obj2Keys.length) return false; for(let key of obj1Keys) { if(!obj2Keys.includes(key)) { return false; }else { if(typeof obj1[key] === "object" || typeof obj2[key] === "object") { // 递归调用, // 注意:这里也可以判断数组,Object.keys([1,2,3]) => ["0", "1", "2"] if(!compare(obj1[key], obj2[key])) return false; } else {// 基本类型 if(obj1[key] !== obj2[key]) return false; } } } return true; }
柯里化
是把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术。
实现思路
- 我们知道函数的length属性是判断形参的个数。
- 判断不断调用的函数传入的中参数是否大于等于函数的形参。
- 如果大于等于,就直接调用该函数,并传入参数
- 如果小于,那就再返回一个函数,并且内部返回递归调用的函数。
function curry(fn, ...args1) { // 获取形参的个数 const length = fn.length; const resFn = function(...args2) { const totalArgs = [...args1, ...args2]; if(totalArgs.length >= length) { // return resFn; fn.call(this, ...totalArgs) }else { // 参数还没有传完 return function(...args3) { return resFn.call(this, ...args2, ...args3) } } } return resFn }
中划线转大写
function lineChangeUpperCase(str) { // 先依据_分割字符串 const strArr = str.split("_"); // 利用map映射将每个短横线后面的字符转大写 return strArr.map((item, index) => { if(index === 0) { return item; }else { return item.slice(0, 1).toUpperCase() + item.slice(1) } }).join("") }
千位分割
使用es5实现es6的let关键字
利用自执行函数
// 下面代码不存在块级作用域 for(var i = 0; i < 5; i++) { setTimeout(() => { console.log(i); // 5 5 5 5 5 }, 0) } // let解决的问题 for(let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); // 0 1 2 3 4 }, 0) } // 立即执行函数代替let for(var i = 0; i < 5; i++){ (function(a){ setTimeout(() => { console.log(a); // 0 1 2 3 4 }, 0) })(i) }
查找数组中的重复元素
- 双重for循环
function getRepeatElement(arr) { let resArr = [] for(let i = 0; i < arr.length; i++) { // for(let i in arr) { // 为什么for in 不正确 for( let j = i + 1; j < arr.length; j++) { if(arr[i] == arr[j]) { resArr.push(arr[i]) } continue; } } return resArr }
- 数组中判断数组中方法(includes, indexOf)实现。
// indexOf / includes实现 function getRepeatElement1(arr) { let resArr = [] for(let i = 0; i < arr.length; i++) { let copyArr = [...arr] let item = arr[i]; copyArr.splice(i, 1); if(copyArr.includes(item)) { // if(copyArr.indexOf(item) !== -1) { resArr.push(item) } } return resArr //[5, 3, 4, 4] }
- 先进行排序,然后再通过比较相邻元素的方式。
// 先排序,然后再比较相邻元素。 function getRepeatElement3(arr) { let resArr = [] copyArr = arr.sort(); for(let i = 0; i < arr.length - 1; i++) { if(arr[i] === arr[i + 1]) { resArr.push(arr[i]); } } return resArr }
数组乱序
- 通过sort结合Math.random()。
// 该方法有缺陷,大多数元素的位置是不变的 function mixArr(arr) { return arr.sort(() => Math.random() - 0.5) }
- 洗牌算法
// 洗牌算法 function shunflee(arr) { const len = arr.length while(len > 1) { const index = parsetInt(Math.random() * len--) [arr[index], arr[len]] = [arr[len], arr[index]] } return arr }