从零开始学_JavaScript_系列(40)——对象的扩展(3)当枚举、原型链遇见对属性的操作

来源:互联网 发布:nginx常用模块 编辑:程序博客网 时间:2024/06/05 22:43

0、一句话总结

  1. 属性名,以及是属性的函数的简洁写法,写起来简单易阅读
  2. 属性名可以用变量字符串拼接起来(话说以前也有吧?)
  3. 函数都有name属性,但set和get也要加前缀
  4. Object.is判断两个变量是否相等
  5. Object.assign可以合并对象的非原型链上,且可枚举属性
  6. Object.getOwnPropertyDescriptor查看属性是否可枚举、可修改、可赋值
  7. Object.keys获取对象非原型链上,且可枚举属性的key
  8. Object.values获取对象非原型链上,且可枚举属性的值
  9. Object.entries同时获取上面两个
  10. 当属性在原型链上,或者不可枚举时,会被很多方法忽视(参照6.1)
  11. __proto__和prototype的关系(参照7.1)
  12. Object.setPrototypeOf(obj, prototype)设置__proto__属性
  13. Object.getPrototypeOf(obj)获取__proto__属性
  14. 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则不存在

var obj = {a: 1};obj.hasOwnProperty("a");    //trueobj.hasOwnProperty("b");    //false

以上验证代码如下:

//继承方式//原型链继承function Child() {    this.a = "a";}function Father() {    this.b = "b";}Child.prototype = new Father();let foo = new Child();//call继承function AnotherFather() {    this.b = "b";}function AnotherChild() {    this.a = "A";    AnotherFather.call(this);}let bar = new AnotherChild();//其他,非原型链继承但enumberable值为falselet obj = Object.defineProperty({}, "a", {    value: "a",    enumerable: false})//遍历方式//for...inconsole.log("for...in");let arrFoo = [];for (let i in foo) {    arrFoo.push(i);}let arrBar = [];for (let i in bar) {    arrBar.push(i);}let objArr = [];for (let i in obj) {    objArr.push(i);}console.log(arrFoo);    //["a", "b"]console.log(arrBar);    //["a", "b"]console.log(objArr);    //[]console.log("-----");//Object.keys();console.log("Object.keys()");console.log(Object.keys(foo));  //["a"]console.log(Object.keys(bar));  //["a", "b"]console.log(Object.keys(obj));  //[]console.log("-----");//JSON.stringify()console.log("JSON.stringify()");console.log(JSON.stringify(foo));   //{"a":"a"}console.log(JSON.stringify(bar));   //{"a":"A","b":"b"}console.log(JSON.stringify(obj));   //{}console.log("-----")//Object.getOwnPropertyNames()返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。console.log("Object.getOwnPropertyNames()");console.log(Object.getOwnPropertyNames(foo));   //["a"]console.log(Object.getOwnPropertyNames(bar));   //["a", "b"]console.log(Object.getOwnPropertyNames(obj));   //["a"]console.log("-----");//Reflect.ownKeys()返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举。console.log("Reflect.ownKeys()");console.log(Reflect.ownKeys(foo));   //["a"]console.log(Reflect.ownKeys(bar));   //["a", "b"]console.log(Reflect.ownKeys(obj));   //["a"]console.log("-----");//Object.getOwnPropertySymbols()返回一个数组,包含对象自身的所有Symbol属性console.log("Object.getOwnPropertySymbols()");console.log(Object.getOwnPropertySymbols(foo));   //[]console.log(Object.getOwnPropertySymbols(bar));   //[]console.log(Object.getOwnPropertySymbols(obj));   //[]//for...of只能用于迭代有迭代器接口的对象,普通对象是没有这个接口的,所以不行

7、__proto__相关

7.1、__proto__属性的来源

7.1.1、__proto__和prototype的关系

关于继承挺复杂的,主要难点是如果通过继承和原型链,以Object为基础,生成了所有数据类型。

有兴趣的可以去谷歌详查,这里简单说说(能力所限,没办法详细的说清楚)。

首先要明确什么时候用prototype表示,什么时候用__proto__来表示:

参考链接:知乎,苏墨橘的回答

prototype:

  1. 显式原型;
  2. 函数专有(因为需要通过函数来创建实例);
  3. 指向函数的原型对象;
  4. 默认情况下(未改变指向目标),有constructor属性,指向函数本身,即Fun.prototype.constructor === Fun为true;
  5. 当改变了prototype的情况下,例如Child和Father都是函数,然后 Child.prototype = new Father()
  6. 那么Child的prototype指向Father的实例,而Father的实例因为不是函数,因此只有 __proto__ 属性,因此Child.prototype也只有__proto__属性;
  7. 又因为Father的实例只有__proto__属性,且该属性指向Father的prototype属性,因此Child.prototype.__proto__ == Father.prototype的值为true;


