原型和原型链

文章の目录

原型和原型链

构造函数

原型对象

什么是原型对象

原型对象的作用

原型链

显式原型和隐式原型

原型链

函数也是一种对象

总结


正文

原型和原型链

构造函数

通过 new 关键词调用,可以创建相同类型的对象的函数,我们称之为构造函数。

(如需了解构造函数相关内容,请阅读笔者另一篇文章:JS 入门之对象中对象构造器)

function Student(no, name) {
    this.stuNo = no;
    this.name = name;
    this.identity = "学生";
    this.task = function() {
        console.log("任务就是学习!");
    }
};

原型对象

什么是原型对象

在 js 中,每个函数类型的数据,都有一个 prototype 属性,该属性指向一个对象,我们称之为原型对象。

对于原型对象来说,它有一个 constructor 属性,指向它的构造函数。

原型对象的作用

原型对象最主要的作用是存放实例对象的公有属性和公有方法。

在上面的例子中,identity 属性和 task 方法对于所有实例来说都一样。如果放在构造函数 Student 内,每创建一个实例对象都会重复创建它们。所以我们可以把这些公有的属性和方法放在原型对象内。

function Student(no, name) {
    this.stuNO = no;
    this.name = name;
};

Student.prototype.identity = "学生";
Student.prototype.task = function() {
    console.log("任务就是学习!");
}

var stu1 = new Student(1001, "zhangsan");
var stu2 = new Student(1002, "lisi");

console.log(stu1.identity);    // 学生
console.log(stu2.identity);    // 学生
stu1.task();                   // 任务就是学习!
stu2.task();                   // 任务就是学习!

注:

  • 在 js 中,如果对象在自己这里找不到某个属性或方法,就会查看该对象构造函数的原型对象,如果上面有对应的属性或方法,就会返回属性值或调用方法。
  • 实例对象 stu1 和 stu2 上不存在 identity 属性和 task方法,则会查看它们构造函数的原型对象。
  • 可以通过 stu1.constructor 查看对象的构造函数。
    • console.log(stu1.constructor)    // Student
      
      // constructor 是实例对象 stu1 的构造函数的原型对象上的属性,该属性指向 stu1 的构造函数

原型链

如果在实例对象的构造函数的原型对象上也没有找到我们需要的属性和方法呢?这时就需要原型链了。

在说原型链之前,我们先了解两个概念:

显式原型和隐式原型

显式原型

显式原型就是利用 prototype 属性查找原型,只是这个属性是函数类型数据的属性。

隐式原型

隐式原型是利用 __proto__ 属性查找原型,这个属性是对象类型数据的属性,指向当前对象的构造函数的原型对象。

console.log(stu1.__proto__ === Student.prototype);    // true
console.log(stu1.__proto__ == Student.prototype);     // true

根据以上内容,我们可以得出 prototype、constructor 和 __proto__ 之间的关系:

原型链

__proto__ 是对象类型数据的属性,而构造函数 Student 的原型对象也是一个对象,那么构造函数 Student 的原型对象也会有 __proto__ 属性,但是它又指向哪里呢?

我们可以分析一下:既然构造函数 Student 的原型对象也是一个对象,那么我们只需找到它的构造函数就能知道它 __proto__ 的指向。在 js 中,对象的构造函数就是 Object(),所以对象的原型对象就是 Object.prototype。所以构造函数 Student 的原型对象的原型对象就是 Object.prototype,所以构造函数 Student 的原型对象的 __proto__ 属性指向 Object.prototype。

Object.prototype 这个原型对象很特殊,它没有上一层的原型对象,或者说它的 __proto__ 指向的是 null。

所以可以将上面的关系图拓展一下:

到这里,就可以回答之前那个问题了,如果某个对象查找属性,自己和原型对象上都没有,那么就会继续往原型对象的原型对象上去找,在这个例子中就是 Object.prototype,这里就是查找的终点,如果在这里也找不到的话,就没有更上一层了(Object.prototype 的更上一层是 null),直接返回 undefined。

可以看出,整个查找过程都是顺着 __proto__ 属性,一步一步往上查找,形成了像链条一样的结构,这样的结构,我们称之为原型链,也叫做隐式原型链

