从零开始学JavaScript--对象

Author Avatar
MagicDo 6月 02, 2017
  • 在其它设备中阅读本文章

JavaScript的对象

面向对象

面向对象方法的特性

1.抽象性
2.继承性
3.多态性(重载和覆盖)

类是用来描述抽象的一种载体,它记录了哪些属性和行为是具有通性的。

1
2
3
4
5
6
7
8
class  Car{
String color;//属性
void shift(){//行为
//换挡
}
}
//创建一个redCar对象
Car redCar=new Car();

可以看到,一个类并不是一个单纯数据集合,它还包括了行为,这是面向对象的另一个特点,封装性。类是描述一类事物的共同特性的,它是抽象、没有具体含义的。而对象是类的实例。

对象

对象从概念上讲,就是类的实现,把抽象的变为具体的。javascript将对象定义为:无序属性的集合,属性可以是任何类型的值,包括其他对象或者函数,当函数作为对象属性时被叫做方法
也就是对象的行为。

创建对象

语法

1
var 对象名=new 构造函数();

javascript 创建对象与绝大多数面向对象语言是一样的,都使用new关键字来实例化一个类,创建该类的一个对象。更确切的说法是,以new关键字来调用构造函数来创建一个对象。

1
2
3
4
var obj=new String();
obj=new Object;//Object 本身也是对象
alert('obj对象的构造函数为:'+obj.constructor);
alert('Object对象的原型为:'+Object.prototype);

对象的属性

从对象定义的角度来看,他可以理解成一个包含很多值的集合,而实际应用中就是使用集合的某一属性参与程序执行。

1.访问属性
无论是函数还是变量,作为对象的属性,他们都可以通过.号来访问,如果对象的属性任然是一个对象,那么还可以通过重复使用.号来进行访问,例如

1
2
3
var a=new Object();
//显示对象a的constructor属性的prototype属性
alert(a.constructor.prototype);

2.添加属性

在javascript中,对象的属性都与普通的函数或其他变量一样,都可以动态的生成

例如

1
2
3
4
5
6
7
var a=new Object();
a.x=1;//生成变量属性
a["func"]=function(){//生成函数属性
++this.x;//this表示对象a本身
}
a.func();
alert(a.x);//显示为2

直接对指定的属性名赋值,就可以生成一个对象的属性,这不需要使用var关键字进行声明

3.删除属性

删除属性首选delete运算符。从资源释放的角度来说,只需要把需要删除的属性设置为null就可以了。

例如:

1
2
3
4
var a=new Object;
a.x=new Object;
//释放属性所引用的对象
a.x=null;

释放对象

在任何编程语言中,释放不再使用的内存空间都是很重要的事。javascript和Java一样提供了自动回收内存的机制–垃圾收集器。

本地对象

1
2
3
4
Global       Object          Function      Array
String Boolean Number Math
Date RegExp Error EvalError
RangeError ReferenceError SyntaxError TypeError

不依赖于浏览器而实现的对象称为本地对象

内置对象

Global 对象

Global对象 是javascript对象模型中最高级,它拥有最大的执行环境,一切声明的变量和函数都是它的属性,Global对象具有以下特性:

Global 对象没有Construct属性,所以它无法通过new操作符被当作类来调用

Global 对象也没有call属性,所以他也无法像函数那样调用

最重要的是Global对象只是一个概念,表示全局对象,他是根据宿主的实现而产生的。在浏览器中Global对象就是window对象

Global对象拥有很多属性,如NaN、Infinity、undefined、属性以及parseInt()、parseFloat()等方法属性,当程序开始运行之前,Global对象就已经被系统创建了,并且它的属性也都已经被赋值了,所以在程序中可以直接使用parseInt等函数

1.全局执行环境
JavaScript中有两种执行环境,全局环境和函数环境

  • 在全局执行环境中代码执行之前,系统会为执行环境创建一个用来管理当前执行环境中所有标识符的对象,也就是管理当前执行环境中声明的所有变量和函数,这个对象就是Global对象,在浏览器中就是window对象
  • 任何在全局执行环境中声明的变量和函数都会作为Global对象的属性存在,属性名,就是定义时所使用的标识符,属性值就是变量值或者函数对象
  • 如果出现重复定义的变量或者函数,只要标识符相同,后定义的值就会覆盖原先的值
    例如:
    1
    2
    3
    4
    5
    6
    7
    8
    function a(){
    alert('a');
    }
    function a(x,y){
    alert('aa');
    }
    var a=10;
    alert(a);

上面程序中,alert函数最终结果将显示10,并且不会引发任何程序错误,因为在javascript中,这几句代码只是改变了Global对象a属性的值而已。
2.浏览器中的Global对象