__proto__:

  1. 隐式原型;
  2. 根据ECMA定义 ‘to the value of its constructor’s “prototype” ’ —-指向创建这个对象的函数的显式原型;
  3. 因此 (new Fun()).__proto__ === Fun.prototype 的值为true;


以代码为例作为解释:

//写一个prototype继承function Child() {    this.a = "a";}function Father() {    this.b = "b";}var middle = new Father();Child.prototype = middle;//创建了一个实例let foo = new Child();//foo是一个对象,不是函数,所以他只有__proto__属性,并且该属性指向他的原型的prototypefoo.__proto__ === Child.prototype;  //true//Child的原型链被修改,并且他的值是一个Father实例。所以如下,但注意两个并不相等Child.prototype;    //Father {b: "b"}foo.__proto__;      //Father {b: "b"}//  1、Child是函数,所以有prototype属性,但是foo是对象,只有__proto__属性//  2、而foo.__proto__也是对象,所以只有foo.__proto__.__proto__这样的属性//  3、Child.prototype也是一个对象,这个对象也只有__proto__属性//  4、由于foo.__proto__和Child.prototype都指向了一个Father对象,而Father对象//     是Father构造函数创建出来的,因此他的__proto__指向Father的prototype(new Father()).__proto__ === Father.prototype;    //true//  于是根据上面的结果可以继续推导出foo.__proto__.__proto__ === Father.prototype;   //true//  而Father的原型是一个Object对象//  这个对象包含一个constructor属性,表示构造函数,指向Father本身Father.prototype.constructor === Father;    //true//  因为Father原型是一个Object对象,而Object对象是通过Object构造函数生成的//  因此这个对象的__proto__属性和他的构造函数的prototype属性一致Father.prototype.__proto__ === Object.prototype;    //true//  而Object的原型显然已经没有原型了,但因为他是个对象,因此依然有__proto__属性//  只不过他的__proto__属性是空,在原型链里,用null表示终结Object.prototype.__proto__;     //null

上面从继承的对象倒推回原型链的最底端。

7.1.2、从Object推导到发生继承的对象

为了方便理解,我们再从最底端推导到foo这个对象。

  1. 首先,除了null和undefined之外,都是对象;
  2. 而Object本身的原型链是Object.prototype,并且这个对象的__proto__的值是null;
  3. 任何构造函数,他首先是一个函数,因此有Father.prototype;
  4. Father.prototype是一个对象,有属性constructor表示这个构造函数的原型;
  5. 因为是对象,所以也有__proto__属性,于是Father.prototype.__proto__ === Object.prototype
  6. 而函数本身的原型是Function,因此也有Father.__proto__;
  7. 并且Father.__proto__ === Function.prototype的值为true;
  8. 根据构造函数Father创建出来的实例,只会有__proto__属性,因此(new Father()).__proto__ === Father.prototype
  9. 可得知(new Father()).__proto__.__proto__ === Object.prototype
  10. 而Child.prototype = new Father(),且foo是Child是实例,因此
  11. foo.__proto__ === Child.prototype,且从上一步可继续推导出
  12. foo.__proto__.__proto__ === Father.prototype,于是可以继续推导出
  13. foo.__proto__.__proto__.__proto__ === Object.prototype


因此,当如上面的继承发生时:

  1. foo.__proto__是Child的原型属性prototype;
  2. foo.__proto__也是Child原型被改变时,那个实例middle(他们是全等的);
  3. foo.__proto__.__proto__是Father的原型属性prototype,原因是Father实例的__proto__属性是Father的prototype属性;
  4. 因为Father不继承于其他,因此他的prototype属性是一个对象,里面有一个constructor属性,且该属性就是Father本身;
  5. 所以foo.__proto__.__proto__.constructor === Father为true
  6. 又因为是对象,对象是基于Object构造函数的实例,因此他也有__proto__属性,且和他的原型保持一致;
  7. foo.__proto__.__proto__.__proto__ === Object.prototype为真
  8. 简而言之,foo的原型链最顶层的是创建他的那个函数的原型(如Child.prototype);
  9. 第二层是Child所继承的那个函数的原型(如Father.prototype);
  10. 依次类推,当无继承时,则指向Object.prototype(因为所有函数默认继承于Object);
  11. 而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);

参数二是他的原型链(对象)。

本方法可以理解为如下代码:

obj.__proto__ = prototype;

但由于修改原型链的性能很差,所以一般不建议这么做。

作为替代,你可以用 Object.create(proto, [ propertiesObject ]) 写一个新的来作为替代。

阅读全文
0 0
原创粉丝点击