如何拷贝一个函数?

web学习 专栏收录该内容
19 篇文章 1 订阅

背景

在学习js的时候,总有各种“拷贝”的问题存在,之前一直也搞不清楚(虽然现在有不怎么清楚2333),在学习和实践的过程中,突然想到一个问题:如何拷贝一个函数?

然后心中略微一懵,啥?这是个啥?然后网上一搜,大家也都表示这个问题很奇葩,主要的观点为:js中,函数是‘一等公民’,不属于任何谁谁谁,因此不论是在哪里,都可以直接调用函数,因此js根本不需要所谓的‘拷贝’,直接调用就行了。

后来想了一下,确实,函数本身就是一个单独的内存块,它不属于任何对象或函数或xxx,function本身有call、bind这些函数用来绑定this值,实际上他就是一个第三方独立的工具一般存在。

再往深的想一想

但如果说,我们就是想拷贝一个函数呐(虽然很难想到应用场景)?那么这个问题也是可以有分析的思路的。

  • 什么是拷贝?
    拷贝,是复制的意思,也就是单独请求一个内存块来存储特定的信息,且这部分信息是与原来的部分独立开的。

  • js中的拷贝
    js 中的基础类型的值,是值类型的,例如 number、string、null、undefined、boolean。这些类型的特点就是,‘是什么,就是什么,改变了,内存中的值也就发生了改变’。
    而 js 中的对象和函数都是‘引用类型’,当我们直接使用赋值运算给一个变量赋值时,其实只是把对象的地址指针给了该变量,举个例子:

    	let func = function () {
    		console.log('this is from func')
    	}
    	let varFunc1 = func
    	let varFunc2 = func
    	varFunc1 = function () {
    		console.log('this is from varFunc1')
    	}
    	varFunc1() // => this is from varFunc1
    	varFunc2() // => this is from func
    

    这个例子中,varFunc1和varFunc2的值其实都是一个指向 func 这个函数内存地址的一个指针(姑且先这么理解吧)。如果我们重新对 varFunc1 赋值,发生的事情仅仅是改变了 varFunc1 这个变量的指针而已。而实际 函数本身 ,一直在内存中的某个位置待着,等着其他地方调用。

  • 要怎么拷贝?
    拷贝的意义就是形成两个‘完全一样’ 却又 ‘完全不相关’的东西,因此,拷贝的过程也可以从这里下手。

    • 对于一个json对象的拷贝
      json对象也就意味着每一层对象中,仅存在:基本类型、对象、数组 这三种,那么最简单的方式就是使用JSON对象提供的两个方法:JSON.stringify() 、JSON.parse()
      举个例子:
    let target = {
    	a: 123,
    	b: { b1: 234, b2: [1, 3, 5]},
    	c: true
    }
    let copy = JSON.parse(JSON.stringify(target))
    // copy 便是一个拷贝后的对象
    

    当然,肯定也是可以用最原始的方法,进行深度循环拷贝,举个例子:

    let target = {
    	a: 123,
    	b: { b1: 234, b2: [1, 3, 5]},
    	c: true
    }	
    let copy = {}
    function copyProperty (val, key, result) {
    	if (Object.prototype.toString.call(val) === '[object Object]') {
    		result[key] = {}
    		Object.keys(val).forEach(childKey => {
    			copyProperty(val[childKey], childKey, result[key])
    		})
    	} else if (Array.isArray(val)) {
    		result[key] = []
    		val.forEach((item, index) => {
    			copyProperty(item, index, result[key])
    		})
    	} else {
    		result[key] = val
    	}
    }
    Object.keys(target).forEach(prop => {
    	copyProperty(target[prop], prop, copy)
    })
    console.log('result is : ', copy) // 得到需要的结果
    

    上面这个例子,在处理普通JSON对象上,是基本没有问题的,但如果在处理一些特殊的对象时,可能就不行了,例如 Set、Map、Date 等内置对象时,就会出问题,当然也包括比较特殊的对象 – Function,因此,要想再深度拷贝这些内置对象,就需要对这些特殊类型的对象进行处理。
    进行一下改造:

    let specialObj = {
    	'[object Date]': date => new Date(date),
    	'[object Set]': setData => new Set(setData),
    	'[object Map]': mapData => new Map(mapData),
    	'[object Function]': func => func,
    	'[object Symbol]': symData => symData
    } 
    let target = {
    	a: new Date(),
    	b: { b1: new Set([1, 3, 5]), b2: new Map(Object.entries({b21: 123, b22: 234}))},
    	c: Symbol('csymbol'),
    	d: () => { console.log('func from d')}
    }	
    let copy = {}
    function copyProperty (val, key, result) {
    	if (['function', 'object'].includes(typeof val)) {
    		result[key] = {}
    		let objType = Object.prototype.toString.call(val)
    		if (specialObj[objType]) {
    			result[key] = specialObj[objType](val)
    		}
    		Object.keys(val).forEach(childKey => {
    			copyProperty(val[childKey], childKey, result[key])
    		})
    	} else if (Array.isArray(val)) {
    		result[key] = []
    		val.forEach((item, index) => {
    			copyProperty(item, index, result[key])
    		})
    	} else {
    		result[key] = val
    	}
    }
    Object.keys(target).forEach(prop => {
    	copyProperty(target[prop], prop, copy)
    })
    console.log('result is : ', copy) // 得到需要的结果
    

    但上面的代码其实还是有问题的,因为在 Set 和 Map 中,还是有可能为‘引用类型’的,因此,还是应该进行遍历,并循环处理每一种情况。

后记

上面的内容还没有完全完成,但整体思路是‘把如何拷贝一个函数 变成了 如何拷贝一个对象’,然后就进行对象的拷贝处理,根据对象的类型,对每种特殊类型的对象进行单独处理。还得注意对象描述符的拷贝(通过Object.defineProperty() 和 getOwnPropertyDescriptor()实现)。

实际上,‘函数的拷贝’可简单也可复杂,简单来说,‘函数是公共的’,不需要拷贝。复杂来说,函数作为一种特殊的对象,既有其‘函数’的特征,又有对象的特征,以及两者的联系 – prototype => __proto__ ,那么‘拷贝’是否需要处理原型的部分呢? 处理了,似乎拷贝下来的就不完整,不处理,拷贝后与拷贝源之间又有大量的联系,23333。(我倾向于不处理)。

过段时间再来看这个问题吧。

贴一个图:
内置对象检测方法

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值