A-A+

JavaScript之创建对象

2017年06月12日 默认文章 暂无评论

第六章 创建对象

1、工厂模式
用函数来封装以特定接口创建对象的细节。
  1. function CreatePerson(name, age, job) {
  2. var o = new Object();
  3. o.name = name;
  4. o.age = age;
  5. o.job = job;
  6. o.dream = function() {
  7. alert(this.age);
  8. };
  9. return o;
  10. }
  11. var preson1 = CreatePerson("秦始皇", "10000", "皇帝");
工厂模式解决了创建多个相似对象的问题,没解决对象识别的问题,即如何知道一个对象的类型。

2、构造函数模式 
ECMAScript 中的构造函数可用来创建特定类型的对象。可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
  1. function CreatePerson(name, age, job) {
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. this.dream = function() {
  6. alert(this.age);
  7. };
  8. }
  9. var preson1 = new CreatePerson("秦始皇", "10000", "皇帝");
  10. //注意和上面的代码对比下 1.无显式地创建对象,直接将属性和方法赋给this,无return语句。
  11. //    new 创建对象
  12. //alert(person1.constructor == Person); //true
    //alert(person2.constructor == Person); //true
     
构造函数通常以大写字母开头。
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方
将构造函数当作函数
构造函数与其他函数的唯一区别:调用方式不同。任何函数,只要通过new操作符调用,就可为构造函数。
构造函数的问题:每个方法都要在每个实例上重新创建一遍。
  1. function Person(name, age, job){
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. this.sayName = new Function("alert(this.name)"); // 与声明函数在逻辑上是等价的
  6. }
以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建 Function 新实例的机制仍然是相同的。因此,不同实例上的同名函数是不相等的,(各自为政)
  1. alert(person1.sayName == person2.sayName); //false
改进后为true:
  1. function CreatePerson(name, age, job) {
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. this.dream = dream;
  6. }
  7. function dream() {//全局作用域中的函数,却只能被某些个对象调用,名不副实。若方法比较多,则需更多的函数
  8. alert(this.age);
  9. }
  10. var preson1 = new CreatePerson("秦始皇", "10000", "皇帝");

原型模式
使用原型对象的好处是可以所有对象实例共享它所包含的属性和方法
  1. function Person() {}
  2. Person.prototype = {
  3. name:"吕不韦",
  4. age:29,
  5. sayName : function() {
  6. alert(this.name);
  7. }
  8. };
  9. var person1 = new Person();
  10. person1.sayName(); //"吕不韦"
  11. //alert(person1.sayName == person2.sayName); //true
函数的prototype属性指向函数的原型对象在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。Person.prototype. constructor 指向 Person。
与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。
这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
person1、person与构造函数没有直接的关系。
在所有实现中都无法访问到[[Prototype]],但可以通过 isPrototypeOf()方法来确定对象之间是否存在这种关系
  1. alert(Person.prototype.isPrototypeOf(person1)); //true
  2. alert(Person.prototype.isPrototypeOf(person2)); //true
  3. alert(Object.getPrototypeOf(person1) == Person.prototype); //true 
Object.getPrototypeOf()  返回[[Prototype]]的值。使用 Object.getPrototypeOf()可以方便地取得一个对象的原型
多个对象实例共享原型所保存的属性和方法的基本原理每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值
原型最初只包含 constructor 属性,而该属性也是共享的,因此可以通过对象实例访问。
对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性设置为 null,也只会在实例中设置这个属性,而不会恢复其指向原型的连接。不过,使用 delete 操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性.

hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法 (不要忘了它是从 Object 继承来的)只在给定属性存在于对象实例中时,才会返回 true。
for in
在单独使用时, in 操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中
  1. "age" in person1;
  2. true
  1. //确定属性是原型中的属性 false && true
  2. function hasPrototypeProperty(object, name){
  3. return !object.hasOwnProperty(name) && (name in object);
  4. }
在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性(即将[[Enumerable]]标记为 false 的属性)的实例属性也会在 for-in 循环中返回.

要取得对象上所有可枚举的实例属性,可以使用 ECMAScript 5 的 Object.keys()方法。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组.
  1. function Person() {}
  2. Person.prototype.name = "Nicholas";
  3. Person.prototype.age = 29;
  4. Person.prototype.job = "Software Engineer";
  5. Person.prototype.sayName = function() {
  6. alert(this.name);
  7. };
  8. var keys = Object.keys(Person.prototype);
  9. alert(keys); //"name,age,job,sayName"
  10. var p1 = new Person();
  11. p1.name = "Rob";
  12. p1.age = 31;
  13. var p1keys = Object.keys(p1);
  14. alert(p1keys); //"name,age"
要得到所有实例属性,无论它是否可枚举,都可以使用 Object.getOwnPropertyNames()方法
  1. var keys = Object.getOwnPropertyNames(Person.prototype);
  2. alert(keys); //"constructor,name,age,job,sayName"//结果中包含了不可枚举的 constructor 属性
