JS高程之对象与创建对象的几种方法

768 阅读5分钟

对象是无序属性的集合,属性可以是基本值,对象或函数。对象是基于引用类型创建的,可以是JS的原生类型,也可以是自定义的。

对象的简单创建方法:

1.创建一个Object实例,再添加属性和方法

    var per=new Object();    
    per.name="Jane";    
    per.age=19;

2.对象字面量,与1创建的一样,原型对象也是Object(可以理解为也是继承自Object)

    var per={        
        name:"Jane",        
        age:19,    
    };

属性

属性分为数据属性和访问器属性两种,两种属性都有4个JS引擎使用的描述这个属性本身的特性。数据属性是比较常见的,普通的,例如上面的name和age属性。

数据属性的四个特性[[Configurable]][[Enumerable]][[Writable]] [[Value]]其中[[value]]特性存的是这个属性的值,例如name中的"Jane"。可以使用Object.defineProperty( )修改这些特性的默认值

访问器属性是不包含数据值的,包含一对getter,setter函数(也不是必须的),且访问器属性是不能直接定义的,必须通过JS内置的函数Object.defineProperty( )来定义。访问器属性包含的四个特性是[[Configurable]][[Enumerable]][[Get]] [[Set]].后面两个属性是通过Object.defineProperty( )定义读写时候调用的函数

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

这个示例中通过对象字面量定义book对象的两个数据属性,hideYearedition,通过Object.defineProperty( )定义了访问器属性year,当book.year访问year属性时候,会调用get函数,返回book对象的hideYear的值,当对book.year进行赋值操作时候会调用set函数,根据函数更改hideYearedition属性值。此时year属性的[[Set]]特性即定义的set函数,year属性的[[Get]]特性即定义的get函数。

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

**设置属性的特性   **通过Object.defineProperty( )可以设置属性的四个特性。

**读取属性的特性   **通过Object.getOwnPropertyDescriptor( )可以获取属性的四个特性,以对象形式打包返回。

关于属性及其四个特性的详细说明可以参考  JavaScript对象的数据属性与访问器属性

juejin.cn/post/684490…

创建对象

当创建大量重复或类似的对象时候,以上的简单创建方法让代码量大且多余,因此需要一些更加合理优雅的创建对象的方法。

1.工厂模式

用函数来封装创建对象的相关代码,创建时候只需要调用一句函数即可,重复的代码大大减小。

function createPerson(name,age){   
    var o=new Object();    
    o.name=name;    
    o.age=age;    
    o.sayName=function(){        
        alert(this.name);    
    };    
    return o;
}
var mother=createPerson("Jane",36);
var son=createPerson("Mike",7);

以上的createPerson函数里创建对象o并返回,因此创建对象时候只需要调用函数并传入不同参数即可创建不同属性值的对象。

缺点:无法知道mother和son两个实例是同一个对象类型,联想到JS的基本引用类型,例如数组的构造,通过new Array( )创建的对象是Array类型的,因此我们想到改进的方法是通过构造函数来创建对象。

2.构造函数模式

function Person(name,age){    
  this.name=name;    
  this.age=age;    
  this.sayName=function(){        
    alert(this.name);    
  }
}
var mother=new Person("Jane",36);
var son=new Person("Mike",7);

用new操作符创建对象,Person( )函数即变成构造函数,与工厂模式相比Person( )中没有显示创建对象,也不需要return语句,且直接将属性和方法赋给this指向的对象,最关键的是此时创建的mother和son对象是Person的实例,有了自定义的类别,可以用

alert(mother instanceof Person);  //true

来验证。

缺点:每个实例mother和son都有一个sayName的函数对象,在内存中是互相独立的。验证如下:

alert(mother.sayName==son.sayName);  //false

这样造成内存的浪费,因为这个方法在每个实例上都是一样的,要是所有的Person实例共用一个sayName对象就会更加合理,也节约内存的使用。改进方法之一是把sayName函数对象在构造函数外部定义,this.sayName赋值一个指向sayName对象的指针,如下:

function Person(name,age){    
  this.name=name;    
  this.age=age;    
  this.sayName=sayName2;
}
function sayName2(){    
  alert(this.name);
}
var mother=new Person("Jane",36);
var son=new Person("Mike",7);

这样每个实例mother和son中的属性sayName是一个指针,共同指向全局作用域中定义的sayName2这个函数对象,然而我们希望sayName2( )函数能够只被Person的实例对象调用,暴露在全局作用域中是不能接受的,因此就有了下面的改进方法:原型模式

3.原型模式结合构造函数模式

我们希望的是Person实例能够共享同一个sayName对象,而JS中每个函数自带的prototype属性保存了指向对象的一个指针就可以用来实现我们的目的。如下所示:

function Person(name,age){        
  this.name=name;        
  this.age=age;    
}    
Person.prototype.sayName=function(){        
  alert(this.name);
}        
var mother=new Person("Jane",36);    
var son=new Person("Mike",7);    
alert(mother.sayName());  // "Jane"

此时Person的每个实例mother和son的属性sayName就是指向同一个函数对象。验证如下:

alert(mother.sayName==son.sayName);  //true

即通过Person函数的prototype属性赋值的属性是所有Person实例对象所共用的,并且这个sayName函数只能由Person实例进行调用,具有封装性,解决了上面全局作用域的问题。关于prototype属性及原型对象的详细说明见 原型对象及原型链

4.动态原型模式

我们希望上面的function Person( )和 Person.prototype.sayName能够封装在一起,便于代码维护,如果直接把Person.prototype.sayName放在Person构造函数中,如下:

function Person(name,age){    
  this.name=name;    
  this.age=age;    
  Person.prototype.sayName=function(){        
    alert(this.name);
  }
}

那么每次用Person构造函数实例化对象时候都会执行Person.prototype.sayName的赋值语句,其中Person.prototype.sayName只需要赋值一次,即会被接下来所有Person实例所共用,不需要每次都进行赋值,因此我们可以加一个 if 判断语句,发现Person.prototype.sayName已经赋值存在后,接下来就不执行赋值操作,代码如下:

function Person(name,age){
    this.name=name;
    this.age=age;
    if(typeof this.sayName != "function"){
        Person.prototype.sayName=function(){
            alert(this.name);
        }
    }
}
var mother=new Person("Jane",36);
var son=new Person("Mike",7);

这样初次调用Person构造函数时候会执行if中的语句,完成原型的初始化,接下来的创建Person实例对象不会执行 if 语句。