prototype 是遗产,proto 是族谱:一文吃透 JS 原型链

300 阅读4分钟

JavaScript 原型系统核心解析

引言:JS 面向对象的独特哲学

在传统的面向对象语言中,对象是基于 “类”(Class) 创建的

  • 类是模板,定义了属性和方法;
  • 对象是类的实例,通过 new Class() 生成;
  • 继承是“血缘关系”——子类继承父类,结构静态且明确。

而 JavaScript 采用 原型式面向对象(Prototype-based OOP)

  • 没有“类”的概念(ES6 的 class 只是语法糖);
  • 对象直接从 另一个对象(原型)  继承属性和方法;
  • 每个对象都有一个内部链接([[Prototype]]),指向它的原型;
  • 继承是“委托关系”——找不到属性时,自动向原型查找。

构造函数与实例创建

function Person(name,age){
    this.name =name;
    this.age =age;
}
Person.prototype.speci = '人类'
  • new 的作用:创建一个新的对象,并返回
  • this 的指向:this会在new这个新对象时绑定到新创建的对象上
  • 实例私有属性的定义:在 JavaScript 中,实例私有属性是指直接定义在对象自身上、不被原型共享的属性。在上面的代码中,name和age是私有的,而speci则是所有对象实例所共有的属性

对象字面量的创建就像是传统工匠
构造函数就像是一个模具,可以快速的创建的出相同的对象实例

prototype 是什么?

4f3dc6bb97deaf5cdd9219bbd8316599.png

  • 定义:他是所有函数独有的一个特殊属性
  • 作用:用来存储所有实例对象所共有的属性和方法
  • 结构:默认包含constructor,指向该实例原型的构造函数,__proto__指向对象原型
{
constructor:...
__proto__:....
}

实例如何访问原型? __ proto __ 与原型链

function Person(name,age){
    this.name =name;
    this.age =age;
}
Person.prototype.speci = '人类'
const person1 =new Person('张三',18);
const person2 =new Person('李四',19);
console.log(person1);
console.log(person1.__proto__);

  • __ proto__是什么:是 JavaScript 中每个对象都具有的一个内部属性,它指向该对象的原型(prototype) ,也就是这个对象“从哪里继承属性和方法”。 等我们通过__proto__去进行访问时,可以得到该实例对象所继承的原型

image.png

但是,值得注意的是__proto__在现在并不推荐使用,__proto__ 是 [[Prototype]] 的 getter/setter。 而在现在的编程当中,我们更推荐使用Object.getPrototypeOfObject.setPrototypeOf 来取代 __proto__ 去 get/set 原型

prototype 与 __proto__的本质区别

  • 归属的对象不同:prototype是只有函数才具有的属性,而__proto__是所有对象都具有的一个内部属性
  • 所代表的含义不同:prototype是对象的原型,它包含了该构造函数所有实例对象的共有属性和方法,是一个对象,而__proto__ 是指向实例对象原型的链路,指向了实例对象的构造函数的prototype
  • 二者的关系:如图,实例.proto ==构造函数.prototype

03df9c3aaed1162b6c865b68407aa508.png

覆盖 prototype 的常见问题

  • constructor 丢失 考虑下面这段代码:

  function Person(name,age){
            this.name =name;
            this.age =age;
        }
        Person.prototype={
            speci:'人类',
        }
        var person1 =new Person('张三',18);
        console.log(Person.prototype.constructor == Person);
        

打印的结果是什么? 你以为是ture吗?实际上却是false!!!
按照一贯的思维来说,Person对象原型的构造函数确实是Person(),但是观察上述代码,我们修改了prototype,并把他赋值了一个普通的对象,但是我们没有重写构造函数,这就导致了实际上person.protope已经变成了Object的原型,所以才会导致false的结果

  • 如何正确重写并修复
    • 方案一:重写构造函数
      在修改prototype时在代码中添加constructor:Person;就能重新告诉引擎当前这个实例的原型依旧是Person
    • 方案二:只添加,不覆盖
      例如:
Person.prototype.speci='人类';

这样的操作只会在原有的基础上扩展出speci属性,而不会覆盖掉整个的prototype

函数也是对象:FunctionObject 的关系

  • Person.__proto__ === Function.prototype
    考虑下面这段代码:
 function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        Person.prototype = {
            speci:"人类",
            
        }
        // 对象的原型对象是?
        console.log(Object.getPrototypeOf(Person)==Person.prototype);//flase
        

我的理解
Person是一个构造函数(所以他是Function的实例),它的__proto__指向的是函数的的原型,也就是Function.prototype
Function则是Object的一个实例,它的__proto__指向了Object.prototype

  • 原型链的完整路径
Person
  └─ __proto__ → Function.prototype
                   └─ __proto__ → Object.prototype
                                    └─ __proto__ → null

原型链的顶点:Object.prototype

所有对象都沿着原型链最终指向Object.prototype,而Object[[Prototype]]指向的null,这意味着它是所有对象的终点 image.png

理解 JS 原型的关键要点

lQLPJyCHLn1iTmPNAg3NAk6wOERTvHM11HQJAGwnY_a5AA_590_525.png 终极记忆法:
prototype是父亲给后代的实际财富,它代表着所有后代所实际拥有的共有属性和方法
__proto__更像是一份DNA的鉴定,它指明了实例对象的父亲或者说祖宗是谁,我继承的谁的财富

虽然在现代的javaScript我们可以使用class语法,但它实际上只是有一个语法糖,它的底层原理依然是构造函数 + prototype,清晰的理解prototype和__proto__有助于后面更高效的学习JS