window对象就是浏览器中的全局对象

3.this指针

this关键字表示对某个对象的引用,可以把它理解为引用类型的变量,但它的值是由系统确定的,也就是说,this无法被赋值

在javascript中,this很强很复杂,任何地方都能使用this,并且根据this出现的位子,它的含义也是有所不同的。

  • 在全局执行环境中,使用this,表示Global对象,在浏览器中就是window对象
  • 当在函数执行环境中使用this时,情况就有些复杂了。如果函数没有明显的非window对象属性,而只是定义了函数,不管这个函数是不是定义在另一个函数中,这个函数中的this仍然表示window对象。如果函数显式的作为一个非window对象的属性,那么函数中的this就代表这个对象

例如:

1
2
3
4
5
6
7
8
var o=new Object
o.func=function(){ //对象o的func属性为一个函数
alert("作为属性的函数:"+(this===o));//判断this是否为函数所绑定的对象,true
(function(){
alert("普通函数:"+(this===window));//判断this是否为window对象,true
})();
}
o.func();

  • 当通过new对象运算符来调用函数时,函数被当做一个构造函数,this指向构造函数创建出来的对象

    Object对象

    Object对象是所有对象的基础,任何其他对象都是从Object对象扩展而来的,或者说是继承,而这一切的实现都是由“原型”来完成的。

1.原型对象

原型对象是对象的一个属性,也就是Prototype内部的属性,每个对象(包括宿主对象)都有这个内部属性,它本身也是一个对象
在javascript中,每个对象,都不是直接包含具体的属性,而是通过原型进行属性的共享。
例如:

1
2
3
4
Object.prototype.num=20;
alert("添加原型属性:"+Object.num);
Object.num=10;
alert("添加对象属性"+Object.num);

实际上,当获取一个对象的属性时,系统首先检测对象是否直接包含这个属性,如果不包含,那就去对象的原型属性中再查找这个属性,如果也没找到,就会返回undefined

表面上看着原型似乎没有存在的必要,但是,它是用来实现继承的关键对象

2.原型链

要理解javascript如何实现继承,必须先区分一个概念:每个对象都具有Prototype内部属性,但不是每个对象都可以通过”对象.prototype”访问到这个属性,只有拥有Construct内部属性的对象(也就是构造函数)才能直接访问到Prototype属性,因为这些对象可以被理解为类,而在面向对象的概念中,只有类才能被继承、扩展.

任何对象都有Prototype属性,当然也包括prototype对象本身。当程序试图获取String对象中属性名为a的属性时,系统会执行如下步骤。

①查找String对象是否有名为a的属性,如果有,返回程序

②查找String对象的prototype属性对象,是否有名为a的属性,如果有,返回程序

③查找属性对象的prototype属性对象中是否有名为a的属性,如果没有,则继续查找下一个原型对象的prototype属性对象,直到它为null

每个对象都隐式或者显式的关联有一个原型对象,并且这个原型对象可能也隐式或者显式的关联有另一个原型对象,这称为原型链。原型链使得原型中的属性得以共享。例如:

1
2
3
Object.prototype.a=3.14;
alert("Object对象的实例:"+new Object().a);
alert("String对象:"+String.a);

当扩展了Object的原型后,所有本地对象都拥有了这个属性,因为所有的本地对象都可以继承自Object对象。但原型链是有先后顺序的,也就是说,扩展了Object原型,String对象就可以得到新的扩展,反之,则不行。

3.原型扩展

有两种方式可以扩展原型,不管那种方式都会对原型所在的原型链产生影响。

①:属性扩展

对构造函数的原型对象进行属性扩展后,扩展结果对继承此原型的对象都有效,通常用在扩展javascript对象的功能。

例如,javascript中字符串类型,没有去掉空格的方法,而通过原型扩展,可以给javascript中的字符串都扩展此功能。例如:

1
2
3
4
5
String.prototype.trim=function(){
//逻辑处理
}
//就可以这样使用
"a b".trim();

②: 对象赋值
当用户对自定义的类进行扩展时,最直接、最简单的方法就是直接对构造函数的原型进行赋值,例如:

1
String.prototype=Object.prototype

这样,String类就完全继承了Object类的所有属性,但是,上面这个例子无法运行,因为所有内置对象的prototype属性都只有只读特性

Function对象

当开发者使用function关键字定义了一个函数的时候,在系统内部的实际上是创建了Function类的一个对象实例,也就是说,javascript中的函数也是对象
例如:

1
2
var func=new Function("a","b","return a-b");
alert(func(3,4));

