但凡了解一件事物,总离不开因果体用。
所谓因果,指的是它是从什么基础形态发展而来的,所谓体,指的是它的内部结构特点,所谓用,指的是它对其它事物的影响。
javascript原型是怎么来的?
先看下面的代码。
function A(name){
var a={};
a.name=name;
a.fn=function(){
console.log(this.name);
}
return a;
}
var a=A('chester');
var b=A('jerry');
console.log(a) //{ name: 'chester', fn: [Function] }
console.log(b)//{ name: 'jerry', fn: [Function] }
console.log(a===b)//false
这是一种常见的做法,在函数内部定义一个对象,将属性和方法赋予对象,然后返回。
但是从console的结果可以看到,每次执行函数所返回的结果,在内存中都新建一个对象。而且会产生闭包的现象,使函数得不到释放,从而导致内存泄漏。
另外,fn这个方法在每个对象中都是相同的,却在创建对象时重复创建了,浪费了内存。
为了解决这些问题,于是有了原型。
怎么理解javascript原型?
先来看看一个简单的构造函数:
function A(){
this.sex="male";
}
这不就是普通的函数吗?
没错。原型和函数本来是一家的,它们的区别是在使用时产生的。
在javascript里,没有独立的函数,所有的函数都属于一个对象,由对象去调用。例如:
A();
console.log(global.sex);//在node中是global,在浏览器中是window
结果
male
在最外层定义的函数,其实属于global对象,执行A()时,到this.sex这一句,this就是global,于是把'male'字符串赋值给global.sex
函数不服气了:为什么一定要从属于一个对象,我要独立!!
于是javascript就让它独立了:那你自己生成一个对象吧,this就代表你生成的对象了。
于是就有了:
function A(){
this.sex="male";
console.log(this.sex);
}
var a=new A();
console.log(a);
结果
male
A { sex: 'male' }
可以看到,我没有执行A(),我只是new A() 但console.log(this.sex)这条语句还是执行了。所以new 一个对象的过程,其实等同于生成一个对象,并把这个构造函数内部的代码执行一遍,再返回所生成的对象。
普通函数变成了构造函数,构造函数产生了对象,终于独立啦!!
prototype和 __proto__ 是什么?
举个例子:
function A(){
this.sex="male";
}
A.prototype.fn=function(){
console.log(this.sex);
}
var a=new A();
console.log(a);// A {sex:'male'}
a.fn(); //male
console.log(a.prototype);//undefined
console.log(a.__proto__);// A {fn:[Function]}
console.log(A.prototype);// A {fn:[Function]}
console.log(a.__proto__===A.prototype)//true
console.log(A.__proto__);//[Function]
可以看到prototype是属于构造函数的,而__proto__是属于对象的,而对象的__proto__与它的构造函数的prototype是同一个东西。
注意看最后一个console,A.__proto__是构造函数的原型, A.__proto__===Function.prototype
如果你再追究下去,很容易得到整个原型链。
console.log(A.__proto__===Function.prototype)//true
console.log(Function.__proto__===Function.prototype)//true
console.log(Function.__proto__.__proto__===Object.prototype)//true
console.log(Function.__proto__)//[Function]
console.log(Function.prototype)//[Function]
console.log(Function.prototype.prototype)//undefined
console.log(Function instanceof Object)//true
console.log(Object instanceof Function)//true
console.log(Object.prototype)//{}
console.log(Object.__proto__)//[Function]
console.log(Object.__proto__.__proto__)//{}
console.log(Object.__proto__.__proto__===Object.prototype)//true
console.log(Object.__proto__.__proto__.__proto__)//null
你会发现javascript很有趣,
看晕了吧。
借一幅图说明:

