虽然Object构造函数或对象字面量都可以用来创建单个对象,但都有一个缺点,使用同一个接口来创建对象,会产生大量重复的代码,为解决这个问题,引出下列方法

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 person1 = createPerson('alice', 29, 'teacher');

var person2 = createPerson('bob', 11, 'student');

解决了创建多个相似对象代码重复的问题,但是没有解决对象识别的问题,无法知道对象的类型

2.构造函数模式

ECMAScript 中的构造函数可以用来创建特定类型的对象,改写之前方法

function Person (name, age, job) {

  this.name = name;

  this.age = age;

  this.job = job;

  this.sayName = function() {

    alert(this.name);

  }

}

var person1 = new Person('alice', 29, 'teacher');

var person2 = new Person('bob', 11, 'student');

使用new操作符调用构造函数的实际过程如下:

(1)创建一个新对象

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

(3)执行构造函数中代码(为新对象添加属性)

(4)返回新对象

创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型

此处解释一下构造函数:

构造函数也是函数!通过new操作符调用就作为构造函数来使用,而没有new,和普通函数没什么两样,同样可以正常调用,例如:

  Person('alice', 29, 'teacher'); //当做普通函数调用,添加到window

  window.sayName();

// 在另一个对象作用域调用

  var o  = new Object();

  Person.call(o, 'bob', 11, 'student');

  o.sayName();   // 'bob'

构造函数模式的问题:

每个方法都要在每个实例上重新创建一遍,如sayName

会导致不同的作用域链和标识符解析

alert(person1.sayName == person2.sayName)  // false

可以通过把函数定义转移到构造函数外解决这个问题

写为:

function Person (name, age, job) {

  this.name = name;

  this.age = age;

  this.job = job;

  this.sayName = sayName;

}

function sayName () {

  alert(this.name);

}

在构造函数内部,我们把sayName属性设置成了全局的sayName函数,由于sayName包含的是一个指向函数的指针,因此

person1 和 person2 对象就共享了在全局作用域中定义的同一个sayName()函数。确实解决了两个函数做同一件事的问题,

新的问题又来了,在全局作用域定义的函数只能被某个对象调用,这让全局作用域有点名不副实。而且如果对象需要定义很多方法,就要定义很多个全局函数,那这个自定义的引用类型就没封装性可言了

3.原型模式

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

例如:

function Person () {}

Person.prototype.name = 'bob';

Person.prototype.age = 29;

Person.prototype.job = 'teacher';

Person.prototype.sayName = function() {

  alert(this.name)

}

var person1 = new Person();

person1.sayName(); // 'bob'

var person2 = new Person();

person2.sayName()  // 'bob'

alert(person1.sayName == person2.sayName)  // true

原型对象

无论什么时候,只要创建了一个新函数,就会为该函数创建一个prototype属性;这个属性指向函数的原型对象

默认下,所有原型对象会获得一个constructor属性,指向prototype属性所在函数的指针

Person.prototype.constructor === Person   // true

alert(Person.prototype.isPrototypeOf(person1));  // true;

alert(Person.prototype.isPrototypeOf(person2));  // true;

alert(Object.getPrototypeOf(person1) == Person.prototype);  // true

alert(Object.getPrototypeOf(person1).name);  // 'bob'

每当代码读取某个对象时,都会进行搜索,从实例对象开始,如果在实例中找到了具有给定名字的属性,则返回该属性的值;

如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找给定名字的属性,一直下去..

!虽然可以通过对象实例访问保存在原型中的值,但是不能通过实例对象重写原型中的值

如果我们在实例中添加了与原型中同名的属性值,那么就在实例中创建该属性,会屏蔽原型中那个属性

通过delete操作符删除实例属性,可以让我们重新访问原型中的属性

person1.name = 'alice';

alert(person1.name);  // 'alice'  来自实例

alert(person2.name)  // 'bob'  来自原型

delete person1.name;

alert(person1.name);  //   'bob'  来自原型

我们还可以通过hasOwnProperty()检测一个属性是否存在于实例中,还是存在于原型中

person1.name = 'alice';

alert(person1.hasOwnProperty('name'))  // true   来自实例

delete person1.name;

alert(person1.hasOwnProperty('name'))  // false   来自原型

in 操作符

(1)单独使用时,in操作符会在通过对象能够访问给定属性时返回true,不管属性存在于实例还是原型中

function hasPrototypeProperty(object,name) {

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

}

上述方法返回true 说明属性存在于原型中

(2)在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,既包括实例中的,也包括原型中的

  屏蔽了原型中不可枚举属性的实例属性也会在for-in循环时返回,所有开发人员定义的属性都是可枚举的。

  IE中不显示,是一个bug

