创建对象的几种方法

1.工厂模式

考虑到ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节,如下所示:

function createPerson(name,age,job){
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.job = job;
  obj.sayName = function(){
    console.log(this.name);
  }
  return obj;
}

var person1 = createPerson('Tom',20,'teacher');
var person2 = createPerson('Jone',27,'Doctor');

函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象。可以无数次的调用这个函数,而每次他都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题。

2.构造函数模式

我们可以使用ECMAScript中的构造函数来创建特定类型的对象,此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。可以使用构造函数模式将前面的例子重写。

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

var person1 = new Person('Tom',20,'Teacher');
var person2 = new Person('Jone',27,'Doctor');

在这个例子中,Person 函数取代了createPerson 函数,两者存在以下的不同之处:

  • 没有显示的创建对象
  • 直接将属性和方法赋值给 this
  • 没有 return 语句

此外,还应该注意到函数名Person使用的是大写字母P。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数应该以一个小写字母开头。

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
(1)创建一个新对象;
(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
(3)执行构造函数中的代码(为这个新对象添加属性);
(4)返回新对象。

其中 person1person2 分别着Person 的一个不同的实例,它们都有一个constructor (构造器)属性,且该属性都指向Person

console.log(person1.constructor == Person);  //true
console.log(person2.constructor == Person);  //true

对象的constructor属性最初是用来标示对象类型的。但是,提到检测对象类型,还是instanceof操作符要更可靠一些。

console.log(person1 instanceof Object);  //true
console.log(person1 instanceof Person);  //true
console.log(person2 instanceof Object);  //true
console.log(person2 instanceof Person);  //true

将构造函数当作函数

构造函数与其他函数的唯一区别,就在于调用他们的方式不同。任何函数,只要通过new操作符来调用,那它就可以作为构造函数;反之,就是普通函数。

//当作构造函数调用
var person = new Person('Tom',20,'Teacher');
person.sayName();

//作为普通函数调用
Person('Jone',27,'Doctor');
window.sayName();

//在另一个对象的作用域中调用
var obj = new Object();
Person.call(obj,'Grey',25,'Nurse');
obj.sayName();

构造函数的问题

当我们每创建一个对象,那么对象中的方法也会被重新创建一遍,这样就会导致不同的作用域链和标识解析。
我们可以像下面这样,通过把函数定义转移到构造函数外部来解决这个问题。

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

function sayName(){
    console.log(this.name)
}

var person = new Person('Tom',20,'Teacher');

如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的应用类型就丝毫没有封装性可言了。好在,这些问题可以通过使用原型模式来解决。

3.原型模式

我们创建的每一个函数都会有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的作用就是包含由特定类型的所有实例所共享的属性和方法。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

function Person(){
}

Person.prototype.name = 'Grey';
Person.prototype.age = 20;
Person.prototype.job = 'Teacher';
Person.prototype.sayName = function(){
  console.log(this.name);
}

var person1 = new Person();
person1.sayName();  //'Grey"

var person2 = new Person();
person2.sayName();  //'Grey"

console.log(person1.sayName == person2.sayName);  //true

在这我们将nameagejobsayName()直接添加到了 Person 中的prototype 属性中,personprototype 中的属性都被 Person 的实例化对象所共享。

理解原型对象

每当我们创建一个函数,就会根据一种特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象都会有一个 constructor 属性,这个属性指向 prototype 属性所在的函数的指针。Person.prototype.constructor 就指向 Person。而通过这个构造函数,我们还可以继续为原型对象添加其他属性和方法。

以前面使用Person构造函数和Person.prototype创建实例的代码为例,下图展示了各个对象之间的关系:

每当读取某个属性时,都会进行一次搜索。首先会搜索对象实例本身,如果搜索到了该具体名字的属性,就会返回该属性的值,如果没有搜索到,那么就会继续搜索指针指向的原型对象,在原型对象中如果搜索到的话就会返回属性值。

可以发现,在搜索的时候是先搜索的是对象实例本身,然后才是原型对象。如果在对象本身和原型对象含有相同的属性,那么原型对象中的属性和方法就会被对象实例中相应的属性和方法所覆盖。如下所示:

function Person(){
}

Person.prototype.name = 'Grey';
Person.prototype.age = 20;
Person.prototype.job = 'Teacher';
Person.prototype.sayName = function(){
  console.log(this.name);
}

var person1 = new Person();
person1.name = 'Tom';  
person1.sayName();  //'Tom'

var person2 = new Person();
person2.sayName();  //'Grey'

当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性,换居话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性(使用delete操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性)。

更简单的原型语法

前面的例子中每添加一个属性和方法就要前一遍Person.prototype。为了减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。

function Person(){
}

Person.prototype = {
  name:'Grey',
  age:20,
  job:'Teacher',
  sayName:function(){
  console.log(this.name);
  }
}

在上面的代码中,我们将Person.prototype设置为等于一个以字面量形式创建的新对象。最终结果相同,但有一个例外:constructor属性不再指向Person了。
如果constructor的值真的很重要,可以像下面这样特意将它设置回适当的值。

function Person(){
}

Person.prototype = {
  constructor:Person,
  name:'Grey',
  age:20,
  job:'Teacher',
  sayName:function(){
  console.log(this.name);
  }
}

原型对象的问题

原型对象省略了构造函数传递参数初始化的步骤,结果导致所有的实例都会共享相同的属性,这是非常不方便的。如果创建的属性而引用类型的话,那么会造成不同的实例的混乱。

function Person(){
}

Person.prototype = {
  constructor:Person,
  name:'Grey',
  age:20,
  job:'Teacher',
  friends:['Jone','Tom'],
  sayName:function(){
  console.log(this.name);
  }
}
var person1 = new Person();
person1.friends.push('Van');
console.log(person1.friends);  //["Jone", "Tom", "Van"]

var person2 = new Person();
console.log(person2.friends);  //["Jone", "Tom", "Van"]

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

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。这样每个实例都会有一份实例属性副本,又同时含有一份共享的属性和方法,这样最大限度的节省了内存。另外,这种混合模式还支持向构造函数传递参数。

function Person(name,age,job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ['Jone','Tom'];
}

Person.prototype = {
  constructor:Person,
  sayName:function(){
  console.log(this.name);
  }
}

var person1 = new Person('Grey',20,'Teacher');
person1.friends.push('Van');
console.log(person1.friends);  //["Jone", "Tom", "Van"]

var person2 = new Person('Nicholas',27,'Doctor');
console.log(person2.friends);  //["Jone", "Tom"]

console.log(person1.friends === person2.friends);  //false
console.log(person1.sayName === person2.sayName);  //true

动态原型模式

动态原型模式将所有信息都封装在了构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。

function Person(name,age,job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ['Jone','Tom'];
  if(typeof this.sayName != 'function'){
    Person.prototype.sayName = function(){
      console.log(this.name);
    }
  }
}

var person1 = new Person('Grey',20,'Teacher');
person1.sayName();

5.寄生构造函数模式

function Person(name,age,job){
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.job = job;
  obj.sayName = function(){
    console.log(this.name);
  }
  return obj;
}

var person = new Person('Grey',20,'Teacher');
person.sayName();  //'Grey'

这个模式可以在特殊的情况下用来为对象构建构造函数。假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式。

function SpecialArray(){
  var newArr = new Array();
  newArr.push.apply(newArr,arguments);
  newArr.toPipedString = function(){
    return this.join('|')
  }
  return newArr;
}

var colors = new SpecialArray('red','pink','greey');
console.log(colors.toPipedString());  //"red|pink|greey"

关于寄生构造函数模式,有一点需要说明:首先,返回的对象与构造函数或者构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。

6.稳妥构造函数模式

所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。
稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。

function Person(name,age,job){
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.job = job;
  obj.sayName = function(){
    console.log(name);
  }
  return obj;
}

var person1 = Person('Grey',20,'Teacher');
person1.sayName();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容