1、前言从JavaScript开始流行到今天,学习它的人都会有个疑惑——JavaScript是不是面向对象语言?其实,ECMA-262早就给出了答案,在ECMAScript的第一个版本中就明确指出,ECMAScript是一种面向对象的语言,从JavaScript开始流行到今天,学习它的人都会有个疑惑——JavaScript是不是面向对象语言?其实,ECMA-262早就给出了答案,在ECMAScript的第一个版本中就明确指出,ECMAScript是一种面向对象的语言,在网上对JavaScript还有另一种评价——“JavaScript是一种基于(object-based)对象的语言”。这确实没有错,但在我认为,这是JavaScript语言的一种特性,与前面说的基于原型(prototype-based)的面向对象所不同,原型是JavaScript实现面向对象编程的一种方法,这个两个不同维度的概念,并不冲突。
2、JavaScript面向对象的基本概念
在讲基于原型的面向对象编程之前,需要先理解JavaScript三个很重要的概念:构造函数(constructor)、原型(prototype)、原型链(prototype chain)。
2.1构造函数(constructor)和原型(prototype)与基于类的面向对象语言不同,JavaScript没有类(Class)的概念,取而代之的是构造函数(constructor)。构造函数是在实例化对象时用来初始化对象的,所有构造函数都包含一个名为“prototype”的不可枚举的属性,这个属性就是原型(prototype),JavaScript就是使用它来实现基于原型的继承以及属性共享。同时,每个原型(prototype)对象又都包含一个名为“constructor”的不可枚举的属性,它应该始终指向到构造函数(constructor)。不论是构造函数(constructor)还是原型(prototype),都是对象。**Note:**JavaScript的数据类型包括两类:5种原始类型和对象类型,函数(function)是一种特殊的对象。function F() {}alert(F.prototype.constructor === F); //true上面为什么说“应该始终指向到构造函数(constructor)”呢?先看下面的例子:function F() {}F.prototype = {};alert(F.prototype.constructor === F); //falsealert(F.prototype.constructor === Object); //true在给“constructor”属性赋值之后,看起来非常的怪异,显然不太符合常理。有两种方式可以避免这个问题。
第一种,给原型对象添加一个构造函数:
function F() {}F.prototype = { constructor: F, method1: function() {}};alert(F.prototype.constructor === F); //true
第二种,使用预定义的原型对象,预定义的原型对象包含“constructor”属性,并且默认指向构造函数。function F() {}F.prototype.method1 = function() {};alert(F.prototype.constructor === F); //true面的这个问题虽然不会影响原型继承,但是很不符合逻辑,建议尽量修复这个问题。
2.2原型链(prototype chain)理解原型链是基于原型面向对象编程中最重要的一个环节,我需要将原型链分成两部分说明。
2.2.1 使用new操作符实例化对象的原型链__proto__是理解原型链的关键对象,每一个使用new操作符实例化的对象和函数对象都包含一个proto属性,它是构造函数“prototype”属性的引用,先看一段代码:function Foo() {}var foo = new Foo();alert(foo.__proto__ === Foo.prototype); //true,使用new运算符实例化对象的__proto__与构造函数Foo.prototype相等在chrome控制台下能看到可访问的proto属性:
通过上面这段代码,既可证明proto属性是构造函数“prototype”属性的引用。继续看一段代码:alert(Foo.prototype.__proto__ === Object.prototype); //true为什么上面这段代码会输出true呢?因为Foo的“prototype”属性是一个对象,Foo.prototype是一个预创建的Object类型实例,所以也会包含一个proto属性,而所有Object类型实例的proto属性都会指向到Object.prototype,所以结果输出true。到这里原型链的脉络就比较清晰了,由于Object.prototype的proto属性指向到null,所以,foo正确的原型链如下图:Note:proto属性只有在chrome或firefox浏览器中才是公开允许访问。
2.2.2 函数(function)对象的原型链在JavaScript中,函数(function)是一个特殊的对象,所有函数都是构造函数Function的实例,所以,函数的原型链与new操作符实例化对象的原型链会不同,先看下面代码:function Foo() {}alert(Foo.__proto__ === Object.prototype); //falsealert(Foo.__proto__ === Function.prototype); //true从上面代码可以看出,函数Foo的proto属性并不是指向到Object.prototype,而是指向到Function.prototype,这就说明函数Foo是Function的一个实例。继续看代码:alert(Function.__proto__ === Function.prototype); //truealert(Function.prototype.__proto__ === Object.prototype); //true上面代码可以看出,函数Function自己本身也是构造函数Function的一个实例,这段读起来非常拗口,看下面的图:由此可见,Object、Function、Array等等这些函数,都是构造函数Function的实例。
2.3 instanceof运算符instanceof运算符返回一个指定的对象是否一个类的实例,格式如:A instanceof B。其中,左操作数必须是一个对象,右操作数必须是一个类(构造函数)。判断过程:如果函数B在对象A的原型链(prototype chain)中被发现,那么instanceof操作符将返回true,否则返回false。对照上文中的原型链图,看下面的代码:
function Foo() {}var foo = new Foo();alert(foo instanceof Foo); //truealert(foo instanceof Object); //truealert(foo instanceof Function); //false, foo原型链中没有Function.prototypealert(Foo instanceof Function); //truealert(Foo instanceof Object); //truealert(Function instanceof Function); //truealert(Object instanceof Function); //truealert(Function instanceof Object); //true
**Note:**instanceof内部是通过[[HasInstance]]方法运算得到结果
这节最后,引用一张来自mollypages.org的JavaScript对象结构图: