跟我学习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 相关文章推荐
jQuery 扩展对input的一些操作方法
Oct 30 Javascript
AngularJS学习笔记之基本指令(init、repeat)
Jun 16 Javascript
Vue.js路由组件vue-router使用方法详解
Dec 02 Javascript
JavaScript用构造函数如何获取变量的类型名
Dec 23 Javascript
React组件的三种写法总结
Jan 12 Javascript
vue中的provide/inject的学习使用
May 09 Javascript
Vue中Axios从远程/后台读取数据
Jan 21 Javascript
构建大型 Vue.js 项目的10条建议(小结)
Nov 14 Javascript
Vue axios与Go Frame后端框架的Options请求跨域问题详解
Mar 03 Javascript
vue2.0实现列表数据增加和删除
Jun 17 Javascript
uniapp实现可以左右滑动导航栏
Oct 21 Javascript
解决ant Design中this.props.form.validateFields未执行的问题
Oct 27 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
php中url传递中文字符,特殊危险字符的解决方法
2013/08/17 PHP
php简单统计中文个数的方法
2016/09/30 PHP
thinkphp自定义权限管理之名称判断方法
2017/04/01 PHP
用js实现的检测浏览器和系统的函数
2009/04/09 Javascript
jQuery学习2 选择器的使用说明
2010/02/07 Javascript
js 自动播放的实例代码
2013/11/19 Javascript
禁用Tab键JS代码兼容Firefox和IE
2014/04/18 Javascript
原生javascript实现隔行换色
2015/01/04 Javascript
jquery模拟多级复选框效果的简单实例
2016/06/08 Javascript
原生JS:Date对象全面解析
2016/09/06 Javascript
简单实现js悬浮导航效果
2017/02/05 Javascript
解析Vue2.0双向绑定实现原理
2017/02/23 Javascript
javascript中call()、apply()的区别
2019/03/21 Javascript
用js简单提供增删改查接口
2019/05/12 Javascript
vue实现页面滚动到底部刷新
2019/08/16 Javascript
vue实现购物车小案例
2019/09/27 Javascript
JavaScript中break、continue和return的用法区别实例分析
2020/03/02 Javascript
Python实现购物车购物小程序
2018/04/18 Python
Python实现括号匹配方法详解
2020/02/10 Python
jupyter note 实现将数据保存为word
2020/04/14 Python
python模拟点击在ios中实现的实例讲解
2020/11/26 Python
Python try except else使用详解
2021/01/12 Python
巴西家用小家电购物网站:Polishop
2016/08/07 全球购物
Bravofly德国:预订廉价航班和酒店
2019/09/22 全球购物
俄罗斯珠宝市场的领导者之一:Бронницкий ювелир
2019/10/02 全球购物
什么是TCP/IP
2014/07/27 面试题
逻辑链路控制协议
2016/10/01 面试题
大学生村官事迹材料
2014/01/21 职场文书
竞选大队长演讲稿
2014/04/29 职场文书
文明班级建设方案
2014/05/15 职场文书
高职教师先进事迹材料
2014/08/24 职场文书
2015年留守儿童工作总结
2015/05/22 职场文书
2015年法律事务部工作总结
2015/07/27 职场文书
Matplotlib可视化之添加让统计图变得简单易懂的注释
2021/06/11 Python
Pygame Time时间控制的具体使用详解
2021/11/17 Python
Java 中的 Lambda List 转 Map 的多种方法详解
2022/07/07 Java/Android