前言
在 JavaScript 中,原型是一个非常重要的概念。
如果不理解原型,很多内容都会变得很模糊,比如:
- 构造函数
- 实例方法共享
- 原型链
- 类的本质
本文重点讲清楚:什么是原型、为什么需要原型、prototype 和 proto 有什么区别、原型链
一、什么是原型?
在 JavaScript 中,每个函数都有一个特殊属性:
prototype
这个属性指向一个对象,这个对象就叫做 原型对象。
看一个例子:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`你好,我是 ${this.name}`);
};
const p1 = new Person('Tom');
const p2 = new Person('Alice');
p1.sayHello(); // 你好,我是 Tom
p2.sayHello(); // 你好,我是 Alice
这里:
Person.prototype
就是构造函数
Person
的原型对象。
二、为什么需要原型?
如果我们把方法写在构造函数内部:
function Person(name) {
this.name = name;
this.sayHello = function () {
console.log(`你好,我是 ${this.name}`);
};
}
那么每创建一个实例,就会重新创建一次
sayHello
方法。
这样会导致:
- 方法重复创建
- 浪费内存
如果写到原型上:
Person.prototype.sayHello = function () {
console.log(`你好,我是 ${this.name}`);
};
那么所有实例就可以共享同一个方法。
所以原型最核心的作用就是:
让实例共享属性和方法。
三、prototype 和 proto 的区别
这是非常高频、也非常容易混淆的知识点。
1)prototype
- 只有函数才有
- 指向构造函数的原型对象
2)__proto__
- 对象才有
- 指向该对象的原型
看例子:
function Person() {}
const p = new Person();
console.log(Person.prototype);
console.log(p.__proto__);
console.log(p.__proto__ === Person.prototype); // true
这个关系非常重要:
实例对象.__proto__ === 构造函数.prototype
四、原型上的属性和实例上的属性
function Person(name) {
this.name = name;
}
Person.prototype.age = 18;
const p = new Person('Tom');
console.log(p.name); // Tom
console.log(p.age); // 18
这里:
-
name是实例自己的属性
-
age是原型上的属性
当访问
p.age
时,JS 发现实例本身没有这个属性,就会去原型上找。
五、原型的实际意义
原型的最大意义就是“共享”。
例如数组为什么都有
push
、
pop
、
map
这些方法?
因为这些方法都定义在:
Array.prototype
上。
所以数组实例本身不需要重复拥有这些方法。
六、总结
原型可以用一句话总结:
原型是 JavaScript 中实现属性和方法共享的机制。
重点记住:
-
函数有
prototype -
对象有
__proto__ -
实例.__proto__ === 构造函数.prototype
学完原型之后,接下来最重要的就是 原型链。
原型链本质上解决的是一个问题:
当我们访问对象属性时,JavaScript 到底是怎么查找的?
理解了原型链,你就能更清楚地看懂:
-
为什么对象可以调用某些方法
-
为什么数组能用
push -
为什么实例可以访问原型方法
原型链
一、什么是原型链?
当访问一个对象的属性或方法时,JavaScript 会先在对象本身查找。
如果找不到,就会去对象的原型上查找。
如果原型上还找不到,就继续去原型的原型上查找。
这一层一层向上查找的结构,就叫做 原型链。
二、属性查找过程
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`你好,我是 ${this.name}`);
};
const p = new Person('Tom');
console.log(p.name); // Tom
p.sayHello(); // 你好,我是 Tom
查找p.name
-
先看
p自身有没有
name -
有,直接返回
查找p.sayHello
-
先看
p自身有没有
sayHello -
没有
-
去
p.__proto__,也就是
Person.prototype上找
-
找到了,执行它
三、原型链的尽头是什么?
function Person() {}
const p = new Person();
console.log(p.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
所以原型链大致是:
p
→ Person.prototype
→ Object.prototype
→ null
null
就是原型链的终点。
四、数组的原型链
数组也是对象,所以也有原型链。
const arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
原型链结构大致是:
arr
→ Array.prototype
→ Object.prototype
→ null
所以数组既能用数组方法,也能用对象原型上的某些方法。
五、为什么原型链重要?
因为 JavaScript 中很多方法并不是对象自身直接拥有的,而是通过原型链继承来的。
例如:
const arr = [1, 2, 3];
arr.push(4);
arr.toString();
这里:
-
push来自
Array.prototype -
toString可能继续来自更上层原型
六、总结
原型链可以简单理解为:
对象查找属性时,沿着原型一层层向上查找的链式结构。
查找规则:
-
先找对象自身
-
再找原型
-
再找原型的原型
-
直到
null