创建对象
var Box = new Object();//创建对象 Box.name = 'Lee'; 添加属性 Box.age = 100; Box.run = function(){ return this.name + this.age + "运行中"; this 表示当前作用域下对象 } this 表示new Object()实例出来的那个对象 alert(Box.run());
这就是创建对象最基本的方法,但是有个缺点,想创建一个类似的对象,就会产生大量的代码。
工厂模式
为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。
createObject(name,age){ var obj = new Object(); obj.name = name; obj.age = age; obj.run = (){ this.name+this.age+"岁年龄"; } return obj; } var Box1 = createObject('Lee',20); var Box2 = createObject('Jack',30); console.log(Box1.run()); console.log(Box2.run());
工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法搞清他们到底是哪个对象的实例。
构造函数
ECAMScript中采用构造函数(构造方法)可用来创建特定的对象。类似于Object对象。
构造函数 Box(name,1)">this.name = name; this.age = age; this.run = this.age +"运行中..."; }; }; var Box1 = new Box('Lee',100var Box2 = new Box('Jack',200); console.log(Box1.run()); console.log(Box2.run());
使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题是,这里并没有new Object(),为什么可以实例化Box(),这个是哪里来的呢?
使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:
- 1.构造函数方法没有显示的创建对象(new Objectt()),但它在后台自动var obj = new Object();
- 2.直接将属性和方法赋值给this对象,this就相当于obj;
- 3.没有return语句,不需要返回对象引用,它是在后台自动返回的。
Dack(name,1)">var Box3 = new Dack('MrLee',300); console.log(Box1.run()); console.log(Box2.run()); console.log(Box3.run()); 解决了对象识别问题 console.log(Box1 instanceof Box); true console.log(Box2 true console.log(Box3 false console.log(Box3 instanceof Dack);true
对象冒充:使用call()方法
var o= Object(); Box.call(o,'Lee',1)">); console.log(o.run());
看下一个问题:
实例化后地址为1 实例化后地址为2 console.log(Box1.name == Box2.name); true console.log(Box1.age == Box2.age); true console.log(Box1.run() == Box2.run());true //构造函数体内的方法的值是相当的 console.log(Box1.run == Box2.run); false //因为他们比较的是引用地址
上面的代码运行说明引用地址不一样,那么构造函数内的方法也可以这样写:
new Function("return this.name + this.age +'运行'")
如何让他们的引用地址一样,下面代码:
this.run = run; }; run(){ this.name +this.age+"运行中..."; } 实例化后地址为2 console.log(Box1.run == Box2.run); true //因为他们比较的是引用地址
把构造函数内部的方法通过全局来实现引用地址一致。
虽然使用了全局函数run()来解决了保证引用地址一致的问题,但是这种方式又带来了一个新的问题,全局中的this在对象调用的时候是Box本身,而当普通函数调用的时候,this又代表window。
原型
Box(){ 构造函数函数体内什么都没有,这里如有过,叫做实例属性,实例方法 } Box.prototype.name="Lee"; 原型属性 Box.prototype.age=100; Box.prototype.run=function(){ 原型方法 ; } var Box1= Box(); var Box2= Box(); console.log(Box1.run == Box2.run); true console.log(Box1.prototype);这个属性是一个对象,访问不到 console.log(Box1.__proto__);这个属性是一个指针指向prototype原型对象。
如果是实例方法,不同的实例化,他们的方法地址是不一样的,是唯一的。
如果是原型方法,那么他们的地址是共享的,大家都一样。
PS:IE浏览器在脚本访问__proto__会不能识别,火狐和谷歌及其他某些浏览器能识别。虽然可以输出,但是无法获取内部信息。
判断一个对象是否指向该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。
原型模式的执行流程:
如何判断属性时构造函数的实例里,还是原型里?可以使用hasOwnProperty()函数来验证:
console.log(Box1.hasOwnProperty('name'));如果实例里有返回true,否则返回false
如何判断属性是原型里的?
Box(){ } Box.prototype.name="Lee"; isProperty(object,property){ return !object.hasOwnProperty(property) && (property in object); } Box(); console.log(isProperty(Box1,'name'));
为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使用字面量的方式
使用字面量的方式创建原型对象,这里的{}就是对象,是object,new Object就相当于{}
字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反。
这里的Box.prototype={}就相当于创建了一个新的对象,所以 Box.constructor是Object。
如何让Box.constructor指向Box呢?
{ constructor:Box,直接强制指向即可 name:'Lee'true
重写原型,不会保留之前原型的任何信息,把原来的原型对象和构造函数对象的实例切断了。
查看sort是否是Array原型对象里的方法
alert(Array.prototype.sort);
在如下 判断String原型对象里是否有substring方法
alert(String.prototype.substring);
注:原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:
Box(){} Box.prototype=],1)">; } }; Box(); console.log(Box1.family); '哥哥','妹妹' Box1.family.push("弟弟"); console.log(Box1.family); Box(); console.log(Box2.family);代码可以看出,在第一个实例修改后引用类型,保持了共享。Box2.family共享了Box1添加后的引用类型的原型。function Box(name,age){ 保持独立的用构造函数 this.name=name; this.age=age; this.family=['哥哥',1)">]; } Box.prototype={ 保持共享的用原型 constructor:Box,1)">); console.log(Box1.family); ); console.log(Box2.family); 502@动态原型模式把原型封装到构造函数里 ]; console.log('原型初始化开始'); 执行了两次 Box.prototype.run=; } console.log('原型初始化结束'); 执行了两次 原型的初始化,只要第一次初始化就可以了,没必要每次构造函数实例化的时候都初始化]; if(typeof this.run!='function'){ console.log('原型初始化开始'); 执行了一次次 Box.prototype.run=(){ ; }; console.log('原型初始化结束'); 执行了一次 } } 502@寄生构造函数
如果以上都不能满足需要,可以使用一下寄生构造函数。
寄生构造函数=工厂模式+构造函数Object(); obj.name=name; obj.age=age; obj.run=502@稳妥构造函数在一些安全的环境中,比如禁止使用this和new,这里的this是构造函数里不使用的this,这里的new是在外部实例化构造函数时不使用new。这种创建方式叫做稳妥构造函数。
继承
继承是面向对象中一个比较核心的概念。其它正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。
Box(){ this.name="Lee" Jack(){ this.age=100; } Jack.prototype = Box(); var jack = Jack(); console.log(jack.name); Lee为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术解决这两个问题。
Box(name){ name; } Box.prototype.age=200; Jack(name){ Box.call(thisnew Jack('Lee'); console.log(jack.name);Lee console.log(jack.age);undefined但是上面的代码可以看出,对象冒充没有继承原型链上的age属性。所以要继承Box的原型,就出现下面的组合继承。
组合继承即是原型链+借用构造函数的模式200
@H_858_502@原型式继承
临时中转函数 obj(o){ F(){}; F.prototype = o; F(); } 这是字面量的声明方式,相当于var Box = new Box(); var Box=] }; var Box1 = obj(Box); console.log(Box1.family);Box1.family.push('弟弟'var Box2 = obj(Box); console.log(Box2.family);502@寄生式继承
把原型式与工厂模式结合起来。F(); } 寄生函数 create(o){ var f=obj(o); f.run=this.name+"方法" f; } create(Box); console.log(Box1.run());@H_858_502@寄生组合继承
create(Box,desk){ obj(Box.prototype); f.constructor=desk; 调整原型构造指针 desk.prototype=f; } age; } Box.prototype.run= Desk(name,age){ Box.call(this,name,age); 对象冒充 通过寄生组合继承来实现继承 create(Box,Desk); 这句话用来替代Desk.prototype = new Box(); var desk = new Desk('Lee',1)">); console.log(desk.run());
猜你在找的JavaScript相关文章