Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Array-Huang opened this issue Nov 15, 2019 · 0 comments
Open

Comments

@Array-Huang
Copy link
Owner

与其它编程语言不一样的是,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

image

从上图可知,利用构造函数A生成的实例对象obj的数据类型为A,另外,我们留意到obj有且只有一个__proto__属性,这是什么呢?先别急,下面就说到。

详述构造函数constructor

从上文可知,要想生成对象实例,必须使用构造函数(constructor),即便是var arr = [];var obj = {}这样的形式,javascript也会在内部调用Function()Object()这样预设的构造函数(由于是预设的构造函数/数据类型,因此不需显式指定)。

继续沿用上述例子,这次我们把构造函数A打印出来看看长什么样儿:

function A() {}   
var obj = new A(); 
console.dir(A); 

image

实际上,构造函数跟普通的函数并无二致,只是因为用途(用来生成对象实例),因此才冠以“构造函数”的大名。从上图看,我们略过一些与面向对象无关的属性(arguments/caller/length/name),以及其函数作用域(<function scope>),剩下的就是与面向对象息息相关的俩属性了: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打印出来的结果:

image

从audi.prototype.status可以看出,此时的audi.prototype的确是指向{status: 'stop'}这个对象实例了。
那么接下来看看利用修改后的构造函数audi所生成的对象实例audiQ3:

image

我们可以发现audiQ3这一实例对象的数据类型是audi(与构造函数同名),另外,audiQ3其下只有__proto__这唯一一个成员属性,继续查看__proto__,赫然发现,里面竟然有status: "stop"。没错,__proto__属性正是指向{status: 'stop'}这个对象实例的一个引用。

原型链的接点:__proto__

通过对象实例中的__proto__属性,继承的对象实例得以与被继承的对象实例链接起来,于是,一环扣一环,形成了一条由对象实例、指向被继承对象实例的引用所构成的链条:原型链。

image

由于__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);

image

怎么样,利用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();
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant