美文网首页Web 前端开发 让前端飞
javascript:我是这样理解原型的

javascript:我是这样理解原型的

作者: 小飞牛牛 | 来源:发表于2017-11-30 19:23 被阅读0次

但凡了解一件事物,总离不开因果体用。

所谓因果,指的是它是从什么基础形态发展而来的,所谓体,指的是它的内部结构特点,所谓用,指的是它对其它事物的影响。

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很有趣,

看晕了吧。

借一幅图说明:


8164530.png

通过上图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在内存中的状况:


8199006.png

原型的继承


通常原型继承包括继承属性和方法。
例如:

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关系介绍)

相关文章

网友评论

    本文标题:javascript:我是这样理解原型的

    本文链接:https://www.haomeiwen.com/subject/ruhbbxtx.html