zoukankan      html  css  js  c++  java
  • JS Pro 面向对象的程序设计 对象

    面向对象语言都有一个标志,就是他们的类概念,通过类,可以创建任意多个具有相同属性和方法的对象。但ECMAScript里头,,没有类的概念,所以它的对象也和基于类的语言中的对象有所不同。

    在ECMA-262中定义的对象为:无序属性的集合,其属性可为基本值,对象或者函数。所以,Javascript中的对象,其实就是一组“名值”组合。

    最基本的创建对象的方法:new Object()。先创建一个Object对象,然后给它添加属性。这种方法有一个很大的问题,就是创建属性时,要重复输入对象名称(person)

    var person = new Object();
    person.name = “Nicholas”;
    person.age = 29;
    person.job = “Software Engineer”;
    person.sayName = function(){
      alert(this.name);
    };

    工厂模式:这种模式抽象的创建具体对象的过程。由于ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节:

    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”);

    但工厂模式无法解决对象识别问题(即怎样知道一个对象的类型)

    构造函数模式:ECMAScript中的构造函数可以用来创建特定类型的对象。通过构造函数重写刚才的例子:

    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”);

    构造函数与工厂模式的差别在于:
    1. 没有显式地创建对象(new Object)
    2. 直接将属性及方法赋予this对象
    3. 没有return

    按照惯例:构造函数名称以大写字母开头,而非构造函数以小写字母开头。

    当使用构造函数创建对象时,我们就需要用到new操作符。而创建过程一共包括以下4步:
    1. 创建一个新对象
    2. 将构造函数的作用域赋给新对象(this这个时候就指向新对象)
    3. 执行构造函数中的代码(即给新对象添加属性方法)
    4. 返回新对象

    person1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor属性,该属性指向Person。

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

    虽说constructor属性可以用来识别对象的类型,但使用instanceof会更可靠。person1和person2既是Person类型的实例,也是Object类型的实例。

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

    以这种方法定义的构造函数是定义在Global对象中(浏览器中即为window对象)

    构造函数和其他函数的唯一区别,在于他们的调用方式的不同。一般函数,直接调用;而构造函数,要通过new操作符来调用。

    对象冒充:call

    function Person(name, age, job){
      this.name = name;
      this.age = age;
      this.job = job;
      this.sayName = function(){
        alert(this.name);
      };
    }
    
    var o = new Object();
    
    Person.call(o, "Rex", 26, "Engineer");  //对象冒充
    alert(o.sayName());

    构造函数的缺点:使用构造函数的主要问题,就是每个方法都要在每一个实例上重新创建一遍。person1和person2都有一个sayName()方法,但两个方法不是同一个Function实例。在ECMAScript里头,函数就是对象,所以定义一个新的函数,也代表了实例化了一个对象。

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

    改进,我们将sayName()函数移到构造函数的外部,然后在构造函数中定义一个属性并使它指向全局的sayName()函数。因为sayName属性只是一个指向sayName()函数的指针,所以无论创建多少个实例,sayName()函数都只有一个。但由于这样要把函数定义到构造函数外,如果程序代码多,这就会变得相当复杂了。所以,又有一个新的办法:原型模式。

    原型模式:我们创建的每一个函数都带有一个prototype属性,这个属性是一个对象,它包含特定引用类型实例中可用的属性和方法。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

    理解prototype:当创建一个函数时,会一并创建它的prototype属性。默认情况下,所有prototype属性都会自动获得一个constructor属性,这个constructor属性包含一个指向prototype属性所在函数的指针。以Person()构造函数为例,Person.prototype.constructor指向Person。通过这个构造函数,我们可以继续为原型添加其他属性和方法。(Prototype和Constructor参考文章

    当创建一个自定义的构造函数后,其原型(prototype)属性默认只会取得constructor属性;至于其他方法,则都是从Object继承而来。当调用构造函数创建一个新实例后,该实例的内容将包含一个指针(内部属性:__proto__),指向构造函数的原型属性。重点:这个连接存在于实例与构造函数的原型属性之间,而不是实例与构造函数之间。以下是Person构造函数的图解:

    让我们来看看Person构造函数,Person原型即Person的两个实例之间的关系吧。Person.prototype指向原型对象,然而Person.prototype.constructor又指回了Person。原型对象除了包含constructor属性以外,还包含其他新添加的属性(name, age, job, sayName)。Person对象的所有实例(person1,person2)有一个内部属性(__proto__)仅仅指向Person.prototype。换句话说,他们与构造函数没有直接的关系。虽然这两个实例都不包含属性和方法,但我们还是可以通过查找对象属性的过程来实现。

    由于在某些时候我们不能访问__proto__属性,但我们可以通过isPrototypeOf()方法来检查对象之间的关系。如果__proto__属性指向调用isPrototypeOf()方法的对象,则返回true。

    alert(Person.prototype.isPrototypeOf(person1)); //true
    alert(Person.prototype.isPrototypeOf(person2)); //true

    查找属性的顺序,先从实例里找,若找不到,则到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();
    var person2 = new Person();
    person1.name = “Greg”;
    alert(person1.name); //”Greg” - from instance
    alert(person2.name); //”Nicholas” - from prototype

    当你为实例的某个属性添加值以后,就无法再访问prototype的同名属性,即使你把值设为null。如果想要重新访问prototype的同名属性,要使用delete操作符,它会完全删除掉实例中的属性。

    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();
    var person2 = new Person();
    person1.name = “Greg”;
    alert(person1.name); //”Greg” - from instance
    alert(person2.name); //”Nicholas” - from prototype
    delete person1.name;
    alert(person1.name); //”Nicholas” - from the prototype

    hasOwnProperty()方法:可以判断该属性是存在于实例中,还是在原型中。只有当属性存在于实例中,才会返回true。

    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();
    var person2 = new Person();
    alert(person1.hasOwnProperty(“name”)); //false
    person1.name = “Greg”;
    alert(person1.name); //”Greg” - from instance
    alert(person1.hasOwnProperty(“name”)); //true
    alert(person2.name); //”Nicholas” - from prototype
    alert(person2.hasOwnProperty(“name”)); //false
    delete person1.name;
    alert(person1.name); //”Nicholas” - from the prototype
    alert(person1.hasOwnProperty(“name”)); //false

     

    原型与in操作符
    in操作符:
    1. 单独使用——查找属性是否存在于实例或原型中。如果属性存在于实例中或者存在于原型中,in操作符都会返回true。
    2. for-in循环——返回的是所有能够通过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。

    更简单的原型语法:我们可以通过字面量来重写整个原型对象。

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

    这么写的结果和之前一样,除了一点,constructor属性不再指向Person。当创建一个函数时,会同时创建它的原型对象,这个对象也会自动获得constructor属性。而我们这里的语法,相当于完全重写了prototype对象,所以constructor属性也变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。如果constructor的属性值非常重要,我们可以手动把它设置为适当的值:

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

    使用字面量方法定义原型的时候要注意,当你重写原型的时候,它会切断与之前原型的关系,所以,之前定义的任何属性或方法,都会被去掉。

    function Person(){
    }
    Person.prototype = {
      constructor: Person,
      name : “Nicholas”,
      age : 29,
      job : “Software Engineer”,
      sayName : function () {
        alert(this.name);
      }
    };
    
    Person.prototype = {
        name : "Rex"
    }
    
    var p1 = new Person();
    p1.sayName();    //会报错!因为Person原型被重写,现在Person中只存在name属性

    原型的动态性:由于在原型中查找值是一个搜索过程,因此我们对原型对象做的任何修改都能立即从实例中反应出来。不管实例创建在修改前还是后。

    var person = new Person();
    Person.prototype.sayHi = function(){
      alert(“hi”);
    };
    person.sayHi(); //”hi” - works!

    虽然person创建在sayHi()前,但我们仍然可以访问这个新方法。

    尽管我们可以在任何时候给原型对象添加属性和方法,而且都可以生效,但是我们不可以重新整个原型对象。调用构造函数时会为实例添加一个指向最初原型的__proto__指针,如果把原型修改为另外一个对象,就等于切断了构造函数和最初原型之间的联系。(注意:实例中的指针仅指向原型,而不指向构造函数。The instance has a pointer to only the prototype, not to the constructor.)

    原生对象的原型:原型模式不仅体现在创建自定义类型,原生的引用类型也是用这种方式创建的。所以,我们采用这种模式更改或向原生引用类型添加方法。

    原型对象的问题:首先,原型模式省略了构造函数初始化参数的过程,结果所有实例在默认情况下都取得相同的属性值。虽然有些不方便,在最重要的问题是由于共享的本性导致的。这些共享值,对于函数很好,基本值也不会有太大问题,但问题就出在引用类型值上。如下面例子:

    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

    可以看到,因为person1和person2公用了原型中friends(数组)这个属性,我们往person1.friends里添加的元素,会影响到person2.friends。因为person1.friends和person2.friends指向的是同一个地址。

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

    既然Constructor和Prototype模式都有他们的优缺点,我们可以把他们结合在一起使用,来达到最优的目的。
    - 构造函数模式:用来定义实例属性(各自不同的属性)
    - 原型模式:定义方法和公用的属性

    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

    稳妥构造函数模式(durable objects)

    所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象,该模式有2个特点:
    1. 创建对象的实例方法不引用this;
    2. 不适用new操作符调用构造函数。

    使用该方法重写前面的Person构造函数:

    function Person(name, age, job){
      //create the object to return
      var o = new Object();
      //optional: define private variables/functions here
      //attach methods
      o.sayName = function(){
        alert(name);
      };
      //return the object
      return o;
    }

    用这种模式创建的对象中,除了sayName()方法以外,没有其他办法访问name的值。可以像下面这样使用稳妥的Person构造函数:

    var person = Person(“Nicholas”, 29, “Software Engineer”);
    person.sayName(); //”Nicholas”

    这样,变量person中保存的就是一个稳妥对象,除了sayName()方法外,没有别的方法可以访问其数据成员。所以,稳妥构造方法提供很高的安全性。

  • 相关阅读:
    html css 学习
    第七天
    第六天
    第五天
    第四天
    第三天
    第二天
    团队敏捷开发day8
    团队敏捷开发day7
    团队敏捷开发day6
  • 原文地址:https://www.cnblogs.com/rexmzk/p/2618558.html
Copyright © 2011-2022 走看看