传统定义类内的函数的弊端
function Person(name)//定义了一个Person类
{
this.name=name;
this.info=function()//定义了一个类内部的函数用来输出name
{
alert("姓名是:"+this.name);
}
}
如上,我们定义了一个类Person,在类中我们定义了一个成员变量name和输出它的函数info,通过这种方式创建出来的函数有两个弊端:
1.性能不好
我们在每次创建类Person的实例时,都会生成一次这个info函数,如果我们创建多个实例,就会生成多个info函数,事实上我们只需要一个info函数就足够了,这样做会导致内存的泄漏
2.函数中的局部变量产生闭包
示例如下:
function Person(name)
{
var name="aaa";//局部变量name
this.info=function()
{
alert("姓名是:"+name);
return name;//此处返回了局部变量的值,造成了函数被调用后可以在函数外访问这个局部变量name
}
}
var p=new Person();
var value=p.info();
alert(value);//在函数外部可以访问到函数内部的局部变量的值,这显然不合理
从上面源码可以看出,由于在函数外可以访问函数内的局部变量,造成变量的作用域被扩大,这是不正确的。
解决方法----prototype属性
JS的所有类都有一个prototype属性,如果为JS类的prototype属性增加属性、方法,则可以视为对原有类的扩展,我们可以说,增加了prototype属性的类变相继承了原有的类,这就是JS提供的其中一种伪继承的实现方式。
我们来看如下代码:
function Person(name)
{
this.name=name;
}
Person p1=new Person("张三");
//此处还不能调用walk()方法,因为此处walk()方法还没有被添加到类中
//添加walk()方法到类中
Person.prototype.walk=function()
{
document.writeln(name+"正在走路");
}
p1.walk();//此时,p1所在的类已经有了walk()方法,因此p1也可以调用walk()方法,JS允许为类的实例动态添加扩展方法
Person p2=new Person("李四");
p2.info();
p2.walk();//同时,此时如果再创建一个类的实例那么这个实例会自动带上后来添加上去的walk()方法
上面的程序中为Person这个类动态地添加了walk()方法,因此,所有基于Person类创建出来的实例对象都会具有这个共享的方法,该方法不在Person构造函数之内,因此不会出现变量作用域扩大的问题
注意事项:
1.虽然可以在任何时候通过prototype属性为类增加属性和方法,但是通常建议在类定义结束之后立即增加该类所需的方法,这样可以避免不必要的混乱。
2.尽量避免直接在类定义中定义方法,这样可能造成内存泄漏和变量作用域扩大,比较安全的方式是通过prototype属性添加属性和方法。
3.与Java等真正面向对象的继承不同,JS只是实现了一种伪继承,这种伪继承的实质是修改了原有的类,并不是产生一个新的子类去继承父类,因此被修改的原有的类将不复存在,取而代之的是通过prototype属性添加过方法和属性的新的类
4.默认情况下,类的prototype的属性值是Object对象,将一个类的prototype属性设置为父类的实例,可以实现Java语言的继承,例如如下程序:
//定义一个父类Person类
function Person(name)
{
this.name=name;
}
Person p1=new Person("张三");
//定义一个子类Student类
function Student(grade)
{
this.grade=grade;
}
Student.prototype=new Person("无名");//将Student的prototype设为Person对象
var stu=new Student(5);
console.log(stu instance of Student);//输出true,即stu是Student类的实例
console.log(stu instance of Person);//输出true,即stu也是Person类的实例
上面的程序中将Student的prototype属性设为Person对象,表明Student的原型是Person对象,也就相当于Student继承了Person,这样Student类会得到Person类的属性和方法。