js获取深层次属性,一道很经典的面试题

题目:有时候我们需要访问一个对象较深的层次,但是如果这个对象某个属性不存在的话就会报错,例如:

1
2
3
var data = { a: { b: { c: 'ScriptOJ' } } }
data.a.b.c // = scriptoj
data.a.b.c.d // = 报错,代码停止执行console.log('ScriptOJ') // = 不会被执行

请你完成一个 safeGet 函数,可以安全的获取无限多层次的数据,一旦数据不存在不会报错,会返回 undefined,
例如:

1
2
3
4
var data = { a: { b: { c: 'ScriptOJ' } } }
safeGet(data, 'a.b.c') // = scriptoj
safeGet(data, 'a.b.c.d') // = 返回 undefined
safeGet(data, 'a.b.c.d.e.f.g') // = 返回 undefined

答案1:

1
2
3
4
5
6
7
const safeGet = (o, path) = {
try {
return path.split('.').reduce((o, k) = o[k], o)
} catch (e) {
return void 666
}
}

答案2:

1
2
3
4
5
6
7
8
function safeGet(o, path){

return path.split('.').reduce((o={},b)={ //用到参数默认值

return o[b]
},o)

}

shvl

1
npm install --save shvl
1
2
3
4
5
6
7
8
9
10
11
export function get (object, path, def) {
return (object = (path.split ? path.split('.') : path).reduce(function (obj, p) {
return obj && obj[p]
}, object)) === undefined ? def : object;
};

export function set (object, path, val, obj) {
return ((path = path.split ? path.split('.') : path).slice(0, -1).reduce(function (obj, p) {
return obj[p] = obj[p] || {};
}, obj = object)[path.pop()] = val), object;
};

USE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import * as shvl from 'shvl';

let obj = {
a: {
b: {
c: 1
d: undefined
e: null
}
}
};

// Use dot notation for keys
shvl.set(obj, 'a.b.c', 2);
shvl.get(obj, 'a.b.c') === 2;

// Or use an array as key
shvl.get(obj, ['a', 'b', 'c']) === 1;

// Returns undefined if the path does not exist and no default is specified
shvl.get(obj, 'a.b.c.f') === undefined;

ES7可选链式调用

1
console.log(data.user?.address?.street) //undefined

题目如下:

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
function Parent() {
this.a = 1;
this.b = [1, 2, this.a];
this.c = { demo: 8 };
this.show = function () {
console.log(this.a , this.b , this.c.demo );
}
}
function Child() {
this.a = 2;
this.change = function () {
this.b.push(this.a);
this.a = this.b.length;
this.c.demo = this.a;
}
}
Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;

parent.show();//Q1
child1.show();//Q2
child2.show();//Q3

child1.change();
child2.change();

parent.show();//Q4
child1.show();//Q5
child2.show();//Q6

知识点

  • 原型链的查找规则

    • 当实例上存在属性时, 用实例上的
    • 如果实例不存在,顺在原型链,往上查找,如果存在,就使用原型链的
    • 如果原型链都不存在,就用Object原型对象上的
    • 如果Object原型对象都不存在, 就是undefined
  • 数组和字面量对象都是引用
  • this指向在引用时确认而不是定义时

    解题思路

    下面分别模拟Q1-Q6的执行情况

    Q1:

    直接调用parent.show(),此时this指向parent,语句中的三条语句相当于分别在给window对象上赋值:

    1
    2
    3
    parent.a = 1;
    parent.b = [1, 2, parent.a];
    parent.c = { demo: 8 };

此时,parent对象应为:

1
2
3
4
5
6
7
{
a:1,
b:[1,2,1],
c:{
demo:8
}
}
Q2:

在执行var child1 = new Child();语句时,child对象的a值为2,而因为后被手动赋值为11,所以child实例上的a被改为11,这时调用原型链上的show()方法,依次打印,

这里this.a根据原型链的查找规则,在实例上有a的赋值,所以直接使用实例上的值也就是11,其他值实例上没有,需要在原型上寻找,所以输出b为[1,2,this.a],

而这里的this.b因为是数组,为引用类型,在执行var parent = new Parent();时被定义在parent实例上,所有this.a的指针指向共同的引用地址,所以为1 ,

this.c因为也是引用类型,指针也被指向共同的引用对象地址。

Q3:

实例上的a被重新赋值,所以this.a的输出被改为12,其余执行步骤同Q2。

Q4:

相当于再次调用parent实例上的show方法,因为数据没有发生变动,所以输出值同Q1。

Q5:

因为在调用this.change时,this.a的值被赋值为this.b数组的长度,所有这里的a输出为4,this.b的值使用引用地址b数组,因为在下一步中又执行了一次对this.b数组的push,所以这里打印this.b是被push两次后的数组,而this.c也是被push两次后的数组的长度,注意因为this.a和this.c的数据类型不同,所以this.a是单独的内存,而this.c则是使用相同一块内存。

Q6:

又对this.b数组执行了一次push,所以这次this.a的输出应为数组的当前长度也就是5,this.c的值也是数组长度也就是5。

答案

在chrome控制台中运行代码得到结果如下:

jietu20181216-183105