JavaScript 原型
一、核心概念
1. 什么是原型
JS 是基于原型的面向对象语言,每个对象都有一个隐式属性 __proto__(原型对象),指向它的构造函数的 prototype。
- prototype:函数独有,函数的显式原型(原型模板)
- __proto__:所有对象独有,对象的隐式原型(访问原型的入口)
2. 三大关系
// 构造函数
function Person() {}
// 实例对象
const p = new Person();
- 实例.proto === 构造函数.prototype
console.log(p.__proto__ === Person.prototype); // true
- 构造函数.prototype.constructor === 构造函数本身
console.log(Person.prototype.constructor === Person); // true
- 所有普通对象最终都指向
Object.prototype
Object.prototype.__proto__ === null(原型链终点)
二、原型链
1. 定义
当访问对象属性/方法时:
1. 先在自身查找
2. 找不到 → 顺着 __proto__ 往上找(原型链)
3. 直到 Object.prototype,还找不到返回 undefined
2. 完整原型链图示
p(实例)
↓ __proto__
Person.prototype
↓ __proto__
Object.prototype
↓ __proto__
null (链结束)
示例:
Person.prototype.say = function(){ console.log('hello') }
p.say() // 自身没有,沿原型链找到 Person.prototype 上的方法
三、函数、对象、内置构造函数原型
1. Function 与 Object 特殊关系
- 所有函数都是
Function的实例 Function.__proto__ === Function.prototype(函数自引用)Object也是一个函数:Object.__proto__ === Function.prototype
2. 常见内置原型
// 数组
const arr = []
arr.__proto__ === Array.prototype
Array.prototype.__proto__ === Object.prototype
// 字符串
const str = ''
str.__proto__ === String.prototype
四、原型上添加属性/方法
1. 原型方法(共享,省内存)
推荐:方法写在 prototype,所有实例共用一份
function Person(name) {
this.name = name // 实例自有属性
}
// 挂载到原型
Person.prototype.sayName = function() {
console.log(this.name)
}
const p1 = new Person('张三')
const p2 = new Person('李四')
p1.sayName() // 张三
p2.sayName() // 李四
2. 实例属性(独有)
this.xxx 挂载在实例自身,每个实例独立一份。
3. 属性遮蔽
实例和原型有同名属性,优先使用实例自身属性(遮蔽):
Person.prototype.age = 18
p1.age = 20
console.log(p1.age) // 20(读自身)
delete p1.age // 删除实例属性
console.log(p1.age) // 18(读原型)
五、原型继承(ES5 经典继承)
1. 寄生组合式继承(ES5 最优继承)
// 父类
function Parent() {
this.parentVal = '父属性'
}
Parent.prototype.parentFn = function(){}
// 子类
function Child() {
Parent.call(this) // 继承父类实例属性
this.childVal = '子属性'
}
// 继承父类原型方法
Child.prototype = Object.create(Parent.prototype)
// 修正 constructor 指向
Child.prototype.constructor = Child
const c = new Child()
六、ES6 class 与原型
class 是语法糖,底层依然是原型:
class Person {
constructor(name) {
this.name = name // 实例属性
}
// 方法自动挂载到 Person.prototype
say() {}
}
const p = new Person()
console.log(p.__proto__ === Person.prototype) // true
class 继承 extends
底层还是原型链继承:
class Parent {}
class Child extends Parent {}
七、常用原型 API
obj.hasOwnProperty(prop)
判断属性是否为实例自有(不查原型)
p.hasOwnProperty('name') // true
p.hasOwnProperty('sayName') // false
Object.getPrototypeOf(obj)
标准获取原型(推荐,替代 proto)
Object.getPrototypeOf(p) === Person.prototype // true
-
Object.setPrototypeOf(obj, proto)
修改对象原型(不推荐,影响性能) -
isPrototypeOf()
判断是否在对方原型链上
Person.prototype.isPrototypeOf(p) // true
八、要点总结
prototype属于函数,__proto__属于对象- 实例
.__proto__永远指向构造函数的 prototype - 原型链终点:
Object.prototype.__proto__ = null - 方法放原型:共享、节省内存
class只是原型的语法糖,本质没变hasOwnProperty区分自身属性 / 原型属性
九、易错点
- 不要直接赋值
Child.prototype = new Parent()(会继承多余实例属性) - 修改原型会影响所有已有实例
- 箭头函数没有
prototype,不能当构造函数 null / undefined没有__proto__