在javascript中,无论是通过function关键字定义一个函数护着通过Function类创建一个函数,他们都是等价的。但是通过Function类创建函数具有无可比拟的动态性,它允许程序运行期间动态的创建函数。

语法:

1
var 函数名=new Function(形参1,形参2,....,形参n,函数体);

通过Function构造函数创建一个函数,从形势上来说读者更容易理解为创建了一个对象实例,这刚好解释了对象的Call属性:函数是一个包含了”参数列表”以及”函数体”的对象,他们都是以字符串格式作为函数对象的属性存在的。

除了通过”函数名()”的方式调用函数外,函数对象还有两个方法属性,用来实现动态的函数调用

①:apply(this参数,函数参数数组)
apply方法可以动态的改变函数中this的引用 例如:

1
2
3
4
5
6
7
var obj=new Object();
var func1=new Function("a","b","alert(\"function1\"+(this===window)+\",\"+(a-b))");
var func2=function(a,b){
alert("func2:"+(this===obj)+","+(a-b))
}
func1(3,4);
func2.appply(obj,[3,4]);

apply方法有两个参数,一个是改变函数中的this引用,在一个就是传递给函数本身的参数列表,他们被放在一个数组中。在需要动态改变函数this引用的情况下,就可以使用这个方法

② call(this参数,函数参数1,函数参数2,…函数参数n)

call方法就是apply的最佳替代者,他可以不用传递数组,例如:

1
2
3
4
5
6
7
var obj=new Object();
var func1=new Function("a","b","alert(\"apply:\"+(this===window)+\",\"+(a-b))");
var func2=function(a,b){
alert("call:"+(this===obj)+","+(a-b))
}
func1.apply(null,[3,4]);
func2.call(obj,3,4);

call和apply的区别就是传递参数的形式不同,对他们来说,当”this”参数不是一个有效对象时,函数中的this引用指向Global对象。

1.函数执行环境

相对于单纯的全局执行环境,函数执行环境多了至少一层被包含的关系,对于嵌套函数来说,被包含的层数可能会很多。这对于函数执行环境中的标识符管理来说会更复杂。

  • 像全局执行环境那样,在函数执行环境中的代码执行之前,系统也会为执行环境创建一个用来管理当前执行环境中所有标识符的对象。在javascript中,称这个对象为活动对象。不像Global对象,活动对象无法通过代码访问到,但它的作用跟Global对象是相同的。
  • 在任何函数执行环境中声明的变量和函数都会作为活动对象的属性存在,属性名就是定义时使用的标识符,属性值就是变量值或者函数对象。
  • 如果出现重复定义的变量或函数,只要标识符相同,后定义的值就会覆盖原先的值。
  • 当在函数执行环境中检索一个标识符P时,也就是调用一个名字为P的属性,如果活动对象没有名为P的属性,那么系统会扩展检索范围为包含当前执行环境的上层环境。如果知道Global对象没有P属性,那么将引起程序错误。例如:
    1
    2
    3
    4
    5
    6
    7
    function func(){
    var parseInt=function(x){
    alert(x<<1^2|3&4>>>x);
    parseInt(1);
    }
    }
    func();

上例中使用parseInt的结果并不是解析整数,而是弹出一个对话框并显示运算结果。因为parseInt是Global对象的而属性,当在函数执行环境中给活动对象定义了重名的属性时,系统会优先检索当前环境中的属性。所以在func()函数中使用parseInt属性时,它的值已经被改变了。如果要使用解析整数的parseInt,必须使用window.parseInt() 。

2.arguments 对象

当一个函数要被执行的时候,系统会在执行函数体代码前做一些初始化的工作,其中之一就是为函数对象创建一个arguments对象属性
arguments对象只能使用函数体中,并用来管理函数的实际参数。

①caller/callee属性
caller属性并不是arguments对象的,而是函数本身的属性。它显示了函数的调用者。如果函数在全局执行环境中被调用,那么它的值为null,如果在另一个函数中被调用,它的值就是个函数。

例如:

1
2
3
4
5
var a=new Function("alert('a:'+a.caller)");
function b(){
a();
alert('b:'+b.caller);
}b();

如果在函数体外部使用caller属性,将始终得到null
calle是arguments对象的属性,表示正在执行的函数,也就是函数本身,例如:

1
2
3
(function(){
alert(arguments.callee)
})()

在匿名函数中,无法获取函数对象的引用。所以arguments对象提供了calle属性,通常它与直接引用函数对象是等价的,起码在功能上是这样。但他们并不是同一个对象,所以最好使用calle属性,而避免直接使用函数对象。

②length属性

