面向对象的程序设计(创建对象——JavaScript高级程序设计(一))

面向对象的程序设计

6.1 理解对象

  • 创建对象

    var person = new Object
    person.name = "Tom"
    person.age = 29
    person.job = "SOftware Engineer"
    
    person.sayName = function(){
          
          
    	alert(this.name)
    }
    

6.1.1 属性类型

  1. 数据属性

    • Configurable

    • Enumerate

    • Writeable

    • Value

      var person = {
              
              }
      Object.defineProperty(person, "name", {
              
              
          writeable: false
          value: "Nicholas"
      })
      
      alert(person.name) // "Nicholas"
      person.name = "Greg"
      alert(person.name) // "Nicholas"
      
  2. 访问器属性

    • Configurable

    • Enumerable

    • Get

    • Set

      var book = {
              
              
          // _year 前面的下划线是一种常用的记号,用来表示只能通过对象方法访问的属性
      	_year = 2004,
      	edition: 1
      }
      
      Object.defineProperty(book, "year", {
              
              
      	get: function(){
              
              
      		return thie._year
      	},
      	set: function(){
              
              
      		if (newValue > 2004) {
              
              
      		this._year = newValue
      		this.edition += newValue - 2004 
      		}
      	}
      })
      
      book.year = 2005
      alert(book.edition); // 2
      

      getter函数返回_year的值, setter函数通过计算确定正确的版本.因此,把year属性修改为2005会导致 _year变成2005, 而edition变成2。

      这是使用访问器属性的常见方式,既设置一个属性的值会导致其他属性发生变化。

      不一定非要同时指定getter和setter,只指定getter意味着属性是不能写的,尝试写入属性会被忽略。

      6.1.2 定义多个属性

      由于为对象定义多个属性的可能性很大,可以使用 Object.defineProperties( )

      	var book = {
              
              };
              
      	Object.defineProperties(book, {
              
              
                  _year: {
              
              
                      value: 2004
                  },
                  
                  edition: {
              
              
                      value: 1
                  },
                  
                  year: {
              
                          
                      get: function(){
              
              
                          return this._year;
                      },
                      
                      set: function(newValue){
              
              
                          if (newValue > 2004) {
              
              
                              this._year = newValue;
                              this.edition += newValue - 2004;
                          }                  
                      }            
                  }        
              });
                 
              book.year = 2005;
              alert(book.edition);   //2
      

      6.1.3 读取属性的特性

      使用 Object.getOwnPropertyDescriptor( )方法,可以取得给定属性的描述符。

      接受两个参数:

      1. 属性所在的对象
      2. 读取其描述符的属性名称

      返回值:一个对象

       var book = {
              
              };
              
              Object.defineProperties(book, {
              
              
                  _year: {
              
              
                      value: 2004
                  },
                  
                  edition: {
              
              
                      value: 1
                  },
                  
                  year: {
              
                          
                      get: function(){
              
              
                          return this._year;
                      },
                      
                      set: function(newValue){
              
              
                          if (newValue > 2004) {
              
              
                              this._year = newValue;
                              this.edition += newValue - 2004;
                          }                  
                      }            
                  }        
              });
                 
              var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
              alert(descriptor.value);          //2004
              alert(descriptor.configurable);   //false
              alert(typeof descriptor.get);     //"undefined"
              
              var descriptor = Object.getOwnPropertyDescriptor(book, "year");
              alert(descriptor.value);          //undefined
              alert(descriptor.enumerable);     //false
              alert(typeof descriptor.get);     //"function"
      
      

      对于数据属性_year, value等于最初的值,configurable是false, 而get等于undefined

      对于访问其属性yearvalue等于undefinedenumerateblefalse,而get是一个指向getter函数的指针。

6.2 创建对象

虽然object构造函数和对象字面量均可以用来创建对象,但这些方式有个很明显的确定:

使用同一个接口创建很多对象,会产生大量的重复代码。

为了解决这个问题,热门尝试使用工厂模式这一种变体。

6.2.1 工厂模式

  • 套路:通过工厂函数(返回一个对象的函数)动态创建对象并返回

  • 使用场景:需要创建多个对象

  • 缺点:对象没有一个具体的类型,都是Object类型

 function createPerson(name, age, job){
    
    
            var o = new Object();
            o.name = name;
            o.age = age;
            o.job = job;
            o.sayName = function(){
    
    
                alert(this.name);
            };    
            return o;
        }
        
        var person1 = createPerson("Nicholas", 29, "Software Engineer");
        var person2 = createPerson("Greg", 27, "Doctor");
        
        person1.sayName();   //"Nicholas"
        person2.sayName();   //"Greg"

6.2.2 构造函数模式

构造函数可以用来创建特定类型的对象

