Node.js基础+原型链污染

Node.js基础

概述:简单来说Node.js就是运行在服务端的JavaScript,Node.js是一个基于Chrome JavaScript运行时建立的一个平台

大小写变换:

toUpperCase():将小写字母转为大写字母,如果是其他字母则原字符不变

toLowerCase():将大写字母的字符转换成小写,如果是其他字符,则原字符保持不变

(但有一些类似字符,函数会将其当作字母字符转换)

弱类型比较:

数字与字符串

数字与字符串比较时,会优先将纯数字型的字符串转为数字之后再进行比较,而字符串与字符串比较时,会将字符串的第一个字符转化为ASCLL码之后再进行比较,因此就会出现(“111”>"3")程序判断为错误的情况;而非数字型字符串与任何数字进行比较都是false

数组比较

空数组之间比较永远为false,数组之间比较数组间的第一个值,对第一个值采用前面总结的比较方面,数组与非数值型字符串比较,数组永远小于非数值型字符串;数组与数值型字符串比较,取第一个之后按前面总结的方法进行比较

关键字比较

null==undefined返回true

null===undefined输出false

NaN==NaN输出false

NaN===NaN输出false

nodejs md5的绕过

补充知识:在Node.js中,可以使用crypto模块来实现MD5加密功能。MD5是一种常见的哈希算法,用于将任意长度的数据转换为固定长度的哈希值。在Node.js中,可以通过以下步骤来生成MD5哈希值

const crypto = require('crypto');

function generateMD5(input) {
    const hash = crypto.createHash('md5');
    hash.update(input);
    return hash.digest('hex');
}

const input = 'Hello, MD5!';
const md5Hash = generateMD5(input);
console.log(md5Hash);

这样就可以在Node.js中使用MD5算法对数据进行加密操作。

编码绕过

用16进制编码,unicode编码,base64编码代替字符

命令执行

exec()child_process 模块中的 exec() 方法。这个方法用于执行 shell 命令并获取其输出。

作用有:执行复杂的 shell 命令,包括管道、重定向等。

处理命令执行的结果:通过回调函数可以处理命令执行的结果,包括标准输出、标准错误等。

eval() 函数(代码执行)用于执行字符串中的 JavaScript 代码,并返回执行结果。它可以用来动态执行代码或计算表达式的值,需要注意的是,eval() 函数在 Node.js 中同样存在安全风险,因为它可以执行任意代码。因此,在实际开发中应该谨慎使用,并避免直接执行来自不受信任来源的代码。

例如(eval(“document.cookie”))执行document.cookie

文件读写

writeFileSync()

require('ls').writeFileSync('input.txt','sss');

解释代码:

require('ls'):这行代码导入了ls模块。

writeFileSync('input.txt','sss'):这行代码调用了ls模块中的writeFileSync方法,传入了两个参数:

第一个参数是文件路径 'input.txt',指定了要写入数据的文件。

第二个参数是字符串'sss',即要写入文件的数据。

补充知识:

writeFileSync方法是一个同步操作,它会阻塞代码的执行,直到写入操作完成。

如果你想在一个Node.js应用程序中使用这段代码,你需要确保ls模块已经安装在你的项目中。

writeFile()

require('fs').writeFile('input.txt','test',(err)=>{})

解释代码:

require('fs'):这行代码导入了Node.js内置的fs模块,用于文件操作。

writeFile('input.txt','test',(err)=>{}):这行代码调用了fs模块中的writeFile方法,传入了三个参数:

第一个参数是文件路径 'input.txt',指定了要写入数据的文件。

第二个参数是字符串'test',即要写入文件的数据。

第三个参数是一个回调函数(err)=>{},用于处理写入操作完成后的回调逻辑。在这个例子中,回调函数为空,即不做任何处理。

补充知识:writeFile方法是一个异步操作,它会将数据写入文件并在完成后调用回调函数。如果写入过程中出现错误,错误信息会通过回调函数的err参数传递。

readFileSync()

require('fs').readFile('/etc/passwd','utf-8',(err,data))=>{

if(err)throw err;

console.log(data);
});

解释代码:

readFileSync(): 这个函数同步地读取文件的内容,即在读取完成之前会阻塞程序的执行。

