深入理解Javascript中构造函数和原型对象的区别
在 Javascript中prototype属性的详解 这篇文章中,详细介绍了构造函数的缺点以及原型(prototype),原型链(prototype chain),构造函数(constructor),instanceof运算符的一些特点。如果对prototype和构造函数不熟悉,可以前往Javascript中prototype属性的详解 和 Javascript 中构造函数与new命令的密切关系 仔细的品味品味。先来做一个简单的回顾。
首先,我们知道,构造函数是生成对象的模板,一个构造函数可以生成多个对象,每个对象都有相同的结构。构造函数的缺点就是,每当你实例化两个对象时,需要调用两次构造函数的某一个方法,这带来的坏处就是占用内存,而且没必要。
其次,为了解决构造函数的属性和方法无法被对象实例所共享的问题,我们可以把需要共享的属性和方法放在原型(prototype)对象上。原型对象上的所有属性和方法,都会被对象实例所共享。对于构造函数来说,prototype是作为构造函数的属性;对于对象实例来说,prototype是对象实例的原型对象。所以prototype即是属性,又是对象。
然后,除了undefined和null之外,每一个数据类型都可以看成一个对象,每一个对象都有它的原型。所有一切对象的原型顶端,都是Object.prototype,即Object构造函数的prototype属性指向的那个对象。当然,Object.prototype对象也有自己的原型对象,那就是没有任何属性和方法的null对象,而null对象没有自己的原型。
原型链的特点有:
a:读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
b:如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overiding)。
c:一级级向上在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
再次,constructor属性是原型对象上的一个属性,可以被所有实例对象所共享。要注意的是,prototype是构造函数的属性,而constructor则是构造函数的prototype属性所指向的那个对象,也就是原型对象的属性。由于constructor属性是一种原型对象和构造函数的关系,所以在修改原型对象的时候,一定要注意constructor的指向问题。
最后,instanceof运算符返回一个布尔值,用于判断对象是否为某个构造函数的实例。
在接下来的分享中,会谈谈Object的部分方法和Object.prototoype的部分方法。虽然都是概念性问题,但是如果理解了这些概念,对于MVVM框架和各种js框架的理解都有相当大的帮助。
以下的分享会分为如下内容:
1.Object和Object.prototype的区别
2.Object.getPrototypeOf()
3.Object.setPrototypeOf()
4.Object.create()
5.Object.prototype.isPrototypeOf()
6.Object.prototype.__proto__
1.Object和Object.prototype的区别
个人认为,要学好javascript的其中一个方法就是,必须理解每一个" . "所代表的意思是什么,是调用自身的属性和方法呢,还是继承原型的对象的属性和方法。来看看Object构造函数和构造函数的原型Object.prototype都有哪些属性和方法。
Object是构造函数,而Object.prototype是构造函数的原型对象。构造函数自身的属性和方法无法被共享,而原型对象的属性和方法可以被所有实例对象所共享。
Object的属性和方法:
Object.prototype的属性和方法:
上面例子中,Object拥有自己的方法prototype,getPrototypeOf(),setPrototypeOf()等,这些方法无法被实例所共享。而Object.prototypeOf()的hasOwnProperty,isPrototypeOf(),constructor等属性和方法是可以被实例对象所共享的。举一个最简单的例子。
function Keith() {}
var a = new Keith();
console.log(a.prototype); //undefined
console.log(a.constructor); //Keith()
上面代码中,构造函数Keith是没有任何属性和方法的。当访问prototype属性时返回undefined,是因为prototype属性没有办法从构造函数中继承,只能由构造函数本身访问。而constructor返回了Keith(),因为constructor属性本身就是Object.prototype中的属性,可以被所有实例对象所共享。
那么问题来了,如何知道实例对象的原型呢?可以通过Object.isPrototypeOf方法和继承原型对象的isPrototypeOf方法实现。
console.log(Keith.prototype.isPrototypeOf(a)); //true
console.log(Object.getPrototypeOf(a) === Keith.prototype) //true
上面代码中,实例对象a的原型就是Keith.prototype。这两个属性会稍后介绍。
2.Object.getPrototypeOf()
Object.getPrototypeOf方法返回一个对象的原型。这是获取原型对象的标准方法。
// 空对象的原型是Object.prototype
console.log(Object.getPrototypeOf({}) === Object.prototype) // true
// 函数的原型Function.prototype
function keith() {}
console.log(Object.getPrototypeOf(keith) === Function.prototype) //true
// 数组的原型Array.prototype
var arr = [1,2,3];
console.log(Object.getPrototypeOf(arr) === Array.prototype) ///true
3.Object.setPrototypeOf()
Object.setPrototypeOf方法可以为现有对象设置原型,然后返回一个新对象。这个可以接收两个参数,第一个是现有对象,第二个是原型对象。
var keith = {
height: 180
};
var rascal = Object.setPrototypeOf({}, keith);
console.log(rascal.height);
//上下两个代码片段相同。
var keith = {
height: 180
};
var rascal ={
__proto__: keith
};
console.log(rascal.height);
上面代码中,rascal对象是Object.setPrototypeOf方法返回的一个新对象。该对象本身为空、原型为keith对象,所以rascal对象可以拿到keith对象的所有属性和方法。rascal对象本身并没有height属性,但是JavaScript引擎找到它的原型对象keith,然后读取keith的height属性。
4.Object.create()
Object.create方法用于从原型对象生成新的对象实例,可以代替new命令。它接受一个参数,这个参数为所要继承的原型对象,然后返回一个实例对象。
var Keith = {
hobby : function() {
return 'Watching Movies';
}
};
var rascal = Object.create(Keith);
console.log(rascal.hobby()) //'Watching Movies'
上面代码中,Object.create方法将Keith对象作为rascal的原型对象,此时rascal就继承了Keith对象中的所有属性和方法。rascal就成为了Keith对象的实例对象。用下面这段代码比较好理解。
function Keith() {};
Keith.prototype.hobby = function() {
return 'Watching Movies';
}
var rascal = Object.create(Keith);
console.log(rascal.hobby()) //'Watching Movies';
new操作符和Object.create方法都是返回一个对象实例,但是两者有一些区别。
function Keith() {}
var a = new Keith();
var b = Object.create(Keith.prototype);
console.log(a instanceof Keith); //true
console.log(b instanceof Keith); //true
上面代码中,可以使用new操作符来调用构造函数,返回对象实例;而Object.create传入的参数必须是构造函数Keith的原型。
实际上,如果有老式浏览器不支持Object.create方法,可以用下面这段代码来构造一个Object.create方法。
if (typeof Object.create !=='function') {
Object.create = function(x) {
function F() {};
F.prototype = x;
return new F();
};
}
下面这三种方式生成的实例对象都是等价的。
var o1 = Object.create({});
var o2 = Object.create(Object.prototype);
var o2 = new Object();
在使用Object.create方法时,要注意的是必须传入原型对象,否则会报错。
var o1 = Object.create();
console.log(o1);//TypeError: Object.create requires more than 0 arguments
Object.create方法生成的对象实例,动态继承了原型对象。也就是说,修改原型对象的属性和方法会反应在对象实例上。
var keith = {
height:180
};
var rascal = Object.create(keith);
keith.height=153;
console.log(rascal.height)
上面代码中,修改原型对象,会影响生成的对象实例。
Object.create方法生成的对象,继承了它的原型对象的构造函数。
function Keith() {};
var boy = new Keith();
var girl = Object.create(boy);
console.log(Object.getPrototypeOf(girl) === boy); //true
console.log(girl.constructor === Keith); //true
console.log(girl instanceof Keith); //true
上面代码中,girl对象的原型是boy对象,girl对象的constructor属性指向了原型对象boy的构造函数Keith。
5.Object.prototype.isPrototypeOf()
对象实例的isPrototypeOf方法,用于判断一个对象对象是否是另外一个对象的原型。
var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);
console.log(o1.isPrototypeOf(o2)); //true
console.log(o2.isPrototypeOf(o3)); //true
console.log(o1.isPrototypeOf(o3)); //true
上面代码中,可以看出,只要某个对象处于原型链上,isPrototypeOf都返回true。
function Keith() {};
console.log(Function.prototype.isPrototypeOf(Keith)); //true
console.log(Object.prototype.isPrototypeOf(Function)); //true
console.log(Object.getPrototypeOf(Object.prototype) === null); //true
上面代码中,构造函数Keith的原型指向了Function.prototype,而构造函数Function的原型指向了Object.prototype。Object的原型指向了没有任何属性和方法的null对象。
6.Object.prototype.__proto__
__proto__属性(前后两条下划线)可以改写某个对象的原型对象。这个属于实例方法。
var keith = {};
var rascal = {};
rascal.__proto__ = keith;
console.log(keith.isPrototypeOf(rascal)); //true
上面代码中,通过rascal对象的__proto__属性,将rascal的原型指向了keith对象。
__proto__属性只有浏览器才需要部署,其他环境可以没有这个属性,而且前后的两根下划线,表示它本质是一个内部属性,不应该对使用者暴露。因此,应该尽量少用这个属性,而是用Object.getPrototypeof()(读取)和Object.setPrototypeOf()(设置),进行原型对象的读写操作。
来做一个小小的总结,上面对一些属性和方法的介绍都可以归结为一句话:
构造函数本身的属性无法被对象实例共享,而原型对象上的属性和方法可以被所用对象实例所共享。
完。
感谢大家的阅读。
转载请注明出处:http://www.cnblogs.com/Uncle-Keith/p/5838876.html
深入理解Javascript中构造函数和原型对象的区别的更多相关文章
- 深入理解Javascript中构造函数和原型对象的区别(转存)
Object是构造函数,而Object.prototype是构造函数的原型对象.构造函数自身的属性和方法无法被共享,而原型对象的属性和方法可以被所有实例对象所共享. 首先,我们知道,构造函数是生成对象 ...
- JS中构造函数与原型对象的同名属性,实例会取哪一个
构造函数与原型对象的同名属性,实例会取哪一个? 看了下面的过程,再回忆JS高程3里关于这部分的示意图.实例my在new的时候,本身就获得了a属性,所以my.a是1,倘若在new的时候如果没有赋予a属性 ...
- javascript中构造函数与普通函数的区别还有关于“new”操作符的一些原理
有一种创建对象的方法叫做工厂模式,例如: function person(name,age){ var o=new Object(); o.name=name; o.age=age; return o ...
- 深入理解JavaScript中 fn() 和 return fn() 的区别
在js中,经常会遇到在函数里调用其它函数的情况,这时候会有 fn() 这种调用方式,还有一种是 return fn() 这种调用方式,一些初学者经常会一脸萌逼地被这两种方式给绕晕了.这里用一个优雅的面 ...
- javascript面向对象系列第一篇——构造函数和原型对象
× 目录 [1]构造函数 [2]原型对象 [3]总结 前面的话 一般地,javascript使用构造函数和原型对象来进行面向对象编程,它们的表现与其他面向对象编程语言中的类相似又不同.本文将详细介绍如 ...
- javascript构造函数以及原型对象的理解
以下是一个构造函数的例子 如果是实例方法,不同的实例化,它们引用的地址是不一样的,是唯一的. //定义一个构造函数 function People(name,age){ this.name=name; ...
- 深入理解JavaScript中创建对象模式的演变(原型)
深入理解JavaScript中创建对象模式的演变(原型) 创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Objec ...
- 理解javascript中的原型模式
一.为什么要用原型模式. 早期采用工厂模式或构造函数模式的缺点: 1.工厂模式:函数creatPerson根据接受的参数来构建一个包含所有必要信息的person对象,这个函数可以被无数次的调用,工厂 ...
- javascript中继承(一)-----原型链继承的个人理解
[寒暄]好久没有更新博客了,说来话长,因为我下定决心要从一个后台程序员转为Front End,其间走过了一段漫长而艰辛的时光,今天跟大家分享下自己对javascript中原型链继承的理解. 总的说来, ...
随机推荐
- MySQL_02之增删改查、PHP数据库操作
1.数据操作: ①插入记录: 插入单条记录:INSERT tbl_name(字段名称,--) VALUES(字段值,--); 插入多条记录:INSERT tbl_name(字段名称,--) VAL ...
- SQL Server 链接服务器的安全
一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 安全设置(Security Settings) 实现效果:用户A能看见能使用,B用户不能看见这 ...
- SVM算法
本文主要介绍支持向量机理论推导及其工程应用. 1 基本介绍 支持向量机算法是一个有效的分类算法,可用于分类.回归等任务,在传统的机器学习任务中,通过人工构造.选择特征,然后使用支持向量机作为训练器,可 ...
- 深入学习jQuery选择器系列第七篇——表单选择器
× 目录 [1]表单元素 [2]对象属性 前面的话 无论是提交还是传递数据,表单元素在动态交互页面的作用是非常重要的.jQuery专门加入了表单选择器,从而能够极其方便地获取到某个类型的表单元素 表单 ...
- EntityFramework之原始查询及性能优化(六)
前言 在EF中我们可以通过Linq来操作实体类,但是有些时候我们必须通过原始sql语句或者存储过程来进行查询数据库,所以我们可以通过EF Code First来实现,但是SQL语句和存储过程无法进行映 ...
- 辨析relative与absolute
谈起它们,想必大家都不陌生.relative,相对定位嘛:absolute,绝对定位嘛.但是它们到底是个啥东东呢? 看看w3c的定义,见下表 定位 含义 relative 元素框偏移某个距离.元素仍保 ...
- Ubuntu杂记——Ubuntu自带拼音输入发杂乱不堪
打开终端,用管理员权限输入ibus-daemon -drx,重启即可
- ssh整合问题总结--运行项目时报java.lang.StackOverflowError(堆栈溢出)异常
今天在整合ssh项目中,碰到一个异常,当我提交购物车数据到订单时,浏览器报了一个这样的异常. 当时,我就吓坏了.尼玛,这不是内存溢出了吗?吓得我赶紧去检查了每一个有遍历语句的代码,结果没有发现一个死循 ...
- 简析GeoServer服务的内部文件组织以及GeoServer自动化服务发布工具的开发思路
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.前言 通过GeoServer发布的服务,在GeoServer内部有 ...
- 【集合框架】JDK1.8源码分析之TreeMap(五)
一.前言 当我们需要把插入的元素进行排序的时候,就是时候考虑TreeMap了,从名字上来看,TreeMap肯定是和树是脱不了干系的,它是一个排序了的Map,下面我们来着重分析其源码,理解其底层如何实现 ...