__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垃圾的时候被我误删了,本来要发表了,结果找不到了,哭晕在厕所。不过第二遍我觉得思路清晰了好多,质量也高了好多。