前言:
而今看官们对“js的原型和原型链”大概比较关怀,咱们都想要了解一些“js的原型和原型链”的相关文章。那么小编同时在网络上网罗了一些有关“js的原型和原型链””的相关内容,希望各位老铁们能喜欢,咱们快快来了解一下吧!对于很多前端新手来说,原型和原型链是个非常让人头痛的东西,prototype、[[prototype]]傻傻分不清楚,今天我们就原型和原型链来尝试做一番解析,如果本文能够让大家有所收获,那就是我最大的成就。
原型对象
我们先来认识几个属性:
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,该属性对应当前对象的原型,也就是说prototype就是通过调用构造函数而创建的那个对象实例的原型对象。
所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性,[[Prototype]],对应Firefox、Safari和Chrome浏览器中的__proto__属性),指向构造函数的原型对象。
看第一遍你可能会有种头晕目眩的感觉,没关系,下面我们来举个栗子展开解析下:
function Person() {
}
Person.prototype.name = '张三';
Person.prototype.age = 18;
Person.prototype.sayMyself = function () {
console.log('my name is ' + this.name + ', I am ' + this.age + ' years old!');
};
var person1 = new Person();
var person2 = new Person();
person1.name = '李四';
person1.age = 27;
person1.sayMyself(); // 实例属性,my name is 李四, I am 27 years old!
person2.sayMyself(); // 原型属性,my name is 张三, I am 18 years old!
delete person1.name;
delete person1.age;
person1.sayMyself(); // 原型属性,my name is 张三, I am 18 years old!
从上面这个例子中,我们可以发现:
构造函数(Person)有prototype属性,指向它的原型对象,即Person.prototype;
原型对象(Person.prototype)又有一个constructor属性,它指向该原型对象所在的函数,即Person.prototype.constructor = Person;
然后用构造函数Person创建两个实例person1和person2,每个实例中都有一个属性__proto__,都指向该构造函数的原型对象,即person1.__proto__ = person2.__proto__ = Person.prototype;
这样解释大家心里对这三个属性应该有了一定的概念了吧。
使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
也就是说,上述例子中的person1和person2访问的都是同一组属性和同一个sayMyself()函数;
当我们为person1添加name和age属性时,此时,person1会优先访问到自己的属性,屏蔽了对原型中对应属性的访问,但不会改变原型中的属性。若用delete,则可以删除person1实例的属性,重新访问原型中的属性。举个不恰当的例子,就好比吃饭,我们肯定是先吃碗里的,碗里没饭了才会吃锅里的,这就是传说中的吃着碗里的想着锅里的吧。
原型链
构造函数、原型和实例的关系:每个构造函数(P)都有一个原型对象(P.prototype),原型对象都包含一个指向构造函数的指针(P.prototype.constructor),而实例(p)都包含一个指向原型对象的内部指针(p.__proto__)。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
概念都是枯燥乏味的,同样来个栗子:
function Person() {
}
Person.prototype.name = '张三';
Person.prototype.age = 18;
Person.prototype.sayMyself = function () {
console.log('my name is ' + this.name + ', I am ' + this.age + ' years old!');
};
function MoreMsg() {
}
MoreMsg.prototype.job = 'teacher';
MoreMsg.prototype = new Person();
var person = new MoreMsg();
person.sayMyself(); // my name is 张三, I am 18 years old!
console.log(person.job); // undefined
MoreMsg.prototype.job = 'programmer';
MoreMsg.prototype.constructor = MoreMsg; // 如需要,使得constructor重新指向MoreMsg
console.log(person.job); // programmer
以上例子定义了两个构造函数Person和MoreMsg,两个构造函数有着各自的属性和方法,MoreMsg继承了Person,而继承是通过重写原型对象MoreMsg.prototype,取而代之是Person的实例。结合前一个例子,我们可以这样理解,本例中的MoreMsg.prototype就相当于是前一个例子中的person1或person2,因为person1包含一个指向对应原型对象的内部指针(person1.__proto__ = Person.prototype),所以可以推断出此时的原型对象MoreMsg.prototype包含一个指向另一个原型对象Person.prototype的指针,即MoreMsg.prototype.__proto__ = Person.prototype,这样就构成了一个实例与原型的链条。
本例中,重写了原型对象MoreMsg.prototype,因此重写之前给MoreMsg.prototype添加的属性全部作废,所以第一次console.log(person.job)才会输出undefined,而且MoreMsg.prototype.constructor属性也就变成了新对象的constructor属性(指向Person构造函数),不再指向MoreMsg函数,如需使得constructor重新指向MoreMsg,则需要执行MoreMsg.prototype.constructor = MoreMsg。
就好比我们玩游戏,一个号玩了很久,突然想重新玩一个号,那前一个号就白玩了,必须重头开始玩。
所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype。
所以,上述例子展示的原型链中还包括另外一个继承层次(Object)。
原型和原型链就介绍到这,如果大家第一遍没看懂,没关系,再看几遍,跟着多敲几遍代码;如果依然不懂,还是没关系,工作一段时间后,等js比较熟练后,回过头来再看看,你会发现你突然就顿悟了。
------------笑对人生,能穿透迷雾;笑对人生,能坚持到底;笑对人生,能化解危机;笑对人生,能照亮黑暗。