跟我学习javascript的prototype,getPrototypeOf和__proto__


Posted in Javascript onNovember 17, 2015

一、深入理解prototype, getPrototypeOf和_ proto _

prototype,getPropertyOf和 _ proto _ 是三个用来访问prototype的方法。它们的命名方式很类似因此很容易带来困惑。

它们的使用方式如下:

  • C.prototype: 一般用来为一个类型建立它的原型继承对象。比如C.prototype = xxx,这样就会让使用new C()得到的对象的原型对象为xxx。当然使用obj.prototype也能够得到obj的原型对象。
  • getPropertyOf: Object.getPropertyOf(obj)是ES5中用来得到obj对象的原型对象的标准方法。
  • _ proto _: obj._ proto _是一个非标准的用来得到obj对象的原型对象的方法。

为了充分了解获取原型的各种方式,以下是一个例子:

function User(name, passwordHash) { 
 this.name = name; 
 this.passwordHash = passwordHash; 
} 
User.prototype.toString = function() { 
 return "[User " + this.name + "]"; 
}; 
User.prototype.checkPassword = function(password) { 
 return hash(password) === this.passwordHash; 
}; 
var u = new User("sfalken", "0ef33ae791068ec64b502d6cb0191387");

User函数拥有一个默认的prototype属性,该属性的值是一个空对象。在以上的例子中,向prototype对象添加了两个方法,分别是toString和checkPassword。当调用User构造函数得到一个新的对象u时,它的原型对象会被自动赋值到User.prototype对象。即u.prototype === User.prototype会返回true。

User函数,User.prototype,对象u之间的关系可以表示如下:

跟我学习javascript的prototype,getPrototypeOf和__proto__

上图中的箭头表示的是继承关系。当访问u对象的某些属性时,会首先尝试读取u对象上的属性,如果u对象上并没有这个属性,就会查找其原型对象。

比如当调用u.checkPassword()时,因为checkPassword定义在其原型对象上,所以在u对象上找不到该属性,就在u的原型上查找,查找顺序是u-> u.prototype(User.prototype)。

前面提到过,getPrototypeOf方法是ES5中用来得到某个对象的原型对象的标准方法。因此:

Object.getPrototypeOf(u) === User.prototype; // true

在一些环境中,同时提供了一个非标准的_ proto _ 属性用来得到某个对象的原型对象。当环境不提供ES5的标准方法getPrototypeOf方法时,可以暂时使用该属性作为替代。可以使用下面的代码测试环境中是否支持_ proto _:

u.__ proto __ === User.prototype; // true

所以在JavaScript中,类的概念是由构造函数(User)和用于实例间共享方法的原型对象(User.prototype)共同完成的。构造函数中负责构造每个对象特有的属性,比如上述例子中的name和password属性。而其原型对象中负责存放所有对象共有的属性,比如上述例子中的checkPassword和toString方法。就像下面这张图表示的那样:

跟我学习javascript的prototype,getPrototypeOf和__proto__

二、获取对象优先使用Object.getPrototypeOf,而不是_ proto _

在ES5中引入了Object.getPrototypeOf作为获取对象原型对象的标准API。但是在很多执行环境中,也提供了一个特殊的_ proto _属性来达到同样的目的。

因为并不是所有的环境都提供了这个_ proto _属性,且每个环境的实现方式各不相同,因此一些结果可能不一致,例如,对于拥有null原型的对象:

// 在某些环境中 
var empty = Object.create(null); // object with no prototype 
"_proto _" in empty; // false (in some environments) 

// 在某些环境中 
var empty = Object.create(null); // object with no prototype 
"_proto_" in empty; // true (in some environments)

所以当环境中支持Object.getPrototypeOf方法时,优先使用它。即使不支持,也可以为了实现一个:

if (typeof Object.getPrototypeOf === "undefined") { 
 Object.getPrototypeOf = function(obj) { 
  var t = typeof obj; 
  if (!obj || (t !== "object" && t !== "function")) { 
   throw new TypeError("not an object"); 
  } 
  return obj._proto_; 
 }; 
}

上述代码首先会对当前环境进行检查,如果已经支持了Object.getPrototypeOf,就不会再重复定义。

三、绝不要修改_ proto _

和Object.getPrototypeOf相比,_ proto _的特殊之处还体现在它能够修改一个对象的原型继承链。因为它是一个属性,除了执行获取它的操作外,还能够对它进行设置。

但是,绝不要修改_ proto _。原因如下:

  • 首先,最显而易见的原因就是便携性。因为不是所有的JavaScript执行环境都支持这一属性,所以使用了_ proto _ 之后,代码就不能在那些不支持_ proto _的环境中运行了。
  • 其次,是性能上的考虑。现在的JavaScript引擎的实现都会针对对象属性的存取作出大量的优化,因为这些操作是最常用的。当修改了对象的_ proto _后,就相当于修改了对象的整个继承结构,这样做回导致很多优化都不再可用。
  • 最后,最重要的原因是需要保证程序的可靠性。因为改变_ proto _属性后,对象的原型继承链也许会被完全地改变。当程序中有其他代码依赖于原来的继承链时,就会出现不可意料的错误。通常而言,原型继承链需要保持稳定。

当需要为一个新创建的对象赋予一个原型对象时,可以使用ES5提供的Object.create方法。对于未实现ES5标准的环境,可以给出来一个不依赖于_ proto _的Object.create方法的实现。

四、解决 _ proto _兼容问题, 让构造函数不再依赖new关键字

在将function当做构造函数使用时,需要确保该函数是通过new关键字进行调用的。

function User(name, passwordHash) { 
 this.name = name; 
 this.passwordHash = passwordHash; 
}

如果在调用上述构造函数时,忘记了使用new关键字,那么:

var u = User("baravelli", "d8b74df393528d51cd19980ae0aa028e"); 
u; // undefined 
this.name; // "baravelli" 
this.passwordHash; // "d8b74df393528d51cd19980ae0aa028e"

可以发现得到的u是undefined,而this.name以及this.passwordHash则被赋了值。但是这里的this指向的则是全局对象。

如果将构造函数声明为依赖于strict模式:

function User(name, passwordHash) { 
 "use strict"; 
 this.name = name; 
 this.passwordHash = passwordHash; 
} 
var u = User("baravelli", "d8b74df393528d51cd19980ae0aa028e"); 
// error: this is undefined

那么在忘记使用new关键字的时候,在调用this.name= name的时候会抛出TypeError错误。这是因为在strict模式下,this的默认指向会被设置为undefined而不是全局对象。

那么,是否有种方法能够保证在调用一个函数时,无论使用了new关键字与否,该函数都能够被当做构造函数呢?下面的代码是一种实现方式,使用了instanceof操作:

function User(name, passwordHash) { 
 if (!(this instanceof User)) { 
  return new User(name, passwordHash); 
 } 
 this.name = name; 
 this.passwordHash = passwordHash; 
} 

var x = User("baravelli", "d8b74df393528d51cd19980ae0aa028e"); 
var y = new User("baravelli", "d8b74df393528d51cd19980ae0aa028e"); 
x instanceof User; // true 
y instanceof User; // true

以上的if代码块就是用来处理没有使用new进行调用的情况的。当没有使用new时,this的指向并不是一个User的实例,而在使用了new关键字时,this的指向是一个User类型的实例。

另一个更加适合在ES5环境中使用的实现方式如下:

function User(name, passwordHash) { 
 var self = this instanceof User ? this : Object.create(User.prototype); 
 self.name = name; 
 self.passwordHash = passwordHash; 
 return self; 
}

Object.create方法是ES5提供的方法,它能够接受一个对象作为新创建对象的prototype。那么在非ES5环境中,就需要首先实现一个Object.create方法:

if (typeof Object.create === "undefined") { 
 Object.create = function(prototype) { 
  function C() { } 
  C.prototype = prototype; 
  return new C(); 
 }; 
}

实际上,Object.create方法还有接受第二个参数的版本,第二个参数表示的是在新创建对象上赋予的一系列属性。

当上述的函数确实使用了new进行调用时,也能够正确地得到返回的新建对象。这得益于构造器覆盖模式(Constructor Override Pattern)。该模式的含义是:使用了new关键字的表达式的返回值能够被一个显式的return覆盖。正如以上代码中使用了return self来显式定义了返回值。

当然,以上的工作在某些情况下也不是必要的。但是,当一个函数是需要被当做构造函数进行调用时,必须对它进行说明,使用文档是一种方式,将函数的命名使用首字母大写的方式也是一种方式(基于JavaScript语言的一些约定俗成)。

以上就是针对javascript的prototype,getPrototypeOf和__proto__进行的深入学习,希望对大家的学习有所帮助。

