理解对象
面向对象最常见的方式就是类,定义一个类之后,由它创建的对象都拥有从类继承而来的方法与属性。然而 JavaScript 里面,至少在 ES6 之前是没有 class的概念的。所以它的对象与传统类的对象还是有区别的。
Js 的对象可以说是一组无序值的集合,可以包括基本类型值、引用类型值、函数
通常采用字面量的方式或者 new Object() 的方式来创建;创建时除定义的值外,还具有一些特殊值,用来定义对象的各种行为。
属性类型
JS 规定了许多属性值用于给引擎使用,但是不能直接访问他们,通常用 [[Value]] 的方式放置
数据属性 四个值,用来描述行为
[[Configurable]]:能否用 delete 删除某个属性,是否可以修改属性的特性,能否改为访问器属性,字面量创建的对象默认值为 true
[[Enumerable]]:能否通过 for-in 遍历属性名字,默认为 true
[[Writable]]:能否直接修改某个属性的值,默认为 true
[[Value]]:读数据时从这读取,写入时放在这里,默认为 undefined
Object.defineProperty()
该方法可以设置上述的特殊值,接受三个参数,参数1 为要修改的对象,参数2 为修改的对象,参数3 可以指定多个特殊值的值
如果是对已有属性操作,则改变相应的特殊值就行
如果没有该属性,则认为是通过该方法添加新属性,此时应该显式的定义各项值,否则就会默认为 false
看实例,允许直接通过该方法定义属性并直接指定对应的特殊值,若没指定的特殊值则按false ,要想让默认值为 true,需要用字面量或 new Object() 来创建
修改 Writable 属性,在严格模式下, writable 值为false时,修改属性值会报错
1 2 3 4 5 6 7 8 9 10 11 12 var great = {}var x = { name : 'Great' } Object .defineProperty (great,'name' ,{ writable : false , value : "Greatiga" }); console .log (x.name ,great.name );x.name = "yes" ; great.name = "no" ; console .log (x.name ,great.name );
1 2 3 4 5 6 7 8 var great = {}Object .defineProperty (great,'name' ,{ configurable : false , value : "Greatiga" }); console .log (great.name );delete great.name ;console .log (great.name );
但是,Configurable 属性一旦被定义为 false,就不能再变为 true 了,同时 Enumberable 属性也不可修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 var great = {}Object .defineProperty (great,'name' ,{ configurable : false , enumerable : true , writable : true , value : "Greatiga" }); Object .defineProperty (great,'name' ,{ writable : false }); Object .defineProperty (great,'name' ,{ configurable : true }); Object .defineProperty (great,'name' ,{ enumerable : false })
访问器属性
访问器属性只能通过 Object.defineProperty() 定义,通过字面量定义不是
四个值
[[Configurable]]:能否用 delete 删除某个属性,是否可以修改属性的特性,能否改为访问器属性,字面量创建的对象默认值为 true
[[Enumerable]]:能否通过 for-in 遍历属性名字,默认为 true
[[Get]]:读取数据时调用的函数,默认为 undefined
[[Set]]:写入数据时调用的函数,默认为 undefined
Object.defineProperty()
但一个属性添加了 get 和 set 方法后,该属性就是一个访问器属性,读取时触发 get ,设置值时触发 set
set 指向了 setter方法,get 指向了 getter 方法
约定属性名前面加上 _ 作为私有变量,即外部不可以直接访问,需要通过 get 与 set 来访问,(事实上也是可以直接访问的,因为都是普通变量,但是既然约定,那么我们在编写我们的对象时,就应该遵守约定,哪些可以给外部看到,哪些对于外部隐藏)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var Great = { _age : 20 } Object .defineProperty (Great ,"age" ,{ get : function ( ) { console .log ('get' ); return this ._age ; }, set : function (s ) { console .log ('set' ); this ._age += s; } }); Great ._age = 25 ;console .log (Great .age )Great .age = 1 ;console .log (Great .age )
getter 与 setter 不一定都要定义,只定义了 get 表示只能读,反之表示只能写
1 2 3 4 5 6 7 8 9 10 11 12 13 var Great = { _age : 20 } Object .defineProperty (Great ,"age" ,{ get : function ( ) { console .log ('get' ); return this ._age ; } }); Great ._age = 25 ;console .log (Great .age )Great .age = 21 ;console .log (Great .age )
IE8 对 Object.defineProperty() 的实现并不全面,建议不要使用在这个版本
defineGetter 和 defineSetter
另一种定义访问器属性的方式
例子
1 2 3 4 5 6 7 var Great = { _age : 20 } Great .__defineGetter__ ("age" , function ( ) { console .log ('get' ); return this ._age ; });
同时定义多个属性 Object.defineProperties() 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 var Great = {}Object .defineProperties (Great ,{ name : { writable : true , enumerable : true , configurable : true , value : 'Greatiga' }, _time : { configurable : true , value : 2020 }, time : { configurable : true , get : function ( ) { return this ._time }, set : function (time ) { this ._time = time; } } }); Great .name = 'Link' ;Great .time = 1999 ;console .log (Great .name ,Great .time );Object .defineProperties (Great ,{ name : { writable : false , enumerable : false }, _time : { writable : true , enumerable : true }, time : { enumerable : true , set : function (time ) { this ._time = time + 10 ; } } }); Great .name = "GG" ;Great .time = 1999 ;console .log (Great .name ,Great .time );
警惕: 上面的例子中,如果一开始没有设置 configurable 为 true,那么后面的步骤除了修改 writable 以外,修改其他特殊属性以及重写 set 方法都会报错,因为这个 configurable 就是规定每个属性在第一次设置之后是否可以再次修改
Uncaught TypeError: Cannot redefine property: 属性名 -> 这是通常的报错信息,表示不能重新定义特殊属性
获取对象属性的特殊属性值 Object.getOwnPropertyDescriptor()
接受两个参数,参数1位对象,参数2为属性值。返回一个对象,里面包括了之前介绍的各种 property 值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var Great = {}Object .defineProperties (Great ,{ name : { writable : true , configurable : true , value : 'Greatiga' }, _time : { configurable : true , value : 2020 }, time : { configurable : true , get : function ( ) { return this ._time } } }); var t1 = Object .getOwnPropertyDescriptor (Great ,'name' );var t2 = Object .getOwnPropertyDescriptor (Great ,'time' );console .log (t1.writable ,t1.enumerable ,t1.set );console .log (t2.writable ,t2.configurable ,typeof t2.get );
总结
首先来看看数据属性与访问器属性是否可以同时定义
1 2 3 4 5 6 7 8 9 10 var test = {}Object .defineProperty (test,'name' ,{ writable : true , configurable : true , get : function ( ) { return this .name ; } })
这样一看就明白了,数据属性是定义某个属性的读取写入功能的,而访问器属性则是用来间接读取写入对象中的属性
所以这很像 公有变量与私有变量,如果要在对象中定义对外开放的变量,此时可以用数据属性来规定它,如果你想定义一个不对外公开的变量,就用访问器属性规定它