ECMAScript有两种开发模式:1.函数式(过程化),2.面向对象(OOP)。面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是,ECMAScript没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
一.创建对象
1.创建一个对象,然后给这个对象新建属性和方法。
var box=new Object(); //创建对象 box.name1='xixi'; //添加属性 box.age=100; //添加属性 box.run=function(){ return this.name1+this.age+'运行中...'; //this表示当前作用域下对象 }; //this表示new Object()实例化出来的那个对象 //this要放在一个作用域下,比如box.run(){},这个是box作用域下的方法,方可用this来表示box本身 alert(box.run()); //xixi100运行中... alert(this.name1); //这里的this代表的是window打印:undefined
上面创建了一个对象,并且创建属性和方法,在run()方法里的this,就是代表box对象本身。这种是JavaScript创建对象最基本的方法,但有个缺点,想创建一个类似的对象,就会产生大量的代码。
2.多个类似对象声明:创建另一个对象box2并把第一个对象box赋值给box2(box2的属性就共享box1的)
var box=new Object(); //创建对象 box.name='xixi'; //添加属性 box.age=100; box.run=function() { return this.name+this.age+'运行中...'; }; var box2=box; //得到box的引用 box2.name='wang'; //直接改变了name属性 box2.age=200; box2.run=function() { return this.name+this.age+'运行中...'; }; alert(box.run()); //wang200运行中 alert(box2.run()); //用box.run()发现name也改变了:wang200运行中
3.工厂模式的方法:为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。
function createObject(name,age) { var obj=new Object(); //创建对象 obj.name=name; //添加属性 obj.age=age; obj.run=function(){ //添加方法 return this.name+this.age+'运行中...'; }; return obj; //返回对象引用 }; var box1 = createObject('wang',100); //创建第一个对象 var box2 = createObject('shi',200); //创建第二个对象 alert(box1.run()); //保持独立:wang100运行中 alert(box2.run()); //保持独立:shi200运行中 alert(box1 instanceof Object); //true alert(box2 instanceof Object); //true
工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法搞清楚他们到底是哪个对象的实例。
4.构造函数:ECMAScript中可以采用构造函数(构造方法)可用来创建特定的对象。类型于Object 对象。
function Box(name,age) { //创建一个对象,所有构造函数的对象其实就是Object this.name=name; //添加一个属性 this.age=age; this.run=function () { //添加一个方法 return this.name+this.age+'运行中...'; }; }; function Desk(name, age) { //创建一个对象,所有构造函数的对象其实就是Object this.name = name; //添加一个属性 this.age = age; this.run = function () { //添加一个方法 return this.name + this.age + '运行中...'; }; }; var box1 = new Box('wang',100); //实例化 var box2 = new Box('xixi',200); //实例化 var box3 = new Desk('kkk',500); //实例化 alert(box1 instanceof Box); //true alert(box2 instanceof Box); //true alert(box3 instanceof Box); //false。可以识别了,因为box3是Desk对象的引用 alert(box3 instanceof Desk); //可以识别:true
使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题是,这里并没有new Object(),为什么可以实例化Box(),这个是哪里来的呢?使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:
(1)构造函数方法没有显示的创建对象(new Object());
(2)直接将属性和方法赋值给this对象;
(3)没有renturn语句。
构造函数的方法有一些规范:
(1)构造函数也是函数,但函数名第一个字母大写
(2)必须new 构造函数名(),new Box(),而这个Box第一个字母也是大写的
(3)必须使用new运算符
既然通过构造函数可以创建对象,那么这个对象是哪里来的,new Object()在什么地方执行了?执行的过程如下:
(1)当使用了构造函数,并且new构造函数(),那么就后台执行了new Object();
(2)将构造函数的作用域给新对象,(即new Object()创建出的对象),而函数体内的this就代表new Object()出来的对象。
(3)执行构造函数内的代码;
(4)返回新对象(后台直接返回)。
5.关于this的使用,this其实就是代表当前作用域对象的引用。如果在全局范围this就代表window对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。
构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用new运算符来调用,否则就是普通函数。
function Box(name,age) { //创建一个对象,所有构造函数的对象其实就是Object this.name=name; //添加一个属性 this.age=age; this.run=function () { //添加一个方法 return this.name+this.age+'运行中...'; }; }; var box1=new Box('xixi',100); //实例化 alert(box1.run()); //打印:xixi100运行中 var user='wang'; alert(this.user); //全局,代表window:打印wang
5.对象冒充
function Box(name,age) { //创建一个对象,所有构造函数的对象其实就是Object this.name=name; //添加一个属性 this.age=age; this.run=function () { //添加一个方法 return this.name+this.age+'运行中...'; }; }; var o= new Object(); Box.call(o,'xixi',100); //对象冒充 alert(o.run()); //打印:xixi100运行中...
6.探讨构造函数内部的方法(或函数)的问题,首先看下两个实例化后的属性或方法是否相等。
function Box(name,age) { //nwe Function()唯一性。创建一个对象,所有构造函数的对象其实就是Object this.name=name; //添加一个属性 this.age=age; this.run=function () { //添加一个方法 return this.name+this.age+'运行中...'; }; }; var box1=new Box('xixi', 100); //传递一致//实例化后地址为1 var box2=new Box('xixi', 100); //传递一致//实例化后地址为2 alert(box1.name == box2.name); //true,属性的值相等 alert(box1.age == box2.age); //true,属性的值相等 alert(box1.run() == box1.run()); //true,构造函数体内的方法的值是相当的 alert(box1.run == box2.run); //false,因为他们比较的是引用地址
(1)可以把构造函数里的方法(或函数)用new Function()方法来代替,得到一样的效果,更加证明,他们最终判断的是引用地址,唯一性。
(2)可以通过构造函数外面绑定同一个函数的方法来保证引用地址的一致性
function Box(user,age) { //创建一个对象,所有构造函数的对象其实就是Object this.user=user; //添加一个属性 this.age=age; this.run=run; }; function run() { //把构造函数内部的方法通过全局来实现引用地址一致 return this.user + this.age + '运行中...'; } var box1=new Box('xixi', 100); //传递一致 var box2=new Box('xixi', 100); //传递一致 alert(box1.run == box2.run); //false,因为他们比较的是引用地址 alert(box1.run()); //xixi100运行中... alert(run()); //NaN运行中...
虽然使用了全局的函数run()来解决了保证引用地址一致的问题,但这种方式又带来了一个新的问题,全局中的this在对象调用的时候是Box本身,而当作普通函数调用的时候,this
二.原型
1.我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype通过调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name='xixi'; //原型属性 Box.prototype.age=100; //原型属性 Box.prototype.run=function () { //原型方法 return this.name+this.age+'运行中...'; }; var box1=new Box(); var box2=new Box(); alert(box1.run()); //xixi100运行中... alert(box1.run==box2.run); //true,方法的引用地址保持一致
(1)如果是实例方法,不同的实例化,他们的方法地址是不一样的,是唯一的

(2)如果是原型方法,那么他们地址是共享的,大家都是一样

2.在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。__proto__ 属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor。通过这两个属性,就可以访问到原型里的属性和方法了。
(1)__proto__ 属性
function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name='xixi'; //原型属性 Box.prototype.age=100; //原型属性 Box.prototype.run=function () { //原型方法 return this.name+this.age+'运行中...'; }; var box = new Box(); alert(box.prototype); //使用对象实例无法访问到prototype输出:undefined alert(box.__proto__); //这个属性是一个指针指向prototype原型对象输出:[object Object] alert(Box.prototype); //使用构造函数名(对象名)访问prototype原型对象输出:[object Object]
注意:IE 浏览器在脚本访问__proto__会不能识别,火狐和谷歌浏览器及其他某些浏览器均能识别。虽然可以输出,但无法获取内部信息。
(2)原型属性constructor
function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name='xixi'; //原型属性 Box.prototype.age=100; //原型属性 Box.prototype.run=function () { //原型方法 return this.name+this.age+'运行中...'; }; var box1=new Box(); alert(box1.constructor); //构造属性,可以获取构造函数本身:function Box {} //作用是被原型指针定位,然后得到构造函数本身 //其实就是对象实例对应的原型对象的作用
(3)判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。
function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name='xixi'; //原型属性 Box.prototype.age=100; //原型属性 Box.prototype.run=function () { //原型方法 return this.name+this.age+'运行中...'; }; var box1=new Box(); //判断一个对象实例(对象引用)是不是指向了原型对象,基本上,只要实例化了,他自动指向的 alert(Box.prototype.isPrototypeOf(box1)); //true
3.原型模式的执行流程:
(1)先查找构造函数实例里的属性或方法,如果有,立刻返回;
(2)如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原型中的值。
function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name='xixi'; //原型属性 Box.prototype.age=100; //原型属性 Box.prototype.run=function () { //原型方法 return this.name+this.age+'运行中...'; }; var box1=new Box(); box1.name='wang'; //实例属性,并没有重写原型属性 alert(box1.name); //就近原则:wang var box2=new Box(); alert(box2.name); //实例属性不会共享,所以box2访问不到实例属性,就只能访问原型:xixi
(3)属性删除:如果想要box1也能在后面继续访问到原型里的值,可以把构造函数里的属性删除即可
function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name='xixi'; //原型属性 Box.prototype.age=100; //原型属性 Box.prototype.run=function () { //原型方法 return this.name+this.age+'运行中...'; }; var box1=new Box(); box1.name='wang'; //实例属性,并没有重写原型属性 alert(box1.name); //就近原则:wang delete box1.name; //删除实例中的属性 alert(box1.name); //打印删除后的实例:xixi
4.如何判断属性是在构造函数的实例里,还是在原型里?可以使用 hasOwnProperty()函数来验证
(1)实例里有返回true:
function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name='xixi'; //原型属性 Box.prototype.age=100; //原型属性 Box.prototype.run=function () { //原型方法 return this.name+this.age+'运行中...'; }; var box1=new Box(); box1.name = 'wang' alert(box1.hasOwnProperty('name')); //返回true
(2)实例里没有返回false:
function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name='xixi'; //原型属性 Box.prototype.age=100; //原型属性 Box.prototype.run=function () { //原型方法 return this.name+this.age+'运行中...'; }; var box1=new Box(); alert(box1.hasOwnProperty('name')); //返回false

5.in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name='xixi'; //原型属性 Box.prototype.age=100; //原型属性 Box.prototype.run=function () { //原型方法 return this.name+this.age+'运行中...'; }; var box1=new Box(); alert('name'in box1); //不管实例属性或原型属性是否存在,只要有就返回true,两边都没有,返回false
我们可以通过hasOwnProperty()方法检测属性是否存在实例中,也可以通过in来判断实例或原型中是否存在属性。那么结合这两种方法,可以判断原型中是否存在属性。
//判断只有原型中有属性, function isProperty(object, property) { //判断原型中是否存在属性 return !object.hasOwnProperty(property) && (property in object) } function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name='xixi'; //原型属性 Box.prototype.age=100; //原型属性 Box.prototype.run=function () { //原型方法 return this.name+this.age+'运行中...'; }; var box1=new Box(); alert(isProperty(box1,'name')) //true,如果原型有
6.为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使用字面量的方式:
function Box() {} //使用字面量的方式创建原型对象,这里{}就是对象,是Object,new Object就相当于{} Box.prototype = { //使用字面量的方式 name : 'xixi', age : 100, run : function () { return this.name+this.age+'运行中...'; } }; var box = new Box(); alert(box instanceof Box); //true alert(box instanceof Object); //true alert(box.constructor == Box); //字面量方式,返回 false,否则,true alert(box.constructor == Object); //字面量方式,返回 true,否则,false
使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区别,字面量创建的方式使用constructor属性不会指向实例,而会指向 Object,构造函数创建的方式则相反。如果想让字面量方式的constructor指向实例对象,那么可以这么做:
function Box() {} //使用字面量的方式创建原型对象,这里{}就是对象,是Object,new Object就相当于{} Box.prototype = { //使用字面量的方式 constructor : Box, //强制指向Box name : 'xixi', age : 100, run : function () { return this.name+this.age+'运行中...'; } }; var box = new Box(); alert(box instanceof Box); //true alert(box instanceof Object); //true alert(box.constructor == Box); //true alert(box.constructor == Object); //false
注意:字面量方式为什么constructor会指向Object?因为 Box.prototype={};这种写法其实就是创建了一个新对象。而每创建一个函数,就会同时创建它prototype,这个对象也会自动获取constructor属性。所以,新对象的constructor重写了Box原来的constructor,因此会指向新对象,那个新对象没有指定构造函数,那么就默认为Object。
原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型
function Box() {} //使用字面量的方式创建原型对象,这里{}就是对象,是Object,new Object就相当于{} Box.prototype = { //使用字面量的方式 constructor : Box, //强制指向Box name : 'xixi', age : 100, run : function () { return this.name+this.age+'运行中...'; } }; //重写了原型对象 Box.prototype = { age : 200 //这里不会保留之前原型的任何信息了。 //把原来的原型对象和构造函数对象实例之前的关系切断了 }; var box = new Box(); //在这里声明 alert(box.name); //box 只是最初声明的原型输出:undefined
原型对象不仅仅可以在自定义对象的情况下使用,而ECMAScript 内置的引用类型都可以使用这种方式,并且内置的引用类型本身也使用了原型。
//查看sort是否是Array原型对象里的方法 alert(Array.prototype.sort); //sort就是Array 类型的原型方法:function sort() {[native code]} alert(String.prototype.substring); //substring就是String类型的原型方法:function substring() {[native code]} //内置引用类型的功能扩展 String.prototype.addstring = function () { //给 String 类型添加一个方法 return this+'被添加了!'; //this 代表调用的字符串 }; var box ='xixi'; alert(box.addstring()); //使用这个方法输出:xixi被添加了!
注意:尽管给原生的内置引用类型添加方法使用起来特别方便,但我们不推荐使用这种方法。因为它可能会导致命名冲突,不利于代码维护
原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题
//原型的缺点 function Box() {} Box.prototype = { constructor : Box, name : 'xixi', age : 100, family : ['哥哥','姐姐','妹妹'], //添加了一个数组属性 run : function () { return this.name+this.age+'运行中...' } }; var box1 = new Box(); alert(box1.family); //输出:哥哥,姐姐,妹妹 box1.family.push('弟弟'); //在第一个实例修改后引用类型,保持了共享,输出:哥哥,姐姐,妹妹,弟弟 alert(box1.family); var box2 = new Box(); alert(box2.family); //共享了box1添加后的引用类型的原型,输出:哥哥,姐姐,妹妹,弟弟
注意:数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化出的数据需要保留自己的特性,而不能共享。
7.构造函数+原型模式:可以解决构造传参和共享问题
//组合构造函数+原型模式 function Box(name, age) { //保持独立的用构造函数 this.name = name; this.age = age; this.family = ['哥哥','姐姐','妹妹']; } Box.prototype = { //保持共享的用原型 constructor : Box, run : function () { return this.name+this.age+'运行中...' } }; var box1 = new Box('xixi',100); alert(box1.family); //输出:哥哥,姐姐,妹妹 box1.family.push('弟弟'); //输出:哥哥,姐姐,妹妹,弟弟 alert(box1.family); var box2 = new Box('wang',200); alert(box2.family); //引用类型没有使用原型,所以没有共享。输出:哥哥,姐姐,妹妹
注意:这种混合模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法。
8.动态原型模式:不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在声明一个对象时,构造函数+原型部分让人感觉又很怪异,最好就是把构造函数和原型封装到一起。为了解决这个问题,我们可以使用动态原型模式。
//动态原型模式 function Box(name,age) { //将所有信息封装到函数体内 this.name = name; this.age = age; this.family = ['哥哥','姐姐','妹妹']; if (typeof this.run != 'function') { //if判断this.run是否存在 alert('原型初始化开始'); //测试用 Box.prototype.run = function() { return this.name + this.age + '运行中...' }; alert('原型初始化结束'); //测试用 } } //原型的初始化,只要第一次初始化,就可以了,没必要每次构造函数实例化的时候都初始化 var box1 = new Box('xixi',100); var box2 = new Box('wang',200);
当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会再初始化了。这样及得到了封装,又实现了原型方法共享,并且属性都保持独立。
注意:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系。
9.寄生构造函数:各种方式对象创建的方法,如果这几种方式都不能满足需求,可以使用一开始那种模式:寄生构造函数。
//寄生构造函数=工厂模式+构造函数 function Box(name,age) { var obj = new Object(); obj.name = name; obj.age = age; obj.run = function () { return this.name+this.age + '运行中...' }; return obj; } var box1 = new Box('xixi',100); alert(box1.run()); //xixi100运行中... var box2 = new Box('wang',200); alert(box2.run()); //wang100运行中...
寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使用之前所说的模式时,不建议使用此模式。
在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议直接String.prototype.addstring,可以通过寄生构造的方式添加。
function myString(string) { var str = new String(string); str.addstring = function () { return this + ',被添加了!'; }; return str; } var box = new myString('xixi'); //比直接在引用原型添加要繁琐好多 alert(box.addstring()); //xixi,被添加了!
10.稳妥构造函数:在一些安全的环境中,比如禁止使用this和new,这里的this是构造函数里不使用this,这里的new是在外部实例化构造函数时不使用 new。这种创建方式叫做稳妥构造函数。
//稳妥构造函数 function Box(name, age) { var obj = new Object(); obj.name = name; obj.age = age; obj.run = function () { return this.name + this.age + '运行中...' /直接打印参数即可 }; return obj; } var box1 = Box('xixi',100); //直接调用函数 alert(box1.run()); //xixi100运行中... var box2 = Box('wang',200); alert(box2.run()); //wang200运行中...
三.继承
1.继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。
//继承,通过原型链实现 function Box() { //被继承的函数叫做超类型(父类,基类) this.name = 'xixi'; } function Desk() { //继承的函数叫做子类型(子类,派生类) this.age = 100; } function Table() { this.level ='CCCCC'; }
2.原型链继承
//通过原型链继承,超类型实例化后的对象实例,赋值给子类型的原型属性 //new Box()会将Box构造里的信息和原型里的信息都交给Desk //Desk的原型,得到的是Box的构造+原型里的信息 Desk.prototype = new Box(); //Desc继承了Box,通过原型,形成链条 Table.prototype = new Desk(); //继续原型链继承 var desk = new Desk(); alert(desk.age); //打印:100 alert(desk.name); //得到被继承的属性打印:xixi var table = new Table(); alert(table.level); //打印:CCCCC alert(desk.age); //打印:100 alert(desk.name); //得到被继承的属性打印:xixi
3.就近原则继承
function Box() { //被继承的函数叫做超类型(父类,基类) //this.name = 'xixi'; } Box.prototype.name = 'wang'; //实例和原型中均包含name=wang function Desk() { //继承的函数叫做子类型(子类,派生类) this.age = 100; } Desk.prototype = new Box(); //Desc继承了Box,通过原型,形成链条 var desk = new Desk(); alert(desk.name); ////就近原则,实例里有,就返回,没有就去查找原型打印:wang
注意:以上原型链继承还缺少一环,那就是Obejct,所有的构造函数都继承自Obejct。而继承Object是自动完成的,并不需要程序员手动继承。
function Box() { //被继承的函数叫做超类型(父类,基类) this.name = 'xixi'; } function Desk() { //继承的函数叫做子类型(子类,派生类) this.age = 100; } Desk.prototype = new Box(); //Desc继承了Box,通过原型,形成链条 var box = new Box(); var desk = new Desk(); //子类型从属于自己或者他的超类型 alert(desk instanceof Object); //true alert(desk instanceof Desk); //true alert(desk instanceof Box); //true alert(box instanceof Desk); //false
在JavaScript里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的函数称为子类型(子类,派生类)。继承也有之前问题,比如字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。
为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。
4.借用构造函数继承(对象冒充继承)
//使用对象冒充继承 function Box(name, age) { this.name = name; this.age = age; this.family = ['哥哥','姐姐','妹妹']; //引用类型,放在构造里就不会被共享 } function Desk(name, age) { Box.call(this,name,age) //对象冒充,对象冒充只能继承构造里的信息 } var desk = new Desk('xixi',100); alert(desk.family); //哥哥,姐姐,妹妹 desk.family.push('弟弟'); alert(desk.family); //哥哥,姐姐,妹妹,弟弟 var desk2 = new Desk('xixi',100); //添加的新数据,只给desk,不给desk2 alert(desk2.family); //哥哥,姐姐,妹妹
5.组合继承(结合前两种):借用构造函数虽然解决了刚才两种问题,但没有原型,复用则无从谈起。所以,我们需要原型链+借用构造函数的模式,这种模式成为组合继承。
function Box(name,age) { this.name = name; this.age = age; this.family = ['哥哥','姐姐','妹妹']; } Box.prototype.run = function () { return this.name + this.age+ '运行中...'; }; //构造函数里的方法,放在构造里,每次实例化,都会分配一个内存地址,浪费,所以最好放在原型里,保证多次实例化只有一个地址 function Desk(name,age) { Box.call(this,name,age) //对象冒充 } Desk.prototype = new Box(); //原型链继承 var desk = new Desk('xixi',100); //xixi100运行中... alert(desk.run());
6.原型式继承:这种继承借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型。
//临时中转函数 function obj(o) { //o表示将要传递进入的一个对象 function F() {} //F构造是一个临时新建的对象,用来存储传递过来的对象 F.prototype = o; //将o对象实例赋值给F构造的原型对象 return new F(); //最后返回这个得到传递过来对象的对象实例 } //这是字面量的声明方式,相当于var box=new Box(); var box = { //字面量对象 name : 'xixi', age : 100, family : ['哥哥','姐姐','妹妹'] }; //box1就等于new F() var box1 = obj(box); //传递 alert(box1.name); //打印出:xixi alert(box1.family); //打印出:哥哥,姐姐,妹妹 box1.family.push('弟弟'); alert(box1.family); //打印出:哥哥,姐姐,妹妹,弟弟 var box2 = obj(box); //传递 alert(box2.name); //打印出:xixi alert(box2.family); //引用类型的属性共享了,打印出:哥哥,姐姐,妹妹,弟弟
7.寄生式继承=原型式+工厂模式:目的是为了封装创建对象的过程。
//临时中转函数 function obj(o) { function F() {} F.prototype = o; return new F(); } //寄生函数 function create(o) { //封装创建过程,通过寄生函数直接调用临时中转函数 var f = obj(o); f.run = function () { return this.name + '方法'; //同样,会共享引用 } return f; } var box = { name : 'xixi', age : 100, family : ['哥哥','姐姐','妹妹'] }; var box1 = create(box); alert(box1.run()); //打印出:xixi方法
组合式继承是JavaScript最常用的继承模式;但,组合式继承也有一点小问题,就是超类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部。
8.寄生组合继承:解决了两次调用的问题
//临时中转函数 function obj(o) { function F() {} F.prototype = o; return new F(); } //寄生函数 function create(box,desk) { var f = obj(box.prototype); f.constructor = desk; //调整原型构造指针 desk.prototype = f; } function Box(name,age) { this.name = name; this.age = age; } Box.prototype.run = function () { return this.name + this.age + '运行中...' } function Desk(name,age) { Box.call(this,name,age); //对象冒充,第二次调用 Box } //通过寄生组合继承来实现继承 create(Box,Desk); //这句话用来替代Desk.prototype = new Box(); var desk = new Desk('xixi',100); alert(desk.run()); //xixi100运行中... alert(desk.constructor); //调整原型构造指针后指针也指向自己本身Desk输出:function Desk(name,age) { // Box.call(this,name,age); //
浙公网安备 33010602011771号