首页
object对象属性的三种类型
object对象属性的三种类型
版权声明:本文为原创内容,转载请声明出处。
原文地址:http://www.excelib.com/article/259/show

三种属性类型

ECMAScript中对象的属性其实有三种类型,我们前面一直在使用的属性只是其中的一种,属性的三种类型分别是:命名数据属性(named data properties)、命名访问器属性(named accessor properties)和内部属性(internal properties),学生下面分别来给大家讲解。

命名数据属性

这种属性就是我们平时使用的最多的属性,他由属性名和属性值组成,我们前面的例子中使用的都是这种类型的属性,这里就不再举例了。

命名访问器属性

命名访问器属性是使用gettersetter或者其中之一来定义的属性,gettersetter是对应的方法,setter方法用于给属性赋值,getter方法用于获取属性的值,如果只有gettersetter其中之一就只能进行单一的操作,比如只有getter方法的属性就是只读属性,只有setter方法的属性就是只可以写入的属性,我们来看下面的例子

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
function log(msg){
      console.log(msg);
  }
  
 var colorManager = {
     _colorNum:3,
     _accessColors:["red","green","blue"],
     _color:"red",
     // colorNum为只读属性,只需定义get方法
     get colorNum(){
         return this._colorNum;
     },
     // accessColors为只可写入属性,只需定义set方法
     set accessColors(colors){
         this._colorNum = colors.length;
         this._accessColors = colors;
         log("accessColors被修改了");
     },
     // color为可读写属性,同时定义了get、set方法
     set color(color){
         if(this._accessColors.indexOf(color)<0){ //判断设置的color是否在允许的范围内
             log("color不在允许范围内");
             return;
         }
         log("color值被修改为" + color);
         this._color = color;
     },
     get color(){
         log("正在获取color值");
         if(this._accessColors.indexOf(this._color)<0){
             return null;
         }
         return this._color;
     }
 }
  
 log(colorManager.color);             //正在获取color值  red
 colorManager.accessColors = ["white""black""red""yellow""orange"];  //accessColors被修改了
 log(colorManager.colorNum);                           //5
 colorManager.color = "blue";                          //color不在允许范围内
 colorManager.color = "orange";                        //color值被修改为orange
 log(colorManager.color);                              //正在获取color值  orange

这个例子中我们定义了一个colorManager对象,他有三个属性:colorNumaccessColorscolor,其中colorNum表示可以使用的color的数量,只有getter方法,是只读属性,accessColors表示可以使用的color的数组,只有setter方法,是只写属性,color表示当前的color值,是可读写属性。当我们给相应的属性设置值的时候就会调用相应的setter方法,调用属性值的时候就会调用相应的getter方法,我们在各个访问器方法中除了修改(或读取)属性值之外还做了一些逻辑判断以及打印了相关的日志。

从这个例子中我们可以看到gettersetter方法本身并不可以保存属性的内容,一般的做法是另外定义一个以下划线开头的属性来保存访问器属性的值,而且为了方便一般会将保存访问器属性值的属性设置为访问器属性名前加下划线,比如我们上面例子中使用_color来保存color访问器属性的值,使用_colorNum来保存colorNum访问器属性的值,不过这并不是强制性的,保存值的属性名称也可以叫别的名称,不过为了方便和代码容易理解最好还是按照这个规则来命名。

这里大家可能会有一个疑问,那就是在定义了保存访问器属性值的属性之后如果直接操作这个属性不就可以绕过访问器来操作其值了吗?比如直接操作_color属性不就可以绕过访问器方法了吗?这个问题我们学习了后面相应的内容之后就可以解决了,这里暂时可以先不考虑。

内部属性

内部属性是对象的一种特殊的属性,他没有自己的名字,当然也就不可以像前两种属性那样使用对象直接访问了。正是因为内部属性没有名字所以前面两种属性才叫做命名XX属性,内部属性使用两对方括号表示,比如[[Extensible]]表示Extensible内部属性。

内部属性的作用是用来控制对象本身的行为,所有对象共同具有的内部属于共有12个:[[Prototype]][[Class]][[Extensible]][[Get]][[GetOwnProperty]][[GetProperty]][[Put]][[CanPut]][[HasProperty]][[Delete]][[DefaultValue]][[DefineOwnProperty]],除了这12个之外不同的对象可能还会有自己的内部属性,比如Function类型对象的[[HasInstance]]RegExp类型对象的[[Match]]等。

通用的12个内部属性中的前三个是用来指示对象本身的一些特性,后九个是对对象进行特定的操作,后九个属性在进行相应的操作时会自动调用,我们这里就不详细介绍了,下面主要给大家解释一下前三个属性

1[[Prototype]]

[[Prototype]]属性就是我们前面讲过的使用function创建对象时function中的prototype属性,在创建完的实例对象中这个属性并不可以直接调用,不过可以使用ObjectgetPrototypeOf方法来获取到,比如下面的例子

1
2
3
4
5
function Car(){}
 Car.prototype = {color:"black"};
 var car = new Car();
 console.log(typeof  car.prototype);        // undefined
 console.log(Object.getPrototypeOf(car));   // Object { color="black"}

这里使用Car新建了car对象,Carprototype属性对象中有一个color属性,这个对象就是car实例的[[Prototype]]属性,不过使用car.prototype是获取不到的,使用Object.getPrototypeOf(car)可以获取到。在有的浏览器中(比如FireFox)还可以使用__proto__属性来获取,这时我们可以使用car. __proto__也同样可以获取到[[Prototype]]属性,不过__proto__属性并不是通用属性,所以最好还是使用ObjectgetPrototypeOf方法来获取。

