原型对象
在JS中,对象有三种创建方式。
//第一种方式:对象字面量
var a = { name:'xiaoming', age:18 };
//第二种方式:使用new调用函数,这样的函数称为构造函数,一般构造函数名首字母大写。本质上还是函数。
function Fun(name, age)
{
this.name = name;
this.age = age;
this.show = function() { console.log(name + age); };
}
var b = new Fun('xiaoming', 18);
//第三种方式:使用 Object类的create方法。
var c = Object.create(null); //create函数的参数为新创建对象的原型对象。
//(原型对象是什么后续会有说明)
JS中有以下几个基本事实
- JS中,万物皆是对象。所有的对象中(无论是内建对象还是自定义对象,或者函数)都会默认有一个
__proto__
属性。基本上所有的浏览器都实现了这个属性,但是不建议在代码中使用这个属性,所以它使用了一个比较怪异的名字__proto__
,表示只能在内部使用,也叫隐式属性,意思是一个隐藏起来的属性。__proto__
属性指向当前对象的构造函数的原型对象,它保证了对象实例能够访问在构造函数原型中定义的所有属性和方法。 - JS中的每一个函数(也可称为函数对象,因为函数是一种特殊的对象,本质上依旧是对象。)当使用function关键字创建函数的时候,会自动创建一个属性
prototype
,该属性指向一个对象,该对象称为该函数的原型对象。(原型对象中也会有一个constructor
属性指向该函数)。 - 一个对象的
__proto__
属性和构造这个对象的函数的prototype
属性指向同一个对象。 - JS通过使对象继承其原型对象链上的属性来达到面向对象编程中的继承特性。
- 需要注意:函数一定是一个对象,但对象不一定是函数。JS中只有函数拥有prototype属性。
接下来,便可以通过上面几个事实分析,JS中对象的原型链
function Fun(name, age)
{
this.name = name;
this.age = age;
this.show = function() { console.log(name + age); };
}
var b = new Fun(name, age);
//由于变量b是一个由Fun实例化的对象。因此,具有__proto__属性,指向构造函数Fun()的原型对象
b.__proto__ == Fun.prototype; //true
//Fun.prototype也是一个对象,在不指定的情况下一般像下面这样(伪码)。一个属性constructor
//指向Fun函数。一个属性__proto__指向构造这个原型对象的函数的原型对象,即Object.prototype。
//注意:下面的这个原型对象只是一个单纯的对象所以没有prototype属性。即不会出现
//Fun.prototype.prototype...这样的链即使出现了,他也不是原型链。原型链应该是
//b.__proto__.__proto__...或者Fun.prototype.__proto__.__proto__这样的链。
//{
// constructor:Fun();
// __proto__:Object.prototype;
//}
b.__proto__.__proto__ == Object.prototype; //true
Object.prototype.__proto__ == null; //true 这里便是JS原型继承链的顶端了
b.__proto__.__proto__.__proto__ == null; //true 与上一句含义相同
//接下来分析几个特别的部分。
//Fun函数。因为Fun函数也是一个对象,它由Function()构造函数创建,所以
Fun.__proto__ == Function.prototype; //true
//Function也是一个对象,它由自己创建,所以
Function.__proto__ == Function.prototype; //true
//Object构造函数也是一个对象,它由Function创建,所以
Object.__proto__ == Function.prototype; //true
//最后,Function的原型对象Function.prototype是由Object构造函数创建的,因此
Function.prototype == Function.__proto__ == Object.__proto__ == Object.prototype;
//true
接下来演示继承怎么实现
function Foo(name, age)
{
var sex = '男'; //这个变量和函数属于函数作用域。既不会在创建的对象实例中看到它,也不会再
function tmp(){} //继承链上看到它。在使用普通函数调用的情况会执行这部分语句。
this.name = name; //使用this的变量,在实例化对象时被执行(即,使用new调用函数),this
//指向被实例化的那个对象
this.age = age; //并赋值,这也是构造函数的原理。
this.show = function(){ return this.name + ':::' + this.age; };
}
//下面这些属性称为函数对象的静态属性,与上面的属性不同。上面的属性是Foo作为函数或构造函数的
//某种属性,只有在调用Foo函数(无论是普通调用还是new)才有实际意义。而静态属性是Foo作为对象
//应有的属性,属于Foo这个对象本身。定义之后就已经存在。静态属性使用Foo.xxx直接访问。如
//Object.hasOwnProperty()就是一个静态属性(方法)。使用时直接Object.hasOwnProperty(参数)即可
Foo.address = 'Beijing';
Foo.show1 = function(){ return this.name + ':::' + this.address; }
//foo1中含有name,age,show属性。至于__proto__属性,由于Foo函数没有指定原型对象,因此应该是一个
//默认的原型对象,上面讲过。使用Foo1可以访问name,age,show属性,但是,不能访问address,show1
//属性,因为他们属于Foo对象。
var foo1 = new Foo('xiaoming', 18);
Foo.show1(); //使用Foo对象访问静态属性,show1函数其实是有问题的,因为他无法访问name属性
//name属性不属于Foo对象。
function Fxx(id)
{
this.id = id;
}
//继承1
Fxx.prototype = Foo;
//使用这种方式继承,fxx1只能继承address,show1。js中的继承方式,只继承原型对象中的属性,注意“对
//象”俩字,即只能Foo作为对象拥有的属性address,show1
var fxx1 = new Fxx(108);
//继承2 一般情况下使用这种方式。
Fxx.prototype = new Foo('xiaoming', 18);
//这样fxx2继承了对象new Foo('xiaoming', 18);中的name,age,show属性。
var fxx2 = new Fxx(125);
JS(构造)函数中的prototype
指向的原型对象用于在实例化对象时使其继承原型对象的内容。可以看做(构造)函数在实例化对象时执行了fxx2.__proto__ = Fxx.prototype
操作,即在要实例化的对象中创建一个__proto__
属性和构造函数中的prototype
属性指向同一个对象。
综上来看,函数对象的__proto__
属性负责本对象从哪里继承属性。prototype属性负责本函数要实例化的对象从哪里继承内容。也正因为如此,当一个函数实例化一个对象后,该函数对于实例化的对象就失去了作用,因为他已经把原型对象赋给了实例化对象的__proto__属性,同时也按照自己的部分属性(this指定的属性)为模板给实例化对象创建了一些属性。
参考文献
[1]JS原型链中的prototype和__proto__的区别
[2]javascript原型链继承
[3]JavaScript原型链和继承