Home  >  Article  >  Web Front-end  >  跟我学习javascript的prototype,getPrototypeOf和__proto___javascript技巧

跟我学习javascript的prototype,getPrototypeOf和__proto___javascript技巧

WBOY
WBOYOriginal
2016-05-16 15:32:031425browse

一、深入理解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方法。就像下面这张图表示的那样:

二、获取对象优先使用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__进行的深入学习,希望对大家的学习有所帮助。

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn