在网上看了很多的文章,但是总是一知半解 ,今天根据之前的笔记和网上的总结来总结一下.
一.在解释这些关系之前需要先了解以下几点:
1.JS内置函数就是对象
2.Function 对象和Object对象这两个内置对象的特殊性
二.首先我们需要了解什么是构造函数
1.当一个普通的函数被用来创建一类对象的时候,就是一个构造函数,构造函数首字母大写
2. 可以通过 对象.constructor 拿到创建该实例对象的构造函数。
3. Function函数和Object函数是JS内置对象,也叫内部类,JS自己封装好的类,这是固定的
4.原型对象即实例对象自己构造函数内的prototype对象。
三.为什么函数就是对象
根据这些结果可以了解到:
-
Person虽被声明为一个函数,但它同样可以通过
Person.constructor输出内容。输出内容说明Function函数是Person函数[普通声明的函数]的构造函数。 -
Function函数同时是自己的构造函数。
-
Function函数同样是Object这类内置对象的构造函数。
总结: JS中,函数就是Function 函数的实例对象,这就是我们说的函数即对象,在JS中 函数和对象的关系是: 对象由函数创建,函数都是Function 对象的实例
四.constructor是什么
function Person (){}
var person1 = new Person()
var person2 = new Person()
下面这个图 可以更好理解constructor 的指向
图中,蓝色底是Person的实例对象,而Person、Function是函数(也是对象)。
首先,我们已经知道每个对象都可以通过对象.constructor指向创建该对象的构造函数。我们先假设每个对象上都有这么个constructor属性,然后理解如下:
**注意:**constructor属性不一定是对象本身的属性,这里只为方便理解将其泛化成对象本身属性,所以用虚线框,第三大点细讲。
- person1与person2是Person对象的实例,他们的constructor指向创建它们的构造函数,即Person函数;
- Person是函数,但同时也是Function实例对象,它的constructor指向创建它的构造函数,即Function函数;
- 至于Function函数,它是JS的内置对象,在第一点我们就已经知道它的构造函数是它自身,所以内部constructor属性指向自己。
所以constructor属性其实就是一个拿来保存自己构造函数引用的属性,没有其他特殊的地方。
在接下来的所有例子都将把Function对象视为Function对象自己的实例对象,通过去掉它的特殊性来更好理解相关概念。
五.prototype 是为什么出现的
如果给Person的两个实例对象加上一个效果相同的方法:
// 下面是给person1和person2实例添加了同一个效果的方法sayHello
person1.sayHello = function() {
console.log('Hello!')
}
person2.sayHello = function() {
console.log('Hello!')
}
console.log(person1.sayHello === person2.sayHello) // false,它们不是同一个方法,各自占有内存
下图可以帮助理解:
总结: prototype 对象是用来放某同一类型实例的共享属性和方法,实质上是为了内存着想。
因为所有函数本身是Function 函数的实例对象,所以所以Function函数中同样会有一个prototype对象
放它自己实例对象的共享属性和方法。
六.真正的constructor 属性在哪
总结:默认constructor实际上是被当做共享属性放在它们的原型对象中。
function Person() {}
var person1 = new Person()
var person2 = new Person()
console.log(person1.constructor) // [Function: Person]
console.log(person2.constructor) // [Function: Person]
person1.constructor = Function
console.log(person1.constructor) // [Function: Function]
console.log(person2.constructor) // [Function: Person] 不是同步为[Function: Function]
这个是因为person1.constructor = Function改的并不是原型对象上的共享属性constructor,而是给实例person1加了一个constructor属性。如下:
console.log(person1) // 结果:Function { constructor: [Function: Function] }
你可以看到person1实例中多了constructor属性。它原型对象上的constructor是没有改的。
七.__proto__让实例能找到自己的原型对象
在对象内部创建一个属性直接指向自己的原型对象,那就可以找到共享属性constructor了,也就是下面的关系:
- 实例对象.__proto__ = 创建自己的构造函数内部的prototype(原型对象)
- 实例对象.__proto__.constructor = 创建自己的构造函数
如图所示:
对象的__proto__属性就是指向自己的原型对象。这里要注意,因为JS内所有函数都是Function函数的实例对象,所以Person函数也有个__proto__属性指向自己的原型对象,即Function函数的prototype。至于Function函数为何有个__proto__属性指向自己(蓝色箭头这个可以当做是特例子.
特例:prototype也是个对象吧,它也有个__proto__吗?
function Person() {}
console.log(Person.prototype.__proto__.constructor) // [Function: Object]
因为__proto__指向原型对象,原型对象中的constructor又指向构造函数,所以Person.prototype.__proto__.constructor指向的就是Person中prototype对象的构造函数,上面的输出结果说明了prototype的构造函数就是Object函数(对象)。
总结:其实函数内的prototype也不过是个普通的对象,并且默认也都是Object对象的实例。
根据上面的内容总结如下:
1.所有函数的__proto__指向他们的原型对象,即Function函数的prototype对象
2.最后一个prototype对象是Object函数内的prototype对象。
Object函数作为JS的内置对象,也是充当了很重要的角色。Object函数是所有对象通过原型链追溯
到最根的构造函数。
3.Object函数的prototype中的__proto__指向null。
这是由于Object函数的特殊性,有人会想,为什么Object函数不能像Function函数一样让__proto__属性
指向自己的prototype?答案就是如果指向自己的prototype,那当找不到某一属性时沿着原型链寻找的时候就会进入死循环,所以必须指向null,这个null其实就是个跳出条件
八.什么是原型链
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型
上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,
这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
function Parent(month){
this.month = month;
}
var child = new Parent('Ann');
console.log(child.month); // Ann
console.log(child.father); // undefined
九.最后总结一下
-
我们需要牢记两点:①
__proto__和constructor属性是对象所独有的;②prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__和constructor属性。 -
__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。 -
prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype。 -
constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。