前言
原型和原型链是 JavaScript 继承的基石,也是面试中除了闭包之外问得最多的基础知识。本文理清 prototype、__proto__、constructor 三者的关系,并结合实际应用和 Vue3 源码加深理解。
一、三个核心概念
先把最容易混淆的三个属性说清楚:
| 属性 |
归属 |
说明 |
prototype |
函数独有 |
函数的原型对象,普通对象没有 |
__proto__ |
所有对象(除 null) |
指向构造函数的 prototype |
constructor |
prototype 上的属性 |
指回构造函数本身 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function Person(name) { this.name = name }
const p = new Person('张三')
console.log(Person.prototype) console.log(p.__proto__) console.log(p.__proto__ === Person.prototype) console.log(Person.prototype.constructor === Person)
console.log(p.constructor === Person)
|
一句话总结:__proto__ 和 constructor 是对象和函数之间的桥梁,prototype 是函数的独有属性,三者构成了一组闭环关系。
二、原型链
1 2 3 4 5 6 7
| function Person() {} const p = new Person()
console.log(p.__proto__) console.log(p.__proto__.__proto__) console.log(p.__proto__.__proto__.__proto__)
|
原型链图示:
1 2 3 4
| p ──__proto__──► Person.prototype ──__proto__──► Object.prototype ──__proto__──► null │ ├── constructor: Person └── sayHi: function() {}
|
属性查找规则
1 2 3 4 5 6 7 8
| function Person() { this.name = '实例属性' } Person.prototype.name = '原型属性'
const p = new Person() console.log(p.name)
delete p.name console.log(p.name)
|
读取时从下往上查找,直到 null;hasOwnProperty 可以判断属性是自身的还是原型链上的。
三、instanceof 原理
instanceof 的本质是沿着 __proto__ 链向上查找,匹配 prototype:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function myInstanceof(instance, constructor) { let proto = Object.getPrototypeOf(instance)
while (proto) { if (proto === constructor.prototype) return true proto = Object.getPrototypeOf(proto) }
return false }
console.log(myInstanceof([], Array)) console.log(myInstanceof([], Object)) console.log(myInstanceof({}, Array))
|
四、继承的 6 种方式
1. 原型链继承
1 2 3 4
| function Parent() { this.colors = ['red', 'blue'] } function Child() {} Child.prototype = new Parent()
|
2. 构造函数继承
1 2
| function Child() { Parent.call(this) }
|
3. 组合继承(最常用)
1 2 3 4
| function Child() { Parent.call(this) } Child.prototype = new Parent() Child.prototype.constructor = Child
|
4. 原型式继承
1 2 3 4 5 6
| function create(o) { function F() {} F.prototype = o return new F() }
|
5. 寄生组合继承(最优方案)
1 2 3 4 5 6 7 8 9 10 11 12
| function inherit(Child, Parent) { Child.prototype = Object.create(Parent.prototype) Child.prototype.constructor = Child }
function Parent(name) { this.name = name } function Child(name, age) { Parent.call(this, name) this.age = age } inherit(Child, Parent)
|
6. ES6 class(语法糖)
1 2 3 4 5 6 7 8 9 10 11
| class Parent { constructor(name) { this.name = name } say() { console.log(this.name) } }
class Child extends Parent { constructor(name, age) { super(name) this.age = age } }
|
五、字符串 / 数字为什么能调方法
1 2 3 4 5 6 7
| const str = 'hello' console.log(str.toUpperCase())
const temp = new String('hello') temp.toUpperCase()
|
1 2 3 4 5 6 7 8 9 10
| const s = 'text'
s.__proto__ String.prototype.__proto__ Object.prototype.__proto__
;(1).__proto__ true.__proto__
|
六、实际开发应用场景
1. 数组方法统一扩展(非侵入式)
1 2 3 4 5 6
| Array.prototype.sum = function () { return this.reduce((a, b) => a + b, 0) }
console.log([1, 2, 3, 4].sum())
|
2. 判断数据类型
1 2 3 4 5 6 7 8 9 10 11
| console.log(typeof [])
function getType(val) { return Object.prototype.toString.call(val).slice(8, -1) }
console.log(getType([])) console.log(getType(null)) console.log(getType(new Date()))
|
3. 类数组转数组
1 2 3 4 5 6 7 8
| function foo() { const args = Array.prototype.slice.call(arguments) const args2 = Array.from(arguments) console.log(args) } foo(1, 2, 3)
|
4. 方法借用
1 2 3 4 5 6 7 8 9
| const obj = { 0: 'a', 1: 'b', length: 2 }
Array.prototype.push.call(obj, 'c') console.log(obj)
const filtered = Array.prototype.filter.call(obj, v => v !== 'b') console.log(filtered)
|
七、Vue3 中原型和原型链的应用
1. Vue 实例的原型方法挂载
Vue3 的 app.config.globalProperties 本质上就是把方法挂到组件实例的原型链上:
1 2 3 4 5 6 7 8 9
| const app = createApp(App)
app.config.globalProperties.$http = axios
app.config.globalProperties = Object.create(null)
|
1 2 3 4 5 6 7 8 9 10
| <script> export default { mounted() { // $http 通过原型链查找获取 this.$http.get('/api/data').then(res => { console.log(res) }) } } </script>
|
2. Vue3 响应式的去原型技巧
Vue3 对某些特殊对象(如纯对象)会劫持 __proto__ 避免原型链上的修改触发更新:
1 2 3 4 5 6 7 8 9 10 11
| const raw = Object.create(null) raw.key = 'value'
function toReactive(value) { if (isProxy(value)) return value return reactive(value) }
|
3. 组件实例原型链
1 2 3 4 5 6 7
| const instance = getCurrentInstance()
instance.__proto__ instance.__proto__.__proto__ instance.__proto__.__proto__.__proto__
|
4. 自定义 Hook 中的原型应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| class ApiClient { constructor(baseURL) { this.baseURL = baseURL }
async get(path) { const res = await fetch(`${this.baseURL}${path}`) return res.json() }
async post(path, data) { const res = await fetch(`${this.baseURL}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) return res.json() } }
class AuthClient extends ApiClient { constructor(baseURL, token) { super(baseURL) this.token = token }
async get(path) { const res = await fetch(`${this.baseURL}${path}`, { headers: { Authorization: `Bearer ${this.token}` } }) return res.json() } }
import { ref } from 'vue'
const client = new AuthClient('/api', localStorage.getItem('token'))
export function useUser() { const user = ref(null)
async function fetchUser(id) { user.value = await client.get(`/users/${id}`) }
return { user, fetchUser } }
|
5. Vue3 的 __v_skip 标记
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
import { markRaw, reactive } from 'vue'
const heavyObj = markRaw({ mapData: new Map(), hugeList: new Array(100000) })
const state = reactive({ normal: 'data', heavyObj })
|
总结
prototype 是函数独有、__proto__ 是所有对象都有、constructor 指回构造函数
- 原型链查找从下往上,终点是
null
instanceof 就是沿着 __proto__ 逐级匹配 prototype
- 寄生组合继承是 ES6
class extends 的底层实现
- 原始值调方法靠自动装箱,临时创建包装对象
- Vue3 的
globalProperties、组件实例原型链、markRaw 都在用原型机制
Object.create(null) 创建无原型对象,避免属性查找污染
内容由 AI 生成,仅供参考。本文发布于 码上学习,转载请注明出处。