JavaScript对象理解

这几天又好好读了JavaScript高级程序设计。其中在理解对象和创建对象这块有了很多新的见解和认知

JavaScript的对象

由于我最开始学习的是Java,所以很容易就会把JavaScript和Java联系到一起。不同于Java中,ES5还是没有类这个概念的。没办法通过类来实例化对象,更别说什么构造函数继承这些了。

ECMA-262把对象定义:无序属性的集合,其属性可以包含基本值,对象或者函数。

简单地创建对象

JavaScript创建对象很简单,最基本的创建方法:

    var obj = new Object();  // 通过Object构造函数来创建对象
    // 或者
    var obj2 = {//....balabala} // 通过对象字面量来创建对象

创建一个对象后,我们来了解一下对象的属性类型:

对象的属性类型

数据属性:

数据属性说的简单点就是对象中具有具体数据值的属性。如下中,name,age均为person的数据属性。

    var person = {
      name: 'www',
      age: 18 
    }

每一个数据属性呢,又包含四个属性值。显示在对象中的是其value属性。可以由Object.defineProperty来定义它的这四个属性。
四个属性:

1、configurable : 表示这个属性是否是可配置的。默认为true,如果修改为false,那么不可再修改这个属性的configurable属性和enumerable属性,也不可用delete来删除这个属性。
2、enumerabel : 表示这个属性是否是可枚举的。默认为true。在比如Array这种引用类型中,它的那些例如sort(),push()等等的方法的这个属性均为false,这样我们就没办法用for in 来枚举处其属性。
3、writable: 表示是否可修改。默认为true。如果改为false,那么就没办法再修改这个属性值。
4、value : 表示这个属性的属性值。上面的name的value属性就死 'www'。

Object.defineProperty方法的使用:

    Object.defineProperty(person,name,{
        // 这里我就取默认情况
        configurable: true,
        enumerable: true,
        writable: true,
        value: 'www'
    })
访问器属性

访问器属性没有具体的数据值,它提供一个getter和setter方法。
 这个就有点绕了。因为我们在定义对象的时候,像上面person这种,它的name属性我们是可以看得到的,很明显就容易理解数据属性。那么具体什么叫做访问器属性呢?
 这里通过一个例子来说明一下。首先我们定义一个girl对象。女孩子的年龄都是保密的,所以我们给它设置一个_age属性。那我们可以通过一个ask访问器属性来获取到这个女孩的年龄。

    var girl = {
      _age:18, // 下划线_开头的属性是一种约定俗成,一般不作为公开属性, 只能通过对象方法访问。
      mood: "happy"      
    }
    // 定义一个ask访问器属性。
    Object.defineProperty(girl,"ask",{
      // 我们可以通过girl.ask 来获取到这个女孩18岁了。
      get: function () {
         return this._age;
      } 
      // 要是我们给这个女孩设置一个 girl.ask = 20; 就像询问这个女孩,你是不是20了?女孩就会很生气。    
         她的mood属性就会变成 angry。
      set: function(val) {
        if(val > 18) {
          this.mood = "angry";
        }
      }
    })

这就是访问器属性。可以通过这个属性获取到其他属性,也可以在设置这个属性的时候,导致别的属性发生变化。同时,这个属性除了get和set两个属性,还有configurable和enumerable两个属性,类似于数据属性。
关于读取属性和设置多个属性等,这些都是基本知识的介绍,可以阅读《JavaScript高级程序设计》

高级程序设计

创建对象

上面说了很简单通过Object构造函数和对象字面量来创建对象。接下来说几个逼格稍微高一点的。

工厂模式

JavaScript工厂模式创建对象就像是无限调用一个方法来创建Object对象。这个方法就像是一个工厂一样,如下代码所示:

    function factory(name,age,sex) {
      var o = new Object();
      o.name = name;
      o.age = age;
      o.sex = sex;
      o.say = function () {
          console.log(o.name+'--'+o.age+'--'+o.sex);
      }
     return o;
  }
  // 创建实例对象
  var p1 = factory("wyh",18,"man");
  p1.say(); // wyh--18--man

缺点:缺点太明显了,我创造的对象全是Object的,没有标识度。实例化的p1,p2,p3也没有任何的联系。
所以,让我们来看构造函数模式。

构造函数模式

还是没办法摒弃Java的学习思想。所以这里我很容易就联想到Java的构造函数。JavaScript的构造函数模式创建对象还真的很像Java的构造函数。如下代码:

    // 与Java的构造函数何其相似。函数内部没有显示的创建对象的动作。 
    function Animal(name,leg) {
        this.name = name;
        this.leg = leg;
        this.sayName = function () {.....};
    }
    // 实例化两个对象:通过new 来创建。
    var tiger = new Animal("tiger",4);
    var lion = new Animal("lion",4);
    console.log(tiger.constructor); // Function: Animal。   

好像很不错。可是有严重的问题所在。每当实例化一个Animal对象,就会给sayName开辟一个内存空间,如果这个对象有很多函数属性,如果要实例化很多的Animal对象,就会很占用内存空间了。当然,我们可以把这个对象的函数属性放在全局变量中,但是同时也会污染了全局环境。
所以,来看原型模式:

原型模式

由于我们创建的每个函数都是有一个叫做prototype(原型)的属性,该属性作为一个指针指向一个对象。
所以我们可以利用这个prototype来进行创建对象。这里仅仅介绍简单的原型模式创建,更具体的可以阅读《JavaScript高级程序设计》。
代码:

    function Person() {
    }
    Person.prototype.name = "wyh";
    Person.prototype.age = 18;
    Person.prototype.list = [],
    Person.prototype.sayName = function () {
        console.log(this.name);
    }
    var p1 = new Person();
    var p2 = new Person();
    p1.list.push("a");
    console.log(p2.list) // ['a']
    console.log(p1.constructor)  // Person
    console.log(p1.sayName == p2.sayName) // true;

解决了上面构造函数模式中的函数占用内存的问题了。因为所有实例化的对象的函数指向的地址是一样的。
缺点:没有办法自定义初值,不具变通能力。
如上面所示,我在p1中操作了数组list,p2的也发生改变,这是不想看到的。原型模式一个实例化对象更改引用类型,所有的都会改变。

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

既然构造函数和原型模式都各自有问题,而且还可以互补。那么我们就把这两种模式结合到一起使用。
代码如下所示:

    function Person(name,age,sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.shoplist = [];
    }
    Person.prototype = {
        sayName: function () {
            console.log(this.name);
        },
        constructor: Person
    }

    var p1 = new Person("wyh",18,"man");
    var p2 = new Person("MJ",16,"wm");
    p1.shoplist.push("apple");
    p2.shoplist.push("shoes");
    console.log(p1.sayName == p2.sayName); // true
    console.log(p1.constructor) // Person
    console.log(p2.shoplist) // ['shoes']

我们把公用的方法用原型模式构造,省去内存消耗。把不公用的引用类型用构造函数模式构造,避免混淆。
同时,也可以通过传入初始化数据来自定义我们需要的对象的样子。
这种方法(模式)是使用度最普遍的一种方法。

动态原型模式

把上面那种模式中,所有的信息都封装在构造函数中。但是又为了防止重复的初始化原型,于是就有了动态原型模式。话不多说,请看代码:

    function Person(name,age) {
        // 属性
        this.name = name;
        this.age = age;
        // 动态地进行原型的构造方法
        if(typeof this.sayName != "function") {
            console.log('----1------');
            Person.prototype.sayName = function () {
                   console.log(this.name);
            }
            // 其他的需要原型处理的属性
        }
    }

    var p1 = new Person("wyh",18); // 1
    p1.sayName(); // wyh
    var p2 = new Person("mj",16); // 什么都没有
    p2.sayName(); // mj

当我们第一次实例化Person的时候,会动态地创建原型属性,并且只创建这一次。

寄生构造函数模式

这个。。。我看不太出来和工厂模式的区别。要说实例化的时候用了new操作符的话,那我还是不明白这个模式的作用所在。

稳妥构造函数模式

同上面这个寄生构造函数模式,我暂时看不出来它的用处所在。

你可能感兴趣的:(JavaScript对象理解)