require('fs').readFile('/etc/passwd', 'utf-8', (err, data) => { ... });: 这行代码使用 Node.js 内置的 fs 模块来读取 /etc/passwd 文件的内容。它使用了 readFile 函数来异步地读取文件,第一个参数是要读取的文件路径,第二个参数是文件的编码格式,第三个参数是一个回调函数,用于处理读取文件时可能出现的错误和读取到的数据。

(err, data) => { ... }: 这是一个回调函数,用于处理读取文件时可能出现的错误和读取到的数据。如果读取文件出现错误,err 参数将会被赋值为错误信息;如果读取成功,data 参数将会包含文件的内容。

if (err) throw err;: 这行代码用于检查是否有错误发生,如果有错误发生,则抛出该错误。

console.log(data);: 这行代码用于将读取到的文件内容输出到控制台

readFile()

require('fs').readFile('/etc/passwd','utf-8'),

与上述函数只有回调函数不同

原型链污染

概述:原型链污染是一种针对JavaScript运行时的注入攻击。通过原型链污染攻击者可能控制对像的默认值。这允许攻击者篡改应用程序的逻辑,还可能导致拒绝服务,或者在极端情况下,远程代码执行 

prototype原型:

在JavaScript中,每个对象都有一个原型(prototype),它是对象的基础,用于继承属性和方法。当你创建一个对象时,这个对象会自动拥有一个指向其原型的引用。

你可以使用 Object.getPrototypeOf() 方法来访问对象的原型,也可以通过 __proto__ 属性来访问对象的原型。

创建对象  let obj = {};

补充知识:

继承

// 父类(超类)
function Animal(name) {
  this.name = name;
}

Animal.prototype.walk = function() {
  console.log(this.name + ' is walking.');
};

// 子类(派生类)
function Dog(name, breed) {
  Animal.call(this, name); // 调用父类构造函数
  this.breed = breed;
}

// 使用原型链继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
  console.log(this.name + ' is barking.');
};

// 创建实例并调用方法
var myDog = new Dog('Buddy', 'Labrador');
myDog.walk(); // 输出:Buddy is walking.
myDog.bark(); // 输出:Buddy is barking.


原型链污染演示

可以发现修改了son的原型属性之后会影响到另外一个具有相同原型的对象,不难看出我们是通过设置了__proto__的值来影响原型的属性

运行代码,发现没有被污染,这是因为,我们用JavaScript创建o2的过程(leto2={a:1,"proto":{b:2}})中,_proto 已经代表o2的原型了,此时遍历o2的所有键名,你拿到的是[a,b],proto 并不是一个key,自然也不会修改Object的原型。

可见,新建的o3对象,也存在b属性,说明Object已经被污染这是因为,JSON解析的情况下--proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键。

merge操作时最常见可能控制键名的操作,也最能被原型链攻击很多常见的库都存在这个问题

补充知识:merge函数的作用是将两个对象合并成一个新的对象,通常是将一个对象的属性复制到另一个对象中。在合并过程中,如果两个对象有相同的属性,通常会使用后者(source对象)的属性值覆盖前者(target对象)的属性值。

具体来说,merge函数通常会遍历source对象的所有属性,将每个属性复制到target对象中。如果两个对象有相同的属性,merge函数会使用source对象的属性值覆盖target对象的属性值。最终,merge函数会返回一个新的对象,其中包含了两个对象合并后的属性。

merge函数在前端开发和后端开发中经常被使用,用于合并配置对象、处理数据等场景。然而,在使用merge函数时,需要注意处理重复属性的情况,避免出现意外的覆盖行为或安全问题,如原型链污染等。

举例假设有一个merge函数,用于将两个对象合并:

function merge(target, source) {

        for (let key in source) {

                if (source.hasOwnProperty(key)) {

                         target[key] = source[key];

}

} return target;

}

然后有一个用于加载用户配置的函数:

function loadUserConfig(config) {

        const defaultConfig = { isAdmin: false };

        const mergedConfig = merge(defaultConfig, config);

         return mergedConfig;

}

攻击者可以构造一个恶意的用户配置对象,将__proto__属性设置为一个包含恶意代码的对象:

const maliciousPayload = { __proto__: { isAdmin: true } };

调用loadUserConfig函数,并传入恶意的用户配置对象:

const userConfig = loadUserConfig(maliciousPayload);

