Object(对象)是在所有的编程语言中都十分重要的一个概念,对于事物我们可以把他们看作是一个对象,而每一个事物都有自己的表示的属性和对于某一信息作出的相应的操作。而这些东西就变成了事物的属性和方法。
在JS中我们可以见到的对象常量有如下的形式:
var obj= { name:"Arvin", lastName:"Huang" , whatsName:function(){ alert(this.name+" "+this.lastName); }, }
由上面的代码我们可以看出实际上在JS中的对象就是一个以键值对形式存储属性的一个集合,每一个属性有一个特定的名称,并与名称相对应的值。其实这种关系是有一个专有名称的,我们可以称之为映射,当然对于对象来说,除了可以通过这种方式来保持自有属性,还可以通过继承的方式来获取继承属性。这种方式我们称作“原型式继承”。
接下来我们将从js对象的属性,属性特性,方法和对象特性等多个方面来学习对象的概念。
一:js对象创建与对对象属性。
我们先看如下的代码。
var built = function(){ var me = this;
me.name = "Arvin"; } built.prototype = {
toStr:function(value){
alert("there have a " + value);
} }; var obj = new built();
上面的代码就描述了一种我们常常使用到的一种创建对象的方法,使用new关键字来创建一个拥有独立内存区域和指向原型的指针的对象。当我们使用的new的时候,js解析器会分配一块内存空间,用以存放当前的对象的自有属性。之后解析器会给这一对象一个_proto_属性指向的原型对象内容。
还有一种方式我们称之为对象直接量申明的方式,代码如下:
var obj = { name:"Arvin", toStr:function(value){
alert("there has a "+value);
}
};
对象直接量就是直接通过花括号包裹的键值对的形式来定义当前对象的。每两个值之间的通过逗号来进行分割。键和值之间通过冒号来分割。放解析器读取到当前的内容的时候会自动的生成一个对象的内容并把当前的对象存储在当前上下文中。
还有一种对象创建的方式是使用Object.create()方法,这一方法是ECMAScript5中定义的一个内容,它是一个静态方法,其使用方法如下。
var obj = Object.create({x:1,y:2});//obj继承了属性x和y
这一方法传入的参数是一个对象,并且这一作用的对象将会作为新的对象的原型存在。
接下来我们来看一看属性。
当我们在chrome的命令台中运行第一段示例代码的时候我们可以看到如下的内容。
可见,当我们在构造函数中直接使用this指针(指代当前的对象)添加属性,这时,其实我们的设置的属性是当前的对象特有的属性,独属于当前的对象的,这样的属性称之为自有属性。而在我们定义构造函数的时候,我们为构造函数的prototype属性(指向原型对象),这时我们定义了prototype的内容和方法。当使用new关键字来进行对象的构造的时候,我们所构造的对象实际上是继承了这一原型对象的内容的,所以我们可以再对象的_proto_属性中可以看见继承与原型对象的内容,但这也是属于当前的对象的,(原型链内容请阅读原型链学习汇总。)这样的属性我们称之为继承属性。
我们对象属性的类型可以是字符串,数字,true,false,null和undefined,或者是对象的内容。当然虽然字符串,数字,和布尔值虽然不是对象,但是性质和不可变对象类似的。
我们之前说过属性的值是名字和值,其中名字是可以是包括空字符串在内的任意字符串,但是属性的值是除这些之外的,还可以是定义好的存取器方法。如下代码:
var o = {
x:1, y:1,
get r(){return x+y;},
set r(value){this.x = value;}
}
如上代码可见,在对象O中r就是一个存取器属性内容,存取器属性其实是不可设置的属性的一种,只有当他拥有了getter方法的时候才可以取值的,而又setter方法的时候表示这一属性是可写的,存取其属性实在ECMAScript5中才有定义的,实际上是把属性通过函数的方式进行与外界的数据交互的。从而使得属性值本身不可以直接进行配置或是获取。
存取器属性的写法就如上面代码书写的一样,get关键字空格之后跟属性名称作为函数的名称,set方法和get方法是一样编写的,只是会传入参数,并且参数的个数严格是一个,否则会报语法错误。在使用的时候我们是通过o.r来调用getter方法,而setter方法的调用时如下,o.r = 2;
当我们没有定义setter方法的时候,使用对象调用setter方法的时候虽然不会报出错误,但是对象中没有任何变化。而当我们在定义对象的时候没有定义getter方法的时候,在我们调用这一属性的时候,将会得到undefined。
二:js对象属性操作
1.访问属性。
对象属性的访问一般是通过obj.attr的方式来访问的,或者是obj[attr]的方式来进行操作,一般情况之下这样都是可以行的通的,但是但我们的是属性名称是一些特殊字段的额时候就要注意了,例如关键字或是保留字段,这是后我们要通过中括号的形式来访问才可以成功,当然这点在ECMAScript5中的已经更改了,但是在我们平常编写的时候还是要注意变量的命名。对于保留字段尽量不要使用。
属性访问的时候,当对象存在而对象中没有这一属性的时候,如果程序中访问了当前的属性的话我们,js的解析器将会返回undefined的数值,但是如果当前需要访问的对象是不存在的,这个时候js就会返回一个类型错误。
2.属性赋值。
我们可以通过赋值运算来为属性中可写的属性赋值,当访问的对象存在,但是表达式中的属性不存在时候,则会返回一个类型错误。如下
var obj = { this.name = "Arvin"; }; obj.lastName = "Huang"; //此时对象将会被赋予一个新的属性为lastName并且其值为Huang
但是虽然说null和undefined在js中是特有的对象,但是我们也不可以为其设置属性,因为他们都是只读的。而上一段代码中的原理其实就是这个。obj.lastName不存在,所以js返回的额是undefined对象,之后再计算赋值,浏览器会为其添加新的属性来存储当前赋予的值。
当然有一些对象属性虽然不能赋值但是我们对其进行赋值的时候还是不会报错,例如如下代码:
Object.prototype = 0;//复制失败但是并不会报错。object原名没有被修改。
在ECMAScript中的严格模式下面已经得到了修复。
我们对于对象中的属性赋值出错的情况会有如下的总结。
- O中的属性是只读的时候,我们对于当前属性是只有访问权限而没有复制权限的。
- O中的某一属性为继承属性的时候,我们是不能对其进行赋值的。
- O中不存在自有属性P,没有继承Setter方法的时候,并且O对象是不可扩展的,则当前的对象是不可定义新的属性的。
3.属性删除:
delete运算符可以删除对象中的属性。这里先讲一下delete运算符的内容。
delete:一元运算符,用于删除对象和数组中的属性的,单它也仅仅是删除一个值,并不会返回删除的内容。
var o = {x:1, y:2};//定义了一个对象
delete o.x;
"x" in o; //这里将会得到false值。 var a = [1,2,3];
delete a[1];
2 in a; //元素2已经不再数组中了。 a.length == 3 //这里将会显示为true,虽然删除了属性但是,留下了一个占位,所以数组长度没有变化。
这是在chrome中运行的结果,我们可以看见,删除属性之后其实只是元素本身与对象的关系被断开了,但是实际上数组中的内容数量还是没有改变的,但是并没有数值,所以通过undefined来进行填充。
delete操作数需要时一个左值(对象的属性,变量),如果不是的话将会不做任何操作返回true。如下图:
图片中可见,返回的值是true,但是7并不是一个左值。
当然也有一些delete没有办法删除的左值。
由此可见,delete是没有办法删除用var关键字定义的变量是不可以删除的。在delete删除元素失败的时候会返回一个false值。当然我们使用群居变量定义的属性也是可以通过delete对象来删除的,因为全局对象也是对象吗。
好接下来回归正题。对象通过delete实际上就是断开当前的对象和属性之间的联系。当我们删除了属性之后还要查找这一属性的时候,我们会得到的值是undefined这个值。当然delete只能删除自有属性,对于继承属性delete是没有作用的。如果想删除继承属性的话,直接在对原型对象属性进行删除不就好了。。还有一点,当对象的属性是不可配置的时候也是不删除的(但是当对象是不可配置的,但是其属性是可以配置的时候,还是可以删除的。对象的特性内容请看后文。)
当然在用delete的时候还是要注意的应为有些情况下将会出现一些问题。如下代码。
var o= {x:1, y:2};
var b = o.x;
delete o.x;
//这个时候b实际上还是只想之前的x的内容的,这种情况很容易导致内存泄漏的额问题。
4.检测属性:
我们经常要判断某一个属性是否存在于某一个对象中。这个时候我们可以通过in运算符,hasOwnProperty()方法或是propertylsEnumerable()方法来进行判断。
首先我们来讲一下IN运算符的内容:in操作符是一个二元操作符,其左边的额数值是字符串或者是可以转化为字符串的,右边的内容是对象。判断的当前的属性是不是存在于对象中。
var point = {x:1}; "x" in point; //这一个表达式最后返回的将会是true。
"toString" in point; //由于toString是继承方法,所以也是返回true.
"z" in point; //这一表达式最后返回false,因为point对象中没有z属性. //数组可以通过索引来判断当前的数组中是否有相应的数据。
所以我们可以通过使用in操作符来判断当前的属性是不是存在与某一对象中的。并且即使是继承属性,也是可以测试的。
第二种检测的方法是hasOwnProperty()方法。代码如下:
var o ={x:1}; o.hasOwnProperty("x"); //true:o有这一属性,
o.hasOwnProperty("y"); //false;
o.hasOwnProperty("toString"); //false
所以我们可以知道,hasOwnProperty方法只能测试当前属性是不是对象的自有属性。
第三种检测方式是用propertylsEnumerable()方法。只有当当前的属性是自有属性,并且是可枚举的的时候,这一方法才会返回true。
5.枚举属性:
遍历属性将会是进场要用到的内容。
- for/in循环遍历当前的对象的内容是一种很常见的手段。其可以遍历对象中的所有的可枚举属性,包括当前对象的自有属性和继承属性。
- Object.key()方法,枚举属性名称的函数,他返回的是一个数组,其中存在的是对象中的可枚举属性名称组成。
- Object.getOwnPropertyNames()方法,其返回的额也是数组,但是是所有的自有属性名称的数组。
三:js属性特性:
属性的特性其实就是值当前的属性是否可以写可以读等等。即外部对象对于属性操作的权限。
当前的js一般的属性都是有4中属性。分别是:数值属性value,可读属性writable,可枚举属性enumerable,和可配置属性configurable。但是由于对象中存在一类特别的属性存取器属性,所以对于存取器属性的值实际上是有点不同的,他有自己的特别的属性特性包括,读取(get),写入(set),可枚举和可配置。为了实现这一对象属性的描述,js中定义了一个属性描述符对象。并且可以通过Object.getOwnPropertyDescriptor()方法来获取某个对象中的特定属性的描述符。当然当前函数只能获取对象自有属性的描述,如果要获取继承属性的描述符的话,需要使用Object.getPrototypeOf();
当然我们可以使用Object.defineProperty方法进行对象内容的进行相关的编辑,如下
Object.defineProperty({},"x", {value:1, writable:true, enumerable:true, configurable:true});
//这是将返回一个对象,并且其中设定了一个可读写,枚举和配置的属性x //当然如果要修改的对象本身其中就有一个这一属性,然后想通过这一方法配置的话。也是可以的。 //特性对象中的内容可以不用写全,当添加属性的时候未写明的内容将会直接设置成为false或是undefined。而在修改属性的时候未写明的内容将不会有任何改变。
当然我们也可以用Object.defineProperties()来进行对个属性的修改和添加,这是我们需要一个对应列表。如下代码
Object.defineProperties({}, {
x:{value:1, writable:true},
y:{value:2, writable:true},
......
});
当我们在定义一个对元素的属性的时候,我们要注意上面两个方法在某些情况之下是会报错误的,情况如下
- 对象是不可扩展的,所以我们只能对原有的自有属性进行编辑,但是不可以添加新的属性。
- 如果属性是不可配置的,则不可以修改他的可配置性和可枚举性。
- 存储器属性是不可配置的,则不可以修改他的setter和getter属性,并且不可以改成数据属性。
- 数据属性不可配置则不能修改为存储器属性。
- 数据属性是不可配置情况下,其可写属性不可改false为true,但是可以修改true为false。
- 如果数据属性是不可配置不可写,不能修改其值,但是可配置不可写,则可以修改。
var obj = {
x:1,
get y(){return x;},
set y(value){x = value;}
}; console.log(""+obj.hasOwnProperty("y"));
console.log(Object.getOwnPropertyNames(obj).toString()); Object.defineProperty(obj,"x",{value:1, writable:true, enumerable:false, configurable:true});//修改当前属性为不可枚举的
console.log(Object.getOwnPropertyNames(obj).toString());
console.log(""+obj.hasOwnProperty("x"));
console.log(""+obj.propertyIsEnumerable("x"));
obj.x = 2;
console.log(obj.x); Object.defineProperty(obj,"x",{value:1, writable:false, enumerable:true, configurable:true});//修改当前属性为不可写的
console.log(Object.getOwnPropertyNames(obj).toString());
console.log(""+obj.hasOwnProperty("x"));
obj.x = 2;
console.log(obj.x); Object.defineProperty(obj,"x",{value:1, writable:true, enumerable:true, configurable:false});//修改当前属性为不可配置的
console.log(Object.getOwnPropertyNames(obj).toString());
console.log(""+obj.hasOwnProperty("x"));
obj.x = 3;
console.log(obj.x); Object.defineProperty(obj,"x",{value:1, writable:false, enumerable:false, configurable:true});//测试不可配置属性是否可以改变属性特性
console.log(Object.getOwnPropertyNames(obj).toString());
console.log(""+obj.hasOwnProperty("x"));
obj.x = 3;
console.log(obj.x);
运行结果如下:
四:js对象方法。
我们这里主要说明的将会是js的Object.prototype里面的原生的方法。
1、toString()方法,是我们常用到的一个方法,当我们要将对象转换为值的时候,都会调用这么一个方法,但是其实现,实际上只是返回了一些对象的信息。例如:
var s = {x:1, y:1}.toString()
//这里返回的内容是[Object, Object]
当然在许多的内置对象中toString方法实际上是被重写了的,例如,array(数组)对象中,我们是童toString方法的话,是吧当前的额数组中的内容以逗号隔开的形式来返回字符串的。函数调用toString方法的时候,是返回函数的源代码。还有Data对象是toString方法返回的是当前时间星系字符串,等等。
2、toLocaleString方法和toString方法是类似的,只是返回的是本地的字符串,实际上就是根据一些当地的用户使用习惯来定义的返回内容。
3、toJSON()方法,实际上在Object.prototype中是没有这一方法的。我们常用的多的是在Date对象中使用,当需要获取当前对象的序列化的时候调用他它怎会获得当前对象的序列化。
4.valueOf()方法,类似于toString()方法,实际上在js只有在要吧当前的对象转换为费字符串的情况之下才会调用这一方法。一般的对象调用这一方法的时候,返回的数值,实际上就是当前对象的内容,当然内置对象也有改变了这一方法的实现的。例如Date.valueOf(),返回的就是从1970年1月1日到现在的毫秒数。
五:js对象的特性。
1、原型属性:原型属性实际上就是当前的对象继承自哪一个对象,当前对象继承了原型对象中的方法和属性,所以我们称之为对象的原型属性,当然也可以称之为对象的原型。当然我们也有一个方法测试某一对象是否是继承与另一对象,使用isPrototypeOf()方法来进行判断。
2、类的属性:其实其本身是一个字符串来着,用以表示对象的类型,上文中的提到的toString方法可以获取类的属性。当然由于toString在许多的内置对象中有重写,所以党我们调用toString的方法的时候最好使用的如下封装方法。
function classof(o){
if(o === null){
return "Null";
}
if( o === undefined){
return "Undefined";
}
return Object.prototype.toString.call(o).slice(8, -1);
}
3、可扩展性:对象的可扩展性表示的是但钱的对象是否是可以扩展的,宿主对象的可扩展性是有但钱的额js的引擎来进行定义的,所有的额内置对象都是可以扩展的,除非我们把其转化成为不可扩展的对象,Object.esExtensible()可以用来进行判断的当前的对象是否是可以扩展到的。Object.preventExtensions()方法设置当前的兑现格式不可以扩展的内容。当我们把对象设置成为不可扩展之后,我们就不可以在准换当前的内容。实际上上述的方法只是对于当前对象的*空间来定义的,所以党我们改变源性对象的时候当前的对象中实际上还是会有一定的变化的。当然还有一些其他的方法是可以来避免对象的受到外来的干扰的。Object.seal()其实和之前的preventExtensible()方法还是挺像的,除了将但前的对象设置成为不可设置的,同事吧其所有的额属性也同时设置成为不可设置的。还有一个函数更绝,Object.freeze()方法不仅仅把对象的属性和本身设置成为不可配置的,同事也设置为不可写的状态,所以所设置完成之后整个就一只读对象。
4.序列化对象:实际上就是指当前的对象转化为字符串,当然也是可以还原的。序列化当前的对象实际上就是通过JSON来进行操作的。其中将Object转化成为json字符串的方法是,JSON.stringify(),其中的参数是需要序列化的对象。而JSON.parse()则是反序列化,但实际上这样得出的对象是序列化的深拷贝。当然JSON只是js的一个子集,支持对象,数组,字符串,无穷大数字,true,false,null。NaN,infinity等等这一类数据在序列化的时候都会统一转化成为null,而Date的数据在序列化的时候是会直接转换成为ISO格式的日期字符串,就类似于Date.toJSON()方法一样的。
知识不完,更新不止。