5个常见的前端手写功能:浅拷贝与深拷贝、函数柯里化、数组扁平化、数组去重、手写类型判断函数

浅拷贝与深拷贝

浅拷贝

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

//实现浅拷贝
function shallowCopy (obj){
    // 只拷贝对象,基本类型或null直接返回
    if(typeof obj !== 'object' || obj === null) {
    	return obj;
    }
    // 判断是新建一个数组还是对象
    let newObj = Array.isArray(obj) ? []: {};
    //for…in会遍历对象的整个原型链,如果只考虑对象本身的属性,需要搭配hasOwnProperty
    for(let key in obj ){
        //hasOwnProperty判断是否是对象自身属性,会忽略从原型链上继承的属性
        if(obj.hasOwnProperty(key)){
        	newObj[key] = obj[key];//只拷贝对象本身的属性
        }
    }
    return newObj;
}
 
//测试
var obj ={
    name:'张三',
    age:8,
    pal:['王五','王六','王七']
}
let obj2 = shallowCopy(obj);
obj2.name = '李四'
obj2.pal[0] = '王麻子'
console.log(obj); //{age: 8, name: "张三", pal: ['王麻子', '王六', '王七']}
console.log(obj2); //{age: 8, name: "李四", pal: ['王麻子', '王六', '王七']}

测试结果:

image.png

深拷贝

深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

function deepCopy (obj, map = new WeakMap()){
    // 基本类型或null直接返回
    if(typeof obj !== 'object' || obj === null) {
    	return obj;
    }
    // 判断是新建一个数组还是对象
    let newObj = Array.isArray(obj) ? []: {};
    //利用map解决循环引用
    if (map.has(obj)) {
        return map.get(obj);
    }
    map.set(obj, newObj);//将当前对象作为key,克隆对象作为value
    for(let key in obj ){	
        if(obj.hasOwnProperty(key)){
        	newObj[key] = deepCopy(obj[key], map);	//递归
        }
    }
    return newObj
}
 
// 测试
let obj1 = {
    name : 'AK、哒哒哒',
    arr : [1,[2,3],4],
};
let obj2=deepCopy(obj1)
obj2.name = "哒哒哒";
obj2.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存
 
console.log('obj1',obj1) // obj1 { name: 'AK、哒哒哒', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: '哒哒哒', arr: [ 1, [ 5, 6, 7 ], 4 ] }

测试结果:

image.png

函数柯里化

函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

作用:可以参数复用(公共的参数已经通过柯里化预置了)和延迟执行(柯里化时只是返回一个预置参数的新函数,并没有立刻执行,在满足条件后才会执行)。

参数定长的柯里化

思路:通过函数的length属性获取函数的形参个数,形参的个数就是所需参数的个数。维护一个数组,当数组的长度与函数接收参数的个数一致,再执行该函数。

// 实现函数柯里化
function curry(fn) {
  // 返回一个新函数
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args); // 如果参数够了,就执行原函数,返回结果
    } else {
      //返回一个新函数,继续递归去进行柯里化,利用闭包,将当前已经传入的参数保存下来
      return function (...args2) {
        //递归调用 curried 函数
        return curried.apply(this, [...args, ...args2]); //新函数调用时会继续传参,拼接参数
      };
    }
  };
}
 
// 测试
function sum(a, b, c) {
  return a + b + c;
}
var curried = curry(sum);
console.log(curried(1, 2, 3)); //6
console.log(curried(1, 2)(3)); //6
console.log(curried(1)(2, 3)); //6
console.log(curried(1)(2)(3)); //6

测试结果:

image.png

参数不定长的柯里化

题目:如何实现一个方法,使计算结果能够满足如下预期。

add(1, 2, 3) // 6
add(1) // 1
add(1)(2) // 3
add(1, 2)(3) // 6
add(1)(2)(3) // 6
add(1)(2)(3)(4) // 10

思路:利用闭包和递归,如果参数为空,则判断递归结束,求和,返回结果。


function addCurry() {
    // 利用闭包的特性收集所有参数值
    let arr = [...arguments]	
    //返回函数
    return function fn() {
        // 如果参数为空,则判断递归结束,即传入一个()执行函数
        if(arguments.length === 0) {	
	    	return arr.reduce((a, b) => a + b)	// 求和
        } else {
            arr.push(...arguments)
            return fn	//递归
        }
    }
}
 