此时,userConfig对象的原型链已经被污染,使得userConfig对象具有isAdmin属性并且值为true,从而导致安全漏洞。

<think> 我们正在讨论的是在Node.js环境中模拟浏览器环境下的document.style属性。由于Node.js本身没有DOM,所以我们需要使用原型链来模拟这个属性。 思路: 1. 创建一个Document构造函数,并为其原型添加style属性。 2. 由于style属性本身是一个对象(CSSStyleDeclaration),我们还需要模拟这个对象及其方法。 3. 使用原型链继承,确保通过Document实例可以访问到style属性,并且每个实例的style属性是独立的(如果需要独立的话,注意不要共享同一个引用)。 但是,注意:在浏览器中,document是全局对象,而每个元素都有一个style属性。我们这里只模拟document.style,实际上在浏览器中document本身并没有style属性,而是元素有。所以这里可能是用户想模拟一个特定的元素(比如document.body)的style?或者是用户误解了,其实是想模拟一个元素的style? 根据问题,我们假设用户确实需要为document对象添加一个style属性(虽然这并不符合标准DOM)。那么我们可以这样做: 步骤: 1. 定义一个Document构造函数。 2. 在Document的原型上定义style属性。但是注意,这样所有实例将共享同一个style对象(如果直接赋值一个对象的话)。为了避免共享,我们可以在构造函数中为每个实例创建自己的style属性。 3. 但是问题要求使用原型链的方式,所以我们考虑将style定义在原型上,但这样就会共享。因此我们需要更复杂的设计:在原型上定义一个getter,每次访问style时返回一个新的对象(或者一个独立的对象)。但这样也不合适,因为每次访问都是新的,而且无法存储状态。 另一种做法:将style作为实例属性,在构造函数中初始化。这样每个实例有自己的style。但是这样就不是通过原型链了。 然而,问题要求使用原型链的方式。这里我们可能需要重新理解:原型链的方式是指通过修改原型对象来为对象添加属性。但是,如果这个属性是每个实例独立的,那么通常是在构造函数中定义。 所以,我们可能需要结合两者:在原型上定义一个getter,但这样每次访问都会返回同一个对象(如果我们在闭包中保存的话,那么每个实例还是共享的)?或者我们可以在实例上定义一个属性,然后通过原型链的getter来返回它?这似乎也不对。 实际上,我们可以这样:在构造函数中初始化一个style属性,然后我们通过原型链上的方法来操作这个属性。但是这样并没有使用原型链来定义属性本身。 考虑到需求是“使用原型链的方式”,可能是指通过原型继承来扩展对象。因此,我们可以创建一个Document类,然后通过其原型来定义方法,但是属性在实例上。 但是,如果我们一定要将style定义在原型链上,那么所有实例共享同一个style对象,这通常不是我们想要的(因为每个文档的样式应该是独立的)。所以这里可能是一个设计上的矛盾。 实际上,在浏览器中,每个DOM元素都有自己的style对象。因此,我们更倾向于模拟一个元素,而不是整个document。但是问题要求为document.style补环境。 由于问题描述可能不够准确,我们假设用户想要在Node.js中模拟一个类似浏览器的document对象,并且希望这个document对象有一个style属性(尽管标准中不存在),那么我们可以这样模拟: 方案: 1. 创建一个Document类。 2. 在Document的原型上定义style属性,但我们使用一个getter函数,这样每次访问时返回一个新的独立对象?或者返回一个预先定义好的对象?但是这样每个实例的style都是同一个对象(共享)。为了避免共享,我们可以在实例化时,在实例上定义自己的style属性(这样就不是在原型链上了)。 所以,如果我们坚持使用原型链,那么只能共享。但是如果我们允许在构造函数中定义,那么就可以独立。 因此,我们有两种选择: 选择一:共享style(通过原型链) 选择二:每个实例独立(在构造函数中定义) 根据问题,用户要求使用原型链方式。因此我们采用选择一,但需要明确这样是共享的。 然而,我们也可以使用一种混合模式:通过原型链上的getter来为每个实例返回其自己的style。我们可以利用Object.defineProperty在实例上定义属性,然后通过原型链的getter来触发这个定义,这样后续访问就直接用实例上的属性了。 具体做法: 在Document的原型上定义get style的getter函数,当第一次访问某个实例的style时,这个getter会为这个实例添加一个自己的style属性(并赋值),然后返回它。这样后续访问就直接访问实例属性了(因为实例属性会覆盖原型链上的getter)。 但是这样会修改实例自身,而且第一次访问后,实例上就有了style属性,后续就不再走原型链的getter。 代码示例: ```javascript function Document() { // 构造函数中不定义style } Document.prototype = { get style() { // 第一次访问时,给实例添加一个自己的style属性 var style = new CSSStyleDeclaration(); // 假设我们有一个模拟CSSStyleDeclaration的构造函数 Object.defineProperty(this, 'style', { value: style, writable: true, configurable: true, enumerable: true }); return style; } }; ``` 这样,第一次访问style时,会触发getter,然后在实例上定义style属性,并返回。后续访问就直接访问实例属性了。 但是,这样我们还需要实现CSSStyleDeclaration类来模拟style对象。 所以,完整的步骤: 1. 定义CSSStyleDeclaration类,模拟浏览器中的CSSStyleDeclaration对象(至少要有一些常用的属性和方法,比如cssText,setProperty, getPropertyValue等)。 2. 定义Document类,并在其原型上定义style的getter(如上所示)。 但是,注意:这样模拟的style属性并不是标准的,因为标准中document并没有style属性。所以用户可能是想要模拟一个元素?如果是这样,那么我们应该模拟一个Element类,并让document.body等元素是Element的实例。 考虑到问题描述为“为node.js中的document.style补环境”,这里我们假设用户确实需要在document对象上有一个style属性(可能是为了某些库的兼容,或者特定的代码)。 因此,我们按照上述思路实现: 步骤1:实现CSSStyleDeclaration 我们简单模拟,只实现部分内容。 ```javascript function CSSStyleDeclaration() { this._styles = {}; } CSSStyleDeclaration.prototype = { constructor: CSSStyleDeclaration, setProperty: function(property, value) { this._styles[property] = value; }, getPropertyValue: function(property) { return this._styles[property] || ''; }, removeProperty: function(property) { delete this._styles[property]; }, // 可以添加更多方法 get cssText() { var text = ''; for (var prop in this._styles) { text += prop + ': ' + this._styles[prop] + '; '; } return text; }, set cssText(text) { // 简单实现:解析text并设置_styles this._styles = {}; var parts = text.split(';'); for (var i = 0; i < parts.length; i++) { var part = parts[i].trim(); if (!part) continue; var index = part.indexOf(':'); if (index === -1) continue; var prop = part.substring(0, index).trim(); var value = part.substring(index+1).trim(); this._styles[prop] = value; } } }; ``` 步骤2:定义Document类 ```javascript function Document() { // 其他初始化 } Document.prototype = { get style() { // 为当前实例添加一个自己的style属性 var style = new CSSStyleDeclaration(); Object.defineProperty(this, 'style', { value: style, writable: true, configurable: true, enumerable: true }); return style; } }; ``` 然后,我们可以这样使用: ```javascript var doc = new Document(); doc.style.color = 'red'; // 会先触发getter,然后给doc实例添加style属性,然后再设置color属性(注意:这里直接设置color属性,因为我们没有在CSSStyleDeclaration上拦截普通属性的设置,所以我们需要进一步处理) // 但是上面的直接设置color属性并不会生效,因为我们的_styles对象是通过setProperty方法设置的。所以我们需要在CSSStyleDeclaration上实现属性的动态拦截。 // 因此,我们可以修改CSSStyleDeclaration,使用Proxy或者使用Object.defineProperty来拦截任意属性的设置和获取。 // 由于兼容性,我们使用Object.defineProperty来为每个实例动态定义属性?这样不太现实,因为CSS属性很多。我们可以重写set和get方法,但是这样不能直接通过.属性设置。 // 因此,我们修改CSSStyleDeclaration,使其能够通过.属性访问和设置,映射到_styles对象。 // 我们可以这样:在CSSStyleDeclaration的构造函数中,为每个属性设置getter和setter?但是属性是动态的。所以我们可以使用Proxy,或者我们可以在getter和setter中动态处理。 // 方法一:使用Proxy(推荐,但需要Node.js支持Proxy) function createCSSStyleDeclaration() { var styles = {}; var handler = { get: function(target, prop) { // 如果是方法,则返回原型上的方法 if (prop in CSSStyleDeclaration.prototype) { return CSSStyleDeclaration.prototype[prop]; } // 否则返回styles中的值 return styles[prop]; }, set: function(target, prop, value) { // 如果是方法,跳过?但这里不会设置方法,因为我们在代理普通属性。 styles[prop] = value; return true; } }; return new Proxy({}, handler); } // 但是这样我们就不能使用原型链了,因为Proxy会拦截所有操作。因此我们可以将原型合并: function CSSStyleDeclaration() { this._styles = {}; } CSSStyleDeclaration.prototype = { // ... 之前的方法 }; function createCSSStyleDeclaration() { var self = new CSSStyleDeclaration(); var handler = { get: function(target, prop) { // 如果prop在self中存在(包括原型链),则返回 if (prop in self) { return self[prop]; } // 否则返回_styles中的属性 return self._styles[prop]; }, set: function(target, prop, value) { // 如果prop是self自身的属性(不包括原型链)或者是_styles的属性,则设置 if (prop in self) { self[prop] = value; } else { self._styles[prop] = value; } return true; } }; return new Proxy({}, handler); } // 但是这样会丢失this绑定。所以我们可以这样: function CSSStyleDeclaration() { var self = this; this._styles = {}; return new Proxy(this, { get: function(target, prop) { if (prop in self || (typeof prop !== 'symbol' && self.hasOwnProperty(prop))) { return self[prop]; } return self._styles[prop]; }, set: function(target, prop, value) { if (prop in self) { self[prop] = value; } else { self._styles[prop] = value; } return true; } }); } // 但是这样会导致new出来的实例是Proxy对象,而不是CSSStyleDeclaration实例,可能会影响instanceof。 因此,我们换一种思路:不使用Proxy,而是将CSSStyleDeclaration设计成可以通过点号访问动态属性的方式。这可以通过在get和set方法中访问_styles对象来实现。但是这样就不能直接通过点号操作了,我们必须通过setProperty和getPropertyValue。 所以,权衡之下,如果用户代码是使用setProperty和getPropertyValue来操作style,那么我们可以不用Proxy。如果是直接通过style.color='red',那么我们就需要支持动态属性。 为了同时支持两种方式,我们可以采用以下方法: 在CSSStyleDeclaration的构造函数中,遍历所有常见的CSS属性,为每个属性定义getter和setter。但是这样不灵活,因为CSS属性很多。 因此,我们采用Proxy方案,并接受Proxy带来的影响(比如instanceof失效,但我们可以通过Symbol.toStringTag来调整)。 我们决定使用Proxy来动态代理所有属性。 重新实现CSSStyleDeclaration: ```javascript const CSSStyleDeclaration = (function() { function CSSStyleDeclaration() { this._styles = {}; return this._proxy || (this._proxy = new Proxy(this, handler)); } const handler = { get(target, prop) { // 如果prop是target自身的属性(包括继承的,但不包括_styles)则返回 if (prop in target && prop !== '_styles' && prop !== '_proxy') { return target[prop]; } // 如果是_styles的属性 if (prop in target._styles) { return target._styles[prop]; } // 否则返回原型上的方法(如果prop是方法) if (prop in CSSStyleDeclaration.prototype) { return CSSStyleDeclaration.prototype[prop]; } return undefined; }, set(target, prop, value) { // 排除一些特殊属性 if (prop === '_styles' || prop === '_proxy') { target[prop] = value; return true; } // 如果prop是target自身的属性(不是_styles中的),则设置 if (prop in target && prop !== '_styles' && prop !== '_proxy') { target[prop] = value; } else { // 设置_styles target._styles[prop] = value; } return true; } }; CSSStyleDeclaration.prototype = { setProperty(property, value) { this._styles[property] = value; }, getPropertyValue(property) { return this._styles[property] || ''; }, removeProperty(property) { delete this._styles[property]; }, get cssText() { let text = ''; for (let prop in this._styles) { text += `${prop}: ${this._styles[prop]}; `; } return text; }, set cssText(text) { this._styles = {}; const parts = text.split(';'); for (let i = 0; i < parts.length; i++) { let part = parts[i].trim(); if (!part) continue; let index = part.indexOf(':'); if (index === -1) continue; let prop = part.substring(0, index).trim(); let value = part.substring(index+1).trim(); this._styles[prop] = value; } } }; return CSSStyleDeclaration; })(); ``` 然后,Document类的实现不变。 但是注意:在Node.js中,我们可能不需要这么复杂。如果用户代码只是简单使用document.style,那么我们可以简化。 根据问题,我们提供了相对完整的方案。但是,如果环境不支持Proxy,那么我们需要回退到其他方法。 因此,我们可以根据环境决定是否使用Proxy,或者不使用Proxy而只提供setProperty/getPropertyValue方法。 由于用户要求使用原型链方式,我们主要关注在Document的原型上定义style的getter,而CSSStyleDeclaration的实现则根据具体需求来定。 总结:我们使用原型链的getter来为Document的实例提供style属性,并在第一次访问时为实例添加自身的style属性(一个Proxy对象,模拟CSSStyleDeclaration)。这样,每个实例都有自己独立的style对象,且支持直接通过点号操作CSS属性。 最后,我们导出一个Document类,然后在Node.js环境中创建一个document实例(或者全局document)即可。 注意:在Node.js中模拟DOM是一个复杂的工程,这里只针对document.style做了模拟。如果需要完整的DOM模拟,可以考虑使用jsdom等库。 以上方法仅供参考,根据具体需求调整。 </think> 在 Node.js 环境中模拟 `document.style` 属性可以通过原型链继承实现。由于 Node.js 本身没有 DOM,需要手动创建 `document` 对象并为其添加 `style` 属性。以下是具体实现方法: ### 实现步骤 1. **创建基础构造函数** 定义 `Document` 和 `CSSStyleDeclaration` 构造函数: ```javascript // 模拟 CSS 样式声明 function CSSStyleDeclaration() { this.styles = {}; } // 添加样式操作方法 CSSStyleDeclaration.prototype.setProperty = function(name, value) { this.styles[name] = value; }; CSSStyleDeclaration.prototype.getPropertyValue = function(name) { return this.styles[name] || ''; }; // 模拟 Document 对象 function Document() { // 初始化 style 属性 this.style = new CSSStyleDeclaration(); } ``` 2. **通过原型链扩展功能** 使用 `Object.create()` 建立原型链继承: ```javascript // 创建子类继承 Document function CustomDocument() { Document.call(this); // 继承实例属性 } // 设置原型链继承 CustomDocument.prototype = Object.create(Document.prototype); CustomDocument.prototype.constructor = CustomDocument; // 添加额外方法 CustomDocument.prototype.getElementById = function(id) { // 模拟 DOM 查找逻辑 return { style: new CSSStyleDeclaration() }; }; ``` 3. **在 Node.js 中使用** 创建实例并操作样式: ```javascript // 创建 document 实例 const document = new CustomDocument(); // 设置样式 document.style.setProperty('color', 'blue'); console.log(document.style.getPropertyValue('color')); // 输出 "blue" // 通过 DOM 方法操作样式 const element = document.getElementById('myElement'); element.style.setProperty('font-size', '16px'); ``` ### 关键点说明 1. **原型链继承机制** - 通过 `Object.create()` 建立 `CustomDocument` 与 `Document` 的原型链 - `CustomDocument.prototype.constructor` 修正确保构造函数指向正确 - 所有实例共享原型方法,节省内存[^1] 2. **环境隔离处理** ```javascript // 避免污染全局对象 if (typeof global.document === 'undefined') { global.document = new CustomDocument(); } ``` 3. **样式操作兼容性** 可扩展 `CSSStyleDeclaration` 支持更多 CSS 属性: ```javascript // 添加直接属性访问 const styleProps = ['color', 'fontSize', 'backgroundColor']; styleProps.forEach(prop => { Object.defineProperty(CSSStyleDeclaration.prototype, prop, { get() { return this.getPropertyValue(prop); }, set(value) { this.setProperty(prop, value); } }); }); ``` ### 应用场景 1. **服务端渲染 (SSR)** 在 Next.js/Nuxt.js 中模拟浏览器环境 2. **爬虫程序** 执行依赖 DOM 的客户端脚本 3. **单元测试** 测试浏览器相关代码逻辑 [^1]: 原型链继承实现方法共享,提高内存效率 [^2]: 通过构造函数+原型链混合模式实现完整继承
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值