JavaScript中的继承与原型链

先看一个例子

function User(){}

var u1 = new User();
console.log(u1.prototype);// undefined 使用对象实例无法访问到prototype
console.log(User.prototype);//{},使用构造函数名访问prototype
console.log(u1.__proto__);//{},使用对象实例访问prototype的指针

这个是 __proto__ 和prototype最基本的区别:说明构造的对象无prototype属性,每一个对象相当于自带一个 __proto__指针,指向其父类,和C中的单链表类似(这里形象的比喻)

每个函数创建时默认带有一个prototype属性,其中包含一个constructor属性,和一个指向Object对象的隐藏属性 __proto__

1、constructor属性的值为该函数的对象

2、在一个函数前面加上new来调用,则会创建一个隐藏连接到该函数prototype 成员的新对象(由__proto__属性来链接),同时函数的this将会被绑定到那个新对象上。

函数总是返回一个值;如果没有指定返回值,就返回undefined;如果当做构造函数来调用,且返回值不是对象,则返回this(该新对象);如果返回值是对象,则它作为构造函数是没有意义的!

定义

继承方面,JavaScript 中的每个对象都有一个内部私有的链接指向另一个对象,这个对象就是原对象的原型。这个原型对象也有自己的原型,直到对象的原型为 null 为止(也就是没有原型)。这种一级一级的链结构就称为原型链

虽然这通常会被称作 JavaScript 的弱点之一,实际上这种原型继承的模型要比经典的继承模型还要强大。虽然在原型模型上构建一个经典模型是相当琐碎的,但如果采取其他方式实现则会更加困难。

使用不同的方法来创建对象和生成原型链

使用普通语法创建对象

var o = {a: 1};

// o这个对象继承了Object.prototype上面的所有属性
// 所以可以这样使用 o.hasOwnProperty('a').
// hasOwnProperty 是Object.prototype的自身属性。
// Object.prototype的原型为null。
// 原型链如下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// 数组都继承于Array.prototype (indexOf, forEach等方法都是从它继承而来).
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函数都继承于Function.prototype(call, bind等方法都是从它继承而来):
// f ---> Function.prototype ---> Object.prototype ---> null

使用构造方法创建对象

在 JavaScript 中,构造方法其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。

function Graph() {
  this.vertexes = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertexes.push(v);
  }
};

var g = new Graph();
// g是生成的对象,他的自身属性有'vertexes'和'edges'.
// 在g被实例化时,g.[[Prototype]]指向了Graph.prototype.

使用 Object.create 创建对象

ECMAScript 5 中引入了一个新方法:Object.create。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:

var a = {a: 1}; 
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype

1、原型链

// 函数声明
function Person() {}
// 函数表达式
var Man = function() {}

console.log(Person.__proto__ === Function.prototype) // true
console.log(Man.__proto__ === Function.prototype) // true 
console.log(Function.prototype.__proto__ === Object.prototype) // true 
console.log(Object.prototype.__proto__ === null ) // true

Object.prototype.a =1 ;
Function.prototype.a =10;

console.log(Man.a); // 10

从上面我们可以看到原型链

2、所有构造器/函数的__proto__都指向Function.prototype

console.log(Number.__proto__ === Function.prototype) // true
console.log(Boolean.__proto__ === Function.prototype) // true
console.log(String.__proto__ === Function.prototype) // true
console.log(Object.__proto__ === Function.prototype) // true
console.log(Function.__proto__ === Function.prototype) // true
console.log(Array.__proto__ === Function.prototype) // true
console.log(RegExp.__proto__ === Function.prototype) // true
console.log(Error.__proto__ === Function.prototype) // true
console.log(Date.__proto__ === Function.prototype) // true 

不过、Math,JSON是以对象形式存在的,无需new。它们的__proto__是Object.prototype

console.log(Math.__proto__ === Object.prototype) // true
console.log(JSON.__proto__ === Object.prototype) // true 

3、所有的构造器都来自于Function.prototype

包括根构造器Object及Function自身。所有构造器都继承了Function.prototype的属性及方法。如length、call、apply、bind(ES5)、Function.prototype也是唯一一个typeof XXX.prototype为 “function”的prototype。其它的构造器的prototype都是一个对象。如下

console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype) // object
console.log(typeof Number.prototype) // object
console.log(typeof Boolean.prototype) // object
console.log(typeof String.prototype) // object
console.log(typeof Array.prototype) // object
console.log(typeof RegExp.prototype) // object
console.log(typeof Error.prototype) // object
console.log(typeof Date.prototype) // object
console.log(typeof Object.prototype) // object 

有一个东西需要理清楚

console.log(typeof Object.prototype) // object 
console.log(Object.__proto__ === Function.prototype) // true 
console.log(Function.prototype.__proto__ === Object.prototype) // true
console.log(Object.__proto__.__proto__=== Object.prototype) // true
console.log(Function.prototype.__proto__ .__proto__=== null) // true

所以最顶级的就是Object.prototype.__proto__ === null

4、上面说的“所有构造器/函数”当然包括自定义的

function Person(name) {
this.name = name
}
var p = new Person('jack')
console.log(p.__proto__.__proto__ === Person.prototype.__proto__) // true 
console.log(Person.prototype.__proto__ === Object.prototype) // true 

5、p是Person的实例对象,p的内部原型总是指向其构造器Person的prototype。 每个对象都有一个constructor属性,可以获取它的构造器,因此以下打印结果也是恒等的

function Person(name) {
this.name = name
}
var p = new Person('jack')
console.log(p.__proto__ === p.constructor.prototype) // true 

