浅解析js中的对象
说到对象,我首先想到的是每逢过年过节,长辈们老是开玩笑的问我“你找了对象没?”。不说大家都知道,这里的“对象”指的是“女朋友”,但是今天我想要说的js中的“对象”和我们生活中谈到的“对象”不是同一回事,但是其中也有着很多相似之处。
在讲js中的对象之前,我想先抛出几个疑问:
什么是对象?
对象有哪些?
对象能做什么?
如何创建对象?
如何对对象进行操作?
对象有特性么?有的话有哪些特性?
对象有属性么?有的话有哪些?对属性如何操作?
……
什么是对象?
在ECMA-262中把对象定义为:“无序属性的集合,其属性可以包含基本值、对象、或者函数。”严格来说,这就是相当于说对象是一组没有特定序列的值。对象的每一个属性或方法都有一个名字,而每一个名字都映射到一个值。(如:“女朋友”[对象]喜欢运动[对象的属性或方法]--打篮球[属性对应的值],我门可以试着这样去理解,但实际可能有所区别)
在经典的面向对象语言中,对象是指数据和在这些数据上进行的操作的集合。与 C++ 和 Java 不同,JavaScript 是一种基于原型的编程语言,并没有 class 语句,而是把函数用作类。
我理解的javascript对象:第一:Javascript对象是基本数据类型之一,是复合类型;第二:Javascript中几乎所有事物都是做对象;第三:Javascript的对象是拥有属性和方法的数据;第四:JavaScript 中的对象可以简单理解成"名称:值"对(name:value)
名称(name):"名称"部分是一个 JavaScript 字符串
在javascript语句中这三种形式是一样的
var obj={prop:1}
var obj={"prop":1}
var obj={‘prop‘:1}
在这里看起来属性名既可以加引号也可以不加引号,但是在实际中有这么几种情况你必须将属性名放到引号中间
如果属性名是Javascript的保留字之一
如果属性名种包含特殊字符(除字母、数字、下划线以外的字符)
如果属性名以数字开头
在ECMAScript5中,保留字可以用作不带引号的属性名,但对于ECMAScript3中必须用引号括起来
在ECMAScript5中对象直接量中的最后一个属性后的逗号将被忽略,在ECMAScript 3的大部分实现中也可以忽略这个逗号,但在IE中报错
值(value):"值"部分可以是任何 JavaScript 的数据类型——包括对象
对象有哪些呢?
学过java的都知道,在java中有一句很经典的描述对象的话“万物皆对象”,是的,在javascript中也是一样。在javascript中我们可以把对象分为两大类,一类是内建对象(数据封装对象,工具类对象,错误对象,后面会有专门针对内建对象的博客),一类是自定义对象(这篇文章里面讲的主要就是自定义对象)
对象能做什么?
在这里我要先卖个关子,到后面的时候告诉大家。
如何创建对象?
既然了解了对象和基本的对象分类,我们是否应该要知道如何去创建一个自定义的对象呢?
1、通过对象字面量的方式创建对象(对象字面量是一个表达式,这个表达式的每次运算都创建并初始化一个新对象。每次计算对象字面量的时候,也都会计算他的每个属性的值。也就是说,如果在一个重复调用的函数中的循环体内使用了对象直接量,它将创建很多新对象,并且每次创建的对象的属性值也有可能不同。)
语法:var obj = {};
实例:
首先,我们创建一个空的对象,里面没有任何属性,我们来获取它的类型
var obj1 = {};//没有任何属性的对象 console.log(typeof obj); //object
它返回的结果是object,说明我们创建的是一个object对象,当然,我们还可以用下面的方法来创建
var obj2={x:1,y:2,z:3}; var obj3={ ‘x‘:1, "y":2, username:‘king‘, ‘for‘:‘javascript关键字必须放到引号中间‘, //for是javascript关键字,必须放到引号中间 ‘first-name‘:‘foodoir‘, //-是特殊字符,也需要放在引号中间 married:true, test:null, test1:undefined, salary:12.3, person:{ //在对象中"值"部分可以是任何 JavaScript的数据类型——包括对象 username:‘king‘, age:21, addr:‘北京‘, }, //最后一个对象属性的后面的逗号可以写可以不写,在ECMAScript 5中自动被忽略,在ECMAScript 3的大部分实现中也可以忽略这个逗号,但在IE中报错 }
2、通过new object创建对象
语法:var obj = new Object();
实例:
var obj4 = new Object();//创建一个空对象 var arr = new Array();//创建一个空数组对象 var date = new Date();//创建一个空时间对象
3、通过构造函数的形式创建对象
语法:function Person(){};或者var Person=function(){};
实例:
var obj5=new Test1(); function Test1(num1,num2){ this.n1=num1; this.n2=num2; } var obj6=new Test1(5,6); console.log(typeof obj6); //object console.log(obj6 instanceof Object); //true
在使用通过构造函数的形式创建对象时应注意:
a.使用的时候通过new操作符得到对象var person1=new Person()
b.用构造器创建对象的时候可以接收参数
c.构造器函数的首字母最好大写,区别其他的一般函数
注意:构造器属性(constructor property),当我们创建对象的时候,实际上同时也赋予了该对象一种特殊的属性,就是构造器属性,这个构造器属性实际上是一个指向用于创建该对象的构造器函数的引用
补充:
typeof 和 instanceof 常用来判断一个变量是否为空,或者是什么类型的。但它们之间还是有区别的:
typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型。
它返回值是一个字符串,该字符串说明运算数的类型。typeof 一般只能返回这几个结果:number,boolean,string,function,object,undefined。
我们可以使用 typeof 来获取一个变量是否存在,如 if(typeof a!="undefined"){alert("ok")},而不要去使用 if(a) 因为如果 a 不存在(未声明)则会出错,对于 Array,Null 等特殊对象使用 typeof 一律返回 object,这正是 typeof 的局限性。
instanceof 用于判断一个变量是否某个对象的实例
在上面的例子中,obj6 instanceof Object判断的为true,则说明创建的obj6也是Object类型
4、通过Object.create()创建对象
var obj7 = Object.create({x:1}); //创建一个普通的空对象 var obj8 = Object.create(null); //创建一个对象的原型属性 var obj9 = Object.create(Object.prototype); //prototype对象的原型属性 console.log(typeof obj7); //object console.log(typeof obj8); //object console.log(typeof obj9); //object //通过instanceof 操作符检测对象是否由某个指定的构造器函数创建的 console.log(obj7 instanceof Object); //true console.log(obj8 instanceof Object); //false,注意:空对象用instanceof判断时,结果为false console.log(obj9 instanceof Object); //true
如何对对象进行操作?
学过数据库的我们都知道,数据库最基本的四个操作分别是“增、删、查、改”,那么在对对象的操作中是否也存在类似的操做呢?是的,在对对象的操作中也有查询,添加,修改,删除操作,都是基于对象的属性的操作。下面我们来介绍对象的几个基本操作。
首先,我们先要通过查询方法,来获取对象中的属性,访问属性有三种方法:对象名.属性名;对象名[属性名];当处于某个对象方法内部的时候,可以通过this来访问同一对象的属性
var person={ username:"foodoir", age:21, sex:"男", addr:"湖南", salary:123456, }; console.log("姓名:"+person.username+"\n"+"性别:"+person.sex); //姓名:foodoir 性别:男 console.log("年龄:"+person[‘age‘]+"\n"+"薪水:"+person["salary"]); //年龄:21 薪水:123456 //如果属性不确定,需要使用[] var key = ‘username‘; console.log(person.key); //undefined 此种方法不能得到key的值,通过[]方法可以得到 console.log(person[key]); //foodoir 当属性不确定时,用该方法 console.log(person[‘key‘]); //undefined
看到这里,你肯定会有有疑问,为什么第9行代码中person[‘age‘]中的age和person["salary"]中的salary都加了引号,且都可以显示出结果,而在第13行代码中person[‘key‘]中的key加了引号,反而返回值是undefined 原因究竟是什么呢?
age为person对象中的属性名,而该对象只对person进行了定义,没对person里面的属性进行定义,故在调用的时候,在[]中间应该加上引号,也就是说,person[‘age‘]和person.age是等价的
而key是在对象外定义的(var key = ‘username‘;),在对象里面它是不存在key这个属性的(即属性不确定),此时直接用person[key]方法就好了,person.key和person[‘key‘]返回的必须是person对象里面确定的属性,key没在person属性里面,故这两种方法返回的值都是undefined
学会了如何获取对象的属性,然后我们该继续学习对象中的属性进行添加,修改和查询操作
function personInfo(name,age,sex){ this.name = name; this.age = age; this.sex = sex; } var person1 = new personInfo(‘foodoir‘,20,‘男‘); console.log(person1.name+person1.sex); //foodoir男
属性的添加有两种方式:对象名.属性名=值;对象名[属性名]=值
//添加属性 var obj = {}; obj.username = ‘foodoir‘; obj.age = 21; obj.sex = ‘男‘; obj.addr = ‘湖南‘; obj[‘test‘] = ‘hello world‘; console.log(obj.username+" "+obj.age+" "+obj.sex+" "+obj.addr+" "+obj[‘test‘]); //foodoir 21 男 湖南 hello world
属性的修改也有两种方式:对象名.属性名=值;对象名[属性名]=值
//修改指定属性 obj.username = ‘chenwu‘; console.log("修改之后的名字:"+obj.username); //修改之后的名字:chenwu obj[‘test‘] = ‘hello javascript‘; console.log("修改之后的test:"+obj[‘test‘]); //修改之后的test:hello javascript
属性的删除也有两种方式:delete 对象名.属性名;delete 对象名[属性名] 。在delete删除指定的属性时应该注意:(后面讲对象的结构的时候会详细介绍)
delete只能删除自身属性,不能删除继承属性
要删除继承属性,只能从定义它属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象
delete只是断开属性和宿主对象的联系,而不会去操作属性的属性
delete不能删除哪些可配制性为false的属性
//通过delete删除指定的属性 delete obj.sex; console.log(obj.sex); //undefined delete obj[‘test‘]; console.log(obj[‘test‘]); //undefined
除了“增、删、改、查”我们还要学会对象中的遍历,对象中的遍历有两种,一种是for/in遍历,一种是通过Object.keys(obj)函数进行遍历
var obj = { x:1, y:2, test:"helloworld", edu:"javascript", }; //通过for/in遍历属性 for(var i in obj){ console.log(i); //x y test edu(换行输出) console.log(obj[i]); //输出的是属性名对应的属性值 } //通过Object.keys(obj)遍历属性 console.log(Object.keys(obj)); //["x", "y", "test", "edu"]
虽然两种方法都可以对对象的属性进行遍历,但是二者之间还是有区别的。Object.keys() 方法会返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in循环遍历该对象时返回的顺序一致(两者的主要区别是 for-in 还会遍历出一个对象从其原型链上继承到的可枚举属性)。
学了前面的觉得没半点难度,我们试着做一下对象中有方法的
var obj = { username:‘foodoir‘, age:21, sex:‘男‘, sayHi:function(){ return ‘say hi‘; }, info:function(){ return obj.username+obj[‘sex‘]; } } console.log(obj.sayHi()); //say hi console.log(obj.info); //在info的后面要加(),不然结果是:function(){ return obj.username+obj[‘sex‘]; } console.log(obj.info()); //foodoir男
注意:在方法的后面要加(),不然,返回的将是function(){ return obj.username+obj[‘sex‘]; }
function Person(username,age,sex){ this.username = username; this.age = age; this.sex = sex; this.info = function(){ return this.name+this.age; } } var person1 = new Person(‘foodoir‘,21,‘男‘); console.log(person1.info()); //NaN String+Number两种类型相加的结果为NaN console.log(Person.person1); //undefined person1不是Person里面的属性,故返回undefined console.log(person1.username); //foodoir //向对象中添加新属性 person1.test = ‘this is a test‘; //this is a test console.log(person1.test); //向对象中添加新的方法 person1.fun = function(){ return ‘hello world‘; } console.log(person1.fun()); //hello world
----------------------------------------------------------------------------------
到此刻,你可能有新的疑问,究竟对象的结构是怎样?下面图片是我自己对于对象的理解画的一幅图
首先从局部来看我在途中举的两个例子,我们通过面向字面量的方法var obj = {x:1,y:2};可以创建出我们想要的对象(如图:最大的椭圆),在obj对象中x,y分别是obj的属性,它们对应的属性值分别是1和2,当然除了属性对应的属性值之外,属性还有右侧的几个属性特性(writable:是否可写、 enumerable:是否可枚举、 configurable:是否可配置、 getter:获取属性的值 、setter:设置属性的值),这些属性的特性是可以设置和更改的(后面会介绍),只要是对象中的属性,都有这些特性,但是这些特性的值要柑橘具体情况来分析。属性有自己的特性,我们的obj是否也有自己的特性呢?从图中我们可以知道,obj对象也有自己的特性([[proto]]对象原型、 [[class]]对象的类、 [[extensible]]对象是否可扩展),这些特性我们和面都会介绍。然后,我们通过构造函数的方法创建对象function foo(){}; foo.prototype.z = 3; var obj = foo();可以创建出我们想要的对象(如图:第二大的椭圆),它依然具有我们之前说的那些特性,但是具体特性要根据具体情况来分析,属性的特性也是一样。
接下来为了理解的方便,我们只看四个椭圆,每上一个椭圆代表着下一个椭圆中的对象的原型,比如说foo.prototype是obj对象的原型,foo.prototype的原型是object.prototype(值得注意的是object.prototype是javascript中所有对象的父级对象,我们创建的所有对象都继承于此),object.prototype往上,它的原型对象是null。这些话说起来有点拗口,我们还是通过具体的例子来了解它。
首先我们来创建一个对象foo,并且设置它在原型中的值为3,设置完之后,再在obj中添加两个属性x,y
function foo(){}; foo.prototype.z = 3; var obj = new foo(); obj.x = 1; obj.y = 2;
此时我们来获取对象中属性对应的属性值
console.log(obj.x); //1 console.log(obj.y); //2 console.log(obj.z); //3
本来obj上是没有z(属性)的,但是它可以返回它的原型上去寻找z,从而得到z属性的属性值为3,下面我们用toString()方法来得到对象的返回值
console.log(obj.toString()); //[object Object]
返回值是Object,说明是Object对象,下面我们在obj上新增属性z=12
obj.z = 12; console.log(obj.z); //12
此时得到的值是12不是3,因为在obj中有了该属性,不用到原型上去寻找,我们再删除obj上的属性z
delete obj.z; console.log(obj.z); //3
此时得到的值是3不是12,因为在obj中原有的该属性被删除了,继续到原型上去寻找z,再删除掉原型上的z
delete foo.prototype.z; console.log(obj.z); //undefined
原型上的z被删除了,在原型上都找不到故返回undefined,下面我们直接看代码,在代码里面作了注释,
//创建新的对象检测对象的属性 function foo1(){}; foo1.prototype.z = 3; var obj1 = new foo1(); obj1.x = 1; obj1.y = 2; //通过in检测对象是否有某个属性(可以检测继承下来的属性) console.log(‘x‘ in obj1); //true console.log(‘y‘ in obj1); //true console.log(‘z‘ in obj1); //true,对象上没有该属性,就继续往上在原型上找 console.log(‘toString‘ in obj1); //true,对象上没有该属性,就继续往上在原型上找 console.log(‘notExists‘ in obj1); //false,对象上没有该属性,就继续往上在原型上找,原型上也不存在该属性 //通过hasOwnProperty检测对象是否有某个属性,只检测对象自身的属性 console.log(obj1.hasOwnProperty(‘x‘)); //true console.log(obj1.hasOwnProperty(‘y‘)); //true console.log(obj1.hasOwnProperty(‘z‘)); //false console.log(obj1.hasOwnProperty(‘toString‘)); //false //删除掉原型上的z delete foo1.prototype.z; console.log(‘z‘ in obj1); //false,对象上没有该属性,就继续往上在原型上找,原型上的也被删除了 console.log(obj1.hasOwnProperty(‘z‘)); //false //通过create创建新的对象 var obj2 = Object.create({x:1}); obj2.y = 2; console.log(‘x‘ in obj2); //true console.log(obj2.hasOwnProperty(‘x‘)); //false,该属性x是继承下来的,不是对象自己的属性 console.log(‘y‘ in obj2); //true console.log(obj2.hasOwnProperty(‘y‘)); //true,该属性x是后来添加的,是对象自己的属性
现在,我们对于对象的了解又多了一点点,下面我们探究我们之前提到的对象的特性和属性的特性。先说说属性的特性吧。
属性包括数据属性和存取器属性
数据属性包含一个数据值的位置,在这个位置可以读取和写入值,总共有4个描述行为的特性
[[writable]]表示能否修改属性的值。默认值为true
[[Enumerable]]表示能否通过for in循环返回属性。代表属性是否可以枚举。直接在对象上定义的属性默认值为true
[[configurable]]表示是否能通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。直接在对象上定义的属性,他们的默认值为true
[[value]]包含这个属性的数据值。读取属性值的时候,从这个位置读取。写入属性值的时候,把新值保存在这个位置。这个特性的默认值为undefined
存取器属性包括get获取属性的值;set设置属性的值。
----------------------------------------------------------未完待续-------------------------------------------------------
越来越觉得自己说不清了,我想先把这些放下,等以后回过头的时候再整理