前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >原型链分析

原型链分析

原创
作者头像
Yerik
修改2021-09-22 11:08:11
2230
修改2021-09-22 11:08:11
举报
文章被收录于专栏:烹饪一朵云烹饪一朵云

学习js的过程中我们绕不开两链一包的学习,尤其是在面试中,关于作用域链,前些时间分析过了,有兴趣的话可以关注一下关于JS中的作用域中的沉思,这篇文章主要是关注原型链性质特点

所谓原型

常言道他山之石可以攻玉,原型的概念在其他的语言中可能不存在,但相似的原理是存在的,比如python中的基类的老爹——元类

代码语言:txt
复制
MyClass = MetaClass() # 使用元类创建出一个对象,这个对象称为“类”
my_object = MyClass() # 使用“类”来创建出实例对象

之前学习他的时候,Tim Peters这老头这么说到,“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。”

哦,对于python来说元类是需要用到时候一定是已经理解了,用不到的时候说明是不理解,唔,我愿称之为薛定谔の学习,但不影响我们互相印证学习。

原型

我们创建的每个函数都有一个prototype(原型)属性,prototype属性指向原型对象。通过该函数创建的实例对象会共享原型对象上的所有属性和方法。

构造如图所示

原型链.png
原型链.png
  • 构造函数可以通过new方法来获得实例对象
  • 构造函数通过.prototype属性来获得原型对象
  • 实例对象通过constructor属性来获得构造函数
  • 实例对象通过.__proto__object.getPrototypeOf()来获得原型对象
  • 原型对象通过constructor属性来获得构造函数

原型链的形成

当我们通过一系列的继承并实例化,在JavaScript中我们如果需要进行对象属性的检索,通过在这个继承链上一步步追溯寻找原型的过程称之为原型链

且看原型链的工作过程:

  • 每当代码读取某个对象的属性时,都会进行一次搜索具有目标属性的名字,首先从对象实例本身搜索,如果搜索到了该属性,则返回该属性的值;
  • 如果没有找到该属性的名字,那么继续搜索指针所指向的原型对象,在原型对象中查找该属性的名字,如果找到该属性,则返回该属性的值;
  • 如果还是找不到,继续寻找原型对象的原型对象,一直寻找,直到找到为止。
  • 如果找到最顶层的Object.prototype还是没有,那么返回undefined

其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。然后层层递进,就构成了实例与原型的链条,这就是所谓原型链的基本概念。

talk is cheap,show the code 不妨设计一套代码

代码语言:txt
复制
function Person(){
    
}

Person.prototype.name = 'naug';
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
}

const person1 = new Person();
person1.sayName(); //'naug'

const person2 = new Person();
person2.sayName(); //'naug'

console.log(person1.sayName === person2.sayName); //true
展开分析.png
展开分析.png

要理解原型链我们必须理解三个名词:

  • 隐式原型:所有引用类型(函数、数组、对象)都有__proto__属性,例如arr.__proto__
  • 显式原型:所有函数拥有prototype属性,例如:func.prototype
  • 原型对象:拥有prototype属性的对象,在定义函数时被创建

基于这三个关键词我们再展开之后的原型链之间的递归关系可以如下图所示

原型链关系.png
原型链关系.png

在JavaScript中只要创建了一个函数,就会为该函数生成一个prototype属性(指向函数的原型对象)。默认情况下,原型对象会自动获得一个constructor属性(构造函数属性),这个属性包含一个指向prototype属性所在函数的指针。当调用该函数创建一个实例后,该实例内部将包含一个指针Prototype(内部属性),指向构造函数的原型对象。虽然在脚本中没有标准的方式访问Prototype,但Firefox、Safari和Chrome在每个对象上都支持一个属性__proto__。而在其他实现中,这个属性对脚本则是完全不可见的。

虽然在所有实现中都无法访问到Prototype,但可以通过isPrototypeOf方法来确定对象之间是否存在这种关系。如果Prototype指向调用isPrototypeOf()方法的对象(Person.prototype),那么这个方法就返回true。

属性搜索.png
属性搜索.png

Object.getPrototypeOf方法可以返回Prototype的值。例如:

对象搜索.png
对象搜索.png

每当读取对象的某个属性时,都会首先从对象实例本身开始搜索,如果没有则继续搜索原型对象中的属性。虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。

代码语言:txt
复制
person2.name = "yerik";
console.log(person1.name); // "yerik" 来自实例本身
console.log(person2.name); // "naug" 来自原型对象

hasOwnProperty方法可以检测一个属性是存在于实例中,还是存在于原型中(从Object继承来的),只在给定属性存在于对象实例中时,才会返回true。

实例搜索.png
实例搜索.png

查找机制

当一个变量在调用某方法或属性时,如果当前变量并没有该方法或属性,就会在该变量所在的原型链中依次向上查找是否存在该方法或属性,如果有则调用,否则返回undefined

什么时候会用到

在开发中,常常会用到toString()valueOf()等方法,array类型的变量拥有更多的方法,例如forEach()map()includes()等等。例如声明了一个arr数组类型的变量,arr变量却可以调用如下图中并未定义的方法和属性。

应用.png
应用.png

通过变量的隐式原型可以查看到,数组类型变量的原型中已经定义了这些方法。例如某变量的类型是Array,那么它就可以基于原型链查找机制,调用相应的方法或属性。

变量属性.png
变量属性.png

风险点

原型中所有属性是被很多实例共享的。对于包含引用类型值的属性的原型对象,会有特别的问题。在实例中修改了原型上的引用类型的属性,会在所有实例上反映出来

原型链污染.png
原型链污染.png

这个其实也就会引出原型链污染的问题,具体的案例实践,不妨看这篇文章利用原型链漏洞污染拿下服务器权限

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 所谓原型
    • 原型
      • 原型链的形成
      • 查找机制
      • 什么时候会用到
      • 风险点
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档