length是arguments对象属性,表示函数被调用时传递的参数个数。可以使用一个小于length的整数,采用数组元素的访问方式来访问arguments对象,例如:

1
2
3
4
function argc(){
alert(arguments[0]+arguments[1]);
}
argc(1,2);

虽然arguments对象属性,表示函数被调用时实际传递的参数个数。但他不是Array类型的一个实例。

3.构造函数

构造函数在javascript中见对象的Construct属性描述。每个对象都有一指向创建自己的构造函数的属性:constructor。通过“对象.constructor” 就可以访问到这个构造函数。

构造函数是创建一切对象的工具,当使用new操作符来调用构造函数F创建对象的时候,构造函数F会执行一系列步骤来完成这个对象的创建。

  • 创建一个本地对象O(为对象分配内存空间等系统操作)。
  • 设置O的class属性为”Object”或者其他本地类型,例如”Number”、”String”等。这是根据
    构造函数的类型来确定的。

  • 设置O的prototype属性为Object.prototype或者其他本地对象的原型,例如Number.prototype、String.prototype等。这是根据构造函数类型确定的

  • 调用F的Call属性,将O作为this的值。
  • 如果Call的返回值的类型是对象(指函数体内的return语句),返回这个对象
  • 返回O

如何使用构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
    function Constructor(){
this.a=1;
return new Array();
}
var obj=new Constructor();
alert("obj对象是否为Array类的实例:"+(obj instanceof Array)+"\n\n属性a:"+obj.a);
又例如:
```javascript
function Constructor(){
this.a=1;
}
var obj=new Constructor();
alert("obj对象是否为Array类的实例:"+(obj instanceof Array) +"\n\n属性a:"+obj.a);

如果函数的返回值是对象,那么构造函数的结果将得到这个对象,并且函数中的this跟这个对象没有任何关系。

如果函数没有返回值,或者返回值不是对象(例如返回一个整数),那么构造函数的结果将是构造函数执行流程中创建的新的本地对象。并且函数中的this指向这个对象。

开发人员只需要知道构造函数能创建一个继承了Object原型属性的对象就可以了。

在javascript中有两类构造函数,一类是系统已经定义好的,例如Object、Function等,另一类就是可以由用户自己定义的函数对象。

1).内置构造函数

内置构造函数指拥有Construct内部属性的内置对象,他们的Prototype属性可以直接访问到。但是这个属性是只读的,也就是不能被赋值。

这些构造函数的执行过程如下,以Function对象为例。

①创建一个新的本地对象F

②设置F的class属性为Function

③设置F的Prototype属性为Function.prototype

④设置F的Construct属性为“Function实例构造函数”

⑤设置F的constructor属性为Function对象

⑥设置F的prototype属性为一个Object对象实例,并且这个对象的constructor属性值为F

⑦返回F

例如:

1
2
3
4
5
6
function Constructor(){
//...
}
alert(Constructor.prototype.constructor===Constructor);//结果为true
alert(Constructor.prototype instanceof Object);
//原型对象是Object构造函数的实例

上面代码演示了,一个构造函数的原型属性的构造器是它本身,但这并不是说原型对象是由构造函数自身创建的,实际上是由Object构造函数创建的。

2).Function实例构造函数

通过new Function()或者function关键字创建的函数对象,都是Function构造函数的实例,为了与Function对象区分,我们在这里将其称为”function对象”

function对象是除了内置构造函数外唯一拥有的Construct属性的本地对象(有些宿主对象也有Construct属性),当一个function对象F被当作构造函数调用时,它的执行过程如下:

①创建一个新的本地对象O

②设置O的class属性为”Object”

③如果F.prototype属性值是一个对象,设置O的prototype属性为F.prototype,否则设置O的Prototype属性为Object.prototype

④调用F的Call属性,将O作为this的值

⑤如果Call的返回值类型是对象(指函数体内的return语句),返回这个对象,它的constructor属性为创建它的构造函数。

⑥设置O的constructor属性为F.prototype.constructor对象

⑦返回O

下面看看构造函数的执行过程

1
2
3
4
5
6
7
//定义函数
function Constructor(){
this.a=3;
}
//调用构造函数
var obj=new Constructor();
alert("属性a:"+obj.a+"\n属性b:"+obj.b+"\n构造函数:"+obj.constructor);

若构造函数执行前扩展了原型,情况就不一样了

1
2
3
4
5
6
7
8
9
10
11
12
13
//当作原型的对象
var p=new Object();
p.a=1;
p.b=2;
//定义函数
function Constructor(){
this.a=3;
}
//改变构造函数原型
Constructor.prototype=p;
//调用构造函数
var obj=new Constructor();
alert("属性a:"+obj.a+"\n属性b:"+obj.b+"\n构造函数:"+obj.constructor);

4.类的模拟

面向对象最重要的概念是继承,继承可以只对基类做需要的扩展,而不需要重写整个基类的代码,例如:

1
2
3
4
5
6
7
8
9
10
//扩展原型
Object.prototype.shift=function(){
alert(this.color);
}
var redCar=new Object();
redCar.color="红色";
var blueCar=new Object();
blueCar.color="蓝色";
redCar.shift();
blueCar.shift();

上面代码扩展了Object构造函数的原型,使shift方法可以重用,但这导致了一个新的问题。如果想要创建一个具有不同行为的shift方法给另一个类型的对象,就不行了,因为属性同名了。并且内置构造函数的prototype属性只能扩展,不能替换。

满足这些要求的只有function对象,它本身是构造函数,而且prototype属性可以扩展和替换,最重要的是从格式上都是最贴近类的。
例如:

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
//创建Car类型
function Car(){
this.color=null;//可省略
this.shift=function(){
alert(this.color+"汽车");
}
}
var redCar=new Car();
redCar.color="红色";
var blueCar=new Car();
blueCar.color="蓝色";
redCar.shift();
blueCar.shift();
//创建Plane类型
function Plane(){
this.shift=function(){
alert(this.color+"飞机");
}
}
var redPlane=new Plane();
redPlane.color="红色";
var bluePlane=new Plane();
bluePlane.color="蓝色";
redPlane.shift();
bluePlane.shift();

Array对象

在javascript中,数组也是对象,这就是为什么数组的长度可以自动变更的原因。当使用一对方括号来创建一个数组的时候,实际上调用了Array构造函数来创建一个Array类的实例。
创建数组有两种方式
语法:

1
var 数组名=new Array(元素1,元素2,...元素n);

或者:

1
var 数组名=new Array(数组长度);

1.删除数组元素
最简单的删除数组元素的方法就是将指定数组元素赋值为null,或者使用delete操作符
例如:

1
2
3
4
var arr=new Array(1,2,3);
arr[1]=null;
delete arr[2];
alert("数组长度为:"+arr.length);

2.线性结构
javascript中的数组是十分强大的对象类型之一,一个数组就可以模拟所有的线性结构。

Array实例对象的方法允许从数组两端访问或者删除数组元素,这种特性直接就可以达到模拟线性结构的要求。关于push()、pop()、unshift()、shift()方法的使用说明,请自行查阅。

3.排序算法

1
2
3
4
5
6
7
var arr=new Array(4,9,3,8,15);
//从小到大排序
arr.sort(function(a,b){ return a-b;});
alert("从小到大:"+arr);
//反置数组中的元素顺序
arr.reverse();
alert("从大到小:"+arr);

String对象

①通过一对双引号或者单引号创建的变量是String类型的一个值,是基本类型

②内置对象String是一个构造函数,类型是Function

③通过new 操作符来调用String构造函数创建的String实例对象,是Object类型的一个值,Object类型表示对象类型,不是基本类型

例如:

1
2
3
4
5
//定义一个String 类型变量
var str="abc";
//通过String 构造函数创建一个Object类型的字符串对象
var strObj=new String("abc");
alert("str的类型:"+(typeof str)+"\nstrObj的类型:"+(typeof(strObj))+"\nString 构造函数类型:"+(typeof String));

Date对象

1.Date实例对象

1
2
var now=new Date();
alert(now);

2.时间比较

1
2
3
4
5
6
7
8
9
//先后比较
var a=new Date();
var b=new Date(1900,1,1);
alert(a>b);//结果为true
//相等比较
var a=new Date();
var b=new Date();
alert(a==b);//返回false
alert(a.valueOf()==b.valueOf())//true

RegExp(正则表达式)对象

1.创建正则表达式

语法:

1
var 对象名=/表达式/;

或者:

1
var 对象名=new RegExp(表达式);

例如:

1
2
3
4
5
6
//方式1
var regExp=/^http[s]?:\/\//;
alert(regExp.test("http://"));//true
//方式2
regExp=new RegExp("^http[s]?:\/\/");
alert(regExp.test("https://"));//true

Math 对象

Math对象提供丰富的数学函数

Math.sqrt(n):计算n的平方根

Math.abs(n):计算n的绝对值

Error对象

1
2
3
4
//创建Error类实例
var custError=new Error("自定义错误");
//抛出异常
throw custError;

JSON对象

1
2
JSON.parse('{"name":"ken","sex":"male"}');//把字符串解析成对象
JSON.stringify({name:"ken",sex:"male"});//结果就是parse的参数