面向对象--对象的创建

对象的创建
 
1. Object() 构造函数
     var foo =  new Object();
 
2. 对象字面量 
     var foo = {name:"name",age:17};
        
以上两种方式 的缺点是: 在创建多个对象时, 会产生大量的重复代码.
 
3. 工厂模式
  将公共的部分提取出来 , 用来创建相似的对象.
  示例 :
    function createPerson(name , job , age){
      var o = new Object();
      o.name = name ;
      o.age = age ;
      o.job = job ;
      o.sayName = function(){
        // do something ..
      };
      return o ;
    }
 
  这种模式 , 解决了创建相似对象 , 代码重复的问题
  但是 , 无法检查 它创建的对象到底是什么类型的. 而且代码耦合性太强 ..
 
4. 构造函数模式
  示例 :
    function Person(name , job , age){
        this.name = name ;
        this.age = age ;
        this.job = job ;
        this.sayName = function(){
          // do something ..
        };
    }
 
    var person = new Person("name", "worker" , 20) ;
    console.log(person instanceof Person); // display true
 
  这种方式 , 与工厂模式的不同之处 :
    a. 没有显示创建对象. 如 var o = new Object();
    b. 直接将属性和方法 赋值给了 this
    c. 没有return 语句
    d. 函数名 最好要首字母大写
    e. 使用时,必须使用 new 操作符.
 
  构造函数 也是 函数的一种 , 因此也可以不使用 new 操作符.
  Person("name", "worker" , 20); // 添加到window
  window.sayName();
  // 在指定作用域中调用
  var o = new Object();
  Person.call(o,"name", "worker" , 20); //添加到 对象o上.
  o.sayName();
 
  使用构造函数模式的缺点是:
    使用构造函数创建的实例, 它们包含的内部函数虽然同名, 但实际上是不同的两个函数(即使处理逻辑是一样的)
    对于同样的方法和逻辑,没有实现抽象,每个实例中都要创建一份
    如果在全局中定义一个函数 , 让构造函数引用它 , 又毁坏了封闭性.
    如果需要多个对象方法呢 , 总不能定义许多全局函数吧.
    且这个可能只能被指定的对象 使用的函数(比如函数内耦合了this.personName),这个'全局函数'的名称也忒名不副实了.
 
  
=========   以上需要了解  , 以下模式 开始划重点 =========
  
5. 原型模式
  每个由构造器模式声明的自定义类型内部都有个prototype属性, 它是个指针, 指向原型对象.
  原型对象用来包含特定类型(即你创建对象的类型)的 被所有该类型的实例所共享的属性和方法.
 
  换句话讲, 可以不用在构造函数中定义 对象实例的信息.将这些信息添加到原型对象上.
 
  示例 :
    function Person(){}
 
    Person.prototype.name = "xx";
    Person.prototype.age = 20 ;
    Person.prototype.sayName = function(){
      console.log(this.name);
    };
 
 
  理解 "原型" :
 
    任何函数(不只是构造函数)在创建的时候, 都会默认生成 prototype 属性 , 它是原型对象的指针.
        -- 因此,为了方便理解 下面再提到 Xxxx.prototype 就是指 原型对象 本身,而不是指 Xxxx 的prototype 指针. Xxxx.prototype 的返回值本来就是原型对象.
 
    当使用构造函数(它就是函数)的时候, 同样有个prototype指针 指向 原型对象.
      例如 : Person的prototype指针 指向了 Person.prototype(这里代指Person的原型对象).
 
    当这个 原型对象 创建后 , 默认只有一个constructor属性(即 Xxxx.prototype.constructor , 也是个指针),
        -- 原型对象创建的时机,就是每次调用构造函数的时候.
    它指向了所属原型对象的所在的函数, 比如 Person.prototype.constructor => Person
    再捋一遍路径 , 是个环形的 :
        Person() --> prototype属性 --> Person.prototype --> Person.prototype.constructor --> Person
    除了默认的constructor属性, 原型对象上 还会包含后添加的 自定义的属性和函数.
 
    当使用构造函数 创建对象实例的时候, 对象实例 默认会有个指针(即__proto__属性) 指向了原型对象(Xxxx.prototype)
    
    现在有了两个东西可以指向原型对象了, 分别是: Object 的prototype 以及 object 的__proto__   -- Object 和 object 分别代指 类 和 对象.
 
    好, 这时候就可以捋一下 访问 对象的属性和函数 的查找链了 , 用Person来举例:
      person          -->       person 的 __proto__  -->  Person.prototype       -->        Person.prototype.constructor --> Person
      从对象本身找  (如果没找到)    通过 __proto__ 属性,找到原型对象,看看它有没有      (如果没找到)    通过原型对象的constructor 看看Person 构造函数里有没有.
 
    注意, 虽然对象实例可以访问到原型对象中保存的值, 但是不可以改变它里面的值. (经测试,这行不对,是可以改变的)
    比如对象实例中定义了一个与原型对象中同名的属性,这会屏蔽原型中的同名属性,而不会覆写它,也就是说不会影响到其他对象.
 
    对象实例会从Object那里继承一个方法 hasOwnProperty(propertyName), 该函数来检查 对象实例 本身中是否存在该属性, 如果存在则返回true.
 
 
  原型 与 in 操作符
    in 操作符无论单独使用, 还是在for-in语句中 , 都可以返回对象实例以及原型对象中的可枚举的属性/函数
    因此 可以利用 hasOwnProperty 与 in 配合使用, 来查看 某个指定的属性 是否在原型对象中存在且没有被屏蔽
    function hasPrototypeProperty(target,propertyName){
      return !target.hasOwnProperty(propertyName) && (propertyName in target);
    }
 
  获取对象上的属性
    Object.keys(target)
        返回指定对象上的 所有可枚举的 属性; 如果该对象不是原型对象 , 该函数也不会通过__proto__向上查找.
    Object.getOwnPropertyNames(target)
        返回对象上所有属性 , 无论可不可枚举 ; 同样不会通过__proto__向上查找.
 
 
  更简单的原型写法
    即使用字面量形式来书写.
    但是有个问题是,这时会默认设置它的constructor指向Object,而不是当前构造函数.(不过不影响正常使用instanceof,会得到正确结果)
    为解决这个问题,可以显式声明.但是显式声明会让contructor的枚举特性设置成true.
    所以如果有需要,最好使用Object.defineProperty()来设置一下constructor.
 
    示例:
      function Person(){}
      // var p = new Person();
      Person.prototype = {
        name:"name",
        age:20,
        sayName : function(){
          console.log(this.sayName);
        }
      };
      Object.defineProperty(Person.prototype,"constructor",{enumerable:false});
      // p.sayName();
 
    如果上面的示例中 的注释去掉. 调用p.sayName()会引发错误.
    因为 对象实例 和 构造函数没有直接关系, 只是刚好分别拥有了指向 原型对象的指针而已,这些指针是相互独立的.
    当创建了对象的时候,两个指针所指向的对象是同一个原型对象.
    当Person的prototype指向了 别的原型对象的时候, 对象实例的__proto__依然指向原来的原型对象,并不会受到影响.
    别的原型对象上添加的属性/方法,自然无法体现在原来的原型对象上,从而不会影响到对象实例.
 
    下面的示例,就不会报错 :
      function Person(){}
      var p = new Person();
      Person.prototype.name="xxx";
      Person.prototype.sayName = function(){console.log(this.name)} ;
      p.sayName(); //在p上调用时,查看自身和原型上有没有该方法 -- 只要调用前,在prototype添加过该函数即可.
 
 
    所有原生的引用类型都是使用这种模式创建对象的.例如 Array,String,Date等
    可以通过原生对象的原型对象的引用 来修改/新增 原生对象上的属性/方法.
    当然了,不到万不得已,这种方式是不推荐的.会影响在其他环境中造成冲突(即你也改原型,我也改原型,造成冲突和覆盖)
 
  使用原型模式的缺点:
    当原型对象上的属性是函数或者基本数据类型时,是没有问题的.对象可以屏蔽这个属性/函数.
    但是,当这个属性是个引用类型时,如果是简单的引用赋值还好(即用=改变指针的指向),
    如果使用了操作函数,会相互影响(比如数组,使用push).
    如果你的初衷是不想让它共享,那这就是个问题了.
 
 
