关于js中的原型、原型链、继承,很多人都是一知半解,甚至从事了多年前端开发的同学,也不能够完全理解,因为大多数我们工作中都是用的框架,也很少涉及这些东西。但如果要想深入理解这些框架的原理和源码,这个坎还是要跨过去的,废话不多说,直接看文章。
一:js中对象的分类—>函数对象和普通对象
JavaScript 中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object 、Function 是 JS 这个语言自带的函数对象。下面举例说明
var obj1 = {};
var obj2 =new Object();
var obj3 = new fn1();
function fn1(){};
var fn2 = function(){};
var fn3 = new Function('name','console.log(name)');
console.log(typeof Object); //function
console.log(typeof Function); //function
console.log(typeof fn1); //function
console.log(typeof fn2); //function
console.log(typeof fn3); //function
console.log(typeof obj1); //object
console.log(typeof obj2); //object
console.log(typeof obj3); //object
在上面的例子中 obj1 obj2 obj3 为普通对象,fn1 fn2 fn3 为函数对象。
怎么区分,其实很简单,
凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。
fn1,fn2,归根结底都是通过 new Function()的方式进行创建的。
Function、 Object 也都是通过 New Function()创建的。
二:构造函数
我们先复习一下构造函数的知识:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() { alert(this.name) }
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');
上面的例子中 person1
和 person2
都是 Person
的实例。这两个实例都有一个 constructor
(构造函数)属性,该属性(是一个指针)指向 Person
。 即:
console.log(person1.constructor == Person); //true
console.log(person2.constructor == Person); //true
什么是构造器呢?这里可以这样理解:constructor的英文单词意识是“构造者,创建者” 。person1和person2都是Person的实例(可以说是儿子),儿子由爹(Person)生的,所以构造器就是实例的爸爸
注意!!!!!
但当我们console打印person1
和person2
的时候,发现其本身并没有constructor
属性。
但为何上面说person1和person2都“有”constructor
属性?
这是因为其顺着原型链(__proto__
)继承Person
的constructor
属性,所以是因为继承才有的
关于原型链和继承,后面的文章会说到。
我们展开__proto__
属性
首先是constructor
指向Person
,这个就是之前说的儿子的构造器是爸爸
然后Person
下面的prototype
属性有constructor
属性
而person1
和person2
的constructor
就是从prototype
这里继承而来的
此处不要搞乱了,再捋一下
实例的constructor指向构造函数,但是实例没有这个属性,就要顺着原型链去找,找到了,就继承过来,再指向构造函数
。
所以要记住两个概念(构造函数,实例):
person1 和 person2 都是 构造函数 Person 的实例
两句话:
实例的构造函数属性(constructor)继承自构造函数
实例的构造函数属性(constructor)指向构造函数。(即:儿子的创造者是他爹)
三:原型对象
在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype
属性,这个属性指向函数的原型对象。(先用不管什么是 __proto__
第后面的文章会有详细解释)
function Person() {}
Person.prototype.name = 'Zaxlct';
Person.prototype.age = 28;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function() {
alert(this.name);
}
var person1 = new Person();
person1.sayName(); // 'Zaxlct'
var person2 = new Person();
person2.sayName(); // 'Zaxlct'
console.log(person1.sayName == person2.sayName); //true
至此,我们得到了本文第一个「定律」:
每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性
那什么是原型对象呢?
我们把上面的例子改一改你就会明白了:
Person.prototype = {
name: 'Zaxlct',
age: 28,
job: 'Software Engineer',
sayName: function() {
alert(this.name);
}
}
原型对象,从现在开始你要牢牢记住原型对象就是 Person.prototype
,如果你还是害怕它,那就把它想想成一个字母 A:var A = Person.prototype
在上面我们给 A 添加了 四个属性:name、age、job、sayName
。其实它还有一个默认的属性:constructor
在默认情况下,所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person)
不要问为什么,js这门语言设计之初就是这样的,记住就行,如果非要一探究竟,就问js之父 Brendan Eich(布兰登·艾奇)去吧
上面这句话有点别扭,我们「翻译」一下:A 有一个默认的 constructor
属性,这个属性是一个指针,指向 Person
。即:
Person.prototype.constructor == Person
在上面第二小节《构造函数》里,我们知道实例的构造函数属性(constructor)指向构造函数 :
person1.constructor == Person
所以 这两个「公式」好像有点联系:
person1.constructor == Person
Person.prototype.constructor == Person
person1
为什么有constructor
属性?那是因为person1
是 Person
的实例,person1
继承了Person
的constructor
属性
那 Person.prototype
为什么有constructor
属性??同理,Person.prototype
(你把它想象成 A) 也是Person
的实例,继承了Person
的constructor
属性。
至此,我们得到了本文第二个「定律」:
原型对象(Person.prototype)是 构造函数(Person)的一个实例。没有通过new 进行实例化
也就是在 Person 创建的时候,创建了一个它的实例对象并赋值给它的 prototype
只不过该实例比较特殊,没有通过new关键字创建,而是默认就有的
那原型对象是用来做什么的呢?主要作用是用于继承。举个例子:
var Person = function(name){
this.name = name; // tip: 当函数执行时这个 this 指的是谁?
};
Person.prototype.getName = function(){
return this.name; // tip: 当函数执行时这个 this 指的是谁?
}
var person1 = new person('Mick');
person1.getName(); //Mick
从这个例子可以看出,通过给 Person.prototype
设置了一个函数对象的属性,那有 Person
的实例(person1)出来的普通对象就继承了这个属性。具体是怎么实现的继承,就要讲到下面的原型链了。
小问题,上面两个 this 都指向谁?
var person1 = new person('Mick');
person1.name = 'Mick'; // 此时 person1 已经有 name 这个属性了
person1.getName(); //Mick
故两次 this 在函数执行时都指向 person1。