// 测试
console.log(addCurry(1)());	//1
console.log(addCurry(1)(2)());	//3
console.log(addCurry(1)(2)(3)());	//6
console.log(addCurry(1, 2)(3)());	//6
console.log(addCurry(1, 2, 3)());	//6

上述写法,总是要以空括号()结尾,于是再改进为隐式转换.toString写法,原理:当用 Function的值做计算的时候,会调用toString做隐式转换。注意一些旧版本的浏览器隐式转换会默认执行,新版本不行了。可以利用隐式转换或者alert。

function addCurry() {
    let arr = [...arguments]
    // 利用闭包的特性收集所有参数值
    var fn = function() {
        arr.push(...arguments);
        return fn;//递归
    };
    // 利用 toString 隐式转换,转换的时候再返回结果
    fn.toString = function () {
        return arr.reduce(function (a, b) {
            return a + b;
        });
    }
    return fn;
}
 
//测试
console.log(addCurry(1)(2) == 3) //true 利用隐式转换,自动调用toString方法得到柯里化的结果
//alert(addCurry(1)(2)(3))//6 alert参数只能是字符串,如果其他类型的值,会转换成字符串,会调用toString方法
console.log(addCurry(1).toString());//1 手动调用toString
console.log(addCurry(1, 2)(3).toString());//6 
console.log(addCurry(1, 2)(3)(4)(5).toString());//15 

测试结果:

image.png

数组扁平化

数组扁平化其实就是将多维数组转为一维数组。

ES6中的flat

const arr = [1,[2,[3,[4,5]]],6]
//  arr.flat([depth]) flat的参数代表的是需要展开几层,如果是Infinity的话,就是不管嵌套几层,全部都展开
console.log(arr.flat(Infinity)) //[1,2,3,4,5,6]

递归

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
  let result = [];
  for (let i = 0; i < arr.length; i++) {
    //如果当前元素还是一个数组
    if (Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]));//递归拼接
    } else {
      result.push(arr[i]);
    }
  }
  return result;
}
console.log(flatten(arr)); //  [1, 2, 3, 4]

reduce函数迭代

从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用reduce来实现数组的拼接,从而简化第一种方法的代码。

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
  return arr.reduce((total, cur) => {
    return total.concat(Array.isArray(cur) ? flatten(cur) : cur);
  }, []); //传递初始值空数组[],就会从数组索引为 0 的元素开始执行
}
console.log(flatten(arr)); //  [1, 2, 3, 4]

split和toString

数组的toString方法可以把数组直接转换成逗号分隔的字符串。如[1, [2, [3, 4]]] => “1,2,3,4”

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
  //先把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组
  return arr.toString().split(",").map(Number); 
}
console.log(flatten(arr)); //  [ 1, 2, 3, 4 ]

数组去重

利用Set。new一个Set,参数为需要去重的数组,Set会自动删除重复的元素,在Array.form将Set转为数组返回

const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log([...new Set(arr)]); //[ 1, 2, 3, 5, 9, 8 ]
console.log(Array.from(new Set(arr))); //[ 1, 2, 3, 5, 9, 8 ]

利用数组的filter()+indexOf去重。利用filter方法,返回arr.indexOf(num)等于index的值。原理就是indexOf会返回先找到的数字的索引。


function unique(arr) {
  return arr.filter((item, index, array) => {
    return array.indexOf(item) === index;
  });
}
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log(unique(arr)); // [1, 2, 3, 5, 9, 8]

利用Map。新建一个数组和map,如果当前值在map中没有出现过,就加入数组,最后返回数组

const unique = (arr) => {
    const map = new Map();
    const res = [];
    for (let item of arr) {
        if (!map.has(item)) {
            map.set(item, true);
            res.push(item);
        }
    }
    return res;
}
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log(unique(arr)); // [1, 2, 3, 5, 9, 8]

手写类型判断函数

思路:如果是null,直接返回String(null);基本类型和函数,直接使用typeof;其它引用类型,使用Object.prototype.toString.call。