Object.keys()

要取得对象上所有可枚举的实例属性,可以使用ECMAScript5的Object.keys()方法,接收一个对象为参数,返回一个包含可枚举属性的字符串数组

var p1 = new Person();

p1.name = 'asd';

p1.age = 31;

var p1keys = Object.keys(p1);

alert(p1keys); // ['name', 'age']

如果还要取得不可枚举的属性,可以用Object.getOwnPropertyNames()方法

var keys = Object.getOwnPropertyNames(Person.prototype);

alert(keys) // "constructor,name,age,job,sayName"

重写原型对象

function Person() {

}

Person.prototype = {

  name: 'bob';

  age: 29;

  job: 'teacher';

  sayName: function () {

    alert(this.name)

  }

}

上述将原型对象设置成等于一个以对象字面量形式创建的新对象,最终结果一样

但有一个例外:constructor 不再指向Person了

每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性;这里本质上完全重写了默认的prototype对象,因此constructor属性也变成了新的constructor属性,指向Object构造函数

var friend = new Person();

alert(friend instanceof Object);  //true

alert(friend instanceof Person);  //true

alert(friend.constructor == Person); //false

alert(friend.constructor == Object); // true

原型的动态性

由于在原型中查找值的过程是一次搜索,因此我们对原型对象的任何修改都能够立即从实例上反映出来,即使是先创建了实例后修改原型也照样如此

var friend = new Person();

Person.prototype.sayHi = function() {

  alert('Hi')

};

friend.sayHi();  'hi'  因为实例与原型之间的连接不过是一个指针

但如果是重写整个原型对象就不一样了

function Person() {

}

var friend = new Person();

Person.prototype = {

  name: 'bob';

  age: 29;

  job: 'teacher';

  sayName: function () {

    alert(this.name)

  }

}

friend.sayName(); // error

重写原型对象切断了现有原型和任何之前已经存在的对象实例之间的联系;它们的引用仍然是最初的原型

原型对象的问题

由共享的本质导致,对于基本值来说,修改实例属性会覆盖原型属性,

但对于引用类型值的属性就不一样了,如下:

function Person() {

}

Person.prototype = {

  constructor: Person,

  name: 'bob',

  age: 29,

  job: 'teacher',

  friends: ['a','b'],

  sayName: function() {

    alert(this.name)

  }

}

var person1 = new Person();

var person2 = new Person();

person1.friends.push('Van');

alert(person1.friends);// 'a,b,Van'

alert(person2.friends); //  'a,b,Van'

alert(person1.friends === person2.friends)  //true

因为friends数组存在于Perosn.prototype上,所有修改后,person2去调用也是同样的结果,

可是我们希望的是实例一般要有自己的全部属性的,所以也很少有人单独使用原型模式

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

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性

每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存

function Person(name, age, job) {

  this.name = name;

  this.age = age;

  this.job = job;

  this.friends = ['a', 'b', 'c']

}

Person.prototype = {

  constructor: Person,

  sayName: function() {

    alert(this.name)

  }

}

5.动态原型模式

function Person(name, age, job) {

  this.name = name;

  this.age = age;

  this.job = job;

  //方法,这里只在sayName方法不存在的情况下加到原型上

  if(typeof this.sayName != 'function') {

    Person.prototype.sayName = function () {

      alert(this.name)

    }

  }

}

if 语句检查的可以是在初始化之后应该存在的任何属性或方法

6.寄生构造函数模式

