对于JavaScript来说,在ES6之前没有引入类的概念,所以创建实例是通过构造函数实现的。
在学习原型和原型链之前我们先要明白构造函数:
一、构造函数
1、什么是构造函数?
所谓构造函数,就是提供一个生成对象的模版,并描述对象的基本结构的函数。一个构造函数,可以生成多个对象,每个对象都有相同的结构。它的基本结构如下:
function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; this.runing = function() { console.log("人走路"); } }
2、构造函数的特点
- 函数名的首字母一般是大写;
- 函数体内使用this关键字;
- 生成对象的时候,必须使用new命令来调用构造函数。
3、构造函数如何生成对象
function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; this.runing = function() { console.log("人走路"); } } const person = new Person("余生", 27, '男'); console.log(person); // {name: "余生", age: 27, sex: "男", runing:f()}
上面的代码首先创建了一个构造函数Person,然后通过new命令调用了Person,并传入参数。最终创建了一个person对象。在Person构造函数中,有一个runing方法,每次new的时候都会生成这个方法,而且每次都是一摸一样的,为了把这个方法单独放到一个地方,能让所有的实例都能访问到,这就需要原型。
二、原型
1、prototype属性
我们知道在JavaScript中函数也是一个对象,是对象就有它的属性。(原型)prototype就是函数对象的一个属性。而且prototype是函数所独有的。
从上面的截图中可以看出:
创建的构造函数Person有一个prototype属性,这个属性是一个对象,这就是构造函数的原型(prototype)。并且原型(prototype)对象有一个constructor属性,constructor属性指向了Person构造函数。
2、 __ proto__ 属性
1)在JavaScript中所有的对象都有 __proto__,并且都指向创建该对象的函数的prototype。
2)所有的函数都是由Function函数创建的,所以函数的__proto__指向Function的prototype。
function Person() {}; Person.__proto__ === Function.prototype; // true
3)Function也是函数,因此它也由Function创建的,也就是说它自己创建了自己!所有Function的 __proto__指向的就是Function的prototype。
Function.__proto__ === Function.prototype; // true
4)Object函数也是Function函数创建的,因此Object的__proto__也是指向Function的prototype。
Object.__proto__ === Function.prototype; // true
5)prototype也是一个对象,它是Object函数创建的,所以prototype的__proto__指向Object的prototype。
function Person() {}; Person.prototype.__proto__ === Object.prototype; // true
6)但是Object.prototype却是一个特例,它的__proto__指向的是null。 设计上是为了避免死循环而设置的。
Object.prototype.__proto__ === null; // true
因此,根据上面的几条基本概念,从这段简单的代码我们可以画出这样一条关系链图:
function Person() {}; const person = new Person();
三、原型链
通过一段代码进入原型链的学习:
function Person(name) { this.name = name; } Function.prototype.runing = function() { console.log('人会走路'); } const person = new Person("余生"); person.runing(); person.hasOwnProperty("name");
我们来分析一下上面这段代码:
首先我们定义了一个方法Person,然后在Function的prototype上添加了一个属性runing,这个属性是一个方法。
接下来我们通过上面的Person方法new出来一个person对象,因此person.__proto__指向了Person.prototype。
前面我们说过,Person.prototype也是一个对象,是通过Object函数生成,所以Person.prototype.__proto__指向了Object.prototype。
上面的代码中,person本身是没有runing属性,但是却能成功访问。这里就需要原型链了。
如果一个对象访问某一个属性的时,它自身没有这个属性,那它就会顺着它的__proto__向上查找,如果它的__proto__上依然没有这个属性,那就继续向上查找,直到找到为止。
runing属性在person对象中没有找到,就会继续找person.__proto__,也就是Person.prototype,很显然,这里找到了,就不会再向上查找了。
hasOwnProperty属性显然person对象中没有找到,就会继续找person.__proto__,也就是Person.prototype,很显然,Person.prototype中依然找不到,于是继续向上在Person.prototype.__proto__中找。
Person.prototype是一个普通对象,它是由Object方法创建的,因此Person.prototype.__proto__就是Object.prototype,很显然,Object.prototype里面已经定义了hasOwnProperty方法(属性),因此在这里也找到了。
上面这种查找形式就成为原型链。