function getType(value) {
  // 判断数据是 null 的情况
  let type;
  if (value === null) {
    return String(value);
  }
  // 判断数据是基本数据类型的情况和函数的情况,使用typeof
  if (typeof value !== "object") {
    return typeof value;
  } else {
    // 判断数据是引用类型的情况,设当前类型为date
    let valueClass = Object.prototype.toString.call(value); //"[object Date]"
    type = valueClass.split(" ")[1].split(""); //[ 'D', 'a', 't', 'e', ']' ] 截取类型并转换为数组
    type.pop(); //[ 'D', 'a', 't', 'e' ],去掉数组最后的右括号"]"
    return type.join("").toLowerCase(); //[ 'D', 'a', 't', 'e' ] => "Date" => "date" 数组转小写字符串
  }
}

// 测试
console.info(getType(null)); // null
console.info(getType(undefined)); // undefined
console.info(getType(100)); // number
console.info(getType("abc")); // string
console.info(getType(true)); // boolean
console.info(getType(Symbol())); // symbol
console.info(getType({})); // object
console.info(getType([])); // array
console.info(getType(() => {})); // function
console.info(getType(new Date())); // date
console.info(getType(new RegExp(""))); // regexp
console.info(getType(new Map())); // map
console.info(getType(new Set())); // set
console.info(getType(new WeakMap())); // weakmap
console.info(getType(new WeakSet())); // weakset
console.info(getType(new Error())); // error
console.info(getType(new Promise(() => {}))); // promise

测试结果:
image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/573689.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据库安全如何保障?YashanDB有妙招(上篇)

数据库作为信息系统的核心&#xff0c;不仅承载着海量的关键数据&#xff0c;还负责向各类用户提供高效、可靠的信息服务&#xff0c;数据库的安全性显得尤为关键&#xff0c;已成为信息安全体系的重中之重。 什么是数据库安全&#xff1f; 数据库安全是数据安全的一个子集&…

AI-数学-高中-45函数单调性与导数

原作者视频&#xff1a;【导数】【一数辞典】5函数单调性与导数&#xff08;重要&#xff09;_哔哩哔哩_bilibili 导数最重要作用&#xff1a;判断函数单调性。 示例&#xff1a;

基于SpringBoot和Leaflet的地震台网信息预警可视化

目录 前言 一、后台管理设计与实现 1、Model层 2、业务层 3、控制层 二、前端预警可视化设计与实现 1、网页结构 2、数据绑定 三、效果展示 总结 前言 在之前的几篇博客中&#xff0c;我们讲解了如何在Leaflet中进行预警信息提示效果&#xff0c;以及基于XxlCrawler进…

智能写作工具,一键改写文章不费力

改写是一种用来创作原创文章的方式&#xff0c;也是用来提升文章质量的方法。无论我们在创作中通过改写来提升文章的质量&#xff0c;还是用改写帮助我们达到原创文章的生成&#xff0c;文章改写都可以实现我们这些目的&#xff0c;然而&#xff0c;想要高效率轻松改写文章我们…

三分钟设计自己的工厂!基于昇腾AI处理器昇思MindSpore打造的智能化工大模型为化工研发效率带来10+倍提升

前言&#xff1a;华为与大连化物所深度合作&#xff0c;联合推出智能化工大模型&#xff0c;AI赋能化工领域&#xff0c;拥抱科学创新&#xff0c;提供了数据驱动化工研发的新范式。 2024年3月22日&#xff0c;在北京国家会议中心召开的昇思人工智能框架峰会上发布了由华为AI4…

VForm3的文件上传方式

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

智能条件单无需盯盘!

一般我们说到量化交易都觉得很困难&#xff0c;写策略难&#xff0c;看python难&#xff0c;不会使用程序难&#xff0c;电脑交易不方便难&#xff0c;今天我们来看看手机电脑都可以使用的量化基础条件单的操作。 迈入量化第一步&#xff0c;条件单的使用&#xff01; 今天小编…

企业微信hook接口协议,标签变动回调

