new Function
# new Function
最近在项目中看到个写的不错的函数实现,拿出来和大家一起分享分享
# 项目内容
# 任务需求
- 两个配置项,
prev
,和curr
- 它们的结构可能是这样:
let prev = {
name: 'prev',
lives: {
eat: '想吃点东西',
play: '想出去玩',
},
}
let curr = {
name: 'curr',
lives: {
eat: 'eat meat',
play: 'play football',
sleep: {
time: 'morning',
place: 'home',
},
},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 我们发现它们有点像,但是数据又不同
- 现在我们要实现一个 将
curr
的某些项(由你传入的参数决定)来更新prev
的数据 - 可能的情况:
prev
有curr
的属性,只是值不同prev
里面没有curr
的属性,这时我们要将该属性对应的值都 在prev
上呈现
- 需要注意的是:我们这个操作并不是将
curr
里的内容和prev
比较,有不同才更新到prev
上,而是根据 用户 输入的 属性 ,将curr
上对应的属性值更新到prev
上
# 原代码
- 有改动
function setConf(prev, curr, fileds, callback) {
if (!Array.isArray(fileds)) throw new Error(`第 3 个参数必须为一个数组`)
const isFunc = typeof callback === 'function'
for (let i = 0; i < fileds.length; i++) {
const key = fileds[i]
if (typeof curr[key] === 'undefined')
throw new Error(`属性${key}的值为undefined`)
prev[key] = isFunc ? callback(key, curr[key]) : curr[key]
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
【使用】:
let prev = {
name: 'prev',
lives: {
eat: '想吃点东西',
play: '想出去玩',
},
}
let curr = {
name: 'curr',
lives: {
eat: 'eat meat',
play: 'play football',
sleep: {
time: 'morning',
place: 'home',
},
},
}
setConf(prev.lives, curr.lives, ['eat', 'sleep'])
console.log(prev)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
结果
【分析】:
- 我们看到上面的结果,我们将
curr
上的eat
和sleep
属性的值 更新到了prev
上 setConf(prev.lives, curr.lives, ['eat', 'sleep'])
我们传入了prev.lives
和curr.lives
, 意思是:我们需要将curr.lives
的某写属性 更新到prev.lives
上,而需要更新哪些内容,我们 第 3 个参数['eat', 'sleep']
就是 需要更新的属性
# 原代码升级版
function setValue(data, key, value) {
const func = new Function(
'obj',
'value',
`return obj['${key.split('.').join("']['")}'] = value`
)
return func(data, value) || ''
}
function getValue(data, key) {
const func = new Function(
'obj',
`return obj['${key.split('.').join("']['")}']`
)
return func(data)
}
function setConf(prev, curr, fileds, callback) {
if (!Array.isArray(fileds)) throw new Error(`第 3 个参数必须为一个数组`)
const isFunc = typeof callback === 'function'
for (let i = 0; i < fileds.length; i++) {
let prevKey = ''
let currKey = ''
if (Array.isArray(fileds[i])) {
prevKey = fileds[i][0]
currKey = fileds[i][1]
} else {
prevKey = currKey = fileds[i]
}
if (typeof curr[currKey] === 'undefined')
throw new Error(`属性${currKey}的值为undefined`)
setValue(
prev,
prevKey,
isFunc
? callback(prevKey, getValue(curr, currKey))
: getValue(curr, currKey)
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
【使用】:
let prev = {
name: 'prev',
lives: {
eat: '想吃点东西',
play: '想出去玩',
sleep: {
time: 'old time',
},
},
}
let curr = {
name: 'curr',
lives: {
eat: 'eat meat',
play: 'play football',
sleep: {
time: 'morning',
place: 'home',
},
newTime: '最新时间',
},
}
setConf(prev.lives, curr.lives, ['eat', ['sleep.time', 'newTime']])
console.log(prev)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 结果
【分析】:
- 现在经过升级,
setConf
第3
个参数数组里面还可以是数组,里面的那个 数组的意思是:['sleep.time', 'newTime']
, 将prev.lives
里的sleep.time
设置为curr.lives
里的newTime
值 - 这样该方法可以接受多个参数,并且
prev
里需要更新的属性 可以和curr
里的属性不一样 - 上面有一个缺陷,就是如果
prev.lives
对象里面 不存在sleep
属性 是会报如下的错
let prev = {
name: 'prev',
lives: {
eat: '想吃点东西',
play: '想出去玩',
// 删除 sleep 属性
},
}
let curr = {
name: 'curr',
lives: {
eat: 'eat meat',
play: 'play football',
sleep: {
time: 'morning',
place: 'home',
},
newTime: '最新时间',
},
}
setConf(prev.lives, curr.lives, ['eat', ['sleep.time', 'newTime']])
console.log(prev)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 结果
# 自己实现一个
- 对于此前没有用过
new Function
创建过函数的我来说,我第一眼看到这个的使用就开始百度 new Function( 'obj', 'value', return obj['${key.split('.').join("']['")}'] = value )
特别是return
后面的内容,还真得找个例子模拟运行一遍才能看懂- 所以花了点时间在思考,可不可以用我自己的方法实现一个类似的功能
# 分析
# getValue
- 该方法就是通过 不断对一个
对象
类型的数据,通过.
取其对象的属性,直到最后你需要的那个属性
对应的值
let prev = {
name: 'prev',
lives: {
eat: '想吃点东西',
play: '想出去玩',
},
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 比如上面这个,如果我要获取到
play
属性 对应的 值 - 我们一般是通过
prev.lives.play
来获取 - 如果对这个获取的过程进行拆分,可以总结如下:
- 首先获取
lives
的属性值:let livesTmp = prev.lives
- 然后通过上一步的值,再获取
play
的属性值:let play = livesTmp.play
- 首先获取
- 所以它的处理过程是,不断获取到
上一个属性
对应的属性值
,然后通过该属性值
继续找下一个属性
对应的属性值
- 我们知道 数组的
reduce
方法,在回调函数中的第一个参数,是上一次处理返回的结果,第二个是当前的值。 - 所以我们用
reduce
方法改写getValue
函数实现,如下:
function getValue(data, keys) {
return keys.split('.').reduce((pre, key) => {
return pre[key]
}, data)
}
1
2
3
4
5
2
3
4
5
- 加上错误处理:
function getValue(data, keys) {
let value
try {
value = keys.split('.').reduce((pre, key) => {
return pre[key]
}, data)
if (typeof value === 'undefined') {
throw new Error(`配置项${keys}的值为undefined`)
}
} catch (e) {
throw new Error(`配置项${keys}的值为undefined`)
}
return value
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# setValue
- 我们借鉴上面的
getValue
的思路,getValue
是通过不断取属性值来取到你想获取的属性对应的值。 - 那我们的
setValue
就是先通过查询当前属性有没有在 对象上,如果没有就添加,存在就查询 该属性值对应的下一个属性值,知道找到最后一个你要修改或者添加的属性和属性值。 - 所以我们也通过
reduce
来改写setValue
函数实现,如下:
function setValue(data, keys, value) {
keys.split('.').reduce((pre, key, i, arr) => {
if (i === arr.length - 1) pre[key] = value
if (i != arr.length - 1 && typeof pre[key] === 'undefined') pre[key] = {}
return pre[key]
}, data)
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 也可以用 如下不太好理解的写法,它和上面是等价的
function setValue(data, keys, value) {
keys.split('.').reduce((pre, key, i, arr) => {
;(i === arr.length - 1 && (pre[key] = value)) ||
(i != arr.length - 1 &&
typeof pre[key] === 'undefined' &&
(pre[key] = {}))
return pre[key]
}, data)
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 最终修改版
function getValue(data, keys) {
let value
try {
value = keys.split('.').reduce((pre, key) => {
return pre[key]
}, data)
if (typeof value === 'undefined') {
throw new Error(`配置项${keys}的值为undefined`)
}
} catch (e) {
throw new Error(`配置项${keys}的值为undefined`)
}
return value
}
function setValue(data, keys, value) {
keys.split('.').reduce((pre, key, i, arr) => {
;(i === arr.length - 1 && (pre[key] = value)) ||
(i != arr.length - 1 &&
typeof pre[key] === 'undefined' &&
(pre[key] = {}))
return pre[key]
}, data)
}
function setConf(prev, curr, fileds, callback) {
const isFunc = typeof callback === 'function'
let prevKey, currKey
fileds.forEach((keys, i) => {
prevKey = currKey = keys
if (Array.isArray(keys)) {
prevKey = keys[0]
currKey = keys[1]
}
let pKey = prevKey.split('.').pop()
let cKey = currKey.split('.').pop()
let value = isFunc
? callback(pKey, getValue(curr, currKey))
: getValue(curr, currKey)
setValue(prev, prevKey, value)
})
}
let prev = {
name: 'prev',
lives: {
eat: '想吃点东西',
play: '想出去玩',
},
province: {
newAddr: '浙江省',
},
}
let curr = {
name: 'curr',
lives: {
eat: 'eat meat',
play: 'play football',
sleep: {
time: 'morning',
place: 'home',
},
},
address: 'xx省xx市xxx',
}
setConf(prev, curr, [
'lives.eat',
['lives.sleep.time', 'lives.sleep.time'],
['province.newAddr', 'address'],
])
console.log(prev)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
【结果】:
- 这样我们通过
setConf(prev, curr, ['lives.eat', ['lives.sleep.time', 'lives.sleep.time'], ['province.newAddr', 'address']])
,实现了追加不同属性并更新给出的属性
上次更新: 2021/03/25, 12:42:28