每一个函数都有一个 prototype
属性,这个属性是指向一个对象的引用,称为 原型对象
,原型对象
中的属性和方法,可以被函数 实例
所继承和共享。
一、私有变量、函数
JavaScript中没有命名空间的概念,同时JavaScript支持函数嵌套定义。但是,如果函数内部定义的变量或函数不对外提供接口,那么外部将无法访问
/**
* obj中的变量和函数都是私有的,外部无法访问
*/
function obj(params) {
var a = 0; // 私有变量
var fn = function(){}; // 私有方法
}
var o = new obj();
console.log(o.a); // undefined
console.log(o.fn); // undefined
二、静态变量和函数
通过 .
操作符,添加的函数或变量,可以通过对象本身访问到,但是其实例对象,仍然访问不到。(用过java的同学很好理解)
/**
* 对象可以访问
*/
function obj(){}
obj.a = 0; // 静态变量
obj.fn = function(){}; // 静态方法
console.log(a); // 0
console.log(typeof obj.fn); // function
/**
* 实例不能访问
*/
var vm = new obj();
console.log(vm.a); // undefined
console.log(typeof vm.fn); // undefined
三、实例变量和函数
在面向对象编程的概念中,我们还是需要在对象在定义的时候能同时定义一些属性和方法,实例化之后,可以访问。
/**
* 对象访问不到
*/
function obj (){
this.a = []; // 实例变量
this.fn = function(){}; // 实例方法
}
console.log(typeof obj.a); //undefined
console.log(typeof obj.fn); // undefined
/**
* 对象实例可以访问到
*/
var vm = new obj();
console.log(typeof vm.a); // object
console.log(typeof vm.fn); // function
然而会遇到如下问题:
/**
* 实例属性不一样
*/
function obj (){
this.a = []; // 实例变量
this.fn = function(){}; // 实例方法
}
var vm1 = new obj();
var vm2 = new obj();
vm1.a.push(1);
vm1.fn = {};
console.log(vm1.a); // [1]
console.log(typeof vm1.fn); // object
console.log(vm2.a); // []
console.log(typeof vm2.fn); // function
从以上代码可以看出,虽然两个实例对象的属性和方法名称是一样的(继承自函数对象),但是是一个复制。对于属性来说我们可以接受。但是对于方法来说,没有必要(如果定义10000个实例,就有10000个copy)。
四、prototype
无论什么时候,只要创建了一个函数,就有根据一组特定的规则,为该函数创造一个prototype
属性。
该属性指向一个对象,称为 原型对象
。这个属性会包含一个constructor
指针,指向函数原型。
/**
* 函数原型-首字母大写
*/
function Person(){}
通过图片可以更好的理解以上内容。
当调用构造函数创建一个实例时,实例内部会包含一个内部指针__proto__
,称为隐式原型,它指向构造函数的prototype
对象。
/**
* 实例和函数原型
*/
function Person(name){
this.name = name; // 实例属性
}
Person.prototype.printName = function(){
alert(this.name);
}
var person1 = new Person('Byron');
var person2 = new Person('Frank');
通过图片可以看出,person1中包含了name,同时会有 __proto__
-> Persion.prototype
(能看到printName),如下图所示
/**
* prototype内容共享测试
*/
function Person(name){
this.name = name;
}
Person.prototype.share = [];
Person.prototype.printName = function(){ alert(this.name); };
var person1 = new Person('Byron');
var person2 = new Person('Frank');
person1.share.push(1);
person2.share.push(2);
console.log(person2.share); // [1,2]
当从实例中获取属性时,会先查找该实例是否包含。如果没有则查找__proto__
指向的prototype
里面是否含有,没有就继续递归查找。直到 Object.prototype
,仍然没有则报错。
prototype中的属性可以通过实例属性去覆盖
/**
* 覆盖prototype中的属性
*/
function Person(name){
this.name = name;
}
Person.prototype.share = [];
var person = new Person('Byron');
person.share = 0;
console.log(person.share); // 0 不是 []
五、总结
如果希望实例复用对象的属性或函数,则放到 prototype
中。 如果希望每个实例单独拥有的属性和方法,则定义到this中,通过构造函数传递实例化参数。
function Person(name){
this.name = name;
}
Person.prototype.share = [];
Person.prototype.printName = function(){
alert(this.name);
}
六、原型链示意图
假设有个Person
function Person(){}
new Person();
此时,会有如下关系:
- 红色:function Person(){}
- 蓝色:Person.prototype
- 绿色: new Person()
- prototype和__proto__(一般浏览器都叫这个)是自动生成
以下图片中线条
- 蓝色:prototype
- 绿色:new
- 黑色:proto
而其中Person函数,又是 new Function()
生成的实例,所以关系图如下:
Person.prototype = {}
他是个Object 实例,相当于 new Object();
,所以又有如下关系图:
同理 Function.prototype
也是如此,所以关系图如下:
关系图中 Object.prototype
比较特殊,它的隐式原型为null
。Object()
函数为new Function()
,所以它是其实例,同时其隐式原型为Function.prototype
。
最后一点是Function()
函数的隐式原型指向Function.prototype
所以最终关系图如下:
当我们去用实例去调用属性或方法时
person1.toString();
查找顺序为(沿最左侧黑色箭头):person1自身属性
-> Person.prototype
-> Object.prototype