function Person(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 friend = new Person('bob',29,'teacher');

friend.sayName()  // 'bob'

除了使用new操作符并把使用的包装函数叫做构造函数之外,这其实和工厂模式没有区别。

构造函数在默认情况下,会返回新对象实例,而通过return语句,可以重写返回值

注意: 寄生构造函数模式,返回的对象与构造函数以及其原型属性没有任何关系,不能依赖instanceof操作符来确定对象类型

7.稳妥构造函数模式

特点:

(1)实例方法不引用this

(2)不使用new操作符调用构造函数

function Person(name, age, job) {

  var o = new Object();

  o.sayName = function () {

    alert(name)

  };

  return o;

}

在这种模式创建的对象里,除了使用sayName()方法外,无法访问name的值,安全性很强

JS面向对象之创建对象模式的更多相关文章

  1. js面向对象 多种创建对象方法小结

    转自js面向对象 多种创建对象方法小结 1.对象字面量 var clock={ hour:12, minute:10, second:10, showTime:function(){ alert(th ...

  2. js面向对象、创建对象的工厂模式、构造函数模式、原型链模式

    JS面向对象编程(转载) 什么是面向对象编程(OOP)?用对象的思想去写代码,就是面向对象编程. 面向对象编程的特点 抽象:抓住核心问题 封装:只能通过对象来访问方法 继承:从已有对象上继承出新的对象 ...

  3. 浅谈JS面向对象之创建对象

    hello,everybody,今天要探讨的问题是JS面向对象,其实面向对象呢呢,一般是在大型项目上会采用,不过了解它对我们理解JS语言有很大的意义. 首先什么是面向对象编程(oop),就是用对象的思 ...

  4. JS面向对象之工厂模式

    js面向对象 什么是对象 "无序属性的集合,其属性可以包括基本值.对象或者函数",对象是一组没有特定顺序的的值.对象的没个属性或方法都有一个俄名字,每个名字都映射到一个值. 简单来 ...

  5. js面向对象之创建对象

    工厂模式 function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = ...

  6. js面向对象编程——创建对象

    JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象. 当我们用obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找 ...

  7. JS面向对象设计-创建对象

    Object构造函数和对象字面量都可以用来创建单个对象,但是在创建多个对象时,会产生大量重复代码. 1.工厂模式 工厂模式抽象了创建具体对象的过程.由于ECMAScript无法创建类,我们用函数来封装 ...

  8. JavaScript---正则使用,日期Date的使用,Math的使用,JS面向对象(工厂模式,元模型创建对象,Object添加方法)

    JavaScript---正则使用,日期Date的使用,Math的使用,JS面向对象(工厂模式,元模型创建对象,Object添加方法) 一丶正则的用法 创建正则对象: 方式一: var reg=new ...

  9. js面向对象学习 - 对象概念及创建对象

    原文地址:js面向对象学习笔记 一.对象概念 对象是什么?对象是“无序属性的集合,其属性可以包括基本值,对象或者函数”.也就是一组名值对的无序集合. 对象的特性(不可直接访问),也就是属性包含两种,数 ...

随机推荐

  1. JAVA代码中可使用中文类名,变量名,对象名,方法名.

    java程序 兔子 public class 兔子{ //构造方法 public 兔子(){} public void 吃草(){ System.out.println("兔子在吃草&quo ...

  2. github.com加速节点

    github.com加速节点 192.30.253.118 github.com192.30.253.119 github.com93.46.8.89 github.com

  3. loss函数学习笔记

    一直对机器学习里的loss函数不太懂,这里做点笔记. 符号表示的含义,主要根据Andrew Ng的课程来的,\(m\)个样本,第\(i\)个样本为\(\vec x^{(i)}\),对应ground t ...

  4. python---使用字典来实现链接表图

    最后一章,坚持!!! # coding = utf-8 class Vertex: def __init__(self, key): self.id = key self.connected_to = ...

  5. jQuery字体缩放缩放插件zoomFontSize.js

    插件描述:jQuery字体缩放缩放插件zoomFontSize.js根据父类进行百分比缩放,兼容性如下: 使用方法 body 的class属性 添加 changbody_fontSize 而且整个页面 ...

  6. MySQL的预处理技术

    所谓的预处理技术,最初也是由MySQL提出的一种减轻服务器压力的一种技术! 传统mysql处理流程 1,  在客户端准备sql语句 2,  发送sql语句到MySQL服务器 3,  在MySQL服务器 ...

  7. Atcoder ARC101 Ribbons on Tree

    题解: 前面牛客网的那个比赛也有一道容斥+dp 两道感觉都挺不错的 比较容易想到的是 f[i][j]表示枚举到了i点,子树中有j个未匹配 这样的话我们需要枚举儿子中匹配状态 这样是n^2的(这是个经典 ...

  8. JMeter实现登录初始化(类似LR的init函数功能实现)

    1.项目背景 在做项目的性能测试过程中,发现系统的登录功能非常慢,所以,在涉及到登录才能操作的场景,尽量避开登录操作 解决方案: 首选设置“登录并生成签名值”线程组

  9. Codeforces 316E3 线段树 + 斐波那切数列 (看题解)

    最关键的一点就是 f[ 0 ] * a[ 0 ] + f[ 1 ] * a[ 1 ] + ... + f[ n - 1] * a[ n  - 1] f[ 1 ] * a[ 0 ] + f[ 2 ] * ...

  10. BZOJ3862 Little Devil I 树链剖分

    原文链接http://www.cnblogs.com/zhouzhendong/p/8081514.html 题目传送门 - BZOJ3862 题意概括 一棵树,n个点,边权为黑或者白,支持3重操作: ...