通过上图Function、Object、Prototype关系图中,可以得出以下几点:
-
所有对象,包括函数对象的原型链最终都指向了Object.prototype,而Object.prototype.__proto__===null,原型链至此结束。
-
Animal.prototype是一个普通对象。
-
Object是一个函数对象,也是Function构造的,Object.prototype是一个普通对象。
-
Object.prototype.__prototype__指向null。
-
Function.prototype是一个函数对象,前面说函数对象都有一个显式的prototype属性,但是Function.prototype却没有prototype属性,即Function.prototype.prototype===undefined,所以Function.prototype函数对象是一个特例,没有prototype属性。
-
Object虽是Function构造的一个函数对象,但是Object.prototype没有指向Function.prototype,即Object.prototype!==Function.prototype。
这里面最难理解的是Object和Function的关系,难在两个怪圈:
1.由Function生成的实例对象还是Function,所以Function.__proto__指向Function.prototype
2.构造函数Object需要Function去生成,但Function.prototype又依赖Object去生成实例。
如果说Function是构造函数的祖先,那么Object就是原型的祖先。因此Function.prototype.prototype=undefined,
而Object.prototype.__proto__=null
prototype与Constructor的关系
简单来说,constructor是prototype的属性,它指向该原型的构造函数。
例如:
function A(){
this.sex="male";
console.log('ddd')
}
A.prototype.fn=function(){
console.log(this.sex);
}
A.prototype.constructor()//ddd
下图说明了prototype和constructor在内存中的状况:

原型的继承
通常原型继承包括继承属性和方法。
例如:
function Father(address){
this.address=address;
}
Father.prototype.fn=function(){
console.log(this.address);
}
function Child(address,height){
Father.apply(this,arguments);
this.height=120;
}
Child.prototype=new Father();
Child.prototype.getHeight=function(){
console.log(this.height)
}
var child=new Child('中国',160);
console.log(child)//Father {address:'中国',height:120}
child.fn();//中国
console.log(Child.prototype)//Father {address:undefined,getHeight:[Function]}
属性的继承是通过apply函数实现的。在生成Child对象时,将this(Child对象)传入到apply,实际在执行Father()时,就将Father内生成的属性指向了Child对象,生成完对象后,Father就被释放了,没它什么事了。
而方法的继承则是通过生成Father的一个实例赋值给Child.prototype,再在其上添加其它方法。从console的结果来看,getHeight其实是挂在Child.prototype对象的一个方法,而fn则属于这个对象的原型。当Child.prototype中找不到fn方法时,会通过原型链在Father中找,以此实现方法的继承。
但需要注意的是
有一种写法可以实现无须new但效果等同于new
function Father(address) {
if (this instanceof Father) {
this.address = address;
} else {
return new Father();
}
}
如果新建的对象是Father则直接对属性赋值,否则视作函数调用,通过return new Father() 返回一个Father对象,这其实是在内部对自身进行创建。从而使 Father()返回的结果等同于new Father()
但这样的方式在Child中用apply函数继承属性时会失效。Father会直接走else的代码,Child会什么也捞不着。
另外,在
Child.prototype = new Father();
中,由于new Father()生成的实例会包含Father的属性,实际上Child.protytype中会又一个address,只不过不传参数,所以为空。
当执行
var child=new Child('中国',160);
时,child对象通过Father.apply得到一个address属性,既然child对象有address,就不会再用Child.prototype.address。 反之,如果没有Father.apply这一步,child.address实际得到的会是Father实例对象的一个address属性。
其它注意事项
this、prototype与内存
function A(num) {
this.fn1 = function () {
console.log('fn1')
}
this.num1 = num;
}
A.prototype.num2 = 2;
A.prototype.fn2 = function () {
console.log('fn2');
}
var a=new A(3);
var b=new A(4);
console.log(a.num1);//3
a.fn1()//fn1
console.log(a.num2);//2
a.fn2()//fn2
console.log(a.fn1===b.fn1)//false
console.log(a.fn2===b.fn2)//true
从代码可以看到,通过this和prototype都可以定义变量和方法,它们有什么不一样呢?
其实this定义的方法是属于具体对象的,指向不同的内存地址,而prototype是属于原型的,原型只有一个,由于fn1是用this定义的,所以每个对象都有一个fn1,而fn2却可以共用。 因此,将共用的属性和方法放在prototype中才可以节约内存。
参考:
http://www.blogjava.net/heavensay/archive/2013/10/20/405440.html(Js中Prototype、proto、Constructor、Object、Function关系介绍)
网友评论