JavaScript原型和继承
翻译
原文:https://portswigger.net/web-security/prototype-pollution/javascript-prototypes-and-inheritance
- name: 翻译
desc: 原文:https://portswigger.net/web-security/prototype-pollution/javascript-prototypes-and-inheritance
bgColor: '#F0DFB1'
textColor: 'green'
2
3
4
# 1JavaScript原型和继承
JavaScript 使用 “原型继承模型”,这与许多其他语言使用的 “基于类的模型” 完全不同。在本节中,我们将对其工作原理作一个基本的概述,这可以给予你足够的理解,从而更好地阅读我们关于原型链污染漏洞 (opens new window)的学习材料。
# 2JavaScript对象?
JavaScript 对象实际上只是一个被称为 “properties”(属性)的key:value
对集合。例如,以下对象可以表示一个用户:
const user = {
username: "wiener",
userId: 01234,
isAdmin: false
}
2
3
4
5
你可以使用点表示法 或 括号表示法,来引用对象各自的键,从而访问对象的属性:
user.username // "wiener"
user['userId'] // 01234
2
除数据外,属性也可以是一个可执行函数。在这种情况下,该函数被称为 “方法”。
const user = {
username: "wiener",
userId: 01234,
exampleMethod: function(){
// do something
}
}
2
3
4
5
6
7
上面的示例是一个 “对象字面量”,它是使用花括号语法来创建的,以显式声明其属性及其初始值。并且, JavaScript 中的几乎所有内容都是一个底层对象,这个理解很重要。在这些材料中,术语 “对象” 指的是所有实体,而不仅仅是对象字面量。
# 3JavaScript原型?
JavaScript 中的每个对象都会链接到某种类型的另一个对象,其被称为原型。默认情况下,JavaScript 会自动为新对象分配一个内置原型。例如,会为字符串对象自动分配内置的String.prototype
。你可以在下面看到这些全局原型的更多示例:
let myObject = {};
Object.getPrototypeOf(myObject); // Object.prototype
let myString = "";
Object.getPrototypeOf(myString); // String.prototype
let myArray = [];
Object.getPrototypeOf(myArray); // Array.prototype
let myNumber = 1;
Object.getPrototypeOf(myNumber); // Number.prototype
2
3
4
5
6
7
8
9
10
11
对象会自动继承所分配原型的所有属性,除非该对象已经定义过了相同的键属性。这使得开发人员可以复用现有的对象属性和方法,基于现有的功能来创建一个新对象。
内置的原型提供了 用于处理基本数据类型的有用属性和方法。例如,String.prototype
对象有一个toLowerCase()
方法,因此,所有的字符串都会自动拥有一个 将自身转换为小写 的现成方法。这样一来,开发人员就不需要手动为每个新字符串添加该方法了。
# 4对象继承在JavaScript中是如何工作的?
每当你引用对象的属性时,JavaScript 引擎首先会尝试在对象自身上访问该属性,如果对象自身没有匹配的属性,则 JavaScript 引擎会在对象的原型上查找该属性。给定以下对象,这使你能够引用myObject.propertyA
,例如:
你可以使用 浏览器控制台 来查看此行为的实际效果。首先,创建一个空对象:
let myObject = {};
接下来,键入myObject
和一个点号。请注意,控制台会提供一个属性和方法列表,供你从中进行选择:
尽管你还没有为 对象本身 定义任何属性或方法,它也会从内置的Object.prototype
上继承一些属性或方法。
# 5原型链
请注意,一个对象的原型 其实就是 另一个对象,而这个对象也应该有自己的原型,以此类推。实际上,JavaScript 中的几乎所有内容都是一个对象,最终,这条原型链会回到顶级的Object.prototype
身上,其原型是一个null
。
至关重要的是,对象不仅会从它们的直接原型上继承属性,而且还会从原型链中的所有对象上继承属性。在上面的示例中,这意味着username
对象可以访问String.prototype
和Object.prototype
上的属性和方法。
# 6使用__proto__访问对象的原型
每个对象都具有一个特殊的属性,你可以通过该属性来访问该对象的原型。虽然这个特殊属性还没有一个正式的标准化名称,但__proto__
是大多数浏览器支持的事实标准。如果你熟悉面向对象的语言,则此属性既可以用作对象原型的 “getter”(获取值)又可以用作 “setter”(设置值)。这意味着,你可以使用它来读取原型及其属性,在必要时也可以进行重新设置。
与任何属性一样,你可以使用括号或点号表示法来访问__proto__
:
username.__proto__
username['__proto__']
2
你甚至可以将__proto__
的引用全部链接在一起,从而沿着原型链访问上一层级:
username.__proto__ // String.prototype
username.__proto__.__proto__ // Object.prototype
username.__proto__.__proto__.__proto__ // null
2
3
# 7修改原型
虽然这通常被认为是不好的做法,但可以像修改任何其他对象一样修改 JavaScript 的内置原型。这意味着,开发人员可以自定义 或 覆盖内置方法的功能,甚至可以添加新方法来执行有用的操作。
例如,现代 JavaScript 为字符串提供了trim()
方法,它使你能够轻松地删除任何前导或尾随空格。在官方引入这个内置方法之前,开发人员有时会执行以下操作,将此功能的自定义实现添加到String.prototype
对象中:
String.prototype.removeWhitespace = function(){
// remove leading and trailing whitespace
}
2
3
由于原型继承的存在,所有字符串都可以访问这个方法:
let searchTerm = " example ";
searchTerm.removeWhitespace(); // "example"
2