6、看一段代码 原文地址 http://segmentfault.com/q/1010000003791600/a-1020000003793415 ,关于prototype 和 __proto__ 的区别

简介:__proto__其实就是 [[prototype]]属性原本是不可用 js 访问的,后来(据说标准里又规定可以)firefox 和 chrome 中把这个属性命名为__proto__。后来ES又添加了函数getPrototypeof,这样就可以通过这个函数来访问这个属性,所以这个__proto__现在不是标准的一部分

代码一

<script type="text/javascript">

var animal = function(){};
var dog = function(){};

animal.price = 2000;
dog.__proto__ = animal;
var tidy = new dog();

console.log(dog.__proto__ === animal) //true
console.log(dog.__proto__.__proto__ === animal.__proto__) //true
console.log(dog.__proto__.__proto__.__proto__  === Object.prototype) //true
console.log(dog.__proto__.__proto__.__proto__.__proto__  === null) //true

console.log(tidy.__proto__  === dog.prototype) //true
console.log(tidy.__proto__.__proto__ === dog.prototype.__proto__) //true
console.log(tidy.__proto__.__proto__ === Object.prototype) //true
console.log(tidy.__proto__.__proto__.__proto__ === null) //true

console.log(dog.price) //2000
console.log(tidy.price) // undefined

</script>

代码二

<script type="text/javascript">

var animal = function(){};
var dog = function(){};

animal.price = 2000;
dog.prototype = animal;

var tidy = new dog();

console.log(tidy.__proto__  === dog.prototype) //true
console.log(tidy.__proto__.__proto__  === animal.__proto__) //true
console.log(tidy.__proto__.__proto__.__proto__ === Object.prototype) //true
console.log(tidy.__proto__.__proto__.__proto__.__proto__ === null) //true

console.log(dog.__proto__  === Function.prototype) //true
console.log(dog.__proto__.__proto__  === Object.prototype) //true
console.log(dog.__proto__.__proto__.__proto__  === null) //true

console.log(dog.price) //undefined
console.log(tidy.price) // 2000

</script>

总结

1、所有对象,包括函数对象的原型链最终都指向了Object.prototype,而Object.prototype.__proto__===null,原型链至此结束。

2、Animal.prototype是一个普通对象。

3、Object是一个函数对象,也是Function构造的,Object.prototype是一个普通对象。

4、Object.prototype.__type__指向null。

5、Function.prototype是一个函数对象,前面说函数对象都有一个显示的prototype属性,但是Function.prototype却没有prototype属性,即Function.prototype.prototype===undefined,所有Function.prototype函数对象是一个特例,没有prototype属性。

6、Object虽是Function构造的一个函数对象,但是Object.prototype没有指向Function.prototype,即Object.prototype!==Function.prototype。

Prototype跟Constructor关系介绍

在 JavaScript 中,每个函数对象都有名为“prototype”的属性(上面提到过Function.prototype函数对象是个例外,没有prototype属性),用于引用原型对象。此原型对象又有名为“constructor”的属性,它反过来引用函数本身。这是一种循环引用(i.e. Animal.prototype.constructor===Animal)

console.log('**************constructor****************'); 

console.log('anim.constructor===Animal:'+(anim.constructor===Animal))    ;    //true
console.log('Animal===Animal.prototype.constructor:'+(Animal===Animal.prototype.constructor))    ;    //true
console.log('Animal.constructor===Function.prototype.constructor:'+(Animal.constructor===Function.prototype.constructor));   //true
console.log('Function.prototype.constructor===Function:'+(Function.prototype.constructor===Function));    //true
console.log('Function.constructor===Function.prototype.constructor:'+(Function.constructor===Function.prototype.constructor));    //true

console.log('Object.prototype.constructor===Object:'+(Object.prototype.constructor===Object));    //true
console.log('Object.constructor====Function:'+(Object.constructor===Function));    //true

上面代码中用到的__proto__目前在IE6/7/8/9中都不支持。IE9中可以使用Object.getPrototypeOf(ES5)获取对象的内部原型。

var p = {}
var __proto__ = Object.getPrototypeOf(p)
console.log(__proto__ === Object.prototype) // true

性能

在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。

遍历对象的属性时,原型链上的每个属性都是可枚举的。

检测对象的属性是定义在自身上还是在原型链上,有必要使用 hasOwnProperty 方法,该方法由所有对象继承自 Object.proptotype

hasOwnProperty 是 JavaScript 中唯一一个只涉及对象自身属性而不会遍历原型链的方法。

注意:仅仅通过判断值是否为 undefined 还不足以检测一个属性是否存在,一个属性可能存在而其值恰好为 undefined

不好的实践:扩展原生对象的原型

一个经常使用的不好实践是扩展 Object.prototype 或者其他内置对象的原型。

该技术被称为 monkey patching,它破坏了对象的封装性。虽然一些流行的框架(如 Prototype.js)在使用该技术,但是该技术依然不是好的实践,附加的非标准的方法使得内置的类型混乱。

扩展内置对象原型的唯一正当理由是移植较新 JavaScript 引擎的特性,如 Array.forEach

结论

在编写使用到原型继承模型的复杂代码前理解原型继承模型十分重要。同时,还要清楚代码中原型链的长度,并在必要时结束原型链,以避免可能存在的性能问题。更进一步,除非为了兼容新 JavaScript 特性,否则永远不要扩展原生对象的原型。

参考文章

http://dmitrysoshnikov.com/ecmascript/javascript-the-core/

http://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript

http://segmentfault.com/q/1010000003791600?_ea=368532

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain