文章の目录
正文
原型和原型链
构造函数
通过 new 关键词调用,可以创建相同类型的对象的函数,我们称之为构造函数。
(如需了解构造函数相关内容,请阅读笔者另一篇文章:JS 入门之对象中对象构造器)
function Student(no, name) {
this.stuNo = no;
this.name = name;
this.identity = "学生";
this.task = function() {
console.log("任务就是学习!");
}
};
原型对象
什么是原型对象
在 js 中,每个函数类型的数据,都有一个 prototype 属性,该属性指向一个对象,我们称之为原型对象。
对于原型对象来说,它有一个 constructor 属性,指向它的构造函数。
原型对象的作用
原型对象最主要的作用是存放实例对象的公有属性和公有方法。
在上面的例子中,identity 属性和 task 方法对于所有实例来说都一样。如果放在构造函数 Student 内,每创建一个实例对象都会重复创建它们。所以我们可以把这些公有的属性和方法放在原型对象内。
function Student(no, name) {
this.stuNO = no;
this.name = name;
};
Student.prototype.identity = "学生";
Student.prototype.task = function() {
console.log("任务就是学习!");
}
var stu1 = new Student(1001, "zhangsan");
var stu2 = new Student(1002, "lisi");
console.log(stu1.identity); // 学生
console.log(stu2.identity); // 学生
stu1.task(); // 任务就是学习!
stu2.task(); // 任务就是学习!
注:
- 在 js 中,如果对象在自己这里找不到某个属性或方法,就会查看该对象构造函数的原型对象,如果上面有对应的属性或方法,就会返回属性值或调用方法。
- 实例对象 stu1 和 stu2 上不存在 identity 属性和 task方法,则会查看它们构造函数的原型对象。
- 可以通过 stu1.constructor 查看对象的构造函数。
-
console.log(stu1.constructor) // Student // constructor 是实例对象 stu1 的构造函数的原型对象上的属性,该属性指向 stu1 的构造函数
-
原型链
如果在实例对象的构造函数的原型对象上也没有找到我们需要的属性和方法呢?这时就需要原型链了。
在说原型链之前,我们先了解两个概念:
显式原型和隐式原型
显式原型
显式原型就是利用 prototype 属性查找原型,只是这个属性是函数类型数据的属性。
隐式原型
隐式原型是利用 __proto__ 属性查找原型,这个属性是对象类型数据的属性,指向当前对象的构造函数的原型对象。
console.log(stu1.__proto__ === Student.prototype); // true
console.log(stu1.__proto__ == Student.prototype); // true
根据以上内容,我们可以得出 prototype、constructor 和 __proto__ 之间的关系:
原型链
__proto__ 是对象类型数据的属性,而构造函数 Student 的原型对象也是一个对象,那么构造函数 Student 的原型对象也会有 __proto__ 属性,但是它又指向哪里呢?
我们可以分析一下:既然构造函数 Student 的原型对象也是一个对象,那么我们只需找到它的构造函数就能知道它 __proto__ 的指向。在 js 中,对象的构造函数就是 Object(),所以对象的原型对象就是 Object.prototype。所以构造函数 Student 的原型对象的原型对象就是 Object.prototype,所以构造函数 Student 的原型对象的 __proto__ 属性指向 Object.prototype。
Object.prototype 这个原型对象很特殊,它没有上一层的原型对象,或者说它的 __proto__ 指向的是 null。
所以可以将上面的关系图拓展一下:
到这里,就可以回答之前那个问题了,如果某个对象查找属性,自己和原型对象上都没有,那么就会继续往原型对象的原型对象上去找,在这个例子中就是 Object.prototype,这里就是查找的终点,如果在这里也找不到的话,就没有更上一层了(Object.prototype 的更上一层是 null),直接返回 undefined。
可以看出,整个查找过程都是顺着 __proto__ 属性,一步一步往上查找,形成了像链条一样的结构,这样的结构,我们称之为原型链,也叫做隐式原型链。
也正是因为这个原因,我们在创建对象,数组,函数等数据的时候,都自带一些属性和方法,这些属性和方法是在它们的原型上面保存着,所以我们可以直接使用它们的属性和方法。
函数也是一种对象
在 js 中,函数也是一种特殊的对象,因此函数也有 __proto__ 属性。
在 js 中,所有函数都可以看作是 Function() 的实例,而 Student() 和 Object() 都是函数,所以它们的构造函数就是 Function()。
Function() 本身也是函数,所以 Function() 的构造函数也是 Function()。
console.log(Student.constructor === Function); // true
console.log(Object.constructor === Function); // true
console.log(Function.constructor === Function); // true
既然已经知道了函数的构造函数,那么函数的 __proto__ 属性的指向也就很明了了,就是 Function.prototype。
console.log(Student.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
根据以上结论,我们可以拓展出一张更大的关系图:
由于上图部分箭头不太清晰,补充下图:
总结
- 构造函数是使用 new 关键字来创建对象的函数。所有函数都是 Function() 的实例。
- 原型对象是用来存放实例对象的公有属性和公有方法的一个公共对象,所有原型对象都是 Object() 的实例。
- 原型链又叫隐式原型链,是由 __proto__ 属性串联起来的。原型链的尽头是 Object.prototype。Object.prototype 的尽头(下一层)是 null。