继承与模仿:
当我们定义好某个类时,将当前类的 prototype 属性指向某个类的对象就可以继承
该类的所有公有属性方法了。
当我们继承了某个类的方法后,在定义当前类的方法时可以使用 this.xxx 直接访问
父类的方法。
如果进行方法重写,在重写的方法中不能再使用父类的同一个方法,但这在java中
可以通过 super 关键字类访问。在javascript中是否可以这样呢?如果有哪位大虾
知晓的话,忘不吝赐教。
MyClass.prototype = new FatherClass(); // 将当前类的原型指向父类的对象 即可实现继承
其实更明确的说这种关系类似于 “模仿” 而不是真正的继承,继承要求两个类具有 血缘关系 。
在java中,子类继承了父类,那么他就拥有了父类的所有公有的方法和属性,他们具有了很近的
血缘关系,而在javascript当中,通常说的是类模仿了某个类而拥有了被模仿类的公有属性和
方法。
模仿 不要求这两个东西是同一个类型的,而继承就规定了 他们两个必须是同一个类型的。
笔记本电脑 是 电脑,台式电脑 是 电脑。他们都属于 电脑 这个大类。
笔记本电脑 模仿了 笔记本造型,台式电脑 模仿了 电视造型。 他们之间没有必然的血缘关系。
在javascript中,这个 prototype 就是来描述 模仿 这个关系。所以在javascript中严格的来
将 是没有 “继承” 的。
原型必须指定一个对象,在javascript中,每个对象的原型默认都是一个 Object 对象,注意是
对象而不是 Object 类型。
每个类有且仅有一个 原型,类间的继承关系有原型链决定。
摘自:《javascript王者归来》
“在现实生活中,我们常常说,某个东西是以另一个东西为原型制作的。这个两个东西可以是
同一个类型的,也可以是不同的类型的。习语‘照猫画虎’,这里猫就是原型,而虎就是类型,
用javascript的prototype来描述就是 虎.prototype=某只猫 或 虎.prototype=new 猫()”
"'原型'是描述自然界事物之间‘归类’关系的一种,另外几种关系包括‘继承’和‘接口’。一般
来说,‘继承’描述的是事物之间存在的固有的衍生关系,能被继承的事物具有很强的相关性。
‘接口’描述的是事物公用方面的共同特征。而‘原型’则倾向于描述事物之间的‘相似性’。从这
一点来看,‘原型’在描述事物的关联性的方面,比继承和接口的意义更广。
如果你是java程序员,上面的例子从继承的角度去考虑,当然不能让‘猫’去继承‘虎’,也
不能让‘虎’去继承‘猫’,要描述它们之间的关系,我们需要建立一个涵盖了它们共性的‘抽象类’
,或者你会叫它‘猫科动物’ 。可是,如果系统中我们只需要用到‘猫’和‘虎’,那么这个多余的
‘猫科动物’对于我们来说没有任何意义,我们只是想表达 ‘虎’ 有点像 ‘猫’ 而已。在这里,
用原型帮我们节省了一个没有必要建立的‘猫科动物’。"
例子:
/******************************************************************************* * 类型: Human 人(父类) * 属性: name 省份 * age 年龄 * gender 性别 * addr 地址 * 类属性: version 版本 * 方法: getInfo 获取人的简单信息 返回类型:String ****************************************************************************** */ function Human(){} // 动态属性 Human.prototype.name = ""; Human.prototype.age = 0; Human.prototype.gender = "male"; Human.prototype.addr = "null"; // 动态方法 Human.prototype.getInfo = function() { return info = "<h1>" + this.name + "</br>" + this.age + "</br>" + this.gender + "</br>" + this.addr + "</br>" + "</h1>"; } Human.version = 1; /******************************************************************************* * 类型: Student 学生 * 属性: className 班级 * grade 年纪 * school 学校 * 方法: getStuInfo 获取学生的简单信息 返回类型:String ****************************************************************************** */ function Student(){} Student.prototype = new Human(); Student.prototype.className = ""; Student.prototype.grade = 0; Student.prototype.school = ""; Student.prototype.getStuInfo = function() { return this.getInfo() // Human 中的方法 + "<h2>" + this.school + "</br>" // Student 中的属性 + "</h2>"; } // / var stu = new Student(); stu.name = "小明"; // 父类中的属性 stu.age = 8; // 父类中的属性 stu.addr = "北京"; // 父类中的属性 stu.school = "朝阳小学"; // 子类中的属性 document.write( stu.getInfo() ); // 父类中的方法 document.write( stu.getStuInfo() ); // 子类中的方法 // / // 定义第三方类 继承 Student function Test(){} Test.prototype = new Student(); // 继承 Student // Test 继承 Student 继承 Human 继承 Object var tt = new Test(); document.writeln(tt instanceof Object); // true 每个类都默认继承自 Object document.writeln(tt instanceof Human); // true document.writeln(tt instanceof Student); // true document.writeln(tt instanceof Test); // true document.writeln(tt instanceof Date); // false document.writeln("</BR>"); // 打印 Test 的所有属性和方法 for (var each in tt) { document.writeln(each); // className grade school getStuInfo name age addr getInfo gender }
使用prototype:
对象的原型属性或方法是对象共有的,如果对某个对象的原型进行操作,那么该类的其他对象
都具有相同的属性。
在访问对象的原型属性时不需要加prototype关键字,直接用点运算符进行访问和赋值。
给对象的属性赋值时,新的值会覆盖原型中的值。之后我们可以用 delete(obj.property) 来删除
某个属性,这时访问该属性的值时就是属性原型的值了。
对象的原型属性值可以看做是属性的默认值,在没有给该属性赋值或删除该属性值的情况下,
对象的属性值保持 默认值 。
例子:
function Human(){} Human.prototype.name = "No name"; Human.prototype.age = 0; Human.prototype.gender = "male"; Human.prototype.getInfo = function() { return this.name + "</br>" + this.age + "</br>" + this.gender + "</br>"; } // // var man1 = new Human(); man1.name = "熊猫"; man1.age = 5; man1.gender = "famale"; var man2 = new Human(); // 每个对象都有 name age gend 属性 document.writeln(man1.getInfo()); document.writeln(man2.getInfo()); document.writeln(man1.name); // 熊猫 document.writeln(man2.name); // 默认值 “No name” delete(man1.name); // 删除属性的值 document.writeln(man1.name); // 恢复 默认值 “No Name” 只读的getter /******************************************************************************* * 类型: Address 地址 * 属性信息: province 省份 * city 城市 * 方法: toString 覆盖Object类中的 toString ****************************************************************************** */ function Address(){} Address.prototype.province = ""; Address.prototype.city = ""; // 覆盖Object类中的 toString 方法 Address.prototype.toString = function() { return this.province + "省" + this.city + "市</BR>"; } /******************************************************************************* * 类型: Human 人 * 属性: name 姓名 * addr 地址 * 方法: getAddr 返回地址信息 * getAddress 返回地址信息 只读方法,不可以通过返回的地址改变地址的内容 ****************************************************************************** */ function Human(name, addr) { this.name = name; this.addr = addr; this.getAddr = function() { return this.addr; } this.getAddress = function() { function Tmp(){} Tmp.prototype = this.addr; return new Tmp(); } } // / var add = new Address(); add.province = "陕西"; add.city = "西安"; var man = new Human("熊猫", add); document.writeln( man.getAddr() ); // 陕西省西安市 // 通过 getAddr 方法获得地址属性并改变它 var temp1 = man.getAddr(); temp1.province = "湖南"; document.writeln( man.getAddr() ); // 湖南省西安市 已修改 // 通过 getAddress 方法获得地址属性并改变它 var temp2 = man.getAddress(); temp2.province = "湖北"; document.writeln( man.getAddr() ); // 湖南省西安市 未修改
prototype 特性:
1.通过 prototype 定义的属性是所有对象共有的,且这个属性的值是只读的。对象只能通过覆盖
该属性的值,当对象的特定属性没有赋值时,它的值就是定义prototype时的原始值。
2.尽量通过prototype来定义类的属性和一些方法,除非需要用到闭包来封装某些私有属性时。
直接在类的定义中通过 this.xxx 定义方法或属性的话,在每一个new对象的时候都会对
这些属性或方法进行初始化,这样不仅降低了效率而且还有初始值被覆盖的风险。
3.通过制定类型的prototype,可以模拟继承。
4.可以以任何一个对象为原型,安全的建立大量的实例。
var p = "asdfasd"; var pp = function(){}; pp.prototype = p; var pps = []; for (var i = 0; i < 10000; i ++) { pps[i] = new pp(); // 创建大量的p 的副本。clone对象。 // 由于pp构造方法是个空的,所以比直接构造 p 对象要快的多。 }
5.类的继承链是有 prototype 构成的,这个继承链当中如果有一个 prototype 的值发生改变,
将传递性的影响这个链接下方的所有对象。且如果其中有一个 prototype 的值是一个引用
(比如数组),那么任何一个对该引用的操作都会影响且改变其它对象中该属性的值。