2

对象的定义:无序属性的集合,属性的值可以是基本值、对象或者函数.
每个对象都是基于一个应用类型创建的,这个引用类型可以是内置的(例如Object Array Math),也可以是用户自定义的.

基于Object创建对象

所有的对象都是继承自Object的,因此我们可以从Object着手创建对象.


//通过new 关键字创建对象
var person = new Ojbect();
person.name = 'yuhualinfeng';
person.age = 30;
person.job = 'web developer';

//通过对象字面量创建对象
var person = {};
person.name = 'yuhualinfeng';
person.age = 30;
person.job = 'web developer';

基于Object创建对象有两种形式,一种是使用new关键字,另一种是使用对象字面量.
使用这种方式创建对象的缺点是:当创建多个相同类型的对象时,会产生许多重复的代码,假如我要三个person对象,我就需要写三相同结构的代码,为了解决这个问题,我们引入了工厂模式创建对象.

使用工厂模式创建对象

工厂模式是软件工厂领域一种广为认知的设计模式,这种模式抽象了创建具体对象的过程.


function createPerson(name,age,job){

var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;

return obj;

}

var person1 = createPerson('yuhualingfeng',30,'web developer');
var person2 = createPerson('obama',45,'president');

我们创建了两个人物对象,假如我们基于Object创建对象,那么createPerson内的代码就会重复编码.
但是使用这种模式创建的对象任然有一个问题:无法得知创建的对象的类型名.解决这问题的可行方法是使用构造函数创建对象.

使用构造函数模式创建对象

function Person(name,age,job){

    this.name = name;
    this.age = age;
    this.job = job;

    this.sayName = function(){
        alert(this.name);
    }

}

var person1 = new Person('yuhualingfeng',30,'web developer');
var person2 = new Person('obama','45','president');

这里我们创建了一个名为Person的引用类型,然后我们用new 关键字实例化此引用类型,这个过程可以细化为以下四个过程:

  1. 创建一个新对象

  2. 将构造函数的作用域赋值给新函数(因此this就指向这个新对象)

  3. 执行构造函数中的代码(为this对象赋值,等同于为新对象赋值)

  4. 返回新对象

我们可以用instanceof来检测person1,person2的对象类型是否为Person.

 
 alert(person1 instanceof Person); //true
 alert(person2 instanceof Person); //true
 alert(person1 instanceof Object); //true 因为Person继承自Object,所以这里一样成立.
  

注:细心的朋友应该会注意到,这里的构造函数的首字母是大写,这里们遵循一个规范,普通函数的首字母大写,普通函数的首字母小写.

构造函数也有自己的缺点,大家可以看到Person包含一个sayName的函数(方法),函数也是对象(函数式Function的实例),所以每实例化一个Person,就会产生一个sayName方法,也就是一个对象,
随着创建的person实例怎多,产生的对象也相应增多,最终导致更多的内存,那么我们能不能找到更好的解决办法呢,答案是肯定的.

使用原型模式创建对象

我们每创建一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象而这个特定对象的用途是包含可以由特定类型的所有实例共享的属性和方法.这就意味着原型对象不会因为实例的增多二占用
更多的内存.每个原型对象都默认有一个constructor属性,故名思议,这个属性指向构造函数.下面展示了通过原型对象来创建对象.

function Person(){
}
Person.prototype.name = "yuhualingfeng";

Person.prototype.age = 30;

Person.prototype.job = "web developer";

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

var person1 = new Person();
person.sayName();  //yuhualingfeng
var person2 = new Person();

这里Person.prototype.constructor指向的是Person.当然你也可以向下面这样直接给原型对象赋值来创建对象.


Person.prototype={
constructor:Person,
name:"yuhualingfeng",
age:30,
job:"web developer"
};

这里之所以添加了constructor属性是应为直接给原型对象赋值会把原型对象的指针指向另一个对象,以前默认的值将无法访问到.

顺便给大家介绍两个与原型对象相关的方法和in关键字:

  1. isPrototypeOf:判断是某对象否为实例的原型.

alert(Person.prototype.isPrototypeOf(person));  //true
  1. hasOwnProperty:检测某属性是存在于实例中,还是原型对象中.

alert(person1.hasOwnProperty('name'));  //false,因为属性存在于原型中.
  1. in操作符:in操作符有两种使用方式,一种是单独使用,一种是和for搭配使用,单独使用的作用是判断某属性是否在某实例中访问到(无论是在实例自身的还是原型对象中的),for-in是枚举(循环)中使用.

//判断属性是否存在原型中
function hasPrototypeProperty(object,name){

    return object.hasOwnProperty(name) && (name in object);

}

原型模式创建对象的缺点:实例的原型对象是共享的,当修改一个实例的属性,如果属性的值为方法或者基本类型时,不会有什么影响,当属性为引用类型时,会影响其他实例的属性值.
综合构造函数模式和原型模式创建对象,我们结合他们的优点,去粗取精,我们组合使用构造函数模式原型模式.

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

通过构造函数创建对象的缺点是每个方法都会在实例上重新创建,造成不必要的内存消耗;通过原型创建对象的缺点在于实例引用类型值的属性会相互影响.综上考虑,我们可以把存储值得属性放在构造函数中,把方法放在原型对象中.这种模式是创建对象使用最广泛的一种,可以说是创建对象的默认模式.

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
}

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

var person = new Person('yuhualingfeng',30,'web developer');
person.sayName();  

以上就是创建对象的几种模式,大家可以结合它们的优缺点和你自身创建对象的用处进行权衡,然后选择适合你的创建对象的模式.


yuhualingfeng
1.7k 声望49 粉丝

前端极致追求者