1、深入理解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之间的关系可以表示如下:

上图中的箭头表示的是继承关系。当访问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方法。就像下面这张图表示的那样:

2、获取对象优先使用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,就不会再重复定义。

3、绝不要修改_ proto _

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

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

  • 首先,最显而易见的原因就是便携性。因为不是所有的JavaScript执行环境都支持这一属性,所以使用了_ proto _ 之后,代码就不能在那些不支持_ proto _的环境中运行了。

  • 其次,是性能上的考虑。现在的JavaScript引擎的实现都会针对对象属性的存取作出大量的优化,因为这些操作是最常用的。当修改了对象的_ proto _后,就相当于修改了对象的整个继承结构,这样做回导致很多优化都不再可用。

  • 最后,最重要的原因是需要保证程序的可靠性。因为改变_ proto _属性后,对象的原型继承链也许会被完全地改变。当程序中有其他代码依赖于原来的继承链时,就会出现不可意料的错误。通常而言,原型继承链需要保持稳定。

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

4、解决 _ 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语言的一些约定俗成)。


系列文章导航:

1、你不知道的JavaScript–Item1 严格模式

2、你不知道的JavaScript–Item2 浮点数精度

3、你不知道的JavaScript–Item3 隐式强制转换

4、你不知道的JavaScript–Item4 基本类型和基本包装类型(引用类型)

5、你不知道的JavaScript–Item5 全局变量

6、你不知道的JavaScript–Item6 var预解析与函数声明提升(hoist )

7、你不知道的JavaScript–Item7 函数和(命名)函数表达式

8、你不知道的JavaScript–Item8 函数,方法,构造函数调用

9、你不知道的JavaScript–Item9 call(),apply(),bind()与回调

10、你不知道的JavaScript–Item10 闭包(closure)

11、你不知道的JavaScript–Item11 arguments对象

12、你不知道的JavaScript–Item12 undefined 与 null

13、你不知道的JavaScript–Item13 理解 prototype, getPrototypeOf 和_ proto_

14、你不知道的JavaScript–Item14 使用prototype的几点注意事项

15、你不知道的JavaScript–Item15 prototype原型和原型链详解

16、你不知道的JavaScript–Item16 for 循环和for…in 循环的那点事儿

17、你不知道的JavaScript–Item17 循环与prototype最后的几点小tips

18、你不知道的JavaScript–Item18 JScript的Bug与内存管理

19、你不知道的JavaScript–Item19 执行上下文(execution context)

20、你不知道的JavaScript–Item20 作用域与作用域链(scope chain)

21、你不知道的JavaScript–Item21 漂移的this


持续更新中……………….

版权声明:本文为小平果原创文章,转载请注明:http://blog.csdn.net/i10630226

你不知道的JavaScript--Item13 理解 prototype, getPrototypeOf 和__proto__的更多相关文章

  1. Effective JavaScript Item 30 理解prototype, getPrototypeOf和__proto__的不同

    本系列作为Effective JavaScript的读书笔记. prototype,getPropertyOf和__proto__是三个用来訪问prototype的方法.它们的命名方式非常类似因此非常 ...

  2. JavaScript:理解prototype与__proto__,原型与原型链

    JS中的继承是原型继承,通过原型实现的.为了理解原型,我想先讲讲对象的内部属性[[prototype]]和属性__proto__,函数的属性prototype. 对象的内部属性[[prototype] ...

  3. 你不知道的JavaScript--Item17 循环与prototype最后的几点小tips

    1.优先使用数组而不是Object类型来表示有顺序的集合 ECMAScript标准并没有规定对JavaScript的Object类型中的属性的存储顺序. 但是在使用for..in循环对Object中的 ...

  4. 《你不知道的JavaScript》系列分享专栏

    <你不知道的JavaScript>系列分享专栏 你不知道的JavaScript”系列就是要让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaScript每一个零部 ...

  5. 深入理解JavaScript原型:prototype,__proto__和constructor

    JavaScript语言的原型是前端开发者必须掌握的要点之一,但在使用原型时往往只关注了语法,其深层的原理并未理解透彻.本文结合笔者开发工作中遇到的问题详细讲解JavaScript原型的几个关键概念, ...

  6. Javascript中的prototype和__proto__的联系区别

    转载至http://www.cnblogs.com/sinstone/p/5136871.html   一.联系 prototype和__proto__都指向原型对象,任意一个函数(包括构造函数)都有 ...

  7. 【你不知道的javaScript 上卷 笔记7】javaScript中对象的[[Prototype]]机制

    [[Prototype]]机制 [[Prototype]]是对象内部的隐试属性,指向一个内部的链接,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就 会继续在 [[Prototyp ...

  8. 简单理解javascript的原型prototype

    原型和闭包是Js语言的难点,此文主要讲原型. 每一个方法都有一个属性是 prototype 每一个对象都有一个属性是 _proto_ 一旦定义了原型属性或原型方法,则所有通过该构造函数实例化出来的所有 ...

  9. JavaScript中的prototype和__proto__细致解析

    最近在学js,体会了一点点它的灵活性.对于初学者的我,总是被它的灵活感到晕头转向,最近发现了一点东西想与大家分享. JavaScript中的prototype和_proto_: 我们先了解一点js中的 ...

随机推荐

  1. jQuery中常用的函数方法总结

    jQuery中为我们提供了很多有用的方法和属性,自己总结的一些常用的函数,方法.个人认为在开发中会比较常用的,仅供大家学习和参考. 事件处理 ready(fn) 代码: $(document).rea ...

  2. linux文件查找及操作

    在linux下查找文件的办法最常用的就是find指令,让我们来看一下find指令如何来使用吧: find find . -name  txt             //在当前目录查找名字为txt的文 ...

  3. bulk-load 装载HDFS数据到HBase

    bulk-load的作用是用mapreduce的方式将hdfs上的文件装载到hbase中,对于海量数据装载入hbase非常有用,参考http://hbase.apache.org/docs/r0.89 ...

  4. 用windbg显示特定进程虚拟地址的方法

    1 必须使用Livekd.exe启动windbg 2 !process PID 3 找到PROCESS对象的地址:PROCADDR 4 找到进程的DirBase 5 r cr3 = DirBase 6 ...

  5. Java IO学习--(三)通道

    Java IO中的管道为运行在同一个JVM中的两个线程提供了通信的能力.所以管道也可以作为数据源以及目标媒介. 你不能利用管道与不同的JVM中的线程通信(不同的进程).在概念上,Java的管道不同于U ...

  6. angular4 ionic3 app

    对于angular系列来说,从2到4仅仅是版本号的变更,绝大部分都是兼容的.  如果按照规范编写代码,一般来说是没有问题的. 学习angular4     快速入门参考  https://www.an ...

  7. day09_request&response学习笔记

    ============================================================ 一.HttpServletResponse接口 p.MsoNormal { m ...

  8. 转载 jQueryEasyUI Messager基本使用

    http://www.cnblogs.com/libingql/archive/2011/07/17/2109020.html 一.jQueryEasyUI下载地址 http://www.jeasyu ...

  9. java线程池原理

    在什么情况下使用线程池?     1.单个任务处理的时间比较短     2.将需处理的任务的数量大     使用线程池的好处:     1.减少在创建和销毁线程上所花的时间以及系统资源的开销     ...

  10. webservice入门简介

    为了梦想,努力奋斗! 追求卓越,成功就会在不经意间追上你 webservice入门简介 1.什么是webservice? webservice是一种跨编程语言和跨操作系统平台的远程调用技术. 所谓的远 ...