与其它编程语言不同的是,javascript的面向对象并不是依赖于抽象的类,而是经过原型链
,将一个个具体的对象实例进行链接,位于原型链下游的对象实例能够读取/使用位于上游的对象实例的属性/方法。
下文由简及深,试图一步步理清javascript面向对象的本质。javascript
Object
javascript定义了最基础的对象类型Object
,而且为这一对象类型定义了许多成员方法。其它许多原生对象类型,实际上都是继承自Object
,好比说Function
、Date
等。想要生成一个Object
对象类型的对象实例也不是一件什么难事:java
var obj = {a: 2333}; //又或者是利用Object()这一构造函数 var obj = new Object();
实际上,更符合面向对象思想的应该是利用构造函数来生成对象实例。编程
new
,以及构造函数constructor
new
这一运算符来生成对象实例在其它编程语言中,new
每每也是用来生成对象实例的,用法通常是这样:new ClassName[([arguments])]
,而生成出来的对象实例也被冠以“ClassName对象”这样的称谓;而javascript则大不相同,因为原生没有类这一律念,所以构造函数
便取而代之:new constructor[([arguments])]
,而称谓也改成“constructor对象”或"constructor类型的对象",构造函数名直接就等同于数据类型了。浏览器
function A() {} //有一函数(如果用来生成对象,则称为构造函数)名A。 var obj = new A(); //使用构造函数A来生成实例对象obj console.dir(obj); //打印实例对象obj
从上图可知,利用构造函数A生成的实例对象obj的数据类型为A,另外,咱们留意到obj有且只有一个__proto__
属性,这是什么呢?先别急,下面就说到。编程语言
constructor
从上文可知,要想生成对象实例,必须使用构造函数(constructor
),即使是var arr = [];
或var obj = {}
这样的形式,javascript也会在内部调用Function()
、Object()
这样预设的构造函数(因为是预设的构造函数/数据类型,所以不需显式指定)。函数
继续沿用上述例子,此次咱们把构造函数A打印出来看看长什么样儿:性能
function A() {} var obj = new A(); console.dir(A);
实际上,构造函数跟普通的函数并没有二致,只是由于用途(用来生成对象实例),所以才冠以“构造函数”的大名。从上图看,咱们略过一些与面向对象无关的属性(arguments/caller/length/name),以及其函数做用域(<function scope>
),剩下的就是与面向对象息息相关的俩属性了:prototype
和__proto__
。这__proto__
分外眼熟,赫然也出如今对象实例obj里,不过这里仍是先跳过,咱们先说这prototype
属性。this
prototype
属性特别注明是“构造函数中的prototype
属性”,是由于,对于通常的函数来讲,prototype
属性没什么意义。prototype
属性指定了使用该构造函数生成的对象实例继承了哪一个对象实例。
如上述的function A()的prototype
属性指向了一个默认的Object
类型的对象实例(在javascript中,变量只是对象实例的一个引用,所以此处用“指向”比较准确),那么,用function A()做为构造函数实例化的obj实际上就是继承了那默认的Object类型的对象实例,虽然obj自己并无自定义的属性/方法,可是能经过obj调用继承回来的全部属性/方法。spa
既然构造函数的prototype
属性能指定继承的对象实例,那么只要咱们修改这prototype
属性,使其指向其它对象实例,那么就能够达到实现继承任意对象的效果了,看下面代码:prototype
var car = { //一个普通的Object类型实例对象 status: 'stop' } function audi() {} //构造函数audi audi.prototype = car; //修改构造函数audi的prototype属性,使其指向car console.dir(audi); var audiQ3 = new audi(); //利用构造函数audi生成的数据类型为audi的实例对象 console.dir(audiQ3);
先来看看构造函数audi打印出来的结果:
从audi.prototype.status能够看出,此时的audi.prototype的确是指向{status: 'stop'}
这个对象实例了。
那么接下来看看利用修改后的构造函数audi所生成的对象实例audiQ3:
咱们能够发现audiQ3这一实例对象的数据类型是audi
(与构造函数同名),另外,audiQ3其下只有__proto__
这惟一一个成员属性,继续查看__proto__
,赫然发现,里面居然有status: "stop"
。没错,__proto__
属性正是指向{status: 'stop'}
这个对象实例的一个引用。
__proto__
经过对象实例中的__proto__
属性,继承的对象实例得以与被继承的对象实例连接起来,因而,一环扣一环,造成了一条由对象实例、指向被继承对象实例的引用所构成的链条:原型链。
因为__proto__
是由构造函数的prototype
属性决定的(也能够说是prototype
直接赋值给__proto__
),所以咱们能够经过修改prototype
属性来操纵这条原型链
。
构造函数,主要用来在建立对象时初始化对象, 即为对象成员变量赋初始值,那么,javascript里的构造函数,是怎么实现这样的功能的呢?如下面的DEMO做为示例说明:
function car() { //定义了一个名为car的构造函数 this.status = 'stop'; //为往后使用car这一构造函数来生成的对象实例添加个status成员变量,并赋初始值'stop' this.start = function() { //为对象实例添加一个名为start的成员方法 this.status = 'running'; } } var audiQ3 = new car(); //利用car生成一个对象实例,并将其赋给变量audiQ3 console.dir(audiQ3); audiQ3.start(); //调用audiQ3的start方法 console.dir(audiQ3);
首先来看构造函数car,咱们看到this.status = 'stop';
,这this
是指代car这一function吗?不是的,这个this
其实是指向当前函数执行时的上下文环境,用在构造函数时,指的则是新生成的对象实例(在本DEMO中指的是audiQ3)。所以,只要利用this
,就能在构造函数中,为将来利用此构造函数生成的对象实例,添加成员属性和成员方法了。
Object.create
ECMAScript 5
定义了一种原生的原型继承方式:Object.create
,咱们能够经过这种方式更简便地实现原型继承。
Object.create(proto, [ propertiesObject ])
proto 一个对象,做为新建立对象的原型。
propertiesObject 可选。该参数对象是一组属性与值,该对象的属性名称将是新建立的对象的属性名称,值是属性描述符(这些属性描述符的结构与Object.defineProperties()的第二个参数同样)。注意:该参数对象不能是undefined,另外只有该对象中自身拥有的可枚举的属性才有效,也就是说该对象的原型链上属性是无效的。
var car = { status: 'stop', start: function() { this.status = 'running' } } var audiQ3 = Object.create(car); console.dir(audiQ3);
怎么样,利用Object.create
这种方法是否是很简单就实现了原型继承呢?实际上,这是ECMAScript 5
给咱们作了一下封装,至关于:
function (proto) { var constructor = function(){} constructor.prototype = proto; return new constructor(); }
考虑到ECMAScript 5
在IE上到IE10
才彻底支持,所以咱们有必要对低版本的IE浏览器进行兼容,实际上也很简单,对上面的代码稍做修改便可:
if(typeof Object.create !== 'function') { Object.create = function(proto) { var constructor = function(){} constructor.prototype = proto; return new constructor(); } }