如何编写高效的js代码(5)

了解prototype,getPrototypeOf和proto的不同

  • C.prototype是用来建立与用new C()生成的对象实例的原型关系
  • Object.getPrototypeOf(obj)是ES5用来获取某个对象原型的方法
  • obj._proto_是某些浏览器获取某个对象原型的方式,该属性并未纳入到标准中

拿代码来举例

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

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

u.__proto__ === Foo.prototype; // true 某些提供了__proto__  

如果说Foo是Class的话,那么Foo.prototype就是这个函数的原型,并被所有实例继承,某些js引擎中实例访问原型能通过 proto属性,再拿一副图来说明

一般人认为Foo就是一个Class,但其实Foo函数和它的原型对象Foo.prototype共同组合起来才有点像是传统意义上的类,User 函数有点像构造函数,而Foo.prototype提供了继承机制,使得各个实例中可以共享方法

另外通过上图可知Foo.prototype.constructor反过来指向了Foo函数

通过new实例出来的构造函数的默认返回值是this指向的对象,如果显示返回其他对象则返回其他对象,如果返回非Obect类型的如string等,则仍然默认返回this指向的对象

如果使用Foo函数的时候忘记new关键字是件很可怕的事情,那样的话Foo函数会返回undefined,并且this指向了全局的window

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

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

如果使用ES5的严格模式,this会为undefined,设置this.name会报错

function User(name, passwordHash) {  
  "use strict";
  this.name = name;//Uncaught TypeError: Cannot set property 'name' of undefined 
  this.passwordHash = passwordHash;
}
var u = User("baravelli","d8b74df393528d51cd19980ae0aa028e");  
// error: this is undefined

为了使得代码更健壮,无论是否遗漏new关键字都能当作构造函数运行,使用如下的方法

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

另外一种借助ES5的方式如下

function User(name, passwordHash) {  
  var self = this instanceof User ? this: Object.create(User.prototype);//Object.create接受一个对象作为原型并返回一个继承自该原型的对象
  self.name = name;
  self.passwordHash = passwordHash;


  return self;
}

对于不支持ES5的浏览器,自己实现Object.create方法

if(typeofObject.create === "undefined") {  
  Object.create = function(prototype) {//注意这里只实现了一个参数的情况,ES5的create后面有第2个可选参数
    function C() { }
    C.prototype = prototype;
    return new C();
  };
}

尽量使用Object.getPrototypeOf,而不要使用 _ proto_

ES5中标准的获取prototype的方法是Object.getPrototypeOf,但是有些js引擎提供了_ proto_ 属性来实现相同的功能,有些则没有

根据js引擎环境的不同,还有些兼容性方面的差异,有些浏览器下,_ proto是继承自Object.prototype,因此如果某个object的原型为null,他们它应该没有 _ proto __

var empty = Object.create(null);// object with no prototype  
"__proto__" in empty; // false (in some environments)

` 但是有些js引擎环境会无论object的状态都会处理_ proto _

var empty = Object.create(null);// object with no prototype  
"__proto__" in empty; // true (in some environments)

以后的ES规范可能会禁用掉_ proto _,因此为了我们的代码足够兼容而且健壮,尽可能的使用Object.getPrototypeOf,除非浏览器不支持此方法

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

不要手动修改 _ proto _

直接修改proto属性会影响对象的整个继承链,同时会产生性能影响,而且由于修改了proto原型链,可能产生未预期的一些行为,如果真的想给某个对象设置原型链,使用ES5的Object.create方法