Javascript 相关文章推荐
checkbox 复选框不能为空
Jul 11 Javascript
IE7中javascript操作CheckBox的checked=true不打勾的解决方法
Dec 07 Javascript
javascript 仿QQ滑动菜单效果代码
Sep 03 Javascript
js获取IFRAME当前的URL的方法
Nov 13 Javascript
深入理解JavaScript系列(43):设计模式之状态模式详解
Mar 04 Javascript
基于JavaScript如何实现私有成员的语法特征及私有成员的实现方式
Oct 28 Javascript
ionic 上拉菜单(ActionSheet)实例代码
Jun 06 Javascript
jQuery插件HighCharts实现的2D条状图效果示例【附demo源码下载】
Mar 15 Javascript
基于JavaScript实现焦点图轮播效果
Mar 27 Javascript
AngularJS实现的JSONP跨域访问数据传输功能详解
Jul 20 Javascript
vue页面离开后执行函数的实例
Mar 13 Javascript
vue学习笔记之slot插槽基本用法实例分析
Feb 01 Javascript
Jquery 垂直多级手风琴菜单附源码下载
Nov 17 #Javascript
JavaScript代码实现禁止右键、禁选择、禁粘贴、禁shift、禁ctrl、禁alt
Nov 17 #Javascript
跟我学习javascript的undefined与null
Nov 17 #Javascript
跟我学习javascript的arguments对象
Nov 16 #Javascript
JavaScript函数学习总结以及相关的编程习惯指南
Nov 16 #Javascript
js实现获取div坐标的方法
Nov 16 #Javascript
跟我学习javascript的闭包
Nov 16 #Javascript
You might like
Docker 如何布置PHP开发环境
2016/06/21 PHP
微信小程序 消息推送php服务器验证实例详解
2017/03/30 PHP
PHP快速推送微信模板消息
2017/04/14 PHP
thinkphp5.1 文件引入路径问题及注意事项
2018/06/13 PHP
php策略模式简单示例分析【区别于工厂模式】
2019/09/25 PHP
js运动框架_包括图片的淡入淡出效果
2013/05/11 Javascript
JavaScript AJAX之惰性载入函数
2014/08/27 Javascript
jQuery 浮动导航菜单适合购物商品类型的网站
2014/09/09 Javascript
angularjs在ng-repeat中使用ng-model遇到的问题
2016/01/21 Javascript
JS 实现倒计时数字时钟效果【附实例代码】
2016/03/30 Javascript
jQuery实现输入框邮箱内容自动补全与上下翻动显示效果【附demo源码下载】
2016/09/20 Javascript
微信小程序开发经验总结(推荐)
2017/01/11 Javascript
JS得到当前时间的方法示例
2017/03/24 Javascript
详解Vue2.0之去掉组件click事件的native修饰
2017/04/20 Javascript
AngularJS中使用ngModal模态框实例
2017/05/27 Javascript
JavaScript正则表达式校验与递归函数实际应用实例解析
2017/08/04 Javascript
javascript显示动态时间的方法汇总
2018/07/06 Javascript
nodejs dgram模块广播+组播的实现示例
2019/11/04 NodeJs
小程序中设置缓存过期的实现方法
2020/01/14 Javascript
JS数组及对象遍历方法代码汇总
2020/06/16 Javascript
node使用async_hooks模块进行请求追踪
2021/01/28 Javascript
python实现提取百度搜索结果的方法
2015/05/19 Python
利用python如何处理百万条数据(适用java新手)
2018/06/06 Python
详解python--模拟轮盘抽奖游戏
2019/04/12 Python
使用 Django Highcharts 实现数据可视化过程解析
2019/07/31 Python
利用Python脚本实现自动刷网课
2020/02/03 Python
使用 pytorch 创建神经网络拟合sin函数的实现
2020/02/24 Python
jupyter notebook 多环境conda kernel配置方式
2020/04/10 Python
Python 跨.py文件调用自定义函数说明
2020/06/01 Python
美国购买当代和现代家具网站:MODTEMPO
2018/07/20 全球购物
GetYourGuide台湾:预订旅游活动、景点和旅游项目
2019/06/10 全球购物
机械工程及自动化专业求职信
2014/09/03 职场文书
房屋所有权证明
2014/10/20 职场文书
2014年护士长工作总结
2014/11/11 职场文书
运动会新闻稿
2015/07/17 职场文书
Win11 25163.1010更新补丁KB5016904推送,测试服务验证管道(附更新修复汇总)
2022/07/23 数码科技