关于这个知识点以前也有写过,这次拿出来专门写一篇加深下记忆

原型与原型链

原型其实就是一个对象,其他的对象可以通过它来实现属性的继承。而且,任何一个对象,都可以成为原型。而且所有的对象,默认情况下都有一个原型。 毕竟原型本身也就是对象,所以每一个原型自身也有其自身的原型。出了一个例外,那就是原型链的顶端–null。

一个对象的真正原型是被对象内部[[Prototype]]所持有,ECMA 引入了标准对象原型访问器,Object.getPropertyOf(object)

var proto = {};
var obj = Object.create(proto);
Object.getPrototypeOf(obj) === proto; // true

当然,我们也可以通过非标准的访问器__proto__来获取实例的原型。

原型的真正魅力在于多个实例之间公用一个通用原型的时候,原型对象的属性一旦被定义,就可以被多个引用他的实例所继承。这种操作在性能和维护方面的意义也是不言而喻的。

不使用原型的例子

var decimalDigits = 2,
  tax = 5;

function add(x, y) {
  return x + y;
}

function subtract(x, y) {
  return x - y;
}

//alert(add(1, 3));

原型使用方式一

var Calculator = function(decimalDigits, tax) {
  this.decimalDigits = decimalDigits;
  this.tax = tax;
};
Calculator.prototype = {
  add: function(x, y) {
    return x + y;
  },

  subtract: function(x, y) {
    return x - y;
  }
};
//alert((new Calculator()).add(1, 3));

原型使用方式二

Calculator.prototype = (function() {
  add = function(x, y) {
    return x + y;
  };

  subtract = function(x, y) {
    return x - y;
  };

  return {
    add: add,
    subtract: subtract
  };
})();

//alert((new Calculator()).add(11, 3));

很明显方式二的写法更好一点,因为它封装了 function,通过 return 形式暴露出简单的使用名称,达到了 private/public 的效果

当然,我们也可以分步声明,可以像给对象添加属性的方式那样来添加原型上的方法

var BaseCalculator = function() {
  //为每个实例都声明一个小数位数
  this.decimalDigits = 2;
};

//使用原型给BaseCalculator扩展2个对象方法
BaseCalculator.prototype.add = function(x, y) {
  return x + y;
};

BaseCalculator.prototype.subtract = function(x, y) {
  return x - y;
};

重写原型

在使用第三方 JS 类库的时候,往往有时候他们定义的原型方法是不能满足我们的需要,但是又离不开这个类库,所以这时候我们就需要重写他们的原型中的一个或者多个属性或 function,我们可以通过继续声明的同样的 add 代码的形式来达到覆盖重写前面的 add 功能,代码如下:

//覆盖前面Calculator的add() function
Calculator.prototype.add = function(x, y) {
  return x + y + this.tax;
};

var calc = new Calculator();
alert(calc.add(1, 1));

但是有一点需要注意:那就是重写的代码需要放在最后,这样才能覆盖前面的代码。

原型链

function Foo() {
  this.value = [1, 2, 4];
}
Foo.prototype = {
  method: function() {}
};

function Bar() {}

// 设置Bar的prototype属性为Foo的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';

// 修正Bar.prototype.constructor为Bar本身
Bar.prototype.constructor = Bar;

var test = new Bar() // 创建Bar的一个新实例

// 原型链
test[Bar的实例]
Bar.prototype[Foo的实例] {
  foo: 'Hello World'
}
Foo.prototype {
  method: ...
};
Object.prototype {
  toString: ... /* etc. */
};

上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;因此,它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。需要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同的 value 属性。

hasOwnProperty

hasOwnProperty 是 Object.prototype 的一个方法,它能判断一个对象是否包含自定义属性而不是原型链上的属性,因为 hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。

// 修改Object.prototype
Object.prototype.bar = 1;
var foo = {
  goo: undefined
};

foo.bar; // 1
'bar' in foo; // true

foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true

只有 hasOwnProperty 可以给出正确和期望的结果,这在遍历对象的属性时会很有用。 没有其它方法可以用来排除原型链上的属性,而不是定义在对象自身上的属性。

但有个恶心的地方是:JavaScript 不会保护 hasOwnProperty 被非法占用,因此如果一个对象碰巧存在这个属性,就需要使用外部的 hasOwnProperty 函数来获取正确的结果。

var foo = {
  hasOwnProperty: function() {
    return false;
  },
  bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); // 总是返回 false

// 使用{}对象的 hasOwnProperty,并将其上下为设置为foo
{}.hasOwnProperty.call(foo, 'bar'); // true

当检查对象上某个属性是否存在时,hasOwnProperty 是唯一可用的方法。同时在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty 方法,这将会避免原型对象扩展带来的干扰。