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")
- 当调用 new 操作符时,解释器会创建一个通用对象(Object),将其作为this传递给构造函数,也就是该例子中的 A()。
- 构造函数中对 this 的属性进行赋值;
- 然后隐式地(由解释器完成,不需要额外的代码)将内部的prototype属性设置为构造函数A的prototype值。
- 返回新创建的对象。
在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
- 构造函数 prototype 中的属性值,对所有对象来说是共享的,类似于Java中的静态成员。
- 在构造函数中通过 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
- 构造函数是对初始化代码的封装;并调用父类的构造函数;
- 原型链是由解释器隐式维护的;就像C++中的虚表,Java对象的头信息;
- 对象的数据成员是由构造函数极其父类构造函数一同决定的,与原型无关;
……