简写的代码中:将 Person.prototype 设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外: constructor 属性不再指向 Person 了。
每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。而我们在这里使用的语法,本质上完全重写了默认的 prototype 对象,因此 constructor 属性也就变成了新对象的 constructor 属性 (指向 Object 构造函数),不再指向 Person 函数。此时,尽管 instanceof操作符还能返回正确的结果,但通过 constructor 已经无法确定对象的类型了。
  1. var friend = new Person();
  2. alert(friend instanceof Object); //true
  3. alert(friend instanceof Person); //true
  4. alert(friend.constructor == Person); //false !!!!!!!!1
  5. alert(friend.constructor == Object); //true !!!!!!!!!!!!11
  
  1. function Person() {}
  2. Person.prototype = {
  3. constructor : Person,
  4. name:"吕不韦",
  5. age:29,
  6. sayName : function() {
  7. alert(this.name);
  8. }
  9. };
  10. var person1 = new Person();
  11. person1.sayName(); //"吕不韦"
以这种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true。默认情况下,原生的 constructor 属性是不可枚举的。
  1. //重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
  2. Object.defineProperty(Person.prototype, "constructor", {
  3.     enumerable: false,
  4.     value: Person
  5. });
由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也照样如此
原型的动态性
  1. var friend = new Person();
  2. Person.prototype.sayHi = function(){
  3.     alert("hi");
  4. };
  5. friend.sayHi(); //"hi"(没有问题!)即使 person 实例是在添加新方法之前创建的,但它仍然可以访问这个新方法。原因可以归结为实例与原型之间的松散连接关系,因为实例与原型之间的连接只不过是一个指针,而非一个副本
实例中的指针仅指向原型,而不指向构造函数。
重写原型对象
调用构造函数时会为实例添加一个指向最初原型的[[Prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。
  1. function Person(){
  2. }
  3. var friend = new Person();
  4. Person.prototype = {
  5. constructor: Person,
  6. name : "Nicholas",
  7. age : 29,
  8. job : "Software Engineer",
  9. sayName : function () {
  10. alert(this.name);
  11. }
  12. };
  13. friend.sayName(); //error 注意顺序

原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的。所有原生引用类型(Object、 Array、 String,等等)都在其构造函数的原型上定义了方法。
原型的缺点:
首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值虽然这会在某种程度上带来一些不方便,但还不是原型的最大问题。原型模式的最大问题是由其共享的本性所导致的。对于包含引用类型值的属性来说,问题就比较突出了。实例一般都是要有属于自己的全部属性的。实例一般都是要有属于自己的全部属性的。而这个问题正是我们很少看到有人单独使用原型模式的原因。
  1. function Person() {}
  2. Person.prototype = {
  3. constructor: Person,
  4. name: "Nicholas",
  5. age: 29,
  6. job: "Software Engineer",
  7. friends: ["Shelby", "Court"],
  8. sayName: function() {
  9. alert(this.name);
  10. }
  11. };
  12. var person1 = new Person();
  13. var person2 = new Person();
  14. person1.friends.push("Van");
  15. alert(person1.friends); //"Shelby,Court,Van"
  16. alert(person2.friends); //"Shelby,Court,Van"
  17. alert(person1.friends === person2.friends); //true

组合使用构造函数模式和原型模式

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数。
  1. function Person(name, age, job) {
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. this.friends = ["Shelby", "Court"];
  6. }
  7. Person.prototype = {
  8. constructor: Person,
  9. sayName: function() {
  10. alert(this.name);
  11. }
  12. }
  13. var person1 = new Person("Nicholas", 29, "Software Engineer");
  14. var person2 = new Person("Greg", 27, "Doctor");
  15. person1.friends.push("Van");
  16. alert(person1.friends); //"Shelby,Count,Van"
  17. alert(person2.friends); //"Shelby,Count"
  18. alert(person1.friends === person2.friends); //false
  19. alert(person1.sayName === person2.sayName); //true
——————————————————————————————————————
动态原型模式
把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
  1. function Person(name, age, job) {
  2. //属性
  3. this.name = name;
  4. this.age = age;
  5. this.job = job;
  6. //方法
  7. if (typeof this.sayName != "function") {
  8. Person.prototype.sayName = function() {
  9. alert(this.name);
  10. };
  11. }
  12. }
  13. var friend = new Person("Nicholas", 29, "Software Engineer");
  14. friend.sayName();
  15. //只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不需要再做什么修改了
————————————————————————————————————————————————
寄生构造函数模式
创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
  1. function Person(name, age, job) {
  2. var o = new Object();
  3. o.name = name;
  4. o.age = age;
  5. o.job = job;
  6. o.sayName = function() {
  7. alert(this.name);
  8. };
  9. return o;
  10. }
  11. var friend = new Person("Nicholas", 29, "Software Engineer");
  12. friend.sayName(); //"Nicholas"
除了使用 new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个 return 语句,可以重写调用构造函数时返回的值
——————————————————————————————
稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其他应用程序(如 Mashup程序)改动时使用
  1. function Person(name, age, job) {
  2. //创建要返回的对象
  3. var o = new Object(); //可以在这里定义私有变量和函数
  4. //添加方法
  5. o.sayName = function() {
  6. alert(name);
  7. };
  8. //返回对象
  9. return o;
  10. } //var friend = Person("Nicholas", 29, "Software Engineer");
    //friend.sayName(); //"Nicholas"
即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据
标签:

给我留言

Copyright © 花未全开月未圆 保留所有权利.   Theme  Ality 海外

用户登录