细探javascript(一)—— 对象
深度理解js面向对象的基础——对象的基本使用及更高级的用法。
创建对象
这里介绍的创建对象,不是面向对象的。创建对象可以使用如下两种方式:
其中,obj1这种对象定义方式叫对象字面量;obj2是使用new关键字加上Object的构造函数创建的对象。
可以看到,定义到对象上的属性都可以正常访问。
值得注意的一点是,对象的属性不止可以使用点操作符来读写,也可以通过方括号。比如给obj1添加age属性:
方括号的应用场景,一般是用来获取用点操作符获取不到的属性,什么意思呢?我们来看下面这个例子:
我们先定义一个这样的对象obj3
可以看出,1,true,null都是js中的数字或者关键字,'fisrt-name'和'child.name'都不符合js中变量的命名规范,但是这些值在实际开发中是可以获取到的,比如你和后台接口对接,请求获取到的数据就是这样的奇葩,这时候,[] 就派上用场了。对了,像数字,关键字这类作为对象的属性的话,js会把它们自动转成字符串类型的属性。我们可以来看看使用方括号来获取值:
用点操作符会报错的取值方法,用[]却毫无问题,是的,方括号就是这么任性。但是在实际开发中,我们一是自己定义对象的时候不要使用数字啊关键字啊保留字啊特殊字符啊作为属性名,二是能使用点操作符的时候就使用点操作符,毕竟js也算是面向对象的语言,用点操作符的话代码的可读性也会强点。
深度理解对象的属性
ECMAScript中定义了描述了对象属性特征的属性,由于是内部属性,所以该规范把它们放在了两对方括号中。该内部属性分为两类,一类是数据属性,一类是访问器属性。
数据属性
数据值包含一个数据值的位置。有以下四个描述其行为的特性:
- [[Configurable]]:能否使用delete删除该属性;能否修改属性的特性;能否把属性修改为访问器属性。
- [[Enumerable]]:能否通过for...in循环到该属性。
- [[Writable]]:该属性是否可以修改
- [[Value]]:该属性的值。
直接在对象上定义的属性,[[value]]的值会赋值为相应的值,而其它三个特性都会赋值为true。
比如我定义一个person对象,给他设置了name和age属性,即这两个属性都是可修改,可delete,可以循环出的。如下例所示:
当你想修改某个属性的特性时,可以使用 Object.defineProperty() 来定义这个属性,该方法接收三个参数,第一个参数为属性所在的对象,第二个参数为属性名称,第三个参数为属性的描述符对象,描述符对象的属性必须是configurable,enumerable,writable,value中的一个或多个。
用这种方式定义的属性,[[writable]],[[configuarble]],[[enumerable]]如果不设置的话,会默认给false,也就是说,上面的person的school属性,现在是只读,不可枚举,不可删除的。如果要强制的修改或者删除它的话,在严格模式下会报错,否则会忽略你这种“违规”操作。
我们来检查一下是不是这样,首先先delete这个'school'属性:
可以看到返回的是false,打印person对象的话,该属性还是完好无损。
再来看看修改值可不可以呢?
很显然还是不行。再看看遍历对象的话能不能获取这个属性,这次我们不用for...in了,而是用对象的另一个方法:Object.keys(),这方法接收一个对象实例,返回该实例的所有可枚举(enumerable为true)的属性的字符串数组。
可以看到,school属性没有被返回,说明它不可枚举。
当我们想自己控制该属性的特性的时候,可以在描述符对象中显式的设置:
在person上有又定义了一个subject属性,并且设置为可写可枚举的,接下来使用Object.keys()遍历就可以获取该属性了,另外给该属性重新赋值也是没问题的。
综上可以得出一个结论,使用Object.defineProperty()定义的属性,如果不显示的设置该属性的描述符的相应属性,那么除value外都默认为false;而直接在对象上定义的属性,除value外默认都是true。
访问器属性
访问器属性不包含数据值,但是提供了getter和setter用于对该属性进行读写(getter和setter都不是必须的)。访问器属性也包含下面四个特性:
- [[Configurable]]:能否使用delete删除该属性;能否修改属性的特性;能否把属性修改为数据属性。
- [[Enumerable]]:能否通过for...in循环到该属性。
- [[Get]]:读取属性时调用的函数。默认undefined。
- [[Set]]:给属性赋值时调用的函数。默认undefined。
访问器属性不能直接在对象上定义,所以要使用对象方法定义,常见的方法如下:
setter方法接收一个参数,即给该属性赋的值。
可以看到,在定义了birthday属性的getter和setter后,我们对这个对象读写都会打印对应的日志;通过这两个函数,可以实现对其它属性的赋值操作,这也是访问器属性最常见的用法。
注意一点:在访问器属性中不能访问自己,用上例来说,在setter里面,不能写诸如this.birthday=val之类的,否则会不断的递归调用引发内存溢出错误。
还有一种不是标准但也被大部分浏览器认可的方法也可以定义getter和setter,这里在上例的基础上修改,仅供了解参考:
上面的数据属性和访问器属性都是一次定义一个属性,倘若一个对象的属性特别多那么就会造成代码臃肿,所以js提供了一个可以一次定义多个属性的方法:Object.defineProperties()。用法也十分简单:
读取属性的特性
Object.getOwnPropertyDescriptor()方法了解一下。它接收一个对象和一个属性,返回一个描述符对象,很简单有木有!
用上例来操作一波:
当然啦,跟Object.defineProperties类似的,也有一个Object.getOwnPropertyDescriptors()的方法,它接收一个对象,返回所有的属性的描述符对象:
对象保护
你可能对这个标题有点困惑。对象保护?对象不就是用来操作的吗?保护它干什么??
OK,你可以对自己的代码为所欲为,但是别人能允许你对他的代码为所欲为吗?你又要问,不对啊,代码不是要求兼容性和扩展性良好吗?是的,但是扩展性良好并不表示可以允许你肆意的修改它的对象。所以,ES5提供了下面几个级别的保护对象的方法,将对象设置为防篡改对象,限制你的操作。
不可扩展对象(preventExtensions)
默认情况下的对象都是可扩展的,就是可以动态的给对象添加属性和方法。但是通过Object.preventExtensions方法可以改变这个行为,你不可以添加了。
如图,一开始我们高高兴兴的添加属性是OK的,但是被禁用掉扩展后,添加后就会没有反应了。但是这个时候属性是可以删除的,不过删掉的话,不能再添加回来了:
密封对象(seal)
密封对象在不可扩展对象的基础上,还将对象中已有属性的[[configurable]]属性设置为false,这下你连删除属性的权力都没有了,而且不能对数据属性和访问器属性进行转换了(参考上面[[configurable]]为false时的情况)。现在你只可以修改已有属性的值,通过使用Object.seal()方法:
看吧,只有修改已有的brand属性成功了。seal看来真的很严格。
冻结对象(freeze)
OK,你应该猜到了这个方法会做出什么事了。使用Object.freeze()操作对象以后,那么这个对象只能看了,你对它无能为力。我们在上例computer的基础上操作:
修改成mac都无济于事,说不能改就不能改。
其实你也应该能猜到了,seal和freeze都是操作了对象的每个属性的描述符对象以保证其安全性,可以看下例来佐证一下观点:
查看对象状态
对应上面三种防篡改的方法,ES也提供了三种对应的对象检查的方法,用于检查对象是不是被怎么样了。这三个方法分别是:Object.isExtensible(),Object.isSealed(),Object.isFrozen()。根据单词意思就知道方法的作用 了。
上图说明了这个paper对象啊,不可扩展,已经被密封了,而且被冻结了。你就看吧。
注意,一旦把对象设置为防篡改的,就无法撤销了。换句话说,这种操作是不可逆的。并且设置为防篡改对象后,你再去试图对应的修改它,非严格模式下会静默失败,严格模式下会报错。以上内容均参考js高程,代码运行环境为chrome控制台。
如有错误,欢迎指正,不胜感激。