从零开始学_JavaScript_系列(40)——对象的扩展(3)当枚举、原型链遇见对属性的操作
来源:互联网 发布:nginx常用模块 编辑:程序博客网 时间:2024/06/05 22:43
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则不存在
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:
- 显式原型;
- 函数专有(因为需要通过函数来创建实例);
- 指向函数的原型对象;
- 默认情况下(未改变指向目标),有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;
以代码为例作为解释:
//写一个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这个对象。
- 首先,除了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);
参数二是他的原型链(对象)。
本方法可以理解为如下代码:
obj.__proto__ = prototype;
但由于修改原型链的性能很差,所以一般不建议这么做。
作为替代,你可以用 Object.create(proto, [ propertiesObject ])
写一个新的来作为替代。
- 从零开始学_JavaScript_系列(40)——对象的扩展(3)当枚举、原型链遇见对属性的操作
- 从零开始学_JavaScript_系列(40)——对象的扩展(3)当枚举、原型链遇见对属性的操作
- 从零开始学_JavaScript_系列(38)——对象的扩展(1)属性的简洁写法
- 从零开始学_JavaScript_系列(39)——对象的扩展(2)Object对象的扩展方法
- 从零开始学_JavaScript_系列(41)——对象的扩展(4)扩展运算符三个点...
- 从零开始学_JavaScript_系列(24)——查看对象属性,合并数组
- 从零开始学_JavaScript_系列(六)——CSS的padding、margin、border属性超详细解释(图文)
- 从零开始学_JavaScript_系列(六)——CSS的padding、margin、border属性超详细解释(图文)
- 从零开始学_JavaScript_系列(20)——js系列<7>(函数原型的两种声明方式、函数的作用域)
- 从零开始学_JavaScript_系列(18)——dojo(7)(dojo中类的继承)
- 从零开始学_JavaScript_系列(26)——只需要前端知识的ajax教程
- 从零开始学_JavaScript_系列(26)——dojo的aspect方法
- 从零开始学_JavaScript_系列(27)——dojo的文档相关模块
- 从零开始学_JavaScript_系列(27)——myblog的优化【1】样式表分离、localStorage
- 从零开始学_JavaScript_系列(28)——dojo的aspect.around方法
- 从零开始学_JavaScript_系列(33)——dojo 的 tree
- 从零开始学_JavaScript_系列(34)——将canvas获取的图片下载到本地
- 从零开始学_JavaScript_系列(35)——继承与遍历的对照表
- hdu-4550-卡片游戏-贪心-java
- Java8-如何将List转变为逗号分隔的字符串
- 浏览器关闭或刷新向后台提交数据(实用解决方法)
- 注册界面刷新事件
- 导入项目 httpservlet报错
- 从零开始学_JavaScript_系列(40)——对象的扩展(3)当枚举、原型链遇见对属性的操作
- 基于Unity3D的并行渲染模型的开发教程(四):Vuforia的开发
- Leetcode(13)
- 论HashMap、Hashtable、TreeMap、LinkedHashMap的内部排序
- 把"Essencial C++"读薄(二)
- linux目录结构及常用命令
- php设计模式之观察者模式
- 从零开始学_JavaScript_系列(41)——对象的扩展(4)扩展运算符三个点...
- 多线程小结