开篇
对于JavaScript学习的深入以后势必会接触到原型以及原型链这俩个骚知识点,对于我像自己这么直的人来说有时候一下还真转不过那个弯弯来,毕竟人比较直男。那么对于理解什么是原型,什么又是原型链,还有什么原型对象啊等等,都是我们需要掌握!那么就从下面的文章开始慢慢聊…..
第一、构造函数模式
对于构造函数在许多的编程语言中,像c++、java等都有它的身影,简直谜一样的身影啊。那么对于JavaScript这门语言来说,构造函数也是必不可少一个部分,而最重要的是构造函数它跟所要介绍的知识点原型以及原型是链息息相关的,至于其他的可以慢慢意淫了。虽然大伙都知道啥是构造函数,但是还是写一个简单的构造函数出来瞅一下:
function Person() {
this.name = "cc";
}
var p = new Person();
console.log(p.name)//cc
从上面的构造函数来看,它和普通的函数只是命名规则不一样,它是首字母大写,我们记住这个是构造函数就可以了,要说其它和普通的函数的区别就是它们的调用方式不同。构造函数本身也是函数,但是它可以用来创建对象(任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数;)。上面的new的操作就是创建了Person的实例,那么new操作符具体又做了什么? new操作符其实做了以下四点:
1、创建一个新的对象。
2、链接到原型。
3、绑定this。
4、返回新对象。
对于构造函数其他的知识可自行百度
第二、原型模式
我们所创建的每一个函数,它们都包含了一个prototype(原型)属性,它呢就是一个指针,指向函数的原型对象,那么prototype属性的值就是原型对象。也就是说:创建一个函数之后就默认有prototype,然后浏览器就会有相应的规则创建一个对象,而这个对象就是原型对象,而prototype就是指向这个对象。
看下面的例子:
function Person() {
this.name = "闪电";
}
Person.prototype.age = 23;
Person.prototype.job = "程序猿";
Person.prototype.sayName = function() {
console.log(11)
}
var p = new Person();
console.log(p.age,p.name)//23,闪电
p.sayName()//11
例子中打印了age和name以及sayName,我们在构造函数中定义了name而在原型对象中定义了age,但是它都全部打印出来了,也就是说它可以让实例共享它的所有属性和方法。既然如此,可是为什么构造函数中的name也被打印出来了呢?原来在默认的情况下,所以的原型对象都会有一个constructor属性,这个属性包含了一个指向原型对象(prototype)所在的函数的指针。从上面的例子就是Person.prototype.constructor指向的是Person,可以在控制台打印一下就知道了。说了这么多,我们也终于知道了什么是原型对象了,有了原型对象之后,我们可以继续为原型对象添加其他的属性还有方法。创建好的实例中内部包含了一个指针,指针指向原型对象,在浏览器中的对象都支持一个属性:__proto__,它是实例和构造函数的原型对象连接的指针。记住了是跟原型对象。(注:__proto__是部分浏览器实现的属性,而js的是[[prototype]])
console.log(p.__proto__)
打印之后得到以下的:
{age: 23, job: "程序猿", sayName: , constructor: }
age: 23
job: "程序猿"
sayName: ()
constructor: Person()
__proto__: Object
从打印得到的可以看出,Person的实例p的__proto__访问到的是上面age和job还有方法sayName,这不就是构造函数Person的原型对象么,所以它指向的是原型对象。还有一个属性constructor它是指向了构造函数Person,那么p.__proto__.__proto__则是指向了Object了。
我们可以使用Object.getPrototypeOf来检测一下:
console.log(Object.getPrototypeOf(p) == Person.prototype)//true
这样我就知道了返回的对象是这个对象的原型了。Object.getPrototypeOf而它可以很快的检测出它的原型对象。对于其他的检测方法像hasOwnProperty等需要的可以继续深入测试。
到现在我们应该明确知道了什么是原型(prototype)什么是原型对象了,那么我们至少入门了这个知识点。
第三、原型链
终于到了重点的地方了,通过上面的知识点之后,我们已经知道了什么是原型还有原型对象,那么现在开始理解原型链了,先看个例子:
function Person() {
this.name = "m";
}
Person.prototype.age = 23;
Person.prototype.job = "程序猿";
Person.prototype.sayName = function() {
console.log("被调用了")
}
var p = new Person();
p.sayName();//被调用了
这个例子还是上面的,变化不大,好像就没变化,先来个简单的看看先,实例p调用了这个sayName这个方法,那么它是怎么样一步一步的查找这方法的呢?首先,它会先在实例当中查找看是否有该方法,没有,那么继续查找,那么这一次是到了构造函数的原型对象上去找,然后在原型对象上面发现了这个方法,然后就打印出了“被调用了”。它们之间是通过__proto__去查找的,那么如果在原型对象上查找不到会继续到对象上原型对象中找只到Null为止,这查找的路径就是一条原型链了。看完这个那么再看一下红宝书上面的原话:构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(__proto__);
再看一个例子:
function Person() {
this.name = "原型链";
}
Person.prototype.sayName = function() {
console.log("被调用了")
};
function Berson() {
this.bname = "y"
}
Berson.prototype = new Person();
Berson.prototype.bsayName = function() {
console.log(1,this.bname)
};
var b = new Berson();
b.sayName();//被调用了
b.bsayName();//1 y
这个例子是一个实现继承的例子,Berson.prototype是Person的实例,构造函数的原型对象都继承了Person的方法还有属性,这个例子是这样的,实例b它(通过__proto__属性)指向了Berson的原型,而Berson的原型又指向了Person的原型,那么它搜索的路径为:1)搜索b实例,2)搜索Berson的原型,3)搜索Person的原型。然后在第三步骤的时候搜索到了该方法sayName,然后就调用了。如果搜索不到则继续到Object的原型查找,直到null位置,这个路径就是原型链了。
console.log(b.__proto__)//Person {name: "原型链", bsayName: ƒ}
console.log(b.__proto__.__proto__)//{sayName: ƒ, constructor: ƒ}
console.log(b.__proto__.__proto__.__proto__)//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(b.__proto__.__proto__.__proto__.__proto__)//null
上面打印的可以自行打印测试一下
总结
对于原型以及原型链只要深入的理解了也没有想象中的那么复杂,只有理解了原型这些东西后面我们再继续深入到继承这方面的知识的时候才游刃有余!如果个人理解有误,万望各位指正,谢谢!