//1 新建对象 var box = new Object(); box.name = "lee"; box.age = 100; box.run = function(){ return this.name+this.age+"运行中"; } alert(box.run()); //lee100运行中 //缺点:不能重复,如果再新建一个对象还要写大量重复代码 //2 工厂模式 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("lee",100); var box2 = box("dang",200); alert(box1.run()); alert(box2.run()); //缺点:无法搞清实例是哪个对象的实例 alert(typeof box1); //object alert(box1 instanceof Object);//true,无法识别是哪个对象的实例 alert(box1 instanceof box); //false,box是function alert(typeof box); //function //3 构造函数创建对象:解决上述问题,不知道是哪个对象的实例 function Box(name,age){ //构造函数名首字母大写(非强制,为了区分),不用返回对象 this.name = name; this.age = age; this.run = function(){ return this.name+this.age+"运行中"; }; } var box1 = new Box("lee",100); //构造函数必须通过new来调用 var box2 = new Box("lee",100); alert(box1.run()); alert(typeof box1);//object alert(box1 instanceof Box); //true,证明是Box的实例 //缺点:引用地址的不一致 alert(box1.name == box2.name); //true alert(box1.run() == box2.run()); //true 方法的值一样,因为传参一直 alert(box1.run == box2.run); //false 方法其实是一种引用地址, //解决办法:使用了全局的函数run()来解决了保证引用地址一致,单没什么必要,了解就行 // 可以使用原型 function Box(name, age) { this.name = name; this.age = age; this.run = run; } function run() { //通过外面调用,保证引用地址一致 return this.name + this.age + ‘运行中...‘; } //原型 //4 构造函数创建原型 //我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype通过调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。 function Box(){} Box.prototype.name = "lee"; Box.prototype.age = 100; Box.prototype.run = function(){ return this.name + this.age + ‘运行中...‘; } var box1 = new Box(); var box2 = new Box(); alert(box1.run == box2.run); //true,方法的引用地址保持一致 alert(Box.prototype.isPrototypeOf(box1));//true,判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。 //在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。__proto__属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor。通过这两个属性,就可以访问到原型里的属性和方法了。 alert(box1.__proto__); //object,但IE浏览器在脚本访问__proto__会不能识别 alert(box1.constructor); //构造函数的函数体返回 /* 原型模式的执行流程:就近原则 1.先查找构造函数实例里的属性或方法,如果有,立刻返回; 2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;*/ var box = new Box(); alert(box.name); //lee box.name = "jack"; alert(box.name); //jack delete box.name; alert(box.name); //lee,可以删除构造函数里的属性 //如何判断属性是在构造函数的实例里,还是在原型里?可以使用hasOwnProperty()函数来验证 alert(box.hasOwnProperty("name")); //实例里有true,没有后返回false //in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。 alert("name" in box); //5 为了让属性和方法更好的体现封装的效果,减少不必要的输入,原型的创建可以使用字面量方式 function Box(){}; Box.prototype={ name:"lee", age:100, run:function(){ return this.name + this.age + ‘运行中...‘; } } //使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区别,字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反。 alert(box.constructor == Box); //字面量方式,返回false,否则,true alert(box.constructor == Object); //字面量方式,返回true,否则,false //如果想让字面量方式的constructor指向实例对象,那么可以这么做: Box.prototype = { constructor : Box, //直接强制指向即可 }; //PS:字面量方式为什么constructor会指向Object?因为Box.prototype={};这种写法其实就是创建了一个新对象。而每创建一个函数,就会同时创建它prototype,这个对象也会自动获取constructor属性。所以,新对象的constructor重写了Box原来的constructor,因此会指向新对象,那个新对象没有指定构造函数,那么就默认为Object。 //原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。 function Box() {}; Box.prototype = { //原型被重写了 constructor : Box, name : ‘Lee‘, age : 100, run : function () { return this.name + this.age + ‘运行中...‘; } }; Box.prototype = { age = 200 }; var box = new Box(); //在这里声明 alert(box.run()); //box只是最初声明的原型 //原型对象不仅仅可以在自定义对象的情况下使用,而ECMAScript内置的引用类型都可以使用这种方式,并且内置的引用类型本身也使用了原型。 alert(Array.prototype.sort); //sort就是Array类型的原型方法 alert(String.prototype.substring); //substring就是String类型的原型方法 String.prototype.addstring = function () { //给String类型添加一个方法 return this + ‘,被添加了!‘; //this代表调用的字符串 }; alert(‘Lee‘.addstring()); //使用这个方法 ////PS:尽管给原生的内置引用类型添加方法使用起来特别方便,但我们不推荐使用这种方法。因为它可能会导致命名冲突,不利于代码维护。 //原型的缺点:不能传参和共享(共享也是原型的最大优点) //原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题: function Box() {}; Box.prototype = { constructor : Box, name : ‘Lee‘, age : 100, family : [‘父亲‘, ‘母亲‘, ‘妹妹‘], //添加了一个数组属性 run : function () { return this.name + this.age + this.family; } }; var box1 = new Box(); box1.family.push(‘哥哥‘); //在实例中添加‘哥哥‘ alert(box1.run()); var box2 = new Box(); alert(box2.run()); //共享带来的麻烦,也有‘哥哥‘了 //6 构造函数+原型模式 为了解决传参和共享的问题 function Box(name,age){ this.name = name; this.age = age; this.family = ["baba","mama","meimei"]; }; Box.prototype={ constructor:Box, run:function(){ return this.name+this.age+this.family; } }; var box1 = new Box(); box1.family.push("gege"); alert(box1.family); var box2 = new Box(); alert(box2.family); 这种方法解决了传参和引用共享的大难题,是比较好的方法 //7 动态原型模式,相比上边的方法封装性好一点,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系。 function Box(name,age){ this.name = name; this.age = age; this.family = ["baba","mama","meimei"]; if(typeof this.run != "function"){ Box.prototype.run = function(){ return this.name+this.age+"运行中"; }; } } var box1 = new Box(); box1.family.push("gege"); alert(box1.family); var box2 = new Box(); alert(box2.family); //8 如果上述都不能满足条件,可以使用寄生构造函数 //其实就是工厂模式+构造函数模式,比较通用,但不能确定对象关系,所以,在可以使用之前所说的模式时,不建议使用此模式。 function Box(name,age){ var obj = new Object(); obj.name = name; obj.age = age; obj.run = function(){ return this.name+this.age+"运行中"; } return obj; } //在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议直接String.prototype.addstring,可以通过寄生构造的方式添加。 function myString(string){ var str = new String(string); str.addstring = function(){ return this+‘,被添加了!‘; }; return str; } var box = new myString("lee"); alert(box.addstring()); //在一些安全的环境中,比如禁止使用this和new,这里的this是构造函数里不使用this,这里的new是在外部实例化构造函数时不使用new。这种创建方式叫做稳妥构造函数。 function Box(name,age){ var obj = new Object(); obj.run = function(){ return name+age+"运行中"; }; return obj; } var box = Box("lee",100); //不使用new alert(box.run()); //稳妥构造函数和寄生相似
时间: 2024-10-11 17:17:44