面向对象的概念
JavaScript 并不是面向对象的程序设计语言,面向对象设计的基本特征:继承、多态等没有得到很好的实现。在纯粹的面向对象语言里,最基本的程序单位是类,类与类之间提供严格的继承关系。比如 Java 中的类,所有的类都可以通过 extends 显式继承父类,或者默认继承系统的 Object 类。而 JavaScript 并没有提供规范的语法让开发者定义类。
JavaScript 中的每个函数都可用于创建对象,返回的对象既是该类的实例,也是Object 实例 看如下代码
<script type=" text/javascript">
//定义简单函数
function Person (name)
{
this.name= name;
}
//使用 new 关键字 简单创建 Person 类的实例
var p = new Person ('yeeku') ;
//如果p 是 Person 类的实例 则输出静态文本
if (p instanceof Person)
document.writeln (" p是Person 的实例<br>");
//如果p 是Object 类的实例,则输出静态文本
if(p instanceof Object)
documen.writeln ("p是Object 的实例<br>") ;
</script>
上面的 JavaScript 在定义 Person 函数的同时 ,也得到了 Person 类,因此程序通过 Person的对象既是 Person 实例 ,也是Object 类的实例
继承和 prototype
当定义函数时,函数中以 修饰的变量是实例属性,如果某个属性值是函数时,即可认为该属性变成了方法 例如如下代码
<script ype="text/javascript>
//创建 Person 函数
function Person(name , age)
{
this.name= name;
this.age = age;
//为 Prson 对象指定 info 方法
this.info= function()
{
//输出 Person 实例的 name、age 属性
docurnent.writeln("姓名:" + this.name);
docurnent.writeln("年龄:" + this.age);
}
//创建 Person 实例 pl
var pl = new Person ('yeeku', 29) ;
//执行 pl的info 方法
pl.info() ;
documen.writeln ("<hr />") ;
//创建 Person 实例 p2
var p2 = new Person ('wawa' , 20) ;
//执行 p2的info 方法
p2.info() ;
</script>
代码中在定义 Person 函数的同时,也定义了一个Person 类,而且该Person 函数就是该Person 类的构造器,该构造器不仅为 Person 实例完成了屈性的初始化,还为 Person 实例提供info 方法。
但使用上面方法为 Person 类定义增加 info 方法相当不好,主要有如下两个原因。
- 性能低下: 因为每次创建 Person 实例时,程序依次向下执行,每次执行程序中粗体字代码时都将创建一个新的info 函数—— 当创建多个Person 对象时,系统就会有很多个info 函数——这就会造成系统内存泄涌,从而引起性能下降 。实际上 info 函数只需一个就够了
- 使得 info 函数中的局部变量产生闭包: 闭包会扩大局部变握的作用域,使得局部变量 一直存活到函数之外的地方。看如下 代码
<script type=" text/javascript " >
//创建 Person 函数
function Person()
{
// locVal 是个局部变量 原本应该该函数结束后立即失效
var locVal = 'XXXXXX';
this.info= function()
{
//此处会形成闭包
document.writeln("locVal 的值为:" + locVal);
return locVal;
}
}
var p = new Person();
//调用p对象的info 方法
var val = p.info () ;
//输出 val 返回值 该返回值就是局部变量 locVal
alert(val);
</script>
由于在 info 函数里访问了局部变量 locVal 所以形成了闭包,而导致 locVal 变量的作用域被扩大,,即使离开 info程序依然可以访问到局部变量的值。
JavaScript 所有类(也就是函数)都有 prototype 属性 如果为 JavaScript类的prototype属性增 加属性、 方法,则可视为对原有类的扩展 我们可理解为 增加了 prototype 性的类继承了原有类——这就是 JavaScript 所提供的一种伪继承机制 看如下程序
<script type="text/javascript">
function Person(name,age)
{
this.name = name;
this.age = age;
// this.info= function()
// {
// document.writeln("姓名 :" + this.name + "<br>" ) ;
// document.writeln ("姓名 :" + this.age + "<br>" ) ;
// }
}
Person.prototype.walk = function()
{
document.writeln(this.name + 'kkk <br>');
}
//document.writeln('<br>');
var p1 = new Person ('yeeku',29);
// p1.info();
var p2 = new Person ('leegang',30);
// p2.info();
//document.writeln ('<br>');
p2.walk();
p1.walk();
</script>
在为 Person 类增加 walk 方法之前 p1 对象不能调用walk方法,当为Person 类增加了walk 实例方法之后 p2 对象以及前面 p1对象都拥有 walk 方法 ,所以可调用walk 方法
上面程序采用 prototype为Person类增加了 walk 方法 这样会让所有 Person共享 walk 方法,而且该walk 方法不在 Person 函数之内 不会产生闭包。
这可被当成一种“伪继承“;但这种“伪继承"的实质是修改了原有的类 并不是产生了一 新的子类,这一点尤其需要注意 因此原有的那个没有 walk 方法的 Person 类将不再存在!
使用 apply call 实现伪继承
上面介绍的伪继承的关键在千子类构造器需要以 this 作为调用者来调用父类构造器 ,这样父类构造器中的 this 就会变成代表子类,子类就可以得到原父类定义的实例属性和方法 ,因此这种伪继承方式完全可以用 apply和call 来实现,只要在使apply 或call 调用时指定 this作为调用者即可。
<script type="text/javascript">
function Animal(sex,age)
{
this.sex = sex;
this.age = age;
}
Animal.prototype.say = function()
{
console.log(this.age + "nian!");
}
//定义狗类
function Dog(sex,age,bark){
Animal.apply(this,[sex,age]);
this.bark = bark;
}
Dog.prototype = new Animal();
Dog.prototype.intro = function(bark)
{
console.log("这是个%s 狗,%s叫", this.sex , this.bark);
}
var hsq = new Dog("母",2,"wang...");
hsq.intro();
hsq.say();
</script>
以下两语句作用完全相同
Person.call(this,name,age);
Person.apply(this,[name,age]);