今天周末,夜里没事再把JS梳理一下。有兴趣的可以静下来当成个故事慢慢看。
首先说的对象。比如 var o={}
创建一个对象o,但是我们没有看到构造函数。根据面向对象的思路,此处应该有个构造函数。比如C++的new 类型。
我们应该猜到,这是语法省略了。可经过这么省略,很多事情就变的迷惑了。事实上,完整的写法是:var o = new Object()
其中,Object()就是一个内置的构造函数。这一步非常关键,是下面一些理论的基础
在书上,我们常常看到原型(prototype)的说法,原型是函数才能拥有的,即原型是函数的原型。就像上面的Object(),它是一个函数,因此
它具有原型。但又因为它是内置的函数,因此赋予了很多方法。查看:Object.prototype。
Object.prototype
对象o因为通过Object()构造而来,因此它能引用Object的原型(prototype),在浏览器中可以通过o.__proto__来查看
因此,可以总结出下面一段话:
原型prototype是函数才具有的,表现形式为:函数.prototype
由此函数创建的对象可以引用原型,表现形式为:对象.__proto__,换句话说,对象要引用构造的原型,需要给它一个属性来引用,这个引用是__proto__
现在,我们再来看下函数,相对来说,它是JS中比较复杂的一种类型。但是在这里,我们首先也把它看成对象。原因是:在JS中一切皆对象。
在学习过程中,需要把思路简化,进行最高抽象,我们记不住那么多的理论,但是最核心的理论需要记住。
1、在js中一共有七种数据类型(不考虑ES6,分别为数值、布尔、字符串、函数、对象、undefined、null)
除了undefined和null(这两者暂时不考虑),甚下的都理解为对象,包括函数。
那么这种抽象的结果是什么呢?就是任何'对象'都可以使用 类型.__proto__,来查看它的原型链
2、函数非常特殊,首先(只有)它有prototype,又因为它是对象,所以又有__proto__,所以它具有两项本领,是极特殊的一个
现在,我们认真分析下函数
比如:创建一个函数
function f(){}
上面说函数是一个对象,那我们查看一下它的__proto__,发现得到的是native code。在这个地方,我们解决一个问题。
比如,问f.call的call方法从来哪来的?我们现在在脑子中可以把对象划分成两种,一种是[普通对象],一种是[函数],原因是函数非常特殊。
一、假如是普通对象,我们问的上一级一定是个函数,因为对象是由[new 构造函数]得来的。
二、假如是函数,因为它也是对象,我们也会问它的出处
下面分别解释
假如是普通对象,我们问它的__proto__,它指向的一定是某个函数的prototype。例如:
let a=10 a.__proto__ //此处等于Number.prototype let b='hello' b.__proto__ //此处等于String.prototype ...
let o ={} o.__proto__ //此处等于Object.prototype let f=function(){} let ob=new f() ob.__proto__ //此等于f.prototype
换句话说,查看一个对象的__proto__就是往上级查找.
好了,现在我们要查看一下ob的某个属性,比如ob.show()方法。查找顺序如下:
首先,ob对象本身有吗?如果没有,我们到它的__proto__里面看看有没有,又因为ob.__proto__=f.prototype,所以就来到f.prototype。但是,我们知道,一般情况下f.prototype至少会挂两个数据:constructor和__proto__
f.prototype={
constructor:..,
__proto__:..
xxx可能的其它属性:...
}
发现里面也没有,那么我们再一次根据prototype里面的__proto__向上查找,为什么呢?因为prototype也是一个对象,完整的话是这样说的:下面的人从我要一个属性,我没有,但又因为我自身也是一个对象,于是我就拿出自己的__proto__查看下有没有?
就来到Object.prototype里面,继续查找,依然没有发现,查找结束。这就是原型链查找模式。
第二,刚才我们说的是普通意义的'对象',下面看下函数
函数也是对象,这个说法已经从最开始到现在重复了很多遍,所以要加深记忆。好了,现在我要查找f的某个属性,比如f.show()方法,首先就看它本身有没有?比如下面代码:
function f(){ let a=10 } f.show=function(){ console.log('show方法') }
这种写法没有任何异议,因为函数也是对象,挂载几个属性没有问题。假如把f.show()去掉,再来调用f.show(),查找顺序如下:
1、f本身有吗?发现没有,进入2
2、因为它是对象,就查看它的__proto__
3、进入f.__proto__,发现它是native code,一步就终止。
所以,像f.call()这种调用就是native code赋给它的本领,因为从字面上无法找到它的踪迹
那么,如果给f.prototype赋予一个show()方法呢?可惜的是,任何对象的原型跟踪只从它的__proto__进行,所以没用,代码如下:
function f(){} f.prototype.show=function(){ console.log('show') } f.show() //可惜未定义,只能从__proto__那条线上查找
下面进行总结
查找一个对象的属性
1、首先看它本身有没有
2、如果没有,就在它的__proto__中看看有没有
3、如果没有,又因为__proto__(做为对象)里面肯定又会包含__proto__,再进入这个__proto__里面看看有没有
4、以此向上类推...一直追踪到源头
如果是一个函数对象,那么向上查找一步就结束,因为函数对象的__proto__是native code
结论的启示:
如果想赋予一个对象的某个属性,只有两个办法:
1、自身赋值,比如
let o={}
o.show=function(){}
2、在它上一级的prototype挂载。如果不想在上一级挂,就在上一级的上一级挂载,这种方式就是继承。比如:
let f=function(){} f.prototpye.show=function(){} let o=new f() o.__proto__=f.prototype //返回true o.show() //等价于 o.show() = o.__proto__.show() = f.prototype.show()
下面做一个综合的例子
<script> function A(){ } A.prototype.show=function(){ console.log('来源于函数A的show') } function B(){} B.prototype=new A() let o=new B() o.show() </script>
对于这个查找属性的例子,只需要两个最终的结论:
1、对象自身有没有
2、如果没有,就在它的__proto__中查找,这个__proto__一定还有__proto__,以此向上类推.. 原因是:对象一定有__proto__。我们会发现,结论越来越短,也越来越明了
【分析如下】
1、首先看对象o是否有show,没有得到,就在它的__proto__中查找,o.__proto__ = B.prototype
2、由于B.prototpye = new A(),可知是一个普通对象。在这里特别说明的是,__proto__和prototype都是普通对象。之所以
在一些似乎非常简单的问题上重复多次,是为了深刻加深印象,把一些概念训练成本能,一次一次又一次的重复
3、我们给new A()起个别名,比如对象aa。那么就在aa中查找是否有show()方法,发现没有
4、于是,问题变成在对象aa中查找show()方法。由于它不具有,就在aa.__proto__中查找,可知 aa.__proto__ = A.prototype
5、于是,又来到A.prototype中查找,发现了show()方法,查找结束
假如函数A自身就具有show()方法,那么就不会在它的prototype中查找,代码如下:
function A(){ this.show=function(){ console.log('来源于对象A的方法show') //对象的方法 } } A.prototype.show=function(){ console.log('来源于A原型上的show') } function B(){} B.prototype=new A() let o=new B() o.show()
经过这些分析,相信对于__proto__和prototype已经分辨清楚了