__proto__ vs prototype
先从一个实验开始了解一下这两个名词。(注意: __proto__在有些场合下可能被写为[[prototype]], 这里以chrome console里的写法为准)
把这段代码复制到chrome console中(下面简称console):
|
|
可以看到普通对象没有prototype属性,而function对象有。并且通过new操作符 + function对象调用得到对象的__proto__指向function对象的prototype。这里都是Foo{}。
继续:
|
|
在js里,每个对象都有一个__proto__属性,指向另外一个对象的prototype,所有对象的root 对象是Object{}。
|
|
一个对象在[[getter]]某个属性值的时候会现在自身对象上找,如果找不到,会通过它的隐藏属性__proto__上去找,如果再找不到,继续去它的__proto__.__proto__上去找,直到找到root.prototype 对象。在代码一中可以继续做测试:
|
|
##constructor
先来搞清楚一个事实,代码一中的a对象,默认有一个a.constructor属性,这个属性指向的是a.__proto__.constructor也就是Foo.prototype.constructor对象。
|
|
那么prototype.constructor的constructor对象什么时候用到呢?–在与new 操作符打交道的时候用到,new 操作符可以这么理解:
|
|
##shadowing
代码五里abar.constructor指向Foo.prototype.constructor。这里做[[getter]]操作读取属性没有问题。如果做[[setter]]操作会发生什么呢?(这里撇开constructor本身的意义,只是介绍setter on __proto__);
|
|
如果一个对象的属性,这里是constructor同时存在于它自身或者它的__proto__ chain上,那么就称这个情况为shadowing。下面讨论myObject.foo = "bar"三种情况下的表现,当foo不在myObject上,但在myObject的__proto__上。
工具方法Object.getOwnPropertyDescriptor(Bar, 'constructor'), 得到对象上某个属性的描述。
- 如果
foo在__proto__chain上找到,并且它不是read-only(`writeable: false)属性,那么就产生正常的shadowing属性情况。 - 如果
foo在__proto__chain上找到,但是它是read-only(`writeable: true)属性,那么在foo上set这个属性或者新建这个属性是不允许的。 - 如果
foo在__proto__chain上找到,并且在定义这个属性的时候设置了它的[[setter]],那么foo不会被加到myObjet上,foo的setter也不会被重新定义。
在#2和#3这种情况下,如果要重新定义foo属性,那就需要使用Object.defineProperty(..)方法了。
##class
######实例化 & 类继承?
传统面向对象语言(以下简称OO)有class的概念。class有两个重要特性–实例化和类继承。
实例化:根据class的模型copy出一个具有该模型特征的新对象。
类继承:子类可以继承或者父类的属性,方法。
那么js是怎么做到这两项的呢?
之前提到了new Foo()这种像极了传统OO方式的新建对象操作。那么var a = new Foo();到底做了哪些具体的事情呢?
- 一个全新的对象被创建了。
- 这个新对象的
__proto__被link了。link到Foo.prototype上。 - 新对象的被当做this出现在Foo()调用的上下文中。
- 除非Foo()返回一个其他的对象,不然就自动返回这个新建的对象。
这里主要看第二步,新对象的__proto__被link了,这意味着这个新对象以后所有的属性查找,找不到就自动会delegate到Foo.prototype上去。只要对Foo.prototype做添加属性,也就对新对象做了添加属性。copy操作就这么被实现了,很像传统OO的实例化操作了。只不过是从对象link到对象,没有class的参与。
上面看到的操作是function 与 object之间的通过new link。那么普通的object 与 object之间如何link呢?还记得代码new 构造器里提到的Object.create(...)吗?Object.create(...)会新建一个对象,并把这个对象的__proto__link到参数里对象的__proto__上。
那么怎么做到js的‘class’继承呢(如下图)?

很简单一句代码Bar.prototype = Object.create(Foo.prototype);
把两个原型链link,那么b1 b2会自动有a1 a2的属性和方法。具体实现:
|
|
关于Object.create(...)通常两种误解,认为下面两种实现效果是一样的:
1. T2.prototype = T1.prototype;
2. T2.prototype = new T1();
第一种直接直接把T2.prototype指向了T1.prototype。对T2.prototype.change操作也就是对T1.prototype.change做操作。这两个是一个对象了,并不是想要的新建一个对象然后把两个对象link到一起。
第二种,可以说和Object.create(...)效果是一样的。但问题在于T1(),T1方法调用的时候,天知道里面做了什么事情(比如log,改变某些全局对象state等)。这里是要Link 两个对象的__proto__,用new的话,没有Object.create()来的纯粹。
|
|
##OO style 与 OLOO style的比较
OO:object-oriented;
OLOO: objects-linked-to-other-objects;
在js里,有时候用oo思想来实现一些东西,远没有用OLOO思想来的clean。
OO style:
|
|
OLOO style:
|
|
可以看到用OLOO style就不需要用js所不擅长的new和prototype来实现了class了,易读性更强一些。
##总结
js没有class只有object(ES6的class也只是语法糖),从一个object.__proto__ link 到另一个object.__proto__来达到copy。link的方式有两种:
1. var anotherobj = new Obj();
2. var anotherobj = object.create(obj);
js是一门弱类型语言,从不需要类型转换。对象继承变得无关紧要。对一个对象来说重要的是它能做什么,而不是它从哪里来。 —《Javascript语言精粹》
P.S 这篇文章是我写的第二遍,第一遍清除mac垃圾的时候被我误删了,本来要发表了,结果找不到了,哭晕在厕所。不过第二遍我觉得思路清晰了好多,质量也高了好多。