美文网首页
js对象——创建对象(1)

js对象——创建对象(1)

作者: 方_糖 | 来源:发表于2018-12-14 16:28 被阅读0次

    创建多个相似对象时使用的方法:

    1. 工厂模式
    //工厂模式
    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 person=createPerson("Jim",22,"Doctor");
    
    
    • 缺点:工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(怎样知道一个对象的类型)。
    2.构造函数模式
    //构造函数模式
    function Person(name,age,job){
      this.name=name;
      this.age=age;
      this.job=job;
      this.sayName=function(){
        alert(this.name);
      }
    }
    
    var person=new Person("Jim",22,"Doctor");
    
    • 构造函数模式相对于工厂模式有几个显著的区别
      (1) 没有显式地创建对象
      (2)直接将属性和方法赋给了this对象
      (3)没有return语句
      (4)函数名的首字母大写(Person和createPerson):按照惯例,构造函数始终都是以大写字母开头,而非构造函数则应该以小写字母开头
    • 解决对象识别问题
      (1)constructor
      上面的例子使person保存这个Person的示实例,因此有了一个constructor(构造函数)属性,该属性指向Person
      alert( person.constructor == Person ); //true
      对象的constructor属性最初是用来标识对象类型的,但是检测对象类型还是instanceof操作符更加可靠
      (2) instanceof
      alert( person instanceof Object ); //true
      alert( person instanceof Person); //true
      这个例子里创建的对象既是Object实例又是Person的实例
    • new Person()这条语句经历了什么
      (a)创建一个新对象
      (b)将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
      (c)执行构造函数中的代码(为这个新对象添加属性)
      (d)返回新对象
    • 构造函数的问题
      ECMAScript中的函数是对象,因此每定义一个函数,也就是实例化了一个对象。而构造函数在每次创建的时候都会完成同样任务的function实例(sayName),这是很没有必要的。因此可以把函数定义转移到构造函数外部来解决这个问题。
    function Person(name,age,job){
      this.name=name;
      this.age=age;
      this.job=job;
      this.sayName=sayName;
    }
    function sayName(){
      alert(this.name);
    }
    

    这样做确实解决了两个函数做同一件事的问题,可是新问题是:(a)全局函数不再“全局”了,而只能由Person对象调用(b)没有了“封装性”。如果对象需要定义很多方法,那么就要定义很多个的全局函数,于是我们的自定义的引用类型就丝毫没有封装性可言了。而接下来的原型模式就可以解决这个问题

    3.原型模式
    //原型模式
    function Person(){}
    Person.prototype.name="Jim";
    Person.prototype.age=22;
    Person.protoType.job="Doctor";
    Person.protoType.sayName=function(){
      alert(this.name);
    }
    var person=new Person();
    person.sayName();   //Jim
    
    • 原型模式的好处
      可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以让这些信息直接添加到原型对象中。
    • 原型对象是什么
      (1)无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象都会自动获得一个constructor(构造函数)的属性,这个属性包含一个指向prototype属性所在的函数的指针, 如下:
      alert( Person.prototype.constructor==Person ); //true
      (2)在我们调用person.sayName();的时候,会先后执行两次搜索,先找实例再找原型。
      首先解释器会问:“实例person有sayName属性吗?”
      答曰:“没有”
      然后继续搜索,再问“person的原型里有sayName属性吗?”
      答曰:“有”
      于是他就读取那个保存在原型对象中的函数
      (3)当给person主动添加属性时,就会屏蔽掉原型的属性(但不会被替代,如果其他Person对象调用name属性,还会默认为Jim),如:
    function Person(){}
    Person.prototype.name="Jim";
    Person.prototype.age=22;
    Person.protoType.job="Doctor";
    Person.protoType.sayName=function(){
      alert(this.name);
    }
    var person=new Person();
    person.name="Tom";
    alert(person.name);   //Tom
    

    此时person的name被一个新值给屏蔽了
    (4)使用delete操作符删除 实例 属性

    person.name="Tom";
    alert(person.name);   //Tom
    delete person.name;
    alert(person.name);   //Jim
    

    (5)hasOwnProperty()方法检测一个属性时存在于实例中还是原型中

    var person=new Person();
    alert(person.hasOwnProperty("name"))  //false:存在原型中
    person.name="Tom";
    alert(person.hasOwnProperty("name"))  //true:存在对象实例中
    

    (6)判断对象中传入的参数类型(只要有prototype属性的对象都能用)Object.prototype.toString.call(options)
    使用方法:https://www.cnblogs.com/wyaocn/p/5796142.html
    例如判断传入的参数是否为对象

    function Person(name) {
        alert(Object.prototype.toString.call(name))
    }
    var Tim={
        age:11,
        job:"student",
    }
    var person=new Person(Tim);  //[object,object]
    
    • 原型与in操作符
      in操作符通过对象访问对象中的属性时,返回true ,无论该属性是存在于实例中还是原型中
    var person=new Person;
    alert( "name" in person );    //true
    person.name="Tom";    //将name写在实例中
    alert( "name" in person );    //true
    

    所以想要确定该属性是存在于原型中还是实例中,只需要同时使用hasOwnPropertyin操作符

    function hasPrototypeProperty(object,name){
      return !object.hasOwnProperty(name)&&(name in object);  //true:在原型中,false:在实例中
    }
    

    for-in循环,所有开发人员定义的属性都是可枚举的(IE8-除外),所以所有属性都可以(实例属性和原型属性)使用for-in循环

    for(var prop in person){
      if(prop=="name"){
        alert("Found name!")  ;  //IE除外
      }
    }
    

    IE8-的解决办法见《JavaScript高级程序设计》(第三版)——人民邮电出版社,P153~154

    • 更简单的原型语法
    function Person(){}
    Person.prototype={
      name:"Jim",
      age:22,
      job:"Doctor",
      sayName:function(){
        alert(this.name);
      }
    }
    

    注意,此时的constructor属性不再指向Person了。前面介绍过,每创建一个函数,会同时创建它的prototype对象,这个对象会自动获取constructor属性,这里本质上重写了默认的prototype对象,此时constructor属性指向Object构造函数,经管如此,instanceof操作符还能返回正确的结果。
    如果此时constructor的值很重要,可以在person.prototype中设置它,但会导致constructor的默认不可枚举特性被设置为可枚举。

    function Person(){}
    Person.prototype={
      constructor:Person,
      name:"Jim",
      age:22,
      job:"Doctor",
      sayName:function(){
        alert(this.name);
      }
    }
    

    如果要兼容ES5,可以试一下object.definProperty()

    function Person(){}
    Person.prototype={
      name:"Jim",
      age:22,
      job:"Doctor",
      sayName:function(){
        alert(this.name);
      }
    };
    //重设构造函数,只适用于ES5兼容的浏览器
    Object.definPrototype(Person.prototype,"constructor",{
      enumerable:false, //不可枚举
      value:Person,
    })
    
    • 原型对象的问题
      对于包含引用类型值的属性来说,原型模式最大的问题——“共享”性,就非常突出了。
      看下面的例子
    function Person(){}
    Person.prototype={
      friends:["Amy","Sam"],
    };
    var person1=new Person();
    var person2=new Person();
    person1.friends.push("Van");
    alert(person1.friends);     //"Amy,Sam,Van"
    alert(person2.friends);     //"Amy,Sam,Van"
    

    当没有对Person的friends赋值时,直接调用原型属性并修改,就会直接导致原型修改。
    而这个问题证实我们很少有人单独使用原型模式的问题所在。解决办法看下面。

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

    创建自定义类型最常见的方式,就是组合构造函数模式和原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性

    //组合使用构造模式和原型模式
    function Person(name,age,job){
      this.name=name;
      this.age=age;
      this.job=job;
      this.friends=["Amy","Tom"];
    }
    Person.prototype={
      constructor:Person,
      sayName:function(){
        alert(this.name);
      }
    }
    var person1=new Person();
    var person2=new Person();
    
    person1.friends.push("Van");
    alert(person1.friends);   //Amy,Tom,Van
    alert(person2.friends);   //Amy,Tom
    

    总结:

    在没有类的情况下,可以使用下列模式创建对象:
    (1)工厂模式:在函数里面创建对象,并添加属性和方法。但没有解决对象识别问题,最后被构造函数模式所取代。
    (2)构造函数模式:可以像创建内置对象实例一样使用new操作符。但没函数不局限于任何对象,因此没有了封装性。
    (3)原型模式:使用构造函数的prototype属性指定应该共享的属性和方法。但单独使用原型模式的话,就会使不需要共享的属性和方法被共享
    (4)组合使用构造函数模式和原型模式:使用构造函数定义实例属性,使用原型定义共享的属性和方法

    ---------------------------《JavaScript高级程序设计(第三版)》读书笔记-------------------------------------------

    扩展:

    5. JSON格式的JS对象

    • 基本写法
      遵照JSON原则 , PI , area相当于关键字,必须要打引号(ps:据说这是现在最流行的写法之一,至于具体原因,以后弄懂了会补充
    var Circle={
       "PI":3.14159,
       "area":function(r){
         return this.PI * r * r;
      }
    };
    alert( Circle.area(1.0) );
    
    • 缺点:只用来初始化对象
    • 优点:可以理解成封包, 且非常灵活,本身就是一种json格式,所以方便应用于json格式传递环境下

    5. ES6的class写法

    //类的基本写法
    class Point{
        //属性
        constructor(x,y){
            this.x=x;
            this.y=y;
        }
        //方法
        toString(){
            console.log(this.x+"-"+this.y)
        }
        //只能通过类(Point)调用的方法,不会被实例(point)继承 
        //静态方法:加上static关键字
        static classMethod(){
            console.log("I can only be used by Point")
        }
    }
    

    ---------------------------《JavaScript高级程序设计(第三版)》读书笔记-------------------------------

    附:相关文章

    js对象——继承(2)

    相关文章

      网友评论

          本文标题:js对象——创建对象(1)

          本文链接:https://www.haomeiwen.com/subject/zfsghqtx.html