原型链污染学习(1)

原型链污染学习

学习参考:p佬的深入理解 JavaScript Prototype 污染攻击
工具:WebStorm

prototype和__proto__

js中,如果要定义一个类,需要以定义构造函数的方式来定义
先整个Tool类

function Tool(){
    this.id = 1
}
new Tool()

Tool函数的内容,就是Tool类的构造函数,其中this.id是Tool类的一个属性

然后我们在这个类里定义一个方法,但是每当我们调用show方法的时候就会执行一次this.show = function(),这时候,这个方法是绑定在对象上面的,而不是绑定在类上面

function Tool(){
    this.id = 1
    this.show = function(){
        console.log(this.id)
    }
}
(new Tool()).show()

接下来,如果我们希望创建类的时候只创建一次show方法,这时需要用到原型prototype

function Tool(){
    this.id = 123
}

Tool.prototype.show = function show(){
    console.log(this.id)
}
var t = new Tool()
t.show()

运行结果
在这里插入图片描述

这个时候我们发现,我们没有在Tool的构造函数中定义show方法,而是用类似于属性的方式在类外定义Tool.prototype.show,然后我们新建一个Tool类对象,我们仍然可以使用show方法
这是为什么呢?

根据P佬的解释:我们可以认为原型prototype是类Tool的一个属性,而所有用Tool类实例化的对象,都将拥有整个属性中的所有内容,包括变量和方法
(这里有点像子类继承父类的关系,一个新建的子类对象可以使用在父类中定义的方法)

但是注意这里,我们是使用Tool.prototype来访问Tool类的原型,但是该类对象是不能通过prototype来访问原型的,这个时候就用到了__proto__

if (t.__proto__ == Tool.prototype){
    console.log("二者相等")
}

运行结果
在这里插入图片描述

所以是两种访问原型的方法(以Tool为例):

  1. 通过Tool.prototype来访问Tool的原型
  2. 通过类对象t.__proto__来访问Tool的原型

一个对象的__proto__属性,指向这个对象所在的类的prototype属性

实现js的继承机制

使用prototype

function father(){
    this.id = "father_id"
    this.name = "father_name"
}
function son(){
    this.id = "son_id"
}

son.prototype = new father()

let s = new son()
console.log(s.id)
console.log(s.name)

运行结果
在这里插入图片描述

这里我们看到,son类中是没有name这个属性的,然而我们却可以正常输出,而且输出的是father类的name属性值

这是因为son类继承了father类的name属性

使用__proto__

然后用__proto__来实现上面的功能

function father(){
    this.id = "father_id"
    this.name = "father_name"
}
function son(){
    this.id = "son_id"
}

// son.prototype = new father()
let fa = new father()
let s = new son()
s.__proto__ = fa
console.log(s.id)
console.log(s.name)

运行结果
在这里插入图片描述

对于son类的对象s,调用name的时候,实际上javascript引擎会进行如下操作:

  1. 在对象s中寻找name属性
  2. 找不到就去son.__proto__中寻找name属性
  3. 如果找不到,就去son.__proto__.__proto__寻找name属性
  4. 一直找下去,直到null结束。Object.prototype.__proto__就是null

在这里插入图片描述

JavaScript的这个查找的机制,被运用在面向对象的继承中,被称作prototype继承链。

注意,尽管s.__proto__ == son.prototype,但是在具体使用的时候,如下(对象对象

//prototype使用方法
son.prototype = new father()
//__proto__ 使用方法
let fa = new father()
let s = new son()
s.__proto__ = fa

原型链污染

测试代码

let a = {id:1}  //a是一个简单的js对象,是一个Object类的实例
console.log(a.id)
a.__proto__.id = 2
console.log(a.id)
let b = {}      //b是一个空对象
console.log(b.id)

运行结果
在这里插入图片描述

第二次输出a.id,结果是1,因为查找顺序,还是会最先从a这个类对象中寻找id的值
而输出b.id,结果是2.是因为a是一个Object类的实例,a.__proto__.id = 2实际上是对Object类增加了一个属性id,且值为2

所以我们后来使用Object类创建b的时候,b对象虽然是空的,但是当输出b.id的时候,会向b.__proto__中查找,也就是向Object类中查找,得到id=2

原型链污染测试

p佬的文章,接下来分析控制数组的键名

测试

测试代码

var a1 ={a:1,b:2}
for(let key in a1){//key是键名
    console.log(key)
    console.log(b1[key])
}

运行结果
在这里插入图片描述
但是如果我们将a1变为{a: 1, "__proto__": {b: 2}}

var a1 = {a: 1, "__proto__": {b: 2}}
for(let key in a1){//key是键
    console.log(key)
    console.log(a1[key])
}

运行的结果为 a 1 b 2
我们此时遍历b1的所有键名,得到a,b ,而不是__proto__
p佬给出了解决方法

var a1 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
for(let key in a1){//key是键
    console.log(key)
    console.log(a1[key])
}

执行结果
在这里插入图片描述

一个merge函数,这段代码的意思是,将source数组的键值复制到target中

function merge(target, source) {
    for (let key in source) {
        if (key in source && key in target) {
            merge(target[key], source[key])
        } else {
            target[key] = source[key]
        }
    }
}

总的代码分析

let source = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
let target =  {}
function merge(target, source) {
    for (let key in source) {
        console.log(key)
        if (key in source && key in target) {//key既在source中,也在target中
            merge(target[key], source[key]) //进里层迭代
        } else {
            console.log(key)
            console.log(target[key])
            console.log(source[key])
            target[key] = source[key]//如果key不在target中,就将source的值传给target
        }
    }
}
merge(target,source)
console.log(target)
console.log(target.a, target.b)
console.log(source)
o3 = {}
console.log(o3.b)

运行结果

在这里插入图片描述
而这会导致Object类增加一个属性b,且值为2
所以最后我们使用Object类实例化o3,输出o3.b会输出2

真正执行的代码

要注意的地方是target[key] = source[key],真正的利用原理应该是这样的

let target = {}
target['__proto__']['b'] = 2

let a = {}
console.log(a.b)

运行结果
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值