再了解原型链之前,我们先来看看构造函数和实例对象的打印结构
- 函数
这里我们定义一个构造函数Fn,然后打印它的结构吧
function Fn(){}
console.dir(Fn)
控制台得到结构
从上面结构我们能看的出来,函数有两种原型,一种是作为函数特有的原型:prototype,另一种是作为对象的__proto__类型(就是上图那个[[Prototype]],__proto__是浏览器自己填充的,新浏览器都能看到,我的浏览器版本低,还看不到哈,先这么写)
我们先来看做为函数特有属性的prototype哈,我们展开看看
可以看到函数的prototype上有一个构造函数constructor和一个__proto__属性,这个constructor实际上就是函数本身,另外一个__proto__呢,你看它的值是一个对象Object呢.也就是说函数的prototype的__proto__指向Object的prototype,我们下面运行看看是不是这样呢?
Fn.prototype.__proto__ === Object.prototype. // true
我们运行结果果然和预期一样返回了true.
我们再来看函数的另外一个原型__proto__,我们展开它
我们看到函数的__proto__是一个Function,我们猜想这个函数的__proto__指向Function的prototype,我们来运行看看
Fn.proto === Function.prototype. // true
果然,和预期一样,所以我们根据上面的一些结果有了总结
1、函数有两个原型,一个是作为函数特有的prototype,另一个是作为对象的__proto__
2、函数的 prototype中constructor就是函数本身
3、函数的 prototype中__proto__属性指向Object的prototype
4、函数的__proto__指向Function的prototype
- 对象
现在我们根据上面的构造函数,我们实例化一个对象来看看结构
let obj = new Fn()
console.dir(obj)
我们来看看obj对象的结构
可以看到上面的obj对象只有一个__proto__原型,我们展开看似乎和它的构造函数的prototype长的一样呢,我们运行看看
obj.proto === Fn.prototype. // true
果然,结果和我们预期一样
所以我们有了结论:
对象只有一条__proto__原型,并且对象的原型__proto__就指向构造函数的prototype
- constrctor
我们再回过头来看刚刚构造函数中那个constrctor,上面你看实例对象obj可以找到构造函数的prototype,而这个prototype上就有constrctor,所以由此得出根据构造函数实例得到的每一个实例对象,都能找到它的构造函数
我们来运行看看哈
obj.__proto__.constructor // ƒ Fn(){}
没有问题,和预期一样,这么看,我们现在就可以把上面写法精简一下,因为对象上的属性和方法,自己上面没有,就会去它的原型链上面去找,也就是对象会有原型的继承
根据这个原则,我们把上面运行的就能精简一下了
obj.constructor. //. ƒ Fn(){}
我们就这样就找到了构造函数了.
这里找到构造函数的目的是啥呢,除了用它来实例化对象之外,还能给原型链添加公共的属性和方法,供所有的实例对象调用.
我们来看一段代码
obj.constructor.prototype.sayHello = function(){ console.log("hello")}
obj.sayHello(). // hello
let obj2 = new obj.constructor()
obj2.sayHello(). // hello
从上得出,我们可以通过constructor的prototype添加通用的属性和方法.
有人提出干嘛那么麻烦,我们通过对象的__proto__直接添加属性和方法不是更好嘛,代码更精简,我们来看看
obj.proto.name = “zhang”
obj.name. // “zhang”
obj.name // “zhang”
我们看到我们通过对象的__proto__也能添加构造函数的公用属性和方法,所以,这样写到底有没有问题呢?
其实这样写虽然在浏览器运行没有问题,但是上面我们提到过,__proto__这个属性是浏览器厂商自己内置的,有的浏览器是不支持的,所以使用constructor更可靠一点。
就先整理到这里