微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

JavaScript中的原型prototype完全解析

要理解JS中的prototype,首先必须弄清楚以下几个概念 1. JS中所有的东西都是对象

2. JS中所有的东西都由Object衍生而来,即所有东西原型链的终点指向Object.prototype

rush:js;"> // ["constructor","toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf",// "propertyIsEnumerable","__defineGetter__","__lookupGetter__","__definesetter__",// "__lookupSetter__"] console.log(Object.getownPropertyNames(Object.prototype));

3. JS中构造函数和实例(对象)之间的微妙关系 构造函数通过定义prototype来约定其实例的规格,再通过 new 来构造出实例,他们的作用就是生产对象. 而构造函数(方法)本身又是方法(Function)的实例,因此也可以查到它的__proto__(原型链)

Object / function F() {} 这样子的就是构造函数啦,一个是JS原生API提供的,一个自定义的 new Object() / new F() 这样子的就是实例啦 实例就"只能"查看__proto__来得知自己是基于什么prototype被制造出来的, 而"不能"再重新定义实例的prototype妄想创造出实例的实例了.

实践出真知,只有自己动手观察/思考才能真正领悟:

rush:js;"> // 先来看看构造函数到底是什么 // function Empty() {} function Empty() {} console.log(Function.prototype,Function.__proto__); // Object {} function Empty() {} console.log(Object.prototype,Object.__proto__); function F() {} // F {} function Empty() {} console.log(F.prototype,F.__proto__);

你可能已经晕了,我们来分解一下。

prototype

prototype输出的格式为: 构造函数名 原型 首先看下Object.prototype输出了什么? Object {} -> 前面的Object为构造函数名称,后面的那个表示原型,这里是一个{},即一个Object对象的实例(空对象) 那么 F {} 我们就明白是什么意思了,F 就是构造函数名称,原型也是一个空对象

rush:js;"> // 再来看看由构造函数构造出来的实例 var o = new Object(); // var o = {}; // undefined Object {} console.log(o.prototype,o.__proto__); function F() {} var i = new F(); // undefined F {} console.log(i.prototype,i.__proto__);

我们再深入一点,定义下 F 的原型看看到底会发生些什么?

rush:js;"> function F() {} F.prototype.a = function() {}; var i = new F(); // undefined F {a: function} console.log(i.prototype,i.__proto__);

这样我们就清楚的看到 i 是由 F 构造出来的,原型是 {a: function},就是原本的空对象原型新增了一个 a 方法

我们再换一种情况,完全覆盖 F 的原型会怎么样?

rush:js;"> function F() {} F.prototype = { a: function() {} }; var i = new F(); // undefined Object {a: function} console.log(i.prototype,i.__proto__);

咦~ 为什么这里表明 i 是由 Object 构造出来的? 不对吧! 因为我们完全将 F 的prototype覆盖,其实也就是将原型指定为对象{a: function},但这会造成原本的constructor信息丢失,变成了对象{a: function}指定的constructor. 那么对象{a: function}的constructor是什么呢? 因为对象{a: function}其实就相对于

rush:js;"> var o = {a: function() {}} // new了一个Object

那么o的constructor当然是 Object 啦

我们来纠正下这个错误

rush:js;"> function F() {} F.prototype = { a: function() {} } // 重新指定正确的构造函数 F.prototype.constructor = F; var i = new F(); // undefined F {a: function,constructor: function} console.log(i.prototype,i.__proto__);

现在又能得到正确的原型信息了~

原型链

然后来看看什么原型链又是个什么东西? 简单的来讲和OOP中的继承关系(链)是一样的,一层一层往上找,直至最终的 Object.prototype

最最关键的是要弄清楚JS中哪些东西是(实例)对象,这个简单了,JS中所有东西都是对象! 再要弄清楚就是任何一个对象都是有一个原型的!

那么我们来证明一下:

rush:js;"> Object // 这是一个函数,函数是 Function 的实例对象,那么就是由 Function 构造出来的 Object.__proto__ == Function.prototype // 那么Object的原型,true // 这个是一个普通对象了,因此属于 Object 的实例 Function.prototype.__proto__ == Object.prototype // true // 这已经是原型链的最顶层了,因此最终的指向 null Object.prototype.__proto__ == null // true

Function // 这也是一个函数,没错吧!
Function.proto == Function.prototype // true

function A() {} // 这是一个自定义函数,终归还是一个函数,没错吧!
A.proto == Function.prototype // 任何函数都是 Function 的实例,因此A的原型是?
var a = new A()
a.proto == A.prototype // 实例a是由A构造函数构造出来的,因此a的原型是由A的prototype属性定义的
A.prototype.proto == Object.prototype // 普通对象都是 Object 的示例

Prototype和__proto__

一个对象都包含一个__proto__,指向这个的对象的“原型”。 类似的事情是,每一个函数都包含一个prototype,这个prototype对象干什么的了?

咱们看看如下代码,用构造函数来创建一个对象(上面是用字面量的形式创建对象)。

rush:js;"> function Foo(){}; var foo = new Foo(); console.log(foo.__proto__);

试想想,这个foo对象的__proto__会指向什么?

一个包含constructor属性的对象?看不太懂没关系,把函数Foo的prototype属性打印出来,对比一下就知道了。

rush:js;"> function Foo(){}; var foo = new Foo(); console.log(foo.__proto__); console.log(Foo.prototype); console.log(foo.__proto__ === Foo.prototype);

原来,new出来的对象foo的__proto__就只指向函数Foo的prototype。

Foo.prototype

JS这么设计有何意义了?回忆下上面说的,在JS的世界中,对象不是根据类(模具)创建出来的,而是从原型(另一个对象)衍生出来的。

当我们执行new操作创建一个新的对象时,先不深入new操作的具体实现,但有一点我们是肯定的——就是为新对象的__proto__指向一个原型对象。

就刚才这段代码

rush:js;"> function Foo(){}; var foo = new Foo();

foo.__proto__到底要指向谁了?你怎么不能指向Foo这个函数本身吧,虽然函数也是对象,这个有机会会详细讲。但如何foo.__proto__指向Foo固然不合适,因为Foo是一个函数,有很多逻辑代码,foo作为一个对象,继承逻辑处理没有任何意义,它要继承的是“原型对象”的属性

所以,每个函数自动生成一个prototype对象,由这个函数new出来的对象的__proto__就指向这个函数的prototype。

Foo.prototype

总结

说了这么多,感觉还是没完全说清楚,不如上一张图。我曾经参考过其他网友的图,但总觉得哪里没说清楚,所以我自己画了一张图,如果觉得我的不错,请点个赞!(老子可是费了牛劲才画出来)。

咱们就着这张图,记住如下几个事实:

1. 每个对象中都有一个_proto_属性

JS世界中没有类(模具)的概念,对象是从另一个对象(原型)衍生出来的,所以每个对象中会有一个_proto_属性指向它的原型对象。(参考左上角的那个用字面量形式定义的对象obj,它在内存中开辟了一个空间存放对象自身的属性,同时生成一个_proto_指向它的原型——顶层原型对象。)

2. 每个函数都有一个prototype属性

“构造函数”为何叫构造函数,因为它要构造对象。那么根据上面第一条事实,构造出来的新对象的_proto_属性指向谁了?总不能指向构造函数自身,虽然它也是个对象,但你不希望新对象继承函数属性方法吧。所以,在每个构造函数都会有一个prototype属性,指向一个对象作为这个构造函数构造出来的新对象的原型。

3. 函数也是对象。

每个函数都有一些通用的属性方法,比如apply()/call()等。但这些通用的方法是如何继承的呢?函数又是怎么创建出来的呢?试想想,一切皆对象,包括函数也是对象,而且是通过构造函数构造出来的对象。那么根据上面第二条事实,每个函数也会有_proto_指向它的构造函数的prototype。而这个构造函数函数就是Function,JS中的所有函数都是由Function构造出来的。函数的通用属性方法就存放在Function.prototype这个原型对象上。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