个人标签新增回调 {"labellist": [{"op": 3, "bDeleted": 0, //0代表新增"create_time": 1678114162, "label_groupid": 14073749131792038, "label_type": 2, "source_appid": 0, "business_typ…

Python 装饰器

Python 装饰器 1. 简单的装饰器 下面是一个简单的装饰器示例&#xff0c;它记录被装饰函数的调用信息&#xff1a; def my_decorator(func):""" 中层函数&#xff1a;接收被装饰的函数 """def wrapper():""" 内层函数&#xf…

嵌入式4-25

整理思维导图在课上练习的基础上&#xff0c;完成拷贝赋值函数完成下列类 #include <iostream> using namespace std;class person {string name;int age;char sex; public:int *p;person(){cout<<"person无参构造"<<endl;};person(const string n…

专业护眼灯什么牌子好?2024年专业护眼灯品牌排行分享

专业护眼灯什么牌子好&#xff1f;各位家长可能已经注意到一个令人关切的现象&#xff1a;戴眼镜的孩子人数在不断上升&#xff0c;许多孩子正在接受眼部治疗。眼睛健康的问题变得越来越普遍&#xff0c;这无疑令人担忧。在当今数字化时代&#xff0c;孩子们每日需长时间阅读和…

对腾讯文档AI助手技术架构的简单分析

腾讯文档全面接入了AI&#xff0c;今天腾讯技术大佬tensorchen作者发表了一篇文章《腾讯文档AI助手技术实践》&#xff0c; 里面讲解了从技术应用架构以及AI大模型赋能角度&#xff0c;介绍腾讯文档AI智能助手的探索和实践之路。作为一款集多功能为一体的AI产品&#xff0c;腾…

Web前端开发之HTML_3

标签之表格Form表单块元素与行内元素&#xff08;内联元素&#xff09;HTML5新增标签 1. 标签之表格 <table></table> 1.1 表格&#xff08;快速生成&#xff1a;table>tr*2>td*3{单元格}&#xff09; 表格由行、列、单元格组成。单元格有同行等高、同列等…

(八)Servlet教程——创建Web项目以及Servlet的实现

1. 打开Idea编辑器 2. 点击界面上的“新建项目”按钮 3. 设置好项目名称和位置 应用服务器选择之前设置好的Tomcat服务器 构建系统默认选择Maven 4. 点击“下一步”按钮 5. 点击“完成”按钮&#xff0c;Idea就创建好了项目&#xff0c;创建完成后的目录结构如下图所示 6. 此…

2024.4.25

#include <iostream> #include <iomanip> using namespace std; class Person{const string name;int age;char sex; public:Person(const string name):name(name){cout << "第一个Person构造函数" << endl;}Person():name("zhangsan&…

js网络请求---fetch和XMLHttpRequest的用法

fetch 语法规则 let promise fetch(url, [options]) //url —— 字符串&#xff1a;要访问的 URL。 //options —— 对象&#xff1a;可选参数&#xff1a;method&#xff0c;header 等。 fetch函数返回一个promise&#xff0c;若存在网络问题&#xff0c;或网址不存在&…

Java通过邮件发送验证码和通过手机号发送验证码

前提&#xff1a;我将验证码存入了map集合&#xff0c;进行验证。 private static HashMap<String, Integer> emailMap new HashMap<>();一、通过邮箱发送验证码&#xff1a; 1、准备条件&#xff1a;引入hutool依赖&#xff0c; <!--hutool--><depend…

【C语言】深入理解KMP算法及C语言实现

一、KMP算法简介 KMP算法&#xff08;Knuth-Morris-Pratt算法&#xff09;是一种高效的字符串匹配算法&#xff0c;由Donald Knuth、James H. Morris和 Vaughan Pratt共同发明。KMP算法的核心思想是当一次字符比较失败时&#xff0c;利用已经得到的部分匹配信息&#xff0c;将模…

Kubernetes TDengine 系列|安装 TDengine 的 Grafana 插件|Grafana监控TDengine数据

为了让Grafana 能够监控到TDengine 数据&#xff0c;快速集成搭建数据监测报警系统&#xff0c;所以直接安装TDengine 插件。 目录 一、安装 TDengine 的 Grafana 插件1、下载TDengine grafana插件2、解压到指定目录3、配置未签名插件 二、配置数据源&#xff0c;简单查询TDen…

想在游泳健身的同时畅听音乐,随时哈氪漫游这款IP68防水的骨传导耳机

平时健身的过程中&#xff0c;音乐是许多健身爱好者的忠实伴侣。无论是在室内的健身房&#xff0c;还是户外的自然风光中&#xff0c;一副优质的耳机可以极大地提升我们的锻炼体验。现在市面上专为运动设计的耳机选择非常多&#xff0c;我喜欢用骨传导耳机&#xff0c;目前在用…