关于JS的原型和原型链

这是一道考察前端基础概念理解深度的典型面试题目。为了更好的理解JS的原型和原型链的概念,我查阅了相关文章(本文中图片来自于参考文章,特此感谢作者),并进行下面的总结。如有错误,欢迎指出,谢谢!

目录

一、原型

二、原型链

三、概念细节

1.当我们说“原型”时,我们在说什么?

2.__proto__和prototype和constructor的区别?

四、举例说明

1.constructor指向

2._proto_指向

3.prototype指向

五、参考文献

一、原型

我对原型最初始的印象来自于C++中的继承,可以看看这段代码,是不是很熟悉:

// 基类(类似 JavaScript 的原型)
class Animal {
public:
    string name;
    
    Animal(string n) : name(n) {}
    
    void speak() {
        cout << name << " makes a sound." << endl;
    }
    
    void eat() {
        cout << name << " is eating." << endl;
    }
};

// 派生类(类似 JavaScript 的实例)
class Dog : public Animal {
public:
    Dog(string n) : Animal(n) {}
    
    // 重写基类方法(类似 JavaScript 的方法覆盖)
    void speak() {
        cout << name << " says: Woof! Woof!" << endl;
    }
    
    // 新增方法
    void fetch() {
        cout << name << " is fetching the ball." << endl;
    }
};

class Cat : public Animal {
public:
    Cat(string n) : Animal(n) {}
    
    void speak() {
        cout << name << " says: Meow!" << endl;
    }
};

大体就是,基类→派生类,变成了父类构造函数→子类构造函数。当构造函数创建出的实例上面没有要找的属性或者方法时,就从原型上面寻找。

关于原型准确的定义是:

原型是每个函数对象都有的一个特殊属性,称为prototype。这个属性指向一个对象,这个对象包含了可以由该函数创建的所有实例共享的属性和方法。

二、原型链

原型链是指当我们访问一个对象的属性时,JavaScript引擎会首先在对象自身的属性中查找,如果找不到,则会沿着__proto__属性指向的原型对象继续查找,直到找到该属性或到达原型链的顶端。

// 原型链的完整路径:
myDog 
→ Dog.prototype 
→ Animal.prototype 
→ Object.prototype 
→ null

// 验证:
console.log(myDog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true  
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

三、概念细节

刚才介绍了原型和原型链,也给出了对应的简单代码。道理我都懂,可我还是分不清__proto__和prototype的区别!原型不是父类的意思吗?怎么又成了属性了?这些东西和构造函数有什么关系?下面就来仔细看一下:

1.当我们说“原型”时,我们在说什么?

场景一:查找“原型”链:指的是沿着[[Prototype]]链进行查找(通常通过_proto_访问);

场景二:给“原型”添加方法时,实际上是指给构造函数的 prototype 添加方法

之所以有“原型不是父类的意思吗?怎么又成了属性了?”这样的疑问,是因为混淆了对象本身和指向他的指针。_proto_可以看作指向原型对象的指针(实际上是访问器属性,不是真正的指针,由实例对象指向原型对象),因此可以通过操作和修改_proto_来修改原型对象。

2.__proto__和prototype和constructor的区别?

_proto_是对象特有的,由一个对象指向另一个对象。由于在JS中函数也是对象,因此JS中函数也有。

// 创建一个对象
const person = {
    name: "John",
    age: 30
};

// 创建另一个对象,设置 person 作为其原型
const student = {
    grade: "A"
};

// 设置原型链
student.__proto__ = person;

console.log(student.name); // "John" - 从原型链找到
console.log(student.age);  // 30     - 从原型链找到
console.log(student.grade); // "A"   - 自身属性

prototype是函数特有的,从函数指向对象。除了箭头函数,都有此属性。但是普通函数一般不会使用其prototype属性操作原型。在原型链的讨论范围内,通常指的是构造函数的prototype属性(即使用new操作符创建实例对象的函数)。

function Person(name) {
    this.name = name;
}

// 给原型添加方法 - 所有实例共享
Person.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
};

// 创建实例
const john = new Person("John");
const mary = new Person("Mary");

john.sayHello(); // "Hello, I'm John"
mary.sayHello(); // "Hello, I'm Mary"

// 验证方法是共享的
console.log(john.sayHello === mary.sayHello); // true

constructor是对象特有的,指向其构造函数。对于纯对象实例,其构造器就是其构造函数;对于构造函数,指向其父类。

四、举例说明

对于下面的场景:

function Foo() {...};
let f1 = new Foo();

1.constructor指向

首先区别构造函数(Foo()、Function()、Object())、实例对象(f1)和构造函数原型属性(Foo.prototype、Function.prototype、Object.prototype)。

  • 构造函数原型属性的构造器就是构造函数本身,使用实线标注。
  • 剩余的构造函数的构造器就是其父类构造函数,实例对象的构造器就是其直接的构造函数。使用虚线标注。

2._proto_指向

依旧是这几个主体,可以发现:

  • 对于对象实例f1,其_proto_等于其构造函数的原型属性。
  • 对于原型属性的这一类对象(也就是构造器指向使用实现的这些对象),由于其本质是通过Object()构造出来的,所以指向的是Object.prototype,对于Object.prototype则指向null。
  • 对于构造函数,其_proto_等于其父类的原型属性。

综上,对于constructor使用实线连接的(通常是xxx.prototype),其_proto_值不能直接看constructor指向,对于虚线连接的,则直接可以等于其构造器的原型属性。

3.prototype指向

这个就比较简单了,和图1中的实现关系是一一对应的。

最后,放一个总图,大家可以捋一捋:

五、参考文献

帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)_js prototype constructor-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值