0、一句话总结
- 属性名,以及是属性的函数的简洁写法,写起来简单易阅读
- 属性名可以用变量字符串拼接起来(话说以前也有吧?)
- 函数都有name属性,但set和get也要加前缀
- Object.is判断两个变量是否相等
- Object.assign可以合并对象的非原型链上,且可枚举属性
- Object.getOwnPropertyDescriptor查看属性是否可枚举、可修改、可赋值
- Object.keys获取对象非原型链上,且可枚举属性的key
- Object.values获取对象非原型链上,且可枚举属性的值
- Object.entries同时获取上面两个
- 当属性在原型链上,或者不可枚举时,会被很多方法忽视(参照6.1)
- __proto__和prototype的关系(参照7.1)
- Object.setPrototypeOf(obj, prototype)设置__proto__属性
- Object.getPrototypeOf(obj)获取__proto__属性
- es7新增对对象有效的扩展运算符…(三个点)
本篇没有的请翻看【对象的扩展】系列的其他篇
6、对属性的操作
6.1、对属性的遍历
遍历可被枚举的属性,可以参考5.3
属性的遍历(或者说获取keys)有若干种方式,而属性来源于本身、继承、以及设置是否可枚举也有三种情况,组合起来情况很多。
故这里列一个表格出来。
方法 | 原型链继承(原型链上的属性) | call继承(在实例上的属性) | 普通属性,但enumerable为false |
for…in | 显示 | 显示 | 不显示 |
Object.keys() | 不显示 | 显示 | 不显示 |
JSON.stringify() | 不显示 | 显示 | 不显示 |
Object.getOwnPropertyNames() | 不显示 | 显示 | 显示 |
Reflect.ownKeys() | 不显示 | 显示 | 显示 |
Object.getOwnPropertySymbols() | 不显示 | 不显示 | 不显示 |
总结一下:
要求 | 方法 |
普通的情况 | Object.keys() |
本身 + 原型链 | for…in |
本身,且包括枚举属性被设置为false的key | Object.getOwnPropertyNames() Reflect.ownKeys() |
兼容低版本IE | for…in(但注意原型链上的是否需要) |
只获取不能枚举的属性 | Object.getOwnPropertyNames()获取全部 再排除Object.keys() |
只获取原型链上(不包括自己的) | for..in先获取 再排除掉普通的 |
原型链上包括不可枚举的 | 手动递归__proto__属性 然后每一层Object.getOwnPropertyNames()获取 |
原型链上,只要不可枚举的 | 手动递归__proto__属性 然后每一层先获取全部再排除普通 |
如果只是单纯检查某个key是否在对象上存在(并且不检查原型链),则可以使用
obj.hasOwnProperty(key);
返回true则存在,false则不存在
- 1
- 2
- 3
- 1
- 2
- 3
以上验证代码如下:
- 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
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 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
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
7、__proto__相关
7.1、__proto__属性的来源
7.1.1、__proto__和prototype的关系
关于继承挺复杂的,主要难点是如果通过继承和原型链,以Object为基础,生成了所有数据类型。
有兴趣的可以去谷歌详查,这里简单说说(能力所限,没办法详细的说清楚)。
首先要明确什么时候用prototype表示,什么时候用__proto__来表示:
参考链接:知乎,苏墨橘的回答
prototype:
- 显式原型;
- 函数专有(因为需要通过函数来创建实例);
- 指向函数的原型对象;
- 默认情况下(未改变指向目标),有constructor属性,指向函数本身,即Fun.prototype.constructor === Fun为true;
- 当改变了prototype的情况下,例如Child和Father都是函数,然后
Child.prototype = new Father()
; - 那么Child的prototype指向Father的实例,而Father的实例因为不是函数,因此只有
__proto__
属性,因此Child.prototype也只有__proto__
属性; - 又因为Father的实例只有
__proto__
属性,且该属性指向Father的prototype属性,因此Child.prototype.__proto__ == Father.prototype
的值为true;
__proto__:
- 隐式原型;
- 根据ECMA定义 ‘to the value of its constructor’s “prototype” ’ —-指向创建这个对象的函数的显式原型;
- 因此
(new Fun()).__proto__ === Fun.prototype
的值为true;
以代码为例作为解释:
- 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
- 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
上面从继承的对象倒推回原型链的最底端。
7.1.2、从Object推导到发生继承的对象
为了方便理解,我们再从最底端推导到foo这个对象。
- 首先,除了null和undefined之外,都是对象;
- 而Object本身的原型链是Object.prototype,并且这个对象的__proto__的值是null;
- 任何构造函数,他首先是一个函数,因此有Father.prototype;
- Father.prototype是一个对象,有属性constructor表示这个构造函数的原型;
- 因为是对象,所以也有__proto__属性,于是Father.prototype.__proto__ === Object.prototype
- 而函数本身的原型是Function,因此也有Father.__proto__;
- 并且Father.__proto__ === Function.prototype的值为true;
- 根据构造函数Father创建出来的实例,只会有__proto__属性,因此(new Father()).__proto__ === Father.prototype
- 可得知(new Father()).__proto__.__proto__ === Object.prototype
- 而Child.prototype = new Father(),且foo是Child是实例,因此
- foo.__proto__ === Child.prototype,且从上一步可继续推导出
- foo.__proto__.__proto__ === Father.prototype,于是可以继续推导出
- foo.__proto__.__proto__.__proto__ === Object.prototype
因此,当如上面的继承发生时:
- foo.__proto__是Child的原型属性prototype;
- foo.__proto__也是Child原型被改变时,那个实例middle(他们是全等的);
- foo.__proto__.__proto__是Father的原型属性prototype,原因是Father实例的__proto__属性是Father的prototype属性;
- 因为Father不继承于其他,因此他的prototype属性是一个对象,里面有一个constructor属性,且该属性就是Father本身;
- 所以foo.__proto__.__proto__.constructor === Father为true
- 又因为是对象,对象是基于Object构造函数的实例,因此他也有__proto__属性,且和他的原型保持一致;
- foo.__proto__.__proto__.__proto__ === Object.prototype为真
- 简而言之,foo的原型链最顶层的是创建他的那个函数的原型(如Child.prototype);
- 第二层是Child所继承的那个函数的原型(如Father.prototype);
- 依次类推,当无继承时,则指向Object.prototype(因为所有函数默认继承于Object);
- 而Object.prototype是继承链的终点,根据规则,终点的值为null
另外提一句,call或者apply方式继承的,不影响constructor属性(因为不涉及到原型链变化);
但Object.create(proto, [ propertiesObject ])是原型链继承,因此会受到影响。
7.2、获取对象的原型链__proto__
Object.getPrototypeOf(obj)
简单来说,这个等价于obj.__proto__
但是在标准里,不推荐使用 obj.__proto__
这样的方法,原因是这个不是标准的方法,只是浏览器基本都这么干而已。
而 obj.__proto__
表示什么呢?大家都知道。
所以这个函数是干嘛用的,大家都知道了吧,就是拉取原型链,并且是最近那一层的。
也就是说,如果A继承B,B继承C,那么对(new A())使用本方法,只能拉取到B.prototype。
7.3、设置原型链
Object.setPrototypeOf(obj, prototype)
参数1是被设置的对象(不能是undefined或者null);
参数二是他的原型链(对象)。
本方法可以理解为如下代码:
- 1
- 1
但由于修改原型链的性能很差,所以一般不建议这么做。
作为替代,你可以用 Object.create(proto, [ propertiesObject ])
写一个新的来作为替代。