当前位置:网站首页>JS常见代码题 数组去重-自定义new-节流和防抖-深拷贝-instanceof-url参数提取-千位分隔符-数组转树形结构-数组扁平化-函数柯里化
JS常见代码题 数组去重-自定义new-节流和防抖-深拷贝-instanceof-url参数提取-千位分隔符-数组转树形结构-数组扁平化-函数柯里化
2022-07-19 18:18:00 【rananie】
文章目录
JS常见代码题
数组
数组去重
方法1: 利用forEach()和indexOf()
说明: 本质是双重遍历, 效率差些
思路1:遍历数组,建立新数组,利用indexOf判断是否存在于新数组,不存在则push,最后返回新数组。
思路2:数组下标判断法, 遍历数组,利用indexOf判断元素的值是否与当前索引相等,如相等则加入
//1
export function unique1 (array) {
const arr = []
array.forEach(item => {
if (arr.indexOf(item)===-1) {
arr.push(item)
}
})
return arr
}
方法2: 利用forEach() + 对象容器
说明: 只需一重遍历, 效率高些
思路: 遍历数组,将数组值保存成object对象的属性,判断数组值是否已经保存在object中,未保存则push到新数组并用object[arrayItem]=1的方式记录保存。
export function unique2 (array) {
const arr = []
const obj = {
}
array.forEach(item => {
if (!obj.hasOwnProperty(item)) {
obj[item] = true
arr.push(item)
}
})
return arr
}
方法3:利用ES6语法: from + new Set或者 … + new Set
说明: 编码简洁
思路:new Set 返回一个没有重复元素的可迭代对象set,利用…继续展开或者利用Array.from根据可迭代对象创建数组
export function unique3 (array) {
// return Array.from(new Set(array))
return [...new Set(array)]
}
使用reduce方法实现
思路:reduce设置初始值为空数组,利用reduce方法,每一次计算都可以知道上一次的值,来进行判断是否重复。最后返回累计值。
export function unique4(array);
let arr = arr.reduce((accumulator,current)=>{
return accumulator.includes(current) ? accumulator : accumulator.concat(current);
},[])
数组扁平化 flat
将多维数组转换为一维数组
核心就是有数组则继续遍历
[1,[2,3,[4,5]]].flat(Infinity)
递归
循环
function flat(arr){
const res = [];//存放每一层的数组
arr.forEach(item =>{
if(Array.isArray(item))flat = res.concat(flat(item));
else res.push(item);
})
return res;//每一层返回的都是数组
}
reduce写法
function flat(arr){
return arr.reduce((accumulator,item)=>{
return accumulator.concat(Array.isArray(item)?flat(item):item)
},[])
}
对象
自定义new
new的过程中发生了什么?
- 创建一个空对象,也就是后面需要返回的实例
- 将实例的隐式原型
__proto__
指向构造函数的显式原型prototype
- 执行构造器函数,将构造器函数的this指向实例,为实例添加方法或属性
- 获取构造器函数执行的结果,如果构造函数有返回对象,那我们将其返回。如果没有,则返回我们创建的实例
function myNew(Fn,...args){
let obj = {
}; //1
let obj.__proto__ = Fn.prototype;//2
let result = Fn.apply(obj,args);//3
return result instanceof Object ? result : obj;//4
}
函数
节流和防抖
作用是:控制回调函数触发的频率
参数: 控制触发频率的回调函数和时间wait
输出: 到时间后,返回callback函数
节流
节流:在函数被频繁触发时, 函数执行一次后,只有大于设定的执行周期后才会执行第二次。
语法:throttle(callback, wait)
实现思路
1.需要一个变量记录上一次执行的时间,才能判断出是否满足执行的时间间隔
2.如果满足执行的时间间隔,则执行函数
注意点
1.返回函数使用了闭包,闭包会永远在内存中保存所以这个pre都是记录的上一次的结果
2.修改this的目的是让函数的指向指向绑定事件的DOM
//使用形式,绑定时候throttle函数就会执行,所以this是window
window.addEventListener('scroll',throttle(()=>{
},500))
//自定义
function throttle(callback,wait){
let pre=0;
//console.log(this);window
//节流函数/真正的事件回调函数
return function(...args){
const now = Date.now();
if(now-pre>wait){
//callback()是window调用的,所以callback函数里的this是window,这里要修改指向事件源,
//console.log('this2',this); //DOM
callback.apply(this,args);
pre = now;
}
}
}
防抖
防抖:触发事件后不会立即执行,需要等待wait时间。如果在等待的过程中再一次触发了事件,计时器重新开始计时wait时间,直到达到wait时间后执行最后一次的回调
语法:debounce(callback, wait)
function debounce(callback, wait){
let timeId=null;
return funtion(...args){
if(timeId){
//之前已经有一个定时器了,这里再一次触发事件,重新开始即使
clearTimeout(timer);
}
timeId = setTimeout(()=>{
callback.apply(this,args);
//执行成功之后,重置timeId,所以这里可以起作用
timeId = null;
},wait)
}
}
深拷贝
深浅拷贝只是针对引用数据类型
复制之后的副本进行修改会不会影响到原来的
浅拷贝:修改拷贝以后的数据会影响原数据,拷贝的引用。使得原数据不安全。(只拷贝一层)
深拷贝:修改拷贝以后的数据不会影响原数据,拷贝的时候生成新数据
如何实现深拷贝: 递归 + map
参数
1.需要拷贝的对象
2.map存储
实现思路
1.判断是否是引用类型,如果是引用类型循环遍历所有元素进行复制
2.保证对象只克隆了一次,使用Map存储已经克隆之后的对象,目的是:防止循环引用时死循环,A引用B,B中又引用了A,防止套娃
function deepClone(target,map={
}){
//1.判断是否是object或者array,如果是引用类型循环遍历所有元素
if(typeof target ==='object' && target!==null){
//2.判断target是否已经被克隆过,已经克隆过就不用克隆了
if(map.has(target))return map.get(target);
//2.说明没有克隆过,进行克隆
let isArray = Array.isArray(target);
const res = isArray? []:{
};
map.set(target,res); //重要!! res引用类型,先将要进行深拷贝的target放入map中,后续修改res时map中res会跟着一起修改
if(isArray){
//3.数组类型,遍历取数
target.forEach((item,index)=>{
res[index] = deepClone(item,map); //数组中的每一个数都需要深拷贝,递归调用
})
}else{
//3.对象类型,Object.keys(target)获得属性值
Object.keys(target).forEach((key)=>{
res[key] = deepClone(target[key],map);
})
}
return res;//将拷贝结果结果返回
}
else{
//1.基本数据类或object array外的数据类型,不需要深拷贝
return target;
}
}
instanceof
对象的隐式原型的值=对应构造函数的显式原型的值
A instanceof B
去A的隐式原型链找有没有B的显式原型,判断A是不是B的实例,主要针对与引用类型
所以instanceof的参数应该有两个一个是A一个是B,A 为对象或者函数,B为构造函数
返回值为布尔值。
function instanceof(A,B){
if (typeof B !== 'function') return false; //B为构造函数
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function'))return false;//A为对象或函数
let bp = B,prototype;//取B的显式原型
let A = A.__proto__;//取A的隐式原型
while(true){
if(A === null)return false; //Object的prototype原型的隐式原型__proto__ 为null,也就是原型链的尽头。
if(A ===bp)return true;
A = A.__proto__;
}
}
学习笔记:https://blog.csdn.net/qq_41370833/article/details/123301249
其他
用setTimeout实现setInterval
setTimeout:指定的毫秒数后调用一次函数
setInterval:每隔指定的毫秒数后调用函数
为什么推荐使用setTimeout
在定时器中指定的时间间隔表示何时将定时器的代码添加到队列,而不是何时实际执行代码。所以添加到队列后,什么时候执行还要看情况。
** 当使用setInterval时,仅当队列中没有该定时器代码实例时,才添加到任务队列中**。
setInterval的缺点:不一定按时执行
- 缺点1:使用 setInterval 时,某些间隔会被跳过;
- 缺点2:可能多个定时器会连续执行;
用setTimeout实现setInterval
利用setTimeout实现setInterval,就是将执行一次的函数周期性调用。
利用递归里面开定时器实现,还可以添加参数表示限制setTimeout执行的次数
//setTimeToInterval需要返回timer
function setTimeToInterval(fn,delay){
let timer = null;
let interv = function(){
func.call(null);
clearTimeout(timer); //清除上一次的setTimeout,使用了闭包,闭包就是在一个函数中能够读取其他函数内部变量
timer=setTimeout(interv, wait);
}
timer = setTimeout(interv, wait);
//return timer;不能直接return,因为循环执行的是interv,setTimeToInterval只会执行一次,所以timer = mySetInterval(fn, delay) 的时候 timer 被固定
return {
//这里也使用了闭包,用的timer就是最新的timer
clear() {
clearTimeout(timer)
}
}
}
//清除定时器
function myClearInterval(flagTimer) {
flagTimer.clear()
}
//测试
function testmySetInterval() {
console.log('testmySetInterval')
}
const timer = mySetInterval(testmySetInterval, 1000)
// 控制台直接调用 myClearInterval(timer)
URL参数提取
输入:URL参数
返回:URL参数组成的对象
- 正则表达式去获取url
- URLSearchParams方法
1.正则表达式匹配
案例
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 结果 { user: 'anonymous', id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型 city: '北京', // 中文需解码 } */
思路
- 获取?后面的字符串
RegExpObject.exec(string) 分别获取匹配结果
- 返回一个数组,其中存放匹配的结果,第一个元素是匹配的文本,第二个开始是子匹配的结果,如果没有找到匹配则返回值为null。
- 加了()表示子匹配,匹配外部大正则的情况下同时匹配()里的内容
- 不管是否/g开启全局,只返回匹配的第一个
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
- 通过&进行取出键值对
const paramsArr = paramsStr.split('&');
- 将params存到对象中
分割key和value,将val需要先解码
如果结果集中有该key,则将值合并成数组
let res = {
}; //返回的结果值
paramsArr.forEach(param =>{
if(/=/.test(param)){
//有value的进行处理
let [key,val] = param.split('='); //分割key和value
val = decodeURIComponent(val); //解码
if(res.hasOwnProperty(key)){
//如果结果集中有该key,则将值合并成数组
res[key] = [].concat(res[key],val);
}else {
res[key] = val;
}
})
代码
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
let res = {
}; //返回的结果值
paramsArr.forEach(param =>{
if(/=/.test(param)){
//有value的进行处理
let [key,val] = param.split('='); //分割key和value
val = decodeURIComponent(val); //解码 先处理一遍
if(res.hasOwnProperty(key)){
//如果结果集中有该key,则将值合并成数组
res[key] = [].concat(res[key],val);
}else {
res[key] = val;
}
})
return res;
}
URLSearchParams方法 Web API 接口
URLSearchParams接口定义一些使用的方法来处理URL的查询字符串
URLSearchParams() 构造器创建并返回一个新的URLSearchParams 对象。 开头的?
字符会被忽略。
function parseParam(url) {
//const paramsStr = /.+\?(.+)$/.exec(url)[1];
let url = URL.split("?")[1];
const urlSearchParams = new URLSearchParams(url);
//urlSearchParams.entries()返回的是一个迭代协议iterator
//Object.fromEntries()方法将把键值对列表转换为一个对象。
const params = Object.fromEntries(urlSearchParams .entries());
return params;
}
千位分隔符
1000 -> 1,000
1000000 -> 1,000,000
1000000000 -> 1,000,000,000
1. Number.prototype.toLocaleString()
语法:Number.prototype.toLocaleString([locales [, options])
- locales 语言代码,表示将数字格式化成哪国语言
- options 格式化时可选的一些配置属性
作用:返回数字在特定语言环境下表示的字符串
const num = 1276482.123;
//默认格式
num.toLocaleString() //1,276,482.123
num.toLocaleString('zh', {
style: 'decimal' }) // 1,276,482.123,纯数字格式,
num.toLocaleString('zh', {
style: 'percent' }) // 127,648,212%,百分数格式
num.toLocaleString('zh', {
style: 'currency', currency: 'CNY' }); // ¥1,276,482.12,人民币形式
2.正则表达式 每个三个数添加一个,
将每隔三个数字替换成三个数字+“,”
1.正向断言(?=)
:x只有在y前面才匹配,必须写成/x(?=y)/
,y作为定位元素
所以我们需要获取的是 从后往前数,3的倍数个数字左边的第一个数字(\d)(?=((\d){3})+ $)
其中$可以表示以xxx结尾,也就是从后往前数
2.string.replace(匹配的内容,替换的内容)
进行内容替换,默认只替换第一个,使用全局匹配模式g
$n
正则表达式匹配的第n个子匹配的结果
vartoThousands = function(number) {
return (number + '').replace(/(\d)(?=(\d{3})+$)/g, '$1,');
//return String(number).replace(/(\d)(?=(\d{3})+$)/g, '$1,')
/* 假设number是1234567 第一次匹配到的数是1 $1=1 修改之后是1,234567 第二次从2开始匹配,匹配到234 修改之后是1,234,567 */
}
3.倒序遍历,使用额外变量记录已拼接字符长度
给你一个整数 n,请你每隔三位添加点(即 “,” 符号)作为千位分隔符,并将结果以字符串格式返回。
思路
将数字转为字符串后,从字符串尾部开始遍历,使用额外变量tmpLength记录已经拼接的字符串长度,tmpLength % 3的值作为是否拼接分隔符的条件
function thousandSeparator2(n: number): string {
// 数值转为字符串,分割,反转数组,重新拼接
const s = String(n);
let newS = '';
// 记录已拼接字符长度 - 这里是刨除千位分隔符.
let tmpLength = 0;
// 索引从s.length - 1开始,倒序
for (let i = s.length - 1; i >= 0; i--) {
// 千位分割数的拼接条件:已拼接字符 % 3 === 0
// 同时排除第一个字符 tmpLength === 0时的情况
if (tmpLength % 3 === 0 && tmpLength !== 0) {
// 拼接千位分割数
newS = s[i] + ',' + newS; //加在新字符串的前面
} else {
// 直接拼接
newS = s[i] + newS; //加在新字符串的前面
}
//表示已经拼接的字符数量
tmpLength++;
}
return newS;
}
数组转树形结构
let arr = [
{
id: 1, name: '部门1', pid: 0},
{
id: 2, name: '部门2', pid: 1},
{
id: 3, name: '部门3', pid: 1},
{
id: 4, name: '部门4', pid: 3},
{
id: 5, name: '部门5', pid: 4},
]
//结果
[
{
"id": 1,
"name": "部门1",
"pid": 0,
"children": [
{
"id": 2,
"name": "部门2",
"pid": 1,
"children": []
},
{
"id": 3,
"name": "部门3",
"pid": 1,
"children": [
// 结果 ,,,
]
}
]
}
]
递归实现
主要的问题是如何根据pid,将数组转换成有层级的结构
思路
通过pid寻找父节点的孩子节点,插入到children属性中。
递归的参数和返回值
原数组:arr
需要插入到父节点的children属性中,所以使用一个数组来表示是父节点的children
当前的节点的父节点:pid 用于判断找的是哪个父节点的孩子节点
function getChildren(arr,res,pid){
};
本层递归的逻辑
需要找到父节点是pid的节点,将该节点转成对象形式插入到父节点的children中
function getChildren(arr,res,pid){
for (const item of arr) {
//寻找父节点是pid的节点
if(item.pid === pid){
const newItem = {
...item,children:[]}; //找到了将孩子节点转化成对象形式
res.push(newItem); //加入父节点的children属性中
getChildren(arr,newItem.children,item.id); //寻找当前孩子节点的孩子节点
}
}
}
const arrayToTree (arr){
const result = [];
getChildren(arr,result ,0);
return result;
}
非递归实现 优先回答-性能好一点
map中存放key为id,value为节点元素
目的是遍历节点的时候,可以通过key来找父节点。
value为节点元素,节点元素是一个对象,所以是引用类型,对引用类型的修改,在最后的结果中也可以看见
function createTree(arr){
const map = {
}
arr.forEach(item=>{
if(!item.children)item.children = []; //没有children 属性添加children 属性
map[item.id] = item; //key为id,value为元素本身
})
const result= [];//存放结果集
arr.forEach(item=>{
const pItem = data[item.pid]; //对象的引用,去找父节点是否存在
if(pItem ){
//找到父节点了
if(pItem.children)pItem.children.push(item); //如果已经有孩子节点了,直接push
else pItem.children=[item]; //没有则创建children数组
}else{
//没找到说明是根节点,根节点可能有多个,所以返回数组,直接将根节点push进去
result.push(item);
}
})
return result;
}
函数柯里化
函数柯里化:将使用多个参数的一个函数变成一系列使用一个或多个参数的函数。
柯里化是一种编程思想,函数执行产生一个闭包,把一些信息预先存储起来,目的是供下级上下文使用。这样预先存储和处理的思想,就叫做柯里化的编程思想。
函数柯里化的主要作用和特点
参数复用
提前返回
延迟执行 要接受3个参数的函数,可以先接收1个或者2个,等接收完3个后再执行
function sum(a,b,c) {
console.log(a+b+c)
}
//方法1:利用工具函数 生成的柯里化函数
let fn = curry(sum);
fn(1,2,3); //6
fn(1)(2)(3); //6
fn(1,2)(3); //6
首先可以知道每一次函数调用的参数个数是不确定的,但是总的个数是确定的,根据柯里化函数curry
接收到的函数参数可以确定。
对柯里化后的fn函数来说,当接受的参数数量小于原函数的形参数量时,返回一个函数用于接收剩余的参数,直至接收的参数数量与形参数量一致,执行原函数。
function curry(fn) {
//进行参数缓存
return function curryFn(...arg){
if(arg.length<fn.length){
//fn.length可以获取函数的参数
return function (...arg2){
return curryFn(...arg.concat(Array.from(arg2)));
}
}else{
return fn(...arg);
}
}
}
curry((a,b,c)=>{
console.log(a+b+c);
})(2)(2)(3)
边栏推荐
- 云服务器ECS老用户专享,10余款实例新购低至3.6折
- nnUNet
- R语言获取data.table数据中指定数据列的第N个最大值所在的数据行
- OPPO回应其折叠屏手机酷似华为Mate X:自研非公模!
- G1垃圾回收器
- Dataset conversion instructions
- volatile详解
- 海上风电消防火灾报警系统中消防主机超远距离联网方案
- Scientific computing library numpy Foundation & Improvement (Understanding + explanation of important functions)
- 终止5G芯片合作!英特尔与展锐的官方回应来了
猜你喜欢
外表简单内里复杂的功能测试,如何进行?
MySQL8通过Data目录恢复数据
Analyze RTP flow packet loss and disorder
软件测试计划包括哪些内容,测试计划如何编写。分享测试计划模板
Super long distance networking scheme of fire engine in offshore wind power fire alarm system
ROS回调函数新发现
基于 SPICE 协议的硬编推流整合方案在云游戏中的应用
Qt QTextEdit 设置 QScrollBar 样式表不生效解决方案
npm run xx 的执行流程
深入了解前后置++、--以及负数取模
随机推荐
ES流、PES流、PS流和TS流介绍
Qt QTextEdit 设置 QScrollBar 样式表不生效解决方案
CodeBlocks下载+界面优化+创建文件+常用快捷键
Web crawler DIY solves the problem of e-commerce data collection
逐步走向响应式编程(三)-常见函数式接口- Function<T, R>
正则表达式语法表
一文解决! A40i最常见的3种网卡软件问题
volatile详解
科学计算库Numpy基础&提升(理解+重要函数讲解)
Super long distance networking scheme of fire engine in offshore wind power fire alarm system
300. 最长递增子序列
#if,#ifdef和#ifndef三者之间的区别
[software test] - test case of image server project
《认知天性》这本书对我的启发,以及我在日常中的应用
乌总统泽连斯基解雇国家安全局副局长
GPS、基站、IP定位的区别及其应用方向
搜索引擎排名对商家的影响大吗?广告情报体现竞争优势
MySQL8通过Data目录恢复数据
Exchange 2010 SSL证书安装文档
js操作数组常用方法