资讯专栏INFORMATION COLUMN

理清javascript中的面向对象(一)——原型继承

beita / 596人阅读

摘要:有一函数若是用来生成对象,则称为构造函数名。属性指定了使用该构造函数生成的对象实例继承了哪个对象实例。因此,只要利用,就能在构造函数中,为未来利用此构造函数生成的对象实例,添加成员属性和成员方法了。

与其它编程语言不一样的是,javascript的面向对象并非依赖于抽象的,而是通过原型链,将一个个具体的对象实例进行连接,位于原型链下游的对象实例可以读取/使用位于上游的对象实例的属性/方法。
下文由简及深,试图一步步理清javascript面向对象的本质。

万物之源:javascript的原生类型——Object

javascript定义了最基础的对象类型Object,并且为这一对象类型定义了许多成员方法。其它许多原生对象类型,实际上都是继承自Object,比如说FunctionDate等。想要生成一个Object对象类型的对象实例也不是一件什么难事:

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),以及其函数作用域(),剩下的就是与面向对象息息相关的俩属性了:prototype__proto__。这__proto__分外眼熟,赫然也出现在对象实例obj里,不过这里还是先跳过,我们先说这prototype属性。

构造函数中的prototype属性

特别注明是“构造函数中的prototype属性”,是因为,对于一般的函数来说,prototype属性没什么意义。prototype属性指定了使用该构造函数生成的对象实例继承了哪个对象实例。
如上述的function A()prototype属性指向了一个默认的Object类型的对象实例(在javascript中,变量只是对象实例的一个引用,因此此处用“指向”比较准确),那么,用function A()作为构造函数实例化的obj实际上就是继承了那默认的Object类型的对象实例,虽然obj本身并没有自定义的属性/方法,但是能通过obj调用继承回来的所有属性/方法。

对象继承的单向链条:原型链

既然构造函数的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,就能在构造函数中,为未来利用此构造函数生成的对象实例,添加成员属性和成员方法了。

javascript原生支持的原型继承方式: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();
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78481.html

相关文章

  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你的“对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    李昌杰 评论0 收藏0
  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你的“对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    Lyux 评论0 收藏0
  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你的“对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    AaronYuan 评论0 收藏0
  • 初学者快速学会javascript原型原型链,原型继承

    摘要:学习,总绕不开原型,原型链,继承等等这些知识。对象那么好,怎么才能找一个呸,其实是创建创建对象的方法对象字面量工厂模式构造函数模式原型模式等。原型链有什么用来谈谈继承,继承可以利用构造函数,使用属性等来实现。 初学者学习javascript可能会感觉很困扰,但是你一旦真正了解了它,我相信你会爱上它。学习ECMAScript,总绕不开原型,原型链,继承等等这些知识。今天把它们放在一块儿,...

    hiYoHoo 评论0 收藏0
  • 理解JavaScript的核心知识点:原型

    摘要:首先,需要来理清一些基础的计算机编程概念编程哲学与设计模式计算机编程理念源自于对现实抽象的哲学思考,面向对象编程是其一种思维方式,与它并驾齐驱的是另外两种思路过程式和函数式编程。 JavaScript 中的原型机制一直以来都被众多开发者(包括本人)低估甚至忽视了,这是因为绝大多数人没有想要深刻理解这个机制的内涵,以及越来越多的开发者缺乏计算机编程相关的基础知识。对于这样的开发者来说 J...

    iKcamp 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<