2[[Class]]

[[Class]]属性是用来区分不同对象的类型的,我们不可以直接访问,不过toString方法默认会返回这个值,默认Object.prototype.toString方法返回的字符串是[object, [[Class]] ],也就是方括号里面两个值,第一个是固定的object,第二个就是[[Class]],所以使用toString方法就可以获取到对象的[[Class]]属性。不过ECMAScript中的内置对象都在prototype中重写了这个方法,所以内置对象的返回值可能不是这个形式,不过浏览器中的宿主对象并没有重新此方法,在浏览器中调用他们的toString方法可以得到[[Class]]的值,比如

1
2
3
4
5
6
7
function log(msg){
     console.log(msg);
 }
  
 log(window.toString());     //[object Window]
 log(document.toString());   //[object HTMLDocument]
 log(navigator.toString());   //[object Navigator]

我们自己创建的object类型对象默认都属于Object类型,所以他们的toString方法默认会返回[object Object]。另外,内置对象因为重写了toString方法,所以不会返回这种结构的返回值,比如字符串会返回自身,数组会返回数组元素连接成的字符串等,对于这种情况我们可以使用Object.prototype.toStringapply属性方法来调用Object原生的toString方法,这样就会得到[object, [[Class]] ]这样的结果,比如下面的例子

1
2
3
var str = "", arr = [];
 console.log(Object.prototype.toString.apply(str));  //[object String]
 console.log(Object.prototype.toString.apply(arr));  //[object Array]

 

多知道点

逆向调用的applycall方法

applycall方法都可以理解为function对象中的隐藏方法,其实他们是Function对象的prototype属性对象中的方法,而function对象是Function的实例对象,所以function可以调用Functionprototype属性对象中的这两个方法。

这两个方法的作用是相同的,都是用于逆向调用。正常的方法调用是通过“对象.方法名(参数)”结构调用的,也就是需要使用对象来调用相应的方法,但是使用applycall这两个方法调用时正好反过来了,他们都可以实现用方法来调用对象,也就是将一个对象传递给方法,然后该方法就可以作为对象的方法来调用了,这样就不需要先将方法添加为对象的属性然后再调用了,比如下面的例子

1
2
3
4
5
6
var obj = {v:237};
 function logV(){
     console.log(this.v);
 }
 logV.apply(obj);    //237
 logV.call(obj);     //237

这个例子中的obj对象并没有logV方法,但是通过logV方法的applycall属性方法调用obj对象可以实现将logV方法设置为obj对象的属性然后再调用相同的效果。

方法在调用时还可能需要传入参数,使用applycall来调用也可以传递参数,不过这两个方法传递参数的方式不一样,这也是他们唯一的区别。使用apply调用时参数需要作为一个数组来传递,而使用call调用时参数直接按顺序传入即可,调用语法分别为

fun.apply(thisArg, [argsArray]);

fun.call(thisArg[, arg1[, arg2[, ...]]]);

他们的第一参数都是this对象,也就是调用方法的对象,后面的参数都是传递给方法的参数,我们来看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function sell(goods,num){
     return this.price.get(goods)*num;
 }
  
 var tmall = {price:new Map([
     ["iphone6_Plus_16g",5628],
     ["iphone6_Plus_64g",6448],
     ["小米Note_顶配版",2999]
 ])}
 var jd = {price:new Map([
     ["iphone6_Plus_16g",5688],
     ["iphone6_Plus_64g",6483],
     ["小米Note_顶配版",2999]
 ])}
  
 console.log(sell.apply(tmall,["iphone6_Plus_64g", 1]));             // 6448
 console.log(sell.call(tmall,"iphone6_Plus_64g", 1));                 // 6448
 console.log(sell.apply(jd,["小米Note_顶配版", 2]));              // 5998
 console.log(sell.call(jd,"iphone6_Plus_16g", 3));                    // 17064

这里我们首先定义了一个sell方法,用于计算价格,他有2个参数,分别表示商品名和数量,计算时需要先从当前对象的price属性中获取到单价然后再乘以数量。接着定义了两个对象tamlljd,他们分别表示天猫和京东,其中都只包含一个price属性,他是Map类型用于表示商品的单价。最后我们使用sell方法用applycall分别来调用tmalljd对象计算了各自的价格,如果还需要计算别的平台(对象)的价格,只需要创建相应的对象就可以了,而不需要将sell方法分别添加到他们的属性中去。

3[[Extensible]]

[[Extensible]]属性用来标示对象是否可扩展,也就是是否可以给对象添加新的命名属性,默认为true,也就是可以扩展。我们可以使用ObjectpreventExtensions方法将一个对象的[[Extensible]]值变为false,这样就不可以扩展了,另外还可以使用ObjectisExtensible方法来获取[[Extensible]]的值,我们看下面的例子

1
2
3
4
5
6
7
8
var person = {nationality: "中国"}; 
  
 console.log(Object.isExtensible(person));             //true
 person.name = "张三";                                 //现在可以添加属性
  
 Object.preventExtensions(person);                     //将[[Extensible]]设置为false
 console.log(Object.isExtensible(person));             //false
 p.age = "108";                                        //抛出异常

这里我们定义了person对象,刚开始他的[[Extensible]]属性为true,我们可以给他添加命名属性,当调用preventExtensions方法对其操作后[[Extensible]]属性就变为了false,这时就不可给他添加新的命名属性了。

这里大家要注意,一旦使用preventExtensions方法将[[Extensible]]的值设置为false后就无法改回true了。