也正是因为这个原因,我们在创建对象,数组,函数等数据的时候,都自带一些属性和方法,这些属性和方法是在它们的原型上面保存着,所以我们可以直接使用它们的属性和方法。

函数也是一种对象

在 js 中,函数也是一种特殊的对象,因此函数也有 __proto__ 属性。

在 js 中,所有函数都可以看作是 Function() 的实例,而 Student() 和 Object() 都是函数,所以它们的构造函数就是 Function()。

Function() 本身也是函数,所以 Function() 的构造函数也是 Function()。

console.log(Student.constructor === Function);    // true
console.log(Object.constructor === Function);     // true
console.log(Function.constructor === Function);   // true

既然已经知道了函数的构造函数,那么函数的 __proto__ 属性的指向也就很明了了,就是 Function.prototype。

console.log(Student.__proto__ === Function.prototype);    // true
console.log(Object.__proto__ === Function.prototype);     // true
console.log(Function.__proto__ === Function.prototype);   // true

根据以上结论,我们可以拓展出一张更大的关系图:

由于上图部分箭头不太清晰,补充下图:

总结

  • 构造函数是使用 new 关键字来创建对象的函数。所有函数都是 Function() 的实例。
  • 原型对象是用来存放实例对象的公有属性和公有方法的一个公共对象,所有原型对象都是 Object() 的实例。
  • 原型链又叫隐式原型链,是由 __proto__ 属性串联起来的。原型链的尽头是 Object.prototype。Object.prototype 的尽头(下一层)是 null。
### JavaScript中的原型原型链继承 #### 原型的本质 在JavaScript中,几乎所有的对象都有一个特殊的内部链接——[[Prototype]],这个属性通常被称为“原型”。当尝试访问一个不存在于当前对象自身的属性时,JavaScript引擎会沿着这条链条向上查找该属性,直到找到为止或到达顶端即`null`。每个函数都自带了一个名为`prototype`的公共属性,默认情况下它是一个具有`constructor`指针的对象[^1]。 #### 原型链示意图 对于任何给定的对象而言,如果它的构造器拥有自己的`prototype`成员,则此对象可通过其隐含的`__proto__`引用到那个特定的`prototype`对象;而后者同样可能关联着更高级别的原型...如此这般形成了一条由多个层次组成的连续路径,这就是所谓的“原型链”。 ```plaintext Object.prototype -> Function.prototype -> ... (其他内置类型) ... ``` #### 继承机制详解 由于存在这种级联式的连接方式,在较低级别的对象里未定义的方法/属性可以从较高级别处获取并使用。这便是JavaScript实现继承的主要手段之一:通过让新创建出来的实体自动获得已有模板里的特性来减少重复劳动,并促进模块化设计思路的发展壮大[^2]。 具体来说: - **单线程模式下的直接继承** 当某个自定义类型的实例试图调用某项操作但本地并未提供相应接口的时候,程序就会顺着`__proto__`所指示的方向去寻找匹配项; - **多层嵌套情况处理** 如果目标仍然未能被定位成功的话,那么继续沿路前进直至触及终点(`null`)或者遇到合适的候选项位置停止搜索过程。 - **修改原型的影响** 对已存在的原型进行更改会影响到所有已经建立起来的关系网内的节点,除非特别指定阻止传播行为的发生[^3]。 #### 示例代码展示 下面是一段简单的HTML文档片段配合JavaScript脚本部分用来说明如何设置构造函数及其对应的原型属性,进而达成预期的功能效果。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>Prototype Example</title> </head> <body> <script type="text/javascript"> // 定义构造函数 function Star(uname){ this.uname = uname; } // 设置原型上的方法 Star.prototype.sing = function(){ console.log(`${this.uname} is singing`); }; const starInstance = new Star('Taylor Swift'); starInstance.sing(); // 输出 "Taylor Swift is singing" </script> </body> </html> ``` 这段代码展示了怎样向构造函数`Star`添加一个新的实例方法`sing()`,并通过实际运行验证了即使是在外部也没有明确定义过这样的能力,但是依然能够正常工作,这是因为得益于背后强大的原型链支持体系[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值