ObjectArray 这样的原生构造函数,爱运行是会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。

 function Person(name, age, job){
    
    
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = function(){
    
    
                alert(this.name);
            };    
        }
        
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");
        
        person1.sayName();   //"Nicholas"
        person2.sayName();   //"Greg"
        
        alert(person1 instanceof Object);  //true
        alert(person1 instanceof Person);  //true
        alert(person2 instanceof Object);  //true
        alert(person2 instanceof Person);  //true
        
        alert(person1.constructor == Person);  //true
        alert(person2.constructor == Person);  //true
        
        alert(person1.sayName == person2.sayName);  //false        
  • 没有显示的创建对象
  • 直接将属性和方法赋值给了this对象
  • 没有 rerturn 语句

要创建Person的新实例,必须使用 new操作符。以这种方式调用操作符实际上会经历一下四个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋值给新对象(因此this就指向了这个新对象)
  3. 执行构造函数中的代码
  4. 返回新对象

在前面的例子中,person1person2 分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person,如下所示:

alert(person1.constructor == Person) // true
alert(person2.constructor == Person) // true

我们在这个例子中创建的所有对象既是Object的实例,也是Person的实例,这一点可以通过instanceof 操作符来验证:

alert(person1 instanceof Object) // true
alert(person1 instanceof Person) // true
alert(person2 instanceof Object) // true
alert(person2 instanceof Person) // true

创建自定义构造函数意味着将来可以将它的实例标识为一种特定的类型,而这正是构造函数胜过工厂模式的地方。

  1. 将构造函数当做函数
function Person(name, age, job){
    
    
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = function(){
    
    
                alert(this.name);
            };
        }
        // 当做构造函数使用
        var person = new Person("Nicholas", 29, "Software Engineer");
        person.sayName();   //"Nicholas"
        // 作为普通函数调用
        Person("Greg", 27, "Doctor");  //adds to window
        window.sayName();   //"Greg"
        // 在另一个对象的作用域中调用
        var o = new Object();
        Person.call(o, "Kristen", 25, "Nurse");
        o.sayName();    //"Kristen"

可以看到,不使用new操作符来调用函数Person()会出现什么结果呢:

属性和方法都被添加给了window对象了。

当在全局作用域中调用一个函数时,this对象总是指向Global对象(在浏览器中就是Window对象)

