1. 在Javascript中, 每定义一个函数, 将伴生一个原型对象. 原型就是用来为同一类对象共享类型信息的, 即每一个通过该函数创建的对象都可以共享函数原型上的信息. 也就是说, 当我们定义一个函数的时候, 实际上得到了两个对象, 一个函数对象, 一个原型对象.
2. 在通过函数并使用new创建对象的过程中, 系统将完成三件事情:
a. 创建一个对象实例, 该实例上没有该实例自己定义的成员.
b. 将这个对象实例的prototype(原型)成员指向创建它的函数的原型对象.
c. 执行构造函数, 在函数执行过程中, this就表示刚刚创建的对象.
函数是一种特殊的对象. 实际上, 任何对象都有原型, 只不过普通对象的原型不能修改, 而函数对象的原型则可以修改. 原因很简单: 因为任何对象都是通过函数(类函数)来创建的, 当对象实例被创建后, 对象实例的prototype就会指向类函数的原型.
3. 类及对象的继承:
在Javascript中没有类的概念, 但可以通过函数来模拟. Javascript中的对象是通过函数来创建的, 在系统中预定义了8中特殊的函数, 用来创建对象, 这些函数有时被称为”类函数”或者类. 参考下图:
如图所示:
Object函数对象是Javascript中定义的顶级函数对象, 在Javascript中的所有对象都直接或间接的使用Object对象的原型. 当访问某对象的属性或方法时, 如果对象本身没有这个属性或方法, 那么Javascript会检查该对象的prototype(原型)对象是否拥有这个属性或方法, 如果有, 则作为对象的属性或方法返回, 如果没有, 那么将通过原型对象的prototype属性继续到父类的原型对象上进行检查, 直到原型对象为Object函数的原型对象为止.
例如: 当访问Student类的对象实例Jane时, 如果jane对象上的没有相关方法, 则会通过jane对象上的prototype属性找到其原型对象(即Student函数的原型对象), 并在其原型对象上查找相关方法.
这里有必要提醒的是: 由于继承关系的存在, jane的prototype属性所指向的原型对象实际上是父类函数的对象实例, 因为当我们通过Student函数创建一个对象jane后, jane的prototype属性指向的是Student函数的原型, 此时根本没继承关系存在. 在Javascript中, 继承关系的实现类似于C#中的继承关系(参考博客里的对象创建过程图), 子类对象之所以能够访问父类对象的成员是因为子类对象中包含一个父类的对象. 在Javascript中, 继承也是如此, 为了实现继承关系, jane对象的prototype属性指向的原型对象(也就是Student函数的原型对象)被一个父类函数(Person)创建的对象实例所替换, 此时jane的原型对象就是Person对象. 而且jane的原型对象(Person对象)上一样会有Person类的属性成员(如: name、age等), 只不过name、age成员对实现继承关系没有影响, 况且继承关系也没有用到name、age成员, 可以认为这种属性是冗余的废属性. 但是关键的是Person对象上除name、age之外的prototype属性, 因为Person对象是通过Person类函数创建的, 所以其原型也一定指向Person函数的原型, 这样我们就可以通过Person对象的prototype属性获得Person函数的原型对象的引用, 从而使jane对象能够找到Person函数的原型对象, 也就是Student函数的父类函数的原型对象, 即: jane对象找到Person对象(jane的原型对象), 在通过Person对象的prototype属性找到父类函数的原型对象, 这样便实现了继承关系. 而Person对象也是类似的情况, 最终通过一层一层的查找, 最终查找到Object函数的原型对象后, 就认为到头了, 于是认为jane对象没有相关方法. 这种方式, 通常也形象的成为”原型链”. 另外, 还有个情况需要引起注意, 就是当查找到某一级的原型对象时, 发现找到了相关方法, 则会终止查找, 直接返回相关方法; 如果该级的上级原型对象仍然有同名的相关方法, 这时也不在查找, 类似于C#中子类方法对父类方法的覆盖.
4. prototype是一个特殊的属性, 在大多数的浏览器上(如:IE), 都不能直接访问对象的prototype成员, 返回的结果为undefined, 少数浏览器可以访问(如: firefox). 函数对象在通过new创建出来时, 会自动将函数的原型赋予新创建出来的对象的prototype属性.
5. Javascript对象是一个动态对象, 对象所拥有的属性和方法是通过字典来进行管理的, 属性或者方法的名称就是字典中的key, 值就是名称所对应的value, 由于是使用字典进行管理, 所以可以动态的增加或删除属性. 因此对于原型对象, 我们只要将要扩展的方法置于某个原型对象中, 那么所有直接或间接指向该原型对象的所有对象均可以具有该扩展方法. 如: 在Person函数的原型上定义showMe()方法, 则jane对象上可以直接使用showMe()方法; 再如: 如果为String对象的原型添加trim()方法, 则所有的字符串对象都可以使用该方法. 虽然字符串是个值类型, 当我们定义string str=”hello”;时, 实际上系统会自动根据String类函数创建String类的对象用来包装字符串, 此时String对象就代替了字符串, 于是就有了String类上的属性和方法,, 不过该对象存在的时间很短, 当访问过字符串之后, 该对象就不复存在了.
以下给出对象继承的示例代码:
//javascript – prototype
<script type="text/javascript"> //定义Person类 function Person(name, age) { this.name = name; this.age = age; } //定义两个所有Person对象共享的方法 Person.prototype.showMe = function() { return " Person \r\n Name: " + this.name + "\r\n Age: " + this.age + "\r\n"; } //alert(Person.valueOf()); //测试 //alert(new Person("哈哈",18).showMe()); //测试 Person.prototype.toString = function() { return "Person Object"; } //定义Student类 function Student(name, age, sid) { Person.apply(this, arguments); //就近原则, this表示Person对象, 使用了apply, 故参数为数组 this.sid = sid; } //实现继承, 替换Student的原型 Student.prototype = new Person(); Student.prototype.toString = function() { //覆盖基类的方法 return "Student Object"; } var mike = new Person("mike", 30); var john = new Student("john", 23,"201008"); alert(mike.showMe()); //showMe方法可以被子类使用 alert(john.showMe()); alert(mike.toString()); alert(john.toString()); //覆盖基类的方法 </script>
6. Javascript中this的5种用法:
this在C#中表示当前对象的引用, 但是在Javascript中, this有5总含义, 下面给出this的示例代码:
//javascript – this
<!--Java script中this对象的5种用法(this必须是对象): 1. 普通用法: 表示当前刚创建的对象, 常和new配合用在类函数(构造函数)中. 2. this隐式传参: 函数有两种传递this参数的特殊方法: 在函数上调用call传递(通过this隐式传递1个对象, 如果要显示传递参数, 可以使用arguments) 3. 通过数组传递参数: 在函数上调用apply通过数组传递参数(通过this传递1个对象), 类似于C#中的params关键字 4. 当在某个对象上调用其方法时, this就表示调用方法的对象 5. 表示全局对象, 即window对象. 当不通过new调用类函数(构造函数时), this对象表示的是window对象. --> <script type="text/javascript"> //1. 普通用法: this表示当前刚刚创建的对象 function CreateCar(name,brand,createdate) { //js中, 构造函数通常首字母大写 this.Name = name; //普通用法: this表示刚创建的对象 this.Brand = brand; this.CreateDate = createdate; this.ShowCar = function() { alert("汽车名: <"+this.Name+">, 制造商: <"+this.Brand+">, 生产日期: <"+this.CreateDate+">"); }; } var benz = new CreateCar("M500", "奔驰.梅赛德斯", "1912-7-8"); //和new配合, this表示刚创建的对象 benz.ShowCar(); alert(benz.Name); //当前对象的Name属性 //2. this隐式参数: 在函数上调用call隐式传递this, this表示某个对象 function newAd(msg) { //return arguments[0] + arguments[1] + ": " + arguments[2]; return this.Brand + this.Name + ": "+msg; } //alert(newAd("Benz", "C300", "梅赛德斯.奔驰将带给你高端高质的生活体验! ")); var benz2 = {}; benz2.Name = "C300"; benz2.Brand = "Benz"; alert(newAd.call(benz2, "梅赛德斯.奔驰将带给你高端高质的生活体验! ", 3, 4, 5)); //this表示对象benz2, 函数将忽略多余的参数 //3. 以数组方式传参: 在函数上调用apply通过数组传递参数(this表示某个对象), apply将忽略多余的参数 alert(newAd.apply(benz2,["梅赛德斯.奔驰将带给你高端高质的生活体验! ",3,4,5])); //this表示对象benz2, 调用apply通过数组传参([]), 将忽略多余的参数 //4. 当在某个对象上调用其方法时, this就表示调用方法的对象 benz2.sayHello = function() { return this.Brand+this.Name+": "+"Hello"+this.Brand}; alert(benz2.sayHello()); //this就表示紧挨着方法的那个对象 //5. this对象表示全局对象, 即windows. 当不通过new调用构造函数时, this对象实际表示的window对象. var benz3 = CreateCar("BenzWindow", "梅赛德斯.奔驰", new Date()); alert(window.Name); //没有使用new, this表示的是window对象, 而输出benz3.Name则没有结果 //alert(benz3.Name); </script>
7. 原型继承:
现在, 我们通过原型模拟了对象的继承过程, 但是还有个遗憾是: 子类函数的原型对象被父类的对象实例替代, 从而实现了对象的继承, 但是替换后的子类函数的原型上多出的name和age成员从不会被用到, 况且我们为了实现对象继承专门使用了一个函数(Person.apply(this, arguments);)来创建对象. 其实我们在Student类中创建了Person对象后, 就不再需要Person.apply(this, arguments)函数了, 该函数存在的目的就是介绍Person对象的原型和执行构造过程. 还记得局部变量和临时变量吗? 我们可以在创建对象的函数中, 嵌套一个临时函数, 当临时函数对象完成介绍Person对象的原型的工作后, 便可以结束函数的执行, 那么根据前面提到过的”使用New创建对象, 系统要执行的3件事”这个过程, 可以得出结论: Student对象获得了原型, 但是确省掉了构造对象的过程(没有执行构造函数). 临时函数执行完就被抛弃, 也不会占用内存. 这种只获得父类原型对象的方式, 就是原型继承. 而我们前面所是实现的是对象的继承.
//javascript – prototype of inherit
<script type="text/javascript" > //1. 定义基类模板 var Person = { //构造函数 Create: function(name, age) { //定义类成员 this.name = name; this.age = age; }, showMe: function() { //定义类上的方法 return " Person \r\n Name: " + this.name + "\r\n Age: " + this.age + "\r\n"; } }; //注意分号 //2. 定义创建某类对象的工具函数 function New(aClass, aParams) { function new_() { //临时函数 aClass.Create.apply(this, aParams); //某个类aClass上必须定义构造函数Create }; new_.prototype = aClass; //通过aClass中的类函数介绍原型对象 var obj = new new_(); //利用临时函数调用基类构造函数, 返回最终的的基类对象 return obj; } // //测试: 创建基类对象 // var mike = New(Person, ["mike", 30]); // alert(mike.showMe()); //3. 创建子类模板 var Student = Class(Person, { Create: function(name, age, sid) { Person.Create.apply(this, [name, age]); this.sid = sid; }, sayHello: function() { //在子类上增加一个方法 return "Hello, My name is : " + this.name + " , I'm a student! "; } }); //4. 创建新类的工具函数 function Class(baseClass, classDefine) { function class_() { //临时函数 for (var member in classDefine) { this[member] = classDefine[member]; //创建子类时, 先复制基类的全部定义到子类 } } class_.prototype = baseClass; //通过基类函数介绍原型对象 var obj = new class_(); //利用临时构造函数, 返回最终的子类对象 return obj; } //alert("hello"); //5. 创建子类对象 var mike = New(Person, ["mike", 30]); alert(mike.showMe()); var jane = New(Student, ["jane", 15, "201008"]); alert(jane.showMe()); alert(jane.sayHello()); </script>
//带类型检查的原型继承:
//javascript – prototype of inherit with typeCheck
<script type="text/javascript"> //1. 定义object基本类(小写, 非Object), 实现最基础的方法,如: 判断类型, (false, 0, undefined, null均表示假) var object = { isA: function(aType) { var self = this; while (self) { //self非null, 当确定某对象实例是否为某种类型时, 就是依次通过对象的type链进行比较, 直到object为止(object本身没有type属性). if (self == aType) { return true; } self = self.Type } return false; } } //2. 定义基类模板 var Person = Class(object, { //构造函数 Create: function(name, age) { //定义类成员 this.name = name; this.age = age; }, showMe: function() { //定义类上的方法 return " Person \r\n Name: " + this.name + "\r\n Age: " + this.age + "\r\n"; } }); //注意分号 //3. 定义创建某类对象的工具函数 function New(aClass, aParams) { function new_() { //临时函数 if (aClass.Create) { //判断对象是否存在Create函数 aClass.Create.apply(this, aParams); //某个类aClass上必须定义构造函数Create } this.Type = aClass; //每创建一个对象都增加Type成员, 指向对象的类函数, 构成type链 }; new_.prototype = aClass; //通过aClass中的类函数介绍原型对象 var obj = new new_(); //利用临时函数调用基类构造函数, 返回最终的的基类对象 return obj; } //4. 创建子类模板 var Student = Class(Person, { Create: function(name, age, sid) { Person.Create.apply(this, [name, age]); this.sid = sid; }, sayHello: function() { //在子类上增加一个方法 return "Hello, My name is : " + this.name + " , I'm a student! "; } }); //5. 创建新类的工具函数 function Class(baseClass, classDefine) { function class_() { //临时函数 for (var member in classDefine) { this[member] = classDefine[member]; //创建子类时, 先复制基类的全部定义到子类 } this.Type = baseClass; //为每个类对象也增加Type成员 } class_.prototype = baseClass; //通过基类对象介绍原型对象 var obj = new class_(); //利用临时构造函数, 返回最终的子类对象 return obj; } //alert("hello"); //6. 创建子类对象 var mike = New(Person, ["mike", 30]); alert(mike.showMe()); alert("mike is Person Type: " + mike.isA(Person)); alert("mike is object Type: " + mike.isA(object)); var jane = New(Student, ["jane", 15, "201008"]); alert(jane.showMe()); alert(jane.sayHello()); alert("jane is Student Type: " + jane.isA(Student)); alert("mike is Person Type: " + jane.isA(Person)); alert("mike is object Type: " + jane.isA(object)); </script>
//8. caller和callee:
caller返回一个对函数的引用, 该函数调用了当前的函数. 即: 谁调用函数, 就返回谁.
callee返回正在被执行的函数, 即: 执行谁, 就返回谁.
注意:
callee是arguments对象的成员, callee也拥有length属性, 不同的是: arguments.length是实参的长度, 而arguments.callee.length是形参的长度. callee可用于判断实参的长度.
callee还常用于递归调用, 使用arguments.callee()调用自身.
//javascript – caller and callee
<script type="text/javascript"> //callerDemo function callerDemo() { if (callerDemo.caller) { var a = callerDemo.caller.toString(); alert(a); } else { alert("this is a top function! "); } } function handCaller() { callerDemo(); } handCaller(); alert("注意: 以下是callee演示! "); //calleeDemo function calleeDemo() { //打印函数本身 alert(arguments.callee); } calleeDemo(); //验证参数 function calleeLength(arg1, arg2) { if (arguments.length == arguments.callee.length) { window.alert("形参和实参的长度匹配! "); return; } else { alert("实参长度: "+arguments.length); alert("形参长度: "+arguments.callee.length); } } calleeLength(3, 4, 5, 6, 7); //递归计算 var sum = function(n) { if (n <= 0) { if (n == 0) { return 0; } else { return 1; } } else return n + arguments.callee(n - 1); } alert(sum(100)); </script>