6. 组合使用构造函数模式和原型模式
    创建自定义类型的最常见方式.
    构造函数里定义实例属性,而原型对象里定义方法和共享属性
 
    示例:
      function Person(name , age){
        this.name = name ;
        this.age = age ;
      }
 
      Person.prototype = {
        constructor : Person,
        legsCnt : 2,
        handsCnt : 2,
        headCnt : 1,
        speak: function(language){
          return `i speak ${language}`;
        }
      };
 
 
7. 动态原型模式
    示例:
      function Person(name , age){
        this.name = name ;
        this.age = age ;
 
        // 判断条件 只需要找 一个 原型对象 创建完成后必定存在的方法即可 -- 找一个就行
        if(typeof this.speak !== "function"){
          Person.prototype.speak = function(language){
            return `i speak ${language}`;
          };
          
          Person.prototype.play = function(){ ... };
        }
      }
 
    这样可以保证,这些方法只加载一次.这种方法 不能用字面量形式重写原型对象.
 
8. 寄生构造函数模式
  本身和工厂模式没有不同, 只不过调用的时候使用了new操作符
  示例 :
    function createPerson(name , age){
      var o = new Object();
      o.name = name ;
      o.age = age ;
      o.sayName = function(){
        // do something ..
      };
      return o ;
    }
 
    var p = new createPerson('窦唯’60);
 
  直接看看用途吧 , 比如创建一个具有额外方法的数组,由于不能直接改Array的构造函数,就可以使用这个模式.
  示例:
    function SpecialArray(){
      var array = new Array();
 
      array.push.apply(array,arguments);
 
      array.toPipedString = function(){
        return this.join("|");
      }
      return array ;
    }
 
    var arr = new SpecialArray([1,2,3,4,5,6]);
    arr.toPipedString(); // display 1|2|3|4|5|6
 
  这种模式的问题是,不能依赖instanceof来判断这个实例的真实类型.
 
9.稳妥构造函数模式
    所谓稳妥,就是在构造函数内部不使用this,调用函数的时候不使用new操作符(像调用普通函数一样调用)
    这种模式适合在一些特殊环境中(这种环境不允许使用this和new:比如ADSafe中,Mashup程序中等)
    示例:
      function Person(name){
        var o = new Object();
 
        var name = name ;
        var func = function(arg){
          // do something
          console.log(`arg is ${arg}`);
        };
 
        o.method = function(age){
          console.log(name);
          func();
        }
 
        return o ;
      }
 
      var p = Person();
      p.method(20);
 
      
posted @ 2019-07-04 13:21  豆豆飞  阅读(226)  评论(0编辑  收藏  举报