原型与原型链是一个比较抽象的东西,因为平时工作中很少用到,所以学了也容易忘。针对这种抽象的概念,最好是用我们熟悉的概念去理解它。下面,我举一个例子:
假设浙大学生的构造函数,叫function ZJUer(),JS中每个构造函数都有一个天然存在的原型对象prototype,它就像一个藏宝库一样,储存了这个构造函数的公共属性和方法,对于ZJUer()来说,即每个浙大在校学生都拥有的权利,如下图:
假如小明是浙大的学生:
function ZJUer(name, age) {
this.name = name;
this.age = age;
}
ZJUer.prototype.studentCard = '浙江大学学生证';
ZJUer.prototype.borrowBook = function (bookName) {
console.log('借书成功:' + bookName);
}
let xiaoming = new ZJUer('小明', 18);
console.log(xiaoming.studentCard);
xiaoming.borrowBook('世界文明史');
代码运行结果:
上面的代码中,我们在ZJUer()这个构造函数的prototype中添加了属性studentCard和borrowBook,然后实例化了一个学生小明,然后对小明进行了访问这两个属性的操作,运行结果说明,对于小明来说,这两个属性都是能被访问到的。但是单看这个实例对象本身,是看不到这两者的,只能看到它的私有属性name和age:
那么为什么访问studentCard和borrowBook的时候没有报错呢?这里就涉及到一个很重要的知识点:如果我们要访问一个对象的某一个属性或方法,会首先在它的私有属性中查找,如果没有找到,会顺着__proto__原型链继续查找,而实例对象的__proto__就是构造函数的prototype。也就是去ZJUer().prototype里去找,果然找到了,就会返回这两个属性。(下图中[[Prototype]]就是__proto__)
如果我们当时没有在ZJUer()的属性库里设置这两个属性怎么办呢?没关系,ZJUer.prototype说白了就是一个对象,既然是对象它就有__proto__,只要有就可以一直往上查下去。那么想要知道ZJUer.prototype.__proto__是什么很简单,只要知道它的构造函数是谁就行了,很明显,对象类型的构造函数就是Object()。所以ZJUer.prototype.__proto__就等于Object.prototype:
console.log(ZJUer.prototype.__proto__)
console.log(ZJUer.prototype.__proto__ === Object.prototype)
那么理论上来说,如果我们把studentCard和borrowBook两个属性从ZJUer.prototype转移到Object.prototype上,小明依然可以访问到他们:
function ZJUer(name, age) {
this.name = name;
this.age = age;
}
Object.prototype.studentCard = '浙江大学学生证';
Object.prototype.borrowBook = function (bookName) {
console.log('借书成功:' + bookName);
}
let xiaoming = new ZJUer('小明', 18);
console.log(xiaoming.studentCard)
xiaoming.borrowBook('世界文明史')
果然,只要原型链上有这个属性,就能访问到,区别只是查到到哪一层而已。
总结
-
所有对象都有__proto__(隐式原型)
-
所有构造函数都有__proto__(构造函数也是对象)和prototype(显式原型)
-
所有实例对象的__proto__都指向其构造函数的prototype
-
原型链指的是__proto__构成的链