也可以使用call()( 或者apply())在某个特殊对象的作用域中来调用Person()函数。这里是在对象o的作用域中调用大,因此调用后o就拥有了所有的属性和sayName()方法。

  1. 构造函数的问题
  • 每个方法都要在每个实例中重新创建一遍

    ​ 在前面的例子中,person1person2都有一个名为sayName()的方法,但两个方法不是同一个Function的实例。(函数就是对象,因此没定义一个函数,也就是实例化了一个对象。

    alert(person1.sayName === person2.sayName) // false
    

    然而,创建两个完成同样任务的Function实例的确没有必要。况且有this对象在,根本就不用在执行代码前就把函数绑定到特定对象上面。 因此,我们可以把函数定义转移到构造函数外部来解决这个问题:

    function Person(name, age, job){
          
          
                this.name = name;
                this.age = age;
                this.job = job;
                this.sayName = sayName;
            }
            
            function sayName(){
          
          
                alert(this.name);
            }
            
            var person1 = new Person("Nicholas", 29, "Software Engineer");
            var person2 = new Person("Greg", 27, "Doctor");
            
            person1.sayName();   //"Nicholas"
            person2.sayName();   //"Greg"
            
            alert(person1 instanceof Object);  //true
            alert(person1 instanceof Person);  //true
            alert(person2 instanceof Object);  //true
            alert(person2 instanceof Person);  //true
            
            alert(person1.constructor == Person);  //true
            alert(person2.constructor == Person);  //true
            
            alert(person1.sayName == person2.sayName);  //true    
    

6.2.3 原型模式

我们创建的每一个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

原型对象的好处:可以让所有对象实例共享它所包含的属性和方法。

function Person(){
    
    
        }
        
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function(){
    
    
            alert(this.name);
        };
        
        var person1 = new Person();
        person1.sayName();   //"Nicholas"
        
        var person2 = new Person();
        person2.sayName();   //"Nicholas"
      
        alert(person1.sayName == person2.sayName);  //true
        
        alert(Person.prototype.isPrototypeOf(person1));  //true
        alert(Person.prototype.isPrototypeOf(person2));  //true
        
        //only works if Object.getPrototypeOf() is available
        if (Object.getPrototypeOf){
    
    
            alert(Object.getPrototypeOf(person1) == Person.prototype);  //true
            alert(Object.getPrototypeOf(person1).name);  //"Nicholas"
        }
  • 使用hasOwnProperty()方法可以检测一个属性是否存在于实例中,还是存在于原型中。只有在给定属性存在于实例对象本身,才会返回true。

  • 原型与 in 操作符

    1. 单独使用(in 操作符会在通过对象能够访问给定属性时返回true, 无论该属性存在于实例中还是原型中)
    2. for-in 循环中使用
  • Object.keys( ): 取得对象上所有可枚举的实例属性

    1. 接受一个对象作为参数

    2. 返回一个包含所有可枚举属性的字符串数组

      		function Person(){
              
              
              }
              
              Person.prototype.name = "Nicholas";
              Person.prototype.age = 29;
              Person.prototype.job = "Software Engineer";
              Person.prototype.sayName = function(){
              
              
                  alert(this.name);
              };
              
              var keys = Object.keys(Person.prototype);
              alert(keys);   // "name,age,job,sayName"
      
      		var p1 = new Person()
              p1.name = "rob"
              p1.age = 31
              var p1keys = Object.keys(p1)
              alert(p1keys) // "name, age"
      

      这里, 变量keys将保存一个数组,数组中的是字符串"name""age""job""sayName"

      如果想要得到所有的实例属性,无论它是否可枚举,都可以使用Object.getOwnPropertyNames( )方法。

      var keys = Object.getOwnPropertyNames(Person.prototype)
      alert(keys) // "constructor, name, age, job, sayName"
      

      注意结果包含了不可枚举的constructor属性

    • 更简单的原型语法

      我们大概注意到了,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。

      Person.prototype () {
              
              
      	name: "Nicholas",
      	age: 29,
      	job: "Software Engineer",
      	sayName: function () {
              
              
      		alert(this.name)
      	}
      }
      

      在上面的代码中,我们将Person.prototype 设置为等于一个以对象字面量形式创建的新对象,最终结果相同,但有一个例外:constructor 属性不再指向Person了。前面介绍过,每创建一个函数,就会同时创建它的prototype对象,这个对象就会自动获得constructor属性。

      而我们在这里的方法,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。

      var friend = new Person()
      
      alert(friend instanceof Object) // true
      alert(friend instanceof Person) // true
      alert(friend.constructor === Person) // false
      alert(friend.constructor === Objectt) // true
      
      

      如果constructor的值真的很重要,可以特意将它设置会合适的值。

      function Person () {
              
              
      }
      
      Person.prototype = {
              
              
      	constructor: Person,
      	name:"Nicholas",
      	age: 29,
      	job: "Software Engineer",
      	sayName: function () {
              
              
      		alert(this.name)
      	}
      	
      }
      

      以上代码特意包含了一个constructor属性,并将它设置为Person,从而确保了通过该属性能够访问到适当的值。

      注意这种方式会使constructor属性的[[Enumberable]]特性被设置为true。默认情况下,原生的constructor属性是不可枚举的。因此,可以试一试Object.defineProperty( )

      function Person () {
              
              
      }
      
      Person.prototype = {
              
              
      	constructor: Person,
      	name:"Nicholas",
      	age: 29,
      	job: "Software Engineer",
      	sayName: function () {
              
              
      		alert(this.name)
      	}
      	
      }
      
      // 重设构造函数
      Object.defineProperty(Person.prototype, "constructor", {
              
              
      	enumberable: false,
      	value: Person
      })
      
  • 原型模式的缺点

    1. 省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。

    2. 原型中的所有属性被很多实例共享,这种共享对于函数非常合适,对于基本值的属性也说得过去。然而对于包含引用类型值得属性来说,问题就比较突出了。

      		function Person(){
              
              
              }
              
              Person.prototype = {
              
              
                  constructor: Person,
                  name : "Nicholas",
                  age : 29,
                  job : "Software Engineer",
                  friends : ["Shelby", "Court"],
                  sayName : function () {
              
              
                      alert(this.name);
                  }
              };
              
              var person1 = new Person();
              var person2 = new Person();
              
              person1.friends.push("Van");
              
              alert(person1.friends);    //"Shelby,Court,Van"
              alert(person2.friends);    //"Shelby,Court,Van"
              alert(person1.friends === person2.friends);  //true
      

      在此,Person.prototype 对象有一个名为friends的属性, 该属性包含一个字符串数组。然后创建了 Person的两个实例。接着,又修改了person1.friends引用的数组,向数组中添加了一个字符串。由于friends存在于Person.prototype而非person1中,所以刚刚提到的修改也会通过person2.friends(与person1.friends指向同一个数组)反映出来。

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

创建自定义类型的最常见模式就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节约了内存。

这种混合模式还支持向构造函数传递参数。

function Person (name, age, job) {
    
    
    this.name = name
    this.age = age 
    this.job = job
    this.friends = ["Shelby", "Court"]
}

Person.prototype = {
    
    
    constructor: Person,
    sayName: function() {
    
    
        alert(this.name)
    }
}

var person1 = new Person("Nicholas", 29, "Software Engineer")
var person2 = new Person("Greg", 27, "Doctor")

person1.friends.push("Van")
alert(person1.friends) // "Shelby, Court, Van"
alert(person2.friends) // "Shelby, Court"
alert(person1.friends === person2.friends) // false
alert(person1.sayName === person2.sayName) // true

在这个例子中,实例属性都是在不同的构造函数中定义的,而由所有实例共享的属性constructor 和方法 sayName()则是在原型中定义的。而修改了person1.friends (向其中添加了一个新字符串),并不会影响person2.friends,因为它们分别引用了不同的数组。

猜你喜欢

转载自blog.csdn.net/qq_27575925/article/details/111658586