【面向对象编程】深入理解JavaScript的对象模型

1 重要陈述

JavaScript 是面向对象语言

它以自己独特的方式实现了面向对象语言中的封装,抽象,继承,多态等特性;

JavaScript 没有类的概念,只有对象;

现实世界也是面向对象的,也没有类,到处都是“人”的实例,但并没有“人类”这个实体;工厂里生产各式各样的汽车,汽车是实例,并没有所谓的“汽车类”;类,从本质上讲是“知识”。

编程的本质

程序的本质是信息处理(存储,传输,转换,检索,呈现…)。汇编语言,基于“数据”和“指令”编程;面向过程语言(c语言),是基于“变量”和“函数”编程;面向对象语言以“面向对象”的思想去思考和处理“数据”和“行为”,基于对象编程。

数据和行为

从编程的角度看,数据就是特定的内存空间(栈/堆),行为是可执行的代码指令。

2 对象

JavaScript的对象可以直接对数据和函数进行封装:

var s = {
    name: "s",
    say: function() {
        console.log("Hello!")
    }
}

s.name
s.say()

从面向对象的角度来看,同一类型的不同对象之间的公共行为是一样,也就是他们能够共享对象方法。在Java中,所有的对象方法都定义在类信息里,对象实例只包含数据成员。

那么JavaScript是如何把行为抽象出来的呢?

2.1 函数

函数是对可执行语句的封装。在JavaScript中,函数也是对象。

以Java为例,抽象本质上就做了两件事——确定对象空间的大小和关联对象方法。Java通过类定义来实现抽象,可是JavaScript并没有类的概念,于是JavaScript只能通过函数来实现对象的抽象。

function Person(name){
    this.name=name;
    this.say = function(){
        console.log("I'm "+this.name)
    }
}

var p = new Person("da bao")
  1. 当调用 new 操作符时,解释器会创建一个通用对象(Object),将其作为this传递给构造函数,也就是该例子中的 A()。
  2. 构造函数中对 this 的属性进行赋值;
  3. 然后隐式地(由解释器完成,不需要额外的代码)将内部的prototype属性设置为构造函数A的prototype值。
  4. 返回新创建的对象。

在JavaScript中,任何函数都可以作为构造函数!如上例,该构造函数很好的完成了封装,指定了对象p的大小{name,say},也实现了创建对象过程的复用。

但是该方式也存在问题:每个对象的存储空间都包含了独立的函数对象 s。如果对象有50个方法呢?将造成极大的空间浪费,Java类的对象可不是这样的哦!

另外一个问题是如何实现类变量(静态变量),也就是所有对象都共享的成员变量,在Java中通过 static 实现!

这就到了 prototype 大显身手的时候了!JavaScript 为每个函数设计了一个 prototype 属性,

2.2 抽象——方法与静态变量

prototype

每个函数都有一个prototype属性,是一个指针,指向该函数的原型对象。

__proto__

是一个对象拥有的内置属性。在JS内部使用寻找原型链。

function Person(name){
    this.name=name;
    Person.prototype.count++;
}

Person.prototype.say = function(){
    console.log("I'm "+this.name)
}

Person.prototyp.count = 0

var p1 = new Person("da bao")
var p2 = new Person("er bao")

p1.say() // I'm da bao
p2.say() // I'm er bao

p1.count // 2
p2.count // 2
  1. 构造函数 prototype 中的属性值,对所有对象来说是共享的,类似于Java中的静态成员。
  2. 在构造函数中通过 this 赋值的变量,以及调用父类构造函数通过 this 赋值的变量,都是对象独享的。

2.3 继承

面向对象的另一大特性——继承,Java 通过 extends和implements,标识类之间的继承关系,编译器会根据标识隐式的进行对象存储空间的分配和类信息的关联。

JavaScript 是如何实现的呢?

function Worker(name, type){
    Person.call(this, name); // (1)
    this.type = type;
    Worker.prototype.num++;
}

Worker.prototype = Object.create(Person.prototype) // (2)
Worker.prototype.num = 0

var w1 = new Worker("da bao", "Teacher")
var w2 = new Worker("er bao", "Coder")

w1.say() // I'm da bao
w2.say() // I'm er bao

w1.__proto__ == Worker.prototype // True
Worker.prototype.__proto__ == Person.prototype // True

例子中,(1)处代码保证为父类数据成员分配内存空间并初始化。(2)处代码保证对象能够调用父类中的成员方法。

JavaScript 是基于原型的面向对象语言,每一个js 对象都有一个原型链,这个原型链是一个原型对象之链;当修改构造函数的原型对象时,并不会影响已经存在的对象的原型链。

特别注意:

prototype 是个指针,当对它重新赋值时,已经生成的对象的原型链并不会被改变。

本例的原型链:

Person.prototype.__proto__ == Object.prototype
Worker.prototype.__proto__ == Person.prototype
w1.__proto__ == Work.prototype

这里写图片描述

  1. 构造函数是对初始化代码的封装;并调用父类的构造函数;
  2. 原型链是由解释器隐式维护的;就像C++中的虚表,Java对象的头信息;
  3. 对象的数据成员是由构造函数极其父类构造函数一同决定的,与原型无关;

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值