先来一个小测试
环境:对象 person1 有一个__proto__属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype
问题:
- person1.__proto__是什么?
- person.__proto__是什么?
- Person.prototype.__proto__是什么?
- Object.__proto__是什么?
- object.prototype.__proto__ 是什么?
答案:
第一题:
因为 person1.__proto__==person1 的构造函数Person .prototype
因为 person1的构造函数 ===Person
所以 person1.__proto__ === Person.prototype
第二题:
因为person.__proto__===Person的构造函数 .prototype
因为Person的构造函数===Function
所以person.__proto__===Function.prototype
第三题:
Person.prototype 是一个普通对象,我们无需关注它有哪些属性,只要记住它是一个普通对象。
因为一个普通对象的构造函数 === Object
所以Person.prototype.__proto__===Object.prototype
第四题,参照第二题,因为 Person 和 Object 一样都是构造函数
第五题:
Object.prototype对象也有proto属性,但它比较特殊,为 null 。因为 null 处于原型链的顶端,这个只能记住。Object.prototype.
Object.prototype.__proto__=== null
先看下图

分析一下:
- 一个函数在生成的时候,javascript都会默认生成一个prototype属性,通过prototype生成一个空对象,也就是原型对象。
- 原型对象通过constructor构造器,来指向它被声明的那个函数。
- 构造函数通过new 和实例产生关联, 实例又通过__ptoto__指向构造函数的原型。

可以看出原型链的以为就是:原型链通过prototype和__proto__来完成原型的查找。从一个实例对象往上找构造这个实例的相关的原型对象,然后相关联的原型对象再往上找创造它的原型对象,一直到object.prototype原型对象终止。
通过原型链,可以进行数据共享,也就是找到原型对象,原型对象的方法被不同的实例所共有。


查找一个实例中的一个方法或者属性,如果这个这个实例没有找到,就会通过__proto__往上一级找原型对象,如果还找不到就会再往上找,直到object.proototype位置,如果还找不到,就会返回这个属性或者方法没有找到或者没有定义。如果在中间任何一个原型对象找到了,那么返回这个属性和方法,就不会再往上寻找。
强调一下只有函数才会有有prototype,对象是没有prototype的,只有实例对象有 __proto__
解释一下为什么函数也会有__proto__

从图中可以看到,函数name()也是函数Function的原型对象,所以函数也即对象。
万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object 、Function 是 JS 自带的函数对象。
原型链污染
看下面例子
//a.js
(function()
{
var secret = ["aaa","bbb"];
secret.forEach();
})();
//b.html
<body>
<script>
Array.prototype.forEach = function() {
var result = 'result: ';
for(var i=0,length = this.length; i<length;i++){
result += this[i];
result += ''
}
document.write(result);
}
</script>
<script src="./a.js"></script>
</body>
运行结果:

在a.js中声明了一个数组 secret ,然后该数组调用了属于 Array.prototype的forEach方法,如下

但是,在调用js文件之前,js代码中将 Array. prototype.forEach 方法进行了重写,而prototype链为 secret -> Array.prototype ->object.prototype,secret中无 foreach 方法,所以就会向上检索,就找到了 Array.prototype 而forEach方法已经被重写过了,所以会执行输出。
这就是原型链污染。很明显,原型链污染就是:在我们想要利用的代码之前的赋值语句如果可控的话,我们进行 __proto__ 赋值,之后就可以利用代码了。