关于js原型的思考
一直想讨论js原型的问题,但却不知道怎么入手,后来突然想到,能不能从作者的角度思考呢?
我要什么
- 我需要面向对象编程
- 我需要一门简单的的语言,所以不需要传统面向对象编程中类的概念,还要能够实现传统面向对象编程语言中类的功能
类和对象
面向对象编程,最重要的是类和对象,我不想要类,所以最重要的就是对象,所以第一步就是一切皆对象。
虽然没有类,但是我需要实现类的功能啊!没有类怎么生成对象呢?其实由类生成对象的过程,在传统面向对象的语言中,是偏语言底层的过程。我不想搞这么复杂,算了,直接用代码生成对象吧。用代码生成对象,就是执行一段js代码,生成了一个对象。
生成对象的问题解决了,要让生成对象这个代码段规范统一起来,就要和普通的代码段不一样,我得给这个东西规范一个名字,而且代码段最能让人想到的东西就是函数了,就叫这个构造函数吧!那么现在新的问题来了,在这个一切皆对象的世界里,构造函数算什么呢?肯定不能是别的,为了更加直接简单,我让构造函数也是对象,不对是所有的函数都是对象。怎样让函数也是对象?这里先不展开,先贯彻函数也是对象的概念就好。
解决了类和对象的问题,接下来就是面向对象编程的几个重要概念的实现了。
封装和继承
先说抽象和封装吧。抽象不说了,直接封装。为了简单,我让对象就是一个键值对的集合,键不能重复,值任意。现在我需要有个地方封装“属性”和“方法”(在js里,全都是对象,只不过表现形式分为属性和对象),而且我需要静态的属性方法,大家共享,不能修改,节省空间。动态的属性方法,大家各用各的。我没有类,我要怎么办才能封装呢?我需要一个容器,这里就是以键值对的方式装着属性方法。又因为类图通常是树形结构的,(看java,哈哈)所以我也要搞一个属性结构的属性方法容器,每一个节点都是一个容器,我用这个容器去关联对象,被同一个容器关联的所有对象,我称他们是同一个类的实例化对象,这样我就实现了静态属性方法的封装功能。(因为属性方法是共享的,所以只能是静态的),动态的属性方法怎么解决呢?必须在创建对象的时候重新开辟内存空间复制这些属性方法给新创建的对象,上文提到了,创建对象使用构造函数,动态属性方法的分配可以在这个构造函数执行的过程中进行。综上所有,动态的属性方法,封装在构造函数中,静态的属性方法封装在容器中,这个容器就是原型,原型到根节点的最短路线,叫做原型链。
再说说继承的问题,继承需要和封装关联起来。静态属性的继承,只需要让原型链上的任一节点上都能访问到从当前节点到原型链顶点所有属性方法,就是功能上实现了继承。静态属性的继承需要原型链路,动态属性的继承,解决问题的方式就是通过一个构造函数链路实现,子构造函数执行的时候调用父构造函数即可。
这样就理清楚了js中,类,对象,原型,构造函数的一些基本的概念,接下来讨论用这种方式解决面向对象编程的一些细节和问题
对象和函数(Object和Function)
首先要讨论的还是先有鸡还是先有蛋的问题,既然一开始就明确了,js一切皆对象,那么这个设定已经基本确定了就是先有蛋了。
又因为js封装继承的原理都是借助于原型链来实现的,所以还是先从原型链开始,顶端(第一个容器,或者说连容器都算不上),没有任何功能,除了代表原型的起点。真正的js万物的起点,应该是这么一个对象:首先,它是有生成对象的能力的,这就决定了,这个对象一定是一个函数。
现在做这么几个规定:
- 每个函数关联一个容器,容器封装静态属性方法,为继承做准备。这个容器叫做
prototype
,prototype
实现的效果是,当使用这个函数做为构造函数的时候,生成的对象拥有prototype
里面的所有属性方法 - 每一个对象都有一个
__proto__
的属性,通过这个属性,可以访问到生成这个对象的构造函数关联的容器,即prototype
。(注意即使不通过__proto__
也是可以访问到原型上的属性的)
这样的话,就基本解决了封装和继承的问题,那么产生了另外一个问题,函数也是对象,那么函数的__proto__
是啥?更要命的是Object的__proto__
是啥?Object
已经是最接近原型链顶端的对象(函数)了,我们之前又约定__proto__
代表了自己的构造函数的prototype
属性,那么到底是谁构造了Object
了呢?
说到这,又要引入另外一个神奇的东西了,Function
—函数的起点。首先,作为具有类能力的对象,Function
也是个函数,又因为函数也是对象,所以Function
只能由Object
拓展而来,即Function
是继承了Object
的属性方法的,Function.prototype.__proto__
=== Object.prototype
。但是作为所有函数的起点,bject
和Function
本身又都是函数,所以Object
和Function
都是由Function
实例化而得,即Object.__proto__
=== Function.__proto__
=== Function.prototype
上面那段话很绕,但是却基本解决了面向对象编程过程中的细节问题。自此,Object实例化出对象,Function实例化处函数,构建了js世界万象。原型和构造函数给了js面向对象编程的能力。
原型相关概念的理解
Object 和 Function 关系
- 从原型角度理解
先看下面四个等式:1
2
3
4console.log(Object.prototype.__proto__); // null
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
另外一种获取对象__proto__
属性的方法是Object.getPrototypeOf
Object
的prototype
的原型,就是原型链路的起点,而Function
的prototype
的原型,是Object
的prototype
,这说明了Function
还是拓展于Object
的。
再说说Object.__proto__
和Function.__proto__
都指向Function.prototype
表明了Function
是所有函数的基类
用一个图来表示
我参考了这篇博客,写的很详细,值得参考
- 从拓展方面理解
先看个图
Object
和Function
分别走了两个方向,Object
偏向于做为所有对象的基类,Function
偏向于做为函数的基类型
拓展Object原型:1
2
3
4Object.prototype.foo = 'bar';
console.log(Function.foo); // bar
console.log(Function.prototype.foo); // bar
console.log(Object.foo); // bar
拓展Object.prototype
,就相当于拓展了Function.prototype
,又因为Object
和Function
都由Function
拓展而来的,所以当你拓展Function.prototype
,那么Function
和Object
就都被拓展了,所以才有了Function.foo
和Object.foo
都等于bar
接着上面的例子1
2
3
4
5
6var foo = new Object();
function Foo() {
}
console.log(foo.foo); // bar
console.log(Foo.foo); // bar
这说明实例对象和函数都被拓展了
总结:Object
拓展了所有的object,Object
拓展了Function
,Object
拓展了自己的属性(通过拓展Function
拓展自己)
拓展Function
原型:1
2
3
4
5
6
7
8
9
10
11
12Function.prototype.foo = 'bar';
console.log(Function.foo); // bar
console.log(Function.prototype.foo); // bar
console.log(Object.foo); // bar
var foo = new Object();
function Foo() {
}
console.log(foo.foo); // undefined
console.log(Foo.foo); // bar
以上说明了Function
原型拓展只能拓展未被实例化的函数,实例化对象无法拓展
__proto 和 prototype 的理解
所有的对象都有__proto__
属性,指向他们构造函数的prototype
当然,所有的函数也都有__proto__
属性,指向Function.prototype
只有函数有prototype
属性,函数的prototype
属性,就是为封装操作提供的,让对象共享属性方法
对于区别:
从实际效果上来说,可以认为__proto__
是用来扩展Function
的,扩展出来的函数,可以直接调用,不需要new
出对象才能用,同时对象是不会扩展通过__proto__
扩展的方法或属性的。
- 扩展
__proto__
1 | function Foo(){} |
对于prototype
来说,它是针对对象的,也就是Function
是无法使用的,只有new
出来的才能有效
- 扩展
prototype
1 | function Foo(){} |
- 通过
__proto__
扩展Object
1 | Object.__proto__.test4extend="123";//扩展Object的原型 |
Function
扩展自Object
,但是Function
对Object
又有影响,这是通过Object.__proto__
就是(===)Function.prototype
建立的联系。记住这个联系后,我们还要记住__proto__
和prototype
的区别,前者扩展的只可以被Function
直接调用,后者扩展的只可以通过其实例调用。另外,还要注意__proto__
和prototype
的链的概念,这是因为,他们可以互相关联,访问到Function
或Ojbect
的内容。
原型操作常用操作符总结
constructor
// 对象有个.constructor 属性,指向自己的构造函数instanceof
// instanceof运算符,判断左边的实例是否又右边的构造函数生成isPrototypeOf
// 这个方法用来判断,某个proptotype对象和某个实例之间的关系 Cat.prototype.isPrototypeOf(cat1)hasOwnProperty
// 每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。in
// in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。in运算符还可以用来遍历某个对象的所有属性