主要参考:
辅助参考:
1.普通对象和函数对象
在JavaScript中,一切皆为对象。分为两种,一种叫普通对象,一种叫函数对象。
凡是通过 new Function() 创建的对象都叫函数对象,其他的都叫普通对象。
//下面三个都是普通对象
var o1 = {};
var o2 =new Object();
var o3 = new f1();
//下面三个都是函数对象
function f1(){};
var f2 = function(){};
var f3 = new Function('str','console.log(str)');
2.构造函数
function Person(){
}
var person1 = new Person();
person1.name = 'Kevin';
console.log(person1.name) // Kevin
上面例子中,Person 就是一个构造函数,person1 就是 Person 的实例对象。
实例对象可以有一个 constructor
(构造函数)属性,这个属性是一个指针,指向的是 Person。
console.log(person1.constructor == Person); //true
在下面的原型对象里面会介绍实际情况并不是这样,实例对象 person1 里面其实是没有 cosntructor
属性的。
3.原型对象
要点
-
原型对象,当 Person 构造函数被定义出来的时候,它就存在了,是一个有特殊地位的普通对象。
-
在JavaScript中,每一个已定义的对象都有一些预定义的属性。
-
在这些预定义的属性中,所有的对象都含有一个
__proto__
属性,其中所有的函数对象都含有额外的一个prototype
属性,这个属性指向函数的原型对象。即:Person.prototype
就是Person的原型对象。 -
每一个原型对象都含有一个
constructor
(构造函数)属性,这个属性指向的又是它对应的那个构造函数本身。即:Person.prototype.constructor == Person
。 -
原型对象是普通对象,但是
Function.prototype
除外,这个是一个函数对象,并且没有prototype
属性(之前提到所有的函数对象都有一个prototype
属性,但是这个特殊的也得除外)。
实例对象和原型对象
下面来解释为什么实例对象没有 constructor
属性,但是 person1.constructor == Person
依然成立?
其实是因为 __proto__
属性,这个是关于原型链的东西,下面会讲,person1 没有 constructor
属性,但是它可以顺着原型链往原型对象上寻找,所以找到了 Person.prototype
上面的 constructor
属性,又因为:
Person.prototype.constructor == Person
所以:
person1.constructor == Person
这个成立。
再举个例子:
function Person() {
}
Person.prototype.name = 'Kevin';
var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy
delete person.name;
console.log(person.name) // Kevin
在这个例子中:
- 我们先在构造函数Person的原型对象中添加了一个
name
属性为 Kevin,接着实例化了 Person 为 person。 - 在 person 中添加了
name
属性为 Daisy。 - 当我们输出 person 的
name
时,因为这时 person 中有这个属性,自然就返回了 Daisy。 - 后面我们删除了 person 中的这个属性,找不到了,于是js顺着 person 的原型链往上找,找到了 person 的构造函数 Person 的原型对象中有
name
属性,于是返回了Kevin。
假如原型对象上也没有这个属性,那么会顺着原型对象的__proto__
继续往上找,最终会找到 Object.prototype.__proto__
,这个是原型链的起点,为 null。如果还是没有就会返回 undefined。
作用
原型对象的作用是用来继承。
var Person = function(name){
this.name = name;
};
Person.prototype.getName = function(){
return this.name;
}
var person1 = new person('Mick');
person1.getName(); //Mick
通过给 Person 的原型对象设置了一个函数对象的属性 getName,使得 Person 的实例 person1 继承了这个属性。实现方法是通过原型链。
4. __proto__
前面提到每个对象都会有一个 __proto__
的预定义属性,这个是用来指向创建它的构造函数的原型对象的。
对象 person1 有一个__proto__
属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype,所以有:
person1.__proto__ == Person.prototype
__proto__
实际上并不存在于Person.prototype
中,而是在Object.prototype
中,与其说是一个属性,倒不如说是一个getter/setter,当使用 obj.__proto__
时,可以理解成返回了Object.getPrototypeOf(obj)
。
最后是关于继承的,前面所述的每个对象会从原型上继承属性。但是实际上继承的说法并不准确。因为继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
5.构造器
即用于创建对象的构造函数。他们本身都是函数对象。常见的有:Object,Array,Date,Function等。
都是通过 new 后面跟构造器来创建实例对象的。所以存在:
var a = new Object();
a.constructor === Object
a.__proto__ === Object.prototype;
var b = new Array();
b.constructor === Array;
b.__proto__ === Array.prototype;
var c = new Date();
c.constructor === Date;
c.__proto__ === Date.prototype;
var d = new Function();
d.constructor === Function;
d.__proto__ === Function.prototype;
6.原型链
五个问题:
person1.__proto__
是什么?Person.__proto__
是什么?Person.prototype.__proto__
是什么?Object.__proto__
是什么?Object.prototype.__proto__
是什么?
解答:
- 每个对象的__proto__都是指向它的构造函数的原型对象的。所以:
person1.__proto__ === Person.prototype
- Person这个构造函数是一个函数对象,是通过 Function 构造器产生的。所以:
Person.__proto__ === Function.prototype
- Person 的原型对象本身是一个普通对象,而普通对象的构造函数都是Object。所以:
Person.prototype.__proto__ === Object.prototype
- 刚刚上面说了,所有的构造器都是函数对象,函数对象都是 Function 构造产生的。所以:
Object.__proto__ === Function.prototype
- Object 的原型对象也有__proto__属性,但是比较特殊,是null。因为null是处于原型链的顶端。
Object.prototype.__proto__ === null
这里有一个问题,就是所有的对象都是继承自Object对象,但是Object对象又是继承自Function对象,这似乎是进入了一个无限套娃的死循环。关于 Object 和 Function 以及其他的对象关系,可以总结为四点:
- 一切对象都是继承自Object对象,Object 对象直接继承自根源对象 null。
- 一切的函数对象(包括 Object 对象),都是继承自 Function 对象。
- Object 对象直接继承自 Function 对象。
- Function对象的
__proto__
会指向自己的原型对象,最终还是继承自Object对象。
下面来理解这几个点:
1.一切对象都是继承自Object对象,Object对象继承自根源对象null。
理解:
- 一切对象的原型链最终追溯到顶层都是
…… → Object.prototype → null
。也就是顺着__proto__
属性走到上面都是指向Object的原型对象,然后再指向 null。 - 一切的对象都包含有Object的原型方法,比如 toString,valueOf 等。
2.一切的函数对象都是继承自Function对象。
理解:函数对象都是由 Function 构造的,所以函数对象的__proto__
都指向 Function 的原型对象。
String.__proto__ === Function.prototype;
Object.__proto__ === Function.prototype;
Function.__proto__ === Function.prototype;
3&4. Object对象直接继承自Function对象。Function对象的__proto__
会指向自己的原型对象,最终还是继承自Object对象。
理解:
Function的原型链是:Function → Function.prototype → Object.prototype → null
Object的原型链是:Object → Function.prototype → Object.prototype → null
和上面的结论其实不冲突。只是在 Function 这里绕了一圈。
几点问题
一切对象继承自Object,Object又继承自Function,那一切对象是不是都有Function的原型方法?
答:不对。普通对象都没有Function的原型方法。有两种解释:
- 从我们所写原型链中可以看出,Object 是继承自 Function,而 Object 也有 Function 的原型方法(比如bind),但 Object 继承得到的方法储存于
__proto__
属性中,普通对象从 Object 继承到的原型方法却在于prototype
属性中,因而不对。 - 一切对象都继承自
Object.prototype
,而一切函数对象都继承自Function.prototype
(且Function.prototype
会最终继承自Object.prototype
),也就是说普通对象和函数对象的区别是:普通对象直接继承了Object.prototype
,而函数对象在中间还继承了Function.prototype
。所以只有函数对象才有Function的原型方法。
Function对象自己继承自己?
答:应该不能这么理解。Function作为一个内置对象,是运行前就已经存在的东西,所以根本就不会根据自己生成自己,所以就没有什么鸡生蛋蛋生鸡,就是鸡生蛋。至于为什么Function.__proto__ === Function.prototype
。我认为有两种可能:一是为了保持与其他函数一致,二是就是表明一种关系而已。
简单的说,我认为:就是先有的Function,然后实现上把原型指向了Function.prototype,但是我们不能倒过来推测:“因为Function.__proto__ === Function.prototype
,所以 Function 调用了自己生成了自己。”