基石:ES5基础(二) 对象& 属性特征

闲白

我认为ES语言之因此强大,有两个方面。node

第一方面就是真正的面向对象语言原型设计理念(真香),可以使ES在众多面向对象语言中可以脱颖而出。编程

第二方面就是函数式编程继承人,经过强大的ES的函数使得他可以更加灵活,高效。数组

在我还未涉足Vue,React以及node.js以前,有必要完全梳理一下关于ES对象的各类特色,trick,以及坑。避免到时候翻阅源码时候迷失自我,风中凌乱...bash

我知道,还差的太多,因此各位大佬!必定要给我补充。感谢!感谢!感谢!

首先本文总结一下对象的特色,以及一部分方法。其中还有一些对象的方法并未剖析,部分会移步 原型 篇总结中。框架

闲言少叙,开始今天的正文函数式编程

扔出问题:

有了解过[[Put]] [[Get]] [[Set]] [[delete]]内部属性吗?函数

判断属性&对象的关系 和 遍历对象一般有哪些方法?post

讲一下你对属性特征的理解?性能

如何设置一个对象为禁止修改ui

[[Put]] [[Get]] [[Set]] [[Delete]]

添加 ([[Put]])

当咱们给对象添加一个属性时,其实ES隐式的调用了[[Put]]内部方法。就像这样

var spy = {};
spy.age = 23; // 调用[[Put]]方法
复制代码

这个操做不只建立了对象的自有属性age,而且指定值为23,同时也定义了一些属性特征(见下文)。

实际上触发[[Put]]时,他的行为会取决于不少因素。例如对象中是否存在这个属性。

若是已存在这个属性,[[Put]]会大体检查以下内容。

  1. 属性是不是访问器,若是是而且存在setter就会调用setter
  2. 属性的数据特征中wirtable属性是否为false?若是是,在非严格模式下静默,严格模式下抛错
  3. 设置该值

若是对象不存在该值,[[Put]]会更加复杂,咱们在下一章 原型 会详细剖析。

获取 ([[Get]])

当咱们进行属性访问时,在ES规范中明确表示,当进行属性访问时,实际是隐式的调用[[Get]]方法。

[[Get]]操做首先会检查对象中是否存在目标属性,若是找到就会返回这个属性。

若是没有找到,他颇有可能会遍历整个原型链。若是仍是没有找到属性,那么[[Get]]操做就会返回undefined

实际上在底层[[Get]]操做对属性获取进行了更复杂的处理。

修改 ([[Set]])

当咱们给修改对象已经存在的属性时,ES会隐式的调用内部[[Set]]方法

var spy = {};
spy.currently = weak;
spy.use = Red Bull;
spy.currently = "硬拉500lb" // 调用[[Set]]方法
复制代码

这个操做会将一个属性当前的值进行替换。

删除 ([[Delete]])

当须要删除对象一个属性时,咱们一般是调用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

基石-ES5基础(一)数据类型&类型转换/判断

所以咱们经过这种方式没法断言属性的存在性,一般咱们使用in操做符来判断该属性是否存在对象中,实际上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循环

一般使用for-in循环遍历可枚举属性。所谓可枚举属性就是内部特征设置[[Enumberable]]true。一般状况下[[Enumberable]]默认值为true(除特殊状况,见下文).

Object.keys

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]]:表示是否能够配置该属性

当设置为不可配置后,其特色能够总结为:

  1. 该属性不能够被删除
  2. 该属性值不能够被修改
  3. 该属性只读
  4. 该属性没法将[[Configurable]]再次设置为true

Object.defineProperty()设置特征属性

一般咱们使用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]]

内部特征[[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()封印的特色能够总结为:

  1. 没法添加新属性
  2. 没法删除属性
  3. 没法改变其属性类型(数据属性和访问器属性)

该方法调用后,[[Extensible]]特征设置为false;同时全部属性的[[Configurable]]特征属性被设置为false。

  • 如何实现一个seal()?

对象冻结

建立不能够拓展对象的最后一种方法是冻结他。可使用Object.freeze()来冻结一个对象,使用Object.isFrozen()来判断一个对象是否被冻结。

使用freeze()来冻结一个对象的特色能够总结为:

  1. 不能添加或删除属性
  2. 不能改变属性类型
  3. 不能写入任何数据属性
  4. 全部属性数据属性都为只读

当一个对象冻结以后,被冻结的对象一般也被认为是不能够拓展对象和被封印对象。使用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.

最后,我相信不少哥哥对开篇提出来的问题早就一目了然,只不过是我帮您加深了一下印象。

若是本篇文章您以为有什么地方有问题,必定要给我指正。若是您以为还不错,而且期待后续文章,点赞关注走一波~

下篇文章咱们来好好总结一下对象的原型,敬请期待!

遗留问题

  • 如何实现一个seal()?
  • 如何实现一个freeze()?
  • 禁止修改对象在工业界的用处是什么?
  • 属性特征在各大框架中的使用
  • 为何要这样设计[[Put]] [[Set]],底层是怎么实现的?他们到底有什么用?是为了配合原型吗?

参考书籍

《红宝书》- Zakas

《JavaScript面向对象精要》 - Zakas

《你不知道的js (上) 》

相关文章
相关标签/搜索