JS面向对象之创建对象模式
虽然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面向对象之创建对象模式的更多相关文章
- js面向对象 多种创建对象方法小结
转自js面向对象 多种创建对象方法小结 1.对象字面量 var clock={ hour:12, minute:10, second:10, showTime:function(){ alert(th ...
- js面向对象、创建对象的工厂模式、构造函数模式、原型链模式
JS面向对象编程(转载) 什么是面向对象编程(OOP)?用对象的思想去写代码,就是面向对象编程. 面向对象编程的特点 抽象:抓住核心问题 封装:只能通过对象来访问方法 继承:从已有对象上继承出新的对象 ...
- 浅谈JS面向对象之创建对象
hello,everybody,今天要探讨的问题是JS面向对象,其实面向对象呢呢,一般是在大型项目上会采用,不过了解它对我们理解JS语言有很大的意义. 首先什么是面向对象编程(oop),就是用对象的思 ...
- JS面向对象之工厂模式
js面向对象 什么是对象 "无序属性的集合,其属性可以包括基本值.对象或者函数",对象是一组没有特定顺序的的值.对象的没个属性或方法都有一个俄名字,每个名字都映射到一个值. 简单来 ...
- js面向对象之创建对象
工厂模式 function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = ...
- js面向对象编程——创建对象
JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象. 当我们用obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找 ...
- JS面向对象设计-创建对象
Object构造函数和对象字面量都可以用来创建单个对象,但是在创建多个对象时,会产生大量重复代码. 1.工厂模式 工厂模式抽象了创建具体对象的过程.由于ECMAScript无法创建类,我们用函数来封装 ...
- JavaScript---正则使用,日期Date的使用,Math的使用,JS面向对象(工厂模式,元模型创建对象,Object添加方法)
JavaScript---正则使用,日期Date的使用,Math的使用,JS面向对象(工厂模式,元模型创建对象,Object添加方法) 一丶正则的用法 创建正则对象: 方式一: var reg=new ...
- js面向对象学习 - 对象概念及创建对象
原文地址:js面向对象学习笔记 一.对象概念 对象是什么?对象是“无序属性的集合,其属性可以包括基本值,对象或者函数”.也就是一组名值对的无序集合. 对象的特性(不可直接访问),也就是属性包含两种,数 ...
随机推荐
- 使用layer弹出Ueditor实现父子传值
Layear的代码: function tankuang() { layer.open({ type: 2, title: false ...
- ElasticSearch简要总览
今天看了一下ElasticSearch的相关文档,我做了如下总结: 发现越来越喜欢画图归纳总结知识了,看图说话 集群特性说明 健康状况的说明 如何配置分片? 接下来一张图总结下操作相关的总结 接下来, ...
- java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.metastore.HiveMetaStoreClient
1.启动hive的时候出现这个问题,报错如下所示: [hadoop@slaver1 conf]$ hive Logging initialized -cdh5.-cdh5.3.6.jar!/hive- ...
- Unicode字符编码表
十进制 十六进制 字符数 编码分类(中文) 编码分类(英文) 起始 终止 起始 终止 (个) 0 127 0000 007F 128 C0控制符及基本拉丁文 C0 Control and B ...
- 一起学Hadoop——二次排序算法的实现
二次排序,从字面上可以理解为在对key排序的基础上对key所对应的值value排序,也叫辅助排序.一般情况下,MapReduce框架只对key排序,而不对key所对应的值排序,因此value的排序经常 ...
- 【Android】android:manageSpaceActivity让应用手动管理应用的数据目录
今天在Android遇到一个需求,不允许用户完全清除应用私有目录(/data/data/包名/),但是Android默认情况下用户通过在应用信息里面点击清除数据按钮把所有的应用私有目录下的的数据文件完 ...
- 【Android】Android 手机忘记锁屏密码的解决办法
对于忘记Android锁屏密码的筒子们,除重新刷包或者wipe data外,还可用adb来删除密码: 方法很简单 1.手机连接电脑,不要打开数据连接,在充电模式下进行. 2.在电脑上,解压adb.zi ...
- [转,讲的非常精彩]CIDR地址块及其子网划分(内含原始IP地址分类及其子网划分的介绍)
http://blog.csdn.net/dan15188387481/article/details/49873923 CIDR地址块及其子网划分(内含原始IP地址分类及其子网划分的介绍) 1. ...
- day33 网络编程之线程,并发以及selectors模块io多路复用
io多路复用 selectors模块 概要: 并发编程需要掌握的知识点: 开启进程/线程 生产者消费者模型!!! GIL全局解释器锁(进程与线程的区别和应用场景) 进程池线程池 IO模型(理论) 1 ...
- Linux git 在自己的服务器上建立 git 仓库(repository)
Linux git 在自己的服务器上建立 git 仓库(repository) 服务器端: 在这里使用 ssh 方式登陆: ssh [username]@server_address(建议用超级用户登 ...