我认为ES语言之因此强大,有两个方面。node
第一方面就是真正的面向对象语言
原型设计理念(真香),可以使ES在众多面向对象语言中可以脱颖而出。编程
第二方面就是函数式编程
继承人,经过强大的ES的函数使得他可以更加灵活,高效。数组
在我还未涉足Vue,React以及node.js以前,有必要完全梳理一下关于ES对象的各类特色,trick,以及坑。避免到时候翻阅源码时候迷失自我,风中凌乱...bash
首先本文总结一下对象的特色,以及一部分方法。其中还有一些对象的方法并未剖析,部分会移步 原型 篇总结中。框架
闲言少叙,开始今天的正文。函数式编程
有了解过[[Put]] [[Get]] [[Set]] [[delete]]
内部属性吗?函数
判断属性&对象的关系 和 遍历对象一般有哪些方法?post
讲一下你对属性特征
的理解?性能
如何设置一个对象为禁止修改?ui
当咱们给对象添加一个属性时,其实ES隐式的调用了[[Put]]内部方法。就像这样
var spy = {};
spy.age = 23; // 调用[[Put]]方法
复制代码
这个操做不只建立了对象的自有属性age
,而且指定值为23,同时也定义了一些属性特征(见下文)。
实际上触发[[Put]]
时,他的行为会取决于不少因素。例如对象中是否存在这个属性。
若是已存在这个属性,[[Put]]
会大体检查以下内容。
setter
就会调用setter
。wirtable
属性是否为false
?若是是,在非严格模式下静默,严格模式下抛错若是对象不存在该值,[[Put]]
会更加复杂,咱们在下一章 原型 会详细剖析。
当咱们进行属性访问时,在ES规范中明确表示,当进行属性访问时,实际是隐式的调用[[Get]]
方法。
[[Get]]
操做首先会检查对象中是否存在目标属性,若是找到就会返回这个属性。
若是没有找到,他颇有可能会遍历整个原型链。若是仍是没有找到属性,那么[[Get]]
操做就会返回undefined
。
实际上在底层[[Get]]
操做对属性获取进行了更复杂的处理。
当咱们给修改对象已经存在的属性时,ES会隐式的调用内部[[Set]]
方法
var spy = {};
spy.currently = weak;
spy.use = Red Bull;
spy.currently = "硬拉500lb" // 调用[[Set]]方法
复制代码
这个操做会将一个属性当前的值进行替换。
当须要删除对象一个属性时,咱们一般是调用delete
方法进行属性删除,由于将属性值设置为null
并不能从对象中完全移除他,只是调用[[Set]]
将原来的值替换为null
。
var spy = {
age : 23,
grade : “大三”,
}
age = null;
delete age.grade;
console.log("age" in spy); //true;
console.log("grade" in spy); // false;
复制代码
delete操做符针对单个对象属性调用[[delete]]
的内部方法。能够理解为在哈希表中删除一个键值对,不过要注意有些属性没法删除(下文会说到,封印和冻结)
当在开发过程当中,是否有这样判断过属性是否存在?
if(spy.age){
// do something
}
复制代码
这样是有隐患的,看过上一篇文章的朋友都知道在条件判断时,当属性的值为null undefined 0 false NaN或者' '
其中任何一个时候都会形成判断结果为false
。
所以咱们经过这种方式没法断言属性的存在性,一般咱们使用in
操做符来判断该属性是否存在对象中,实际上in
操做符就是检查哈希表中是否存在一个键。
使用in操做符来判断一个属性是否存在时,他的好处主要体如今减少性能损耗上。是由于它只判断属性是否存在,而不会评估属性的值。
可是in
操做符不只会检查本身的属性,还会检查原型链的属性。所以当咱们须要判断是对象本身的属性时,咱们一般使用hasOwnProperty()
来进行配合。
例如
var spy = {
age : 23,
grade : “大三”,
}
console.log("age" in spy); // true;
console.log("toString" in spy); // true;
spy.hasOwnProperty("age"); // true;
spy.hasOwnProperty("toString"); // false;
复制代码
一般使用for-in循环遍历可枚举属性。所谓可枚举属性就是内部特征设置[[Enumberable]]
为true
。一般状况下[[Enumberable]]
默认值为true
(除特殊状况,见下文).
ES5新增了Object.keys
方法。他能够获取可枚举属性的名字的数组。
用例你们都太熟了,简单手写一下Object.keys
方法(只是为了表示该方法的含义)。
function keys (object){
var result = [];
for(var key in object){
if(object.hasOwnProperty(key){
result[key] = object[key];
})
}
return result;
}
复制代码
经过简单模拟的keys函数能够理解Object.keys
方法和for-in
循环的区别:
Ojbcet.keys将键值对输出成数组形式,同时不会遍历原型链。
在ES5以前,咱们没法控制一个属性是否可枚举的,其实是根本没有办法访问属性的任何内部特征。
为了使ES可以更增强大,在ES5中添加了属性特征来支持额外的功能。
简单的来讲分为通用特征、数据属性特征以及访问器属性特征。
请注意:咱们没法同时对一个属性设置数据属性特征和访问器属性特征
通用特征是数据和访问器属性都具备的特征
[[Enumberable]]
:是否能够遍历到该属性
[[Configurable]]
:表示是否能够配置该属性
当设置为不可配置后,其特色能够总结为:
[[Configurable]]
再次设置为true
一般咱们使用var test = new Object()
建立对象时,其特征属性默认都是true
。但若是咱们使用Object.defineProperty()设置属性特征时,默认属性特征值为false
。
Object.defineProperty(object,"prop",{
enumberable: false;
//configurable-->false;
})
复制代码
所以咱们在使用Object.defineProperty()
设置属性特征时,须要将其全部特征属性指定一个值。即便想更改他属性特征值为flase
,我认为仍是要显示的指明,这样可以增长代码的可读性。
一样Object.defineProperties()
能够为一个对象设置同时定义多个属性,惟一不一样的是在接受参数上有细微调整,在此不作赘述。
使用Object.PropertyDescriptor()
方法获取自有属性特征。
一般咱们默认建立的对象就是数据属性的对象,数据属性拥有两个私有特征。
[[Value]]
:任何属性的值都保存在[[Value]]
中,哪怕值是一个函数。
[[Writable]]
:指示属性是否可写。
若是使用Object.defineProperty()
方法建立一个数据属性时,即便该属性不存在也能够。
var spy = {};
Object.deineProperty(spy,"name",{
value: "S!",
writable: true,
enumberable : true,
configurable : true,
})
复制代码
一般这样作是不必的,咱们能够直接定义对象的属性。但若是你但愿该对象是不能够写的,你会须要这样操做。
例如上文说到的当[[Put]]
内部方法在调用时,其实是定义了其属性的特征为数据属性,将属性值设置在[[value]]
属性中。
访问器属性不须要存储值,而是操做值的访问和设置。能够理解成设置一层代理函数(也可能够理解为拦截函数),同时访问器属性也有两个私有特征。
[[Get]]
: 对读取属性进行额外操做的函数
[[Set]]
: 对设置属性进行额外操做的函数
var spy = {
_sex : male,
};
Object.defineProperty(spy,"sex",{
get : function () {
console.log("获取spy的性别");
return this.name;
}
set : function () {
console.log("spy不能变性");
return "scram!";
}
})
console.log(spy.sex) //调用get函数
console.log(spy.sex == "feme"); //调用set函数
复制代码
注意当咱们访问设置了访问器属性特征的属性时,会执行get()
和set()
。
在使用访问器属性特征时,依然遵循默认值为false
。所以若是只定义get()
或 只定义set()
,代表该属性是只读的 或 只写的。
一般建立的对象默认都是能够拓展的,意味着新的属性能够随时被添加。一般有三种方法可以帮助咱们锁定对象的属性。
内部特征[[Extensible]]
是一个布尔值,他表示该对象自己是否能够被修改。咱们能够经过使用Object.preventExtensions( )
建立一个不能够扩展的对象。该方法接受一个参数,是你但愿使其变为不可拓展的对象。
var spy = {
age : 23,
grade : “大三”,
}
console.log(Object.preventExtensions(spy)); // true;
spy.health = strong;
console.log("health" in spy) // false;
console.log(Object.isExtensible(spy)); // false;
复制代码
一旦在对象上使用Object.preventExtensions( )
方法,就永远不能再给他添加新的属性。
可使用Object.isExtensible
来检查[[Extensilbe]]
属性的值
封印是建立不能够扩展对象的第二种方法。一旦对象封印后,其全部属性都不能够配置,但能够读写他的属性。经过使用Object.seal()
来封印一个对象,使用Object.isSealed()
判断对象是否被封印。
使用seal()
封印的特色能够总结为:
该方法调用后,[[Extensible]]
特征设置为false
;同时全部属性的[[Configurable]]
特征属性被设置为false。
建立不能够拓展对象的最后一种方法是冻结他。可使用Object.freeze()
来冻结一个对象,使用Object.isFrozen()
来判断一个对象是否被冻结。
使用freeze()
来冻结一个对象的特色能够总结为:
当一个对象冻结以后,被冻结的对象一般也被认为是不能够拓展对象和被封印对象。使用Object.isExtensible()
返回false
,同理使用Object.isSealed()
返回true
.
经过以上三种方式禁止修改对象,只是建立了浅不变性。只影响目标对象和他的直接关系(也就是没法修改指针指向),可是咱们能够直接修改目标对象,例如
var obj = {
a : 1,
arr : [1,2,3]
}
Object.freeze(obj);
obj.arr.push(1);
console.log(obj.arr); // [1,2,3,1];
obj.a = 2;
console.log(obj.a); // 1;
复制代码
使用Object.isExtensible()
返回false
,同理使用Object.isSealed()
返回true
.
若是本篇文章您以为有什么地方有问题,必定要给我指正。若是您以为还不错,而且期待后续文章,点赞关注走一波~
《红宝书》- Zakas
《JavaScript面向对象精要》 - Zakas
《你不知道的js (上) 》