高频js手写题之实现数组扁平化、深拷贝、总线模式
前言
古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。看懂一道算法题很快,但我们必须将这道题的思路理清、手写出来。
三道js手写题的思路和代码实现
数组扁平化
演示效果
将[1, [1, 2], [1, [2]]] 变成 [1, 1, 2, 1, 2]
第一种: 直接使用.flat
console.log([1, [1,2],[1,[2]]].flat(3));
- 可以将多维数组,降维,传的参数是多少就降多少维
- 一般直接传参数为 Infinity(简单粗暴) 第二种: 递归方法的方法 + 借用数组的API完成
(1)
function flattten(arr) {var result = [];for(var i = 0, len = arr.length; i < len; i++) {if(Array.isArray(arr[i])) { // Array.isArray 判断是否为数组result = result.concat(flattten(arr[i])) // concat() 方法用于连接两个或多个数组。} else {result.push(arr[i])}}return result;
}
(2)
function flatten(arr) {return arr.reduce((pre, cur) => {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);}, []);
}
第四种: some + …(扩展运算符) + .concat
function flattten(arr) {// some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。// some() 方法会依次执行数组的每个元素:// 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。// 如果没有满足条件的元素,则返回false。while(arr.some(item => Array.isArray(item))) {console.log(arr)arr = [].concat(...arr)// ... 会将多维数组降维一层}return arr
}
第五种: 将多维数组转换成字符串,在进行操作
(1)
function flatten(arr) {let str = arr.toString();str = str.replace(/(\[|\])/g, '').split(',').map(Number)return str;
}
- /([|])/g 正则表达式 () 代表一个分组, \是转义字符(因为正则表达式规则中有 [ 和 ]的语法, 用\就可以让规则忽略[和]) /g 为全局匹配, 只要遇到了[ 和 ], 就用’'这个来代替。
- replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
(2)
function flatten(arr) {let result = arr.toString();result = result.replace(/(\[|\])/g, '');result = '[' + result + ']';result = JSON.parse(result);// JSON.parse()可以把JSON规则的字符串转换为JSONObjectreturn result;
}
深浅拷贝
浅拷贝的实现
- 明白浅拷贝的局限性: 只能拷贝一层对象。 如果存在对象的嵌套, 那么浅拷贝将无能为力
- 对于基础数据类型做一个最基本的拷贝
- 对引用类型开辟一个新的存储, 并拷贝一层对象属性
参考 前端进阶面试题详细解答
function deepClone(target) {if(typeof target === 'object' && target != null) {// 判断是数组还是对象const targetclone = Array.isArray(target)? []:{}// 键值是否存在for(let prop in target) {if(target.hasOwnProperty(prop)) {// hasOwnProperty() 方法不会检测对象的原型链,// 只会检测当前对象本身,只有当前对象本身存在该属性时才返回 true。targetclone[prop] = (typeof target[prop] === 'object')?deepClone(target[prop]):target[prop]}}return targetclone;} else {return target;}
}let arr1 = [ 1, 2, { val: 4, xdm: { dd: 99 } } ];let str = shallowerClone(arr1)console.log(arr1, 'arr1')console.log(str, 'str')str.push({mo: '兄弟们'})console.log('str.push-----------')console.log(arr1, 'arr1')console.log(str, 'str + push')

深拷贝的最终版 ,
深拷贝的思路:
- 对于日期和正则的类型时, 进行处理 new一个新的
- 对a: { val: a } 这种循环引用时, 使用以weakMap进行巧妙处理
- 使用Reflect.ownKeys返回一个由目标对象自身的属性键组成的数组,
- 对于剩下的拷贝类型为object和function但不是null进行递归操作,
- 对于除了上述的类型外直接进行"key"的赋值操作。 细节处理:
- 利用getOwnPropertyDescriptors返回指定对象所有自身属性(非继承属性)的描述对象
- 将得到的属性利用Object.create进行继承原型链
- 对于a: { val: a} 循环引用使用weakMap.set和get进行处理。 实现代码
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {if (obj.constructor === Date) return new Date(obj) // 日期对象直接返回一个新的日期对象if (obj.constructor === RegExp)return new RegExp(obj) //正则对象直接返回一个新的正则对象//如果循环引用了就用 weakMap 来解决if (hash.has(obj)) return hash.get(obj)let allDesc = Object.getOwnPropertyDescriptors(obj)//遍历传入参数所有键的特性let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)//继承原型链hash.set(obj, cloneObj)for (let key of Reflect.ownKeys(obj)) { // 针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]// typeof obj[key] !== 'function')}return cloneObj
}
检测代码
let obj = {num: 0,str: '',boolean: true,unf: undefined,nul: null,obj: { name: '我是一个对象', id: 1 },arr: [0, 1, 2],func: function () { console.log('我是一个函数') },date: new Date(0),reg: new RegExp('/我是一个正则/ig'),[Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {enumerable: false, value: '不可枚举属性' }
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj // 设置loop成循环引用的属性
let cloneObj = deepClone(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)
console.log(cloneObj.func)
实现了对象的循环应用的拷贝
对于上述代码进行说明:
Object.getOwnPropertyDescriptors 返回指定对象所有自身属性(非继承属性)的描述对象。可以去这里了解更多api
-
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__,
-
Object.create 如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
const person = {isHuman: false,
};
const me = Object.create(person);
console.log(me.__proto__ === person); // true
Object.getPrototypeOf 方法返回指定对象的原型(内部[[Prototype]]属性的值)继承原型链
WeakMap 对象是一组键值对的集合,其中的键是弱引用对象,而值可以是任意。因为 WeakMap 是弱引用类型,可以有效防止内存泄漏,作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值。可以从这里了解更多的WeapMap和Map的区别
-
Reflect.ownKeys == Object.getOwnPropertyNames(target) contact (Object.getOwnPropertySymbols(target)。
-
Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
-
Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组
事件总线(发布订阅模式)
原理:
事件总线
是发布/订阅模式的实现,其中发布者发布数据,
并且订阅者可以监听这些数据并基于这些数据作出处理。
这使发布者与订阅者松耦合。发布者将数据事件发布到事件总线,
总线负责将它们发送给订阅者
on 或 addListener(event, listenr)
就是为指定事件添加一个监听器到监听数组的尾部。
off 或 removeListener(event, listenr)
移除指定事件的某个监听器, 监听器必须是该事件已经注册过的监听事件。
emit(event, [arg1], [arg2] …)
按照参数的顺序执行每个监听器, 如果事件有注册监听返回true, 否则返回false。 利用Node.js来了解 事件总线
var events = require('events');
var eventEmitter = new events.EventEmitter();
eventEmitter.on('say', function(name) {console.log('Hello', name);
})
eventEmitter.emit('say', '若离老师');
function helloA(name) {console.log("helloAAAAAAA", name)
}function helloB(name) {console.log("helloBBBBBBB", name)
}eventEmitter.on('say', helloA)
eventEmitter.on('say', helloB)
eventEmitter.emit('say', '若离老师')
eventEmitter.off('say', helloB);
eventEmitter.emit('say', '若离老师')
新定义的eventEmitter 是接收 events.EventEmitter 模块 new 之后返回的一个实例,eventEmitter 的 emit 方法,发出 say 事件,通过 eventEmitter 的 on 方法监听,从而执行相应的函数。当触发off时, 将say事件上的响应函数删除。
on实现代码:
on的实现思路
对于on为指定事件添加一个监听器: 形式为{“say”: [ {listener:(函数) , once:(false or true)}, {}, {} ] }
- 参数有两个(name, fn)name为指定事件, fn是一个回调函数
- 对于fn进行判断: 是否不存在、是否是合法的(为function)、判断不能重复添加事件 on的如下代码
function EventEmitter() {this.__events = {}
}// 判断是否是合法的 listener
function isValidListener(listener) {if (typeof listener === 'function') {return true;} else if (listener && typeof listener === 'object') {// listener 作为自定义事件的回调,必须是一个函数,// 另外判断是否是object这块递归的去找对象中是否还存在函数,如果不是函数,// 自定义事件没有回调肯定是不行的return isValidListener(listener.listener);} else {return false;}
}
// 顾名思义,判断新增自定义事件是否存在
function indexOf(array, item) {var result = -1item = typeof item === 'object' ? item.listener : item;for (var i = 0, len = array.length; i < len; i++) {if (array[i].listener === item) {result = i;break;}}return result;
}
EventEmitter.prototype.on = function(eventName, listener){if (!eventName || !listener) return;// 判断回调的 listener 是否为函数if (!isValidListener(listener)) {throw new TypeError('listener must be a function');}let events = this.__events;console.log(events)// var listeners = events[eventName] = events[eventName] = events[eventName] || [];events[eventName] = events[eventName] || [];let listeners = events[eventName]// listenerIsWrapped 表示是否已经封装了{listener: listener,once: false}let listenerIsWrapped = (typeof listener === 'object');// 不重复添加事件,判断是否有一样的if (indexOf(listeners, listener) === -1) {listeners.push(listenerIsWrapped ? listener : {listener: listener,once: false});}return this;// this指向EventEmitter,返回的是实际调用这个方法的实例化对象
};
连等赋值操作的坑:
A = B = C 其中执行的顺序为 B=C A = B emit的代码实现
emit的思路
从this._events中拿出相应的监听事件进行执行(注意多个事件的执行)
emit的如下代码
EventEmitter.prototype.emit = function(eventName,...args) {// 直接通过内部对象获取对应自定义事件的回调函数let listeners = this.__events[eventName];if (!listeners) return;// 需要考虑多个 listener 的情况for (let i = 0; i < listeners.length; i++) { let listener = listeners[i];if (listener) {listener.listener.call(this, ...args || []);// 给 listener 中 once 为 true 的进行特殊处理if (listener.once) {this.off(eventName, listener.listener)}}}return this;
};
listener.listener.call(this, …args || []); 将this绑定到listener.listener然后进行执行相应的函数。 例如:当执行到fn1时, fn1.call(this, name, age)。相当于执行函数fn1()。 off的代码实现
off的思路
将监听事件上相应的函数进行删除
off的代码如下
EventEmitter.prototype.off = function(eventName, listener) {// 进行基础的判断let listeners = this.__events[eventName];let index = -1;if(!listeners) return;for(let i = 0; i < listeners.length; i++) {if(listeners[i] && listeners[i].listener === listener) {index = i;break;}}if(index !== -1) {listeners.splice(index, 1, null);}return this;}
发布订阅模式的检测代码:
let eventBus = new EventEmitter()
let fn1 = function(name, age) {console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {console.log(`hello, ${name} ${age}`)
}
let fn3 = function(name, age) {console.log(`hello myname is, ${name} ${age}`)
}
eventBus.on('say', fn1)
eventBus.on('say', fn2)
eventBus.on('say', fn3)
eventBus.emit('say','布兰', 12)
eventBus.off('say', fn1)
console.log('使用off删除了say事件上的fn1函数-------')
eventBus.emit('say','布兰', 12)
相关文章:
高频js手写题之实现数组扁平化、深拷贝、总线模式
前言 古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。看懂一道算法题很快,但我们必须将这道题的思路理清、手写出来。 三道js手写题的思路和代码实现 数组扁平化 演示效果 将[1, [1, 2], [1, [2]]] 变成 [1, 1, 2, 1, 2] 第一种&…...
HTML介绍
文章目录一. HTML介绍二. 创建三. HTML结构四. 常见的标签五. 链接标签六. 图片标签七. 资源路径八. 列表标签九. 表格标签十. 表单标签十一. 表单提交一. HTML介绍 HTML: 超文本标记语言作用: 书写前端页面前端三大技术(三大标准): HTML: 结构标准 (页面中有什么内容)CSS: 样…...
【C++】string的9道OJ题
要么庸俗,要么孤独… 文章目录一、仅仅反转字母二、字符串中的第一个唯一字符(计数排序的思想)三、字符串相加(做好加进位的工作即可)四、把字符串转换成整数五、反转字符串中的单词 III六、字符串相乘(高…...
Odoo丨Odoo框架源码研读三:异常处理与定制化开发
Odoo丨Odoo框架源码研读三:异常处理与定制化开发 Odoo源码研读的第三期内容:异常处理与定制化开发。 *异常处理* Odoo中的Exception是对Python内置异常做了继承和封装,设定了自己核心的几个Exception。 而对异常的处理和Python内置异常的…...
Python概述 基础语法 判断 循环
Python概述常用快捷键第二章-Python基础语法01-字面量02-注释03-变量04-数据类型05-数据类型转换06-标识符07-运算符08-字符串的三种定义方式09-字符串的拼接10-字符串格式化11-字符串格式化的精度控制 12-字符串格式化-快速写法13-对表达式进行格式化14-字符串格式化练习题讲解…...
什么是品牌营销?学会正确推广您的业务
什么是品牌营销? 品牌营销涉及长期战略规划,以推广整个品牌,而不是营销单个产品或服务。它分享了一个引人入胜的故事,以在潜在客户中产生品牌知名度并建立声誉。 面向消费者的品牌使用品牌智能软件来了解人们对其品牌的看法&#…...
Golang学习Day1
😋 大家好,我是YAy_17,是一枚爱好网安的小白。本人水平有限,欢迎各位大佬指点,欢迎关注 😁,一起学习 💗 ,一起进步 ⭐ 。⭐ 此后如竟没有炬火,我便是唯一的光…...
《设计模式》工厂模式
《设计模式》工厂模式 工厂模式又分为简单工厂(Simple Factory)、工厂方法(Factory Method)和抽象工厂(Abstract Factory)都是常用的创建型设计模式,它们的主要区别如下: 简单工厂…...
JS - 原型对象、原型链是什么
一 阅读掘金 https://juejin.cn/post/7007416743215759373 https://juejin.cn/post/7007416743215759373 二 阅读掘金小册原型知识点 原型 涉及面试题:如何理解原型?如何理解原型链? 当我们创建一个对象时 let obj { age: 25 }࿰…...
STM32f103 CubeMX封装 led程序
本文代码使用 HAL 库。 文章目录前言一、LED 原理图二、CubeMX创建工程三、LED 相关函数1. 输出电平函数:2. 延时函数:3. 翻转电平函数:四、详细代码实验现象 :总结代码 源码:前言 从这篇文章开始,我们讲解…...
智慧教室系统--温湿度控制系统
随着科技的不断进步,智能化已经成为了各个行业的发展趋势,智慧教室作为未来教育的主流趋势之一,也将受益于这一趋势。而智慧教室中的温湿度控制系统是其中的重要组成部分,为了创造一个舒适、健康、安全的教学环境,智慧…...
只要一直向前定能到达远方,社科院与杜兰大学金融管理硕士项目为你注入动力
在人生这条道路上,我们很远的路要走,不管前方是否平坦,我们只要坚持前向,终将抵达远方。一路上我们付出很多,也收获很多。想要变得更强大,就要不断优化自身,积攒更多的能量,社科院与…...
Java性能-回收算法-Throughout回收算法
垃圾回收算法 理解Throughput回收器 回收器三个基本操作——回收 找到不使用的对象 释放内存 压缩堆碎片 Minor GC和Full GC,每个操作都会标记,释放和压缩对应的目标分代 [63.205s][info][gc,start ] GC(13) Pause Full (Ergonomics) [63.205s][info][…...
立项近7年,索尼产品经理分享PS VR2开发背后的故事
备受期待的索尼PS VR2终于正式发售,VR爱好者们终于有机会体验到《地平线:山之呼唤》等PS VR2独占的VR大作。近期,为了解PS VR2头显诞生背后的故事,外媒AV Watch采访到PS VR2的开发负责人Yasuo Takahashi,在本次采访中&…...
Kubernetes 如何通过ingress-nginx实现应用灰度发布?
在日常的工作中,我们会经常对应用进行发版升级,在互联网公司尤为频繁,主要是为了满足快速的业务发展。我们经常用到的发布方式有滚动更新、蓝绿发布、灰度发布。滚动更新:依次进行新旧替换,直到旧的全部被替换为止。蓝…...
华为OD机试 - 密室逃生游戏(Java) | 机试题+算法思路+考点+代码解析 【2023】
密室逃生游戏 小强增在参加《密室逃生》游戏,当前关卡要求找到符合给定 密码K(升序的不重复小写字母组成) 的箱子, 并给出箱子编号,箱子编号为 1~N 。 每个箱子中都有一个 字符串s ,字符串由大写字母、小写字母、数字、标点符号、空格组成, 需要在这些字符串中找到所有…...
redis的主从复制细节
文章目录复制机制的运作复制的一些事实master持久化关闭时,复制的安全性Redis复制是如何工作的只读性质的slave设置一个slave对master进行验证允许只写入N个附加的副本Redis如何处理过期键重新启动和故障转移后的部分同步复制机制的运作 master和slave的复制运作依…...
SparkSQL
第1章 SparkSQL 概述1.1 SparkSQL 是什么Spark SQL 是 Spark 用于结构化数据(structured data)处理的 Spark 模块。1.2 Hive and SparkSQLSparkSQL 的前身是 Shark,给熟悉 RDBMS 但又不理解 MapReduce 的技术人员提供快速上手的工具。Hive 是早期唯一运行在 Hadoop …...
Python|每日一练|栈|数组|字典树|数组|树|广度优先搜索|单选记录:逆波兰表达式求值|回文对|二叉树的层序遍历
1、逆波兰表达式求值(栈,数组) 根据 逆波兰表示法(https://baike.baidu.com/item/%E9%80%86%E6%B3%A2%E5%85%B0%E5%BC%8F/128437),求表达式的值。 有效的算符包括 、-、*、/ 。每个运算对象可以是整数,也可以是另一个…...
慧教室系统--远程控制系统
随着科技的不断进步,越来越多的教育机构开始使用智慧教室系统来提升教学效果和学生体验。智慧教室系统不仅可以自动化管理设备,还可以实现远程控制,帮助教师和学生更加便捷地使用教室设备。智慧教室系统作为一款领先的智慧教育解决方案&#…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...
【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL
ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...
