2024最新版JavaScript逆向爬虫教程-------基础篇之面向对象

目录
  • 一、概念
  • 二、对象的创建和操作
    • 2.1 JavaScript创建对象的方式
    • 2.2 对象属性操作的控制
    • 2.3 理解JavaScript创建对象
      • 2.3.1 工厂模式
      • 2.3.2 构造函数
      • 2.3.3 原型+构造函数
  • 三、继承
    • 3.1 通过原型链实现继承
    • 3.2 借用构造函数实现继承
    • 3.3 寄生组合式继承
      • 3.3.1 对象的原型式继承
      • 3.3.2 寄生组合式继承的实现
    • 3.4 对象的方法补充
    • 3.5 原型继承关系

一、概念

① 面向对象是现实的抽象方式。 对象是 JavaScript 中一个非常重要的概念,这是因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物:

  1. 比如我们可以描述一辆车:Car,具有颜色(color)、速度(speed)、品牌(brand)、价格(price),行驶(travel)等等;
  2. 比如我们可以描述一个人:Person,具有姓名(name)、年龄(age)、身高(height),吃东西(eat)、跑步(run)等等;

用对象来描述事物,更有利于我们将现实的事物,抽离成代码中某个数据结构,所以有一些编程语言就是纯面向对象的编程语言,例如:Java;你在实现任何现实抽象时都需要先创建一个类,根据类再去创建对象;
在这里插入图片描述
② JavaScript的面向对象。 虽然说在 JavaScript 编程语言中,函数是一等公民,但是 JavaScript 不仅支持函数式编程,也支持面向对象编程。JavaScript 对象被设计成了一组属性的无序集合,像是一个哈希表,由 key 和 value 组成,key 为一个标识符名称,而 value 可以是任意类型的值,也可以是其他对象或者函数类型,当函数作为对象的属性值时,这个函数就可以称之为对象的方法。

二、对象的创建和操作

2.1 JavaScript创建对象的方式

一般地,常用于创建对象的方式有两种,早期经常使用 Object 类,通过 new 关键字来创建一个对象,有点类似于 Java 中创建对象,后来为了方便就直接使用对象字面量的方式来创建对象了,这种形式看起来更加的简洁,并且对象和属性之间的内聚性也更强,所以这种方式后来就流行了起来。创建 Object 的一个新实例,然后再给它添加属性和方法,如下例所示:

let person = new Object(); // 创建一个空对象
person.name = "Amo"; // 往对象中添加属性 
person.age = 18;
person.job = "Spider Engineer";
person.sayName = function() {console.log(this.name);
};

使用对象字面量则可以这样写:

let person = {name: "Amo",age: 18,job: "Spider Engineer",// sayName(){  // es6后的写法//     console.log(this.name)// }// 常规写法sayName: function (){console.log(this.name)}
}

这个例子中的 person 对象跟前面例子中的 person 对象是等价的,它们的属性和方法都一样。这些属性都有自己的特征,而这些特征决定了它们在 JavaScript 中的行为。

2.2 对象属性操作的控制

在前面我们的属性都是直接定义在对象内部,或者直接添加到对象内部的,但是这样来做的时候我们就不能对这个属性进行一些限制:比如这个属性是否是可以通过 delete 删除?这个属性是否在 for-in 遍历的时候能被遍历出来?如果我们想要对一个属性进行比较精准的操作控制,那么我们就可以使用 属性描述符。 通过属性描述符可以精准的添加或修改对象的属性;属性描述符需要使用 Object.defineProperty() 方法来对属性进行添加或者修改;

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。语法格式如下:

Object.defineProperty(obj, prop, descriptor)
// ①:obj: 要定义属性的对象
// ②:prop: 一个字符串或 Symbol,指定了要定义或修改的属性键。
// ③:descriptor: 要定义或修改的属性的描述符。
// ④返回值: 传入函数的对象,其指定的属性已被添加或修改。

什么是 属性描述符? 顾名思义就是对对象中的属性进行描述,简单来说就是给对象某个属性指定一些规则。属性描述符主要分为数据属性描述符和访问器属性描述符两种类型。对于属性描述符中的属性是否两者都可以设置呢?其实数据属性和访问器属性描述符两者是有区别,下面的表格统计了两者可用和不可用的属性:
在这里插入图片描述
数据属性描述符和访问器属性描述符所担任的角色不一样,下面就来详细介绍一下,它们两者的区别。

① 数据属性描述符。 包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。从上表可以看出数据属性有4个特性描述它们的行为:

  1. configurable:表示是否可以通过 delete 删除对象属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符。当通过 new Object() 或者字面量的方式创建对象时,其中的属性的 configurable 默认为 true,当通过属性描述符定义一个属性时,其属性的 configurable 默认为 false。
  2. enumerable:表示是否可以通过 for-in 或者 Object.keys() 返回该属性。当通过 new Object() 或者字面量的方式创建对象时,其中的属性的 enumerable 默认为 true,当通过属性描述符定义一个属性时,其属性的 enumerable 默认为 false。
  3. writable:表示是否可以修改属性的值。当通过 new Object() 或者字面量的方式创建对象时,其中的属性的 writable 默认为 true,当通过属性描述符定义一个属性时,其属性的 writable 默认为 false。
  4. value:含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性的默认值为 undefined。

例子:

const o = {name: "Amo"
}; // 创建一个新对象// 通过 defineProperty 使用数据描述符添加对象属性的示例
Object.defineProperty(o, "age", {value: 37,// age属性的值,默认undefinedwritable: false,// age属性是否可以写入(修改),默认falseenumerable: true, // age属性是否可以枚举,默认falseconfigurable: true,//a 属性是否可以删除,默认false
});
// 'age' 属性存在于对象 o 中,其值为 37
console.log(o)  // { name: 'Amo', age: 37 }
console.log(Object.keys(o))
for(const key in o){console.log(key)
}
// 当writable为false,age属性的值是不可被修改的 严格模式下会报错
o.age = 18
console.log(o) //{ name: 'Amo', age: 37 }
delete o['age'] // 当configurable为true时,age属性是可被删除的
console.log(o) // { name: 'Amo' }

访问器属性不包含数据值。相反,它们包含一个 获取(getter)函数和一个设置(setter)函数,不过这两个函数不是必需的。在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。在写入访问器属性时,会调用设置函数并传入新值,这个函数必须决定对数据做出什么修改。访问器属性同样有4个特性描述它们的行为:

  1. configurable:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性都是 true。当通过访问器属性描述符定义一个属性时,其属性的 configurable 默认为 false。
  2. enumerable:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是 true。当通过访问器属性描述符定义一个属性时,其属性的 enumerable 默认为 false。
  3. get:获取函数,在读取属性时调用。默认值为 undefined。
  4. set:设置函数,在写入属性时调用。默认值为 undefined。

访问器属性同样是不能直接定义的,必须使用 Object.defineProperty()。下面是一个例子:

// 通过 defineProperty 使用访问器属性描述符添加对象属性的示例
o = {name: "Amo",_age: 38,
}
Object.defineProperty(o, "age", {enumerable: true,configurable: true,get: function () {console.log("age属性被访问了")return this._age},set: function (newValue) {console.log("age属性被设置了")this._age = newValue}
});
console.log(o.age); // age属性被访问了
o.age = 18 // age属性被设置了
console.log(o.age); // 18 age属性被访问了

get 和 set 的使用场景(规范):

  • 隐藏某一个私有属性,不希望直接被外界使用和赋值。如上面代码中的 _age 表示不想直接被外界使用,外界通过使用 age 的 set 和 get 才能来访问和设置 _age 了。
  • 如果希望截获某一个属性它访问和设置值的过程。

同时给多个属性定义属性描述符。 在一个对象上同时定义多个属性的可能性是非常大的。为此, ECMAScript 提供了 Object.defineProperties() 方法。这个方法可以通过多个描述符一次性定义多个属性。它接收两个参数:要为之添加或修改属性的对象和另一个描述符对象,其属性与要添加或修改的属性一一对应。比如:

let book = {};
Object.defineProperties(book, {year_: {value: 2017,},edition: {value: 1},year: {get() {return this.year_;},set(newValue) {if (newValue > 2017) {this.year_ = newValue;this.edition += newValue - 2017;}}}
});

这段代码在 book 对象上定义了两个数据属性 year_edition,还有一个访问器属性 year。所有的属性都是同时定义的,并且数据属性的 configurable、enumerable 和 writable 特性值都是 false。

读取属性的特性。 使用 Object.getOwnPropertyDescriptor() 方法可以取得指定属性的属性描述符。这个方法接收两个参数:属性所在的对象和要取得其描述符的属性名。返回值是一个对象,对于访问器属性包含 configurable、enumerable、get 和 set 属性,对于数据属性包含 configurable、enumerable、writable 和 value 属性。比如:

let book = {};
Object.defineProperties(book, {year_: {value: 2017},edition: {value: 1},year: {get: function () {return this.year_;},set: function (newValue) {if (newValue > 2017) {this.year_ = newValue;this.edition += newValue - 2017;}}}
});let descriptor = Object.getOwnPropertyDescriptor(book, "year_");
console.log(descriptor.value);          // 2017
console.log(descriptor.configurable);   // false
console.log(typeof descriptor.get);     // "undefined"
// let descriptor = Object.getOwnPropertyDescriptor(book, "year");
// console.log(descriptor.value);          // undefined
// console.log(descriptor.enumerable);     // false
// console.log(typeof descriptor.get);     // "function"

ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors() 静态方法。这个方法实际上会在每个自有属性上调用 Object.getOwnPropertyDescriptor() 并在一个新对象中返回它们。对于上面的例子,使用这个静态方法会返回如下对象:

let book = {};
Object.defineProperties(book, {year_: {value: 2017},edition: {value: 1},year: {get: function() {return this.year_;},set: function(newValue){if (newValue > 2017) {this.year_ = newValue;this.edition += newValue - 2017;}}}
});
console.log(Object.getOwnPropertyDescriptors(book));

运行结果如下图所示:
在这里插入图片描述

2.3 理解JavaScript创建对象

虽然使用 Object 构造函数或对象字面量可以方便地创建对象,但这些方式也有明显不足:创建具有同样接口的多个对象需要重复编写很多代码。在介绍 ES6 的类之前,本小节会循序渐进地介绍被类取代的那些底层概念。

2.3.1 工厂模式

工厂模式是一种众所周知的设计模式,广泛应用于软件工程领域,用于抽象创建特定对象的过程。下面的例子展示了一种按照特定接口创建对象的方式:

function createPerson(name, age, job) {let obj = {}  //创建一个空对象obj.name = name //设置对应属性值obj.age = ageobj.job = job//公共方法obj.desc = function () {console.log(`我的名字是${this.name},今年芳龄${this.age},是一名${this.job}`)}//将对象返回return obj
}let person1 = createPerson('amo', 18, 'teacher')
let person2 = createPerson('jerry', 38, 'doctor')
console.log(person1)
console.log(person2)
person1.desc()
person2.desc()

这里,函数 createPerson() 接收3个参数,根据这几个参数构建了一个包含 Person 信息的对象。可以用不同的参数多次调用这个函数,每次都会返回包含3个属性和1个方法的对象。这种工厂模式虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)。

2.3.2 构造函数

工厂模式创建对象有一个比较大的问题:我们在打印对象时,对象的类型都是 Object 类型。但是从某些角度来说,这些对象应该有一个他们共同的类型,下面我们来看一下另外一种模式:构造函数的方式。

什么是构造函数?

  1. 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数;
  2. 在其他面向对象的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法;
  3. 如果一个普通的函数被使用 new 操作符来调用了,那么就可以称这个函数为一个构造函数;
  4. 一般规定构造函数的函数名首字母大写;

new 操作符调用函数的作用,当一个函数被new操作符调用了,默认会进行如下几部操作:

① 在内存中创建一个新的对象(空对象)
② 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
③ 构造函数内部的this,会指向创建出来的新对象
④ 执行函数的内部代码(函数体代码)
⑤ 如果构造函数没有返回对象,则默认返回创建出来的新对象

示例代码:

function Person(name, age) {this.name = namethis.age = agethis.sayHello = function () {console.log(`My name is ${this.name}, I'm ${this.age} years old.`)}
}const p1 = new Person('curry', 30)
const p2 = new Person('kobe', 24)
console.log(p1) // Person { name: 'curry', age: 30, sayHello: [Function (anonymous)] }
console.log(p2) // Person { name: 'kobe', age: 24, sayHello: [Function (anonymous)] }

这个构造函数可以确保我们的对象是 Person 的类型的,美中不足的是我们需要为每个对象的函数去创建一个函数对象实例。

2.3.3 原型+构造函数

对象的原型: JavaScript 中每个对象都有一个特殊的内置属性 [[prototype]](我们称之为隐式原型),这个特殊的属性指向另外一个对象。那么这个属性有什么用呢?前面介绍了,当我们通过对象的 key 来获取对应的 value 时,会触发对象的 get 操作;首先,get 操作会先查看该对象自身是否有对应的属性,如果有就找到并返回其值;如果在对象自身没有找到该属性就会去对象的 [[prototype]] 这个内置属性中查找。

那么对象的 [[prototype]] 属性怎么获取呢?主要有两种方法:

  1. 通过对象的 __proto__ 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题);
  2. 通过 Object.getPrototypeOf() 方法获取;

示例:

const obj = {name: 'amo',age: 30
}console.log(obj.__proto__)
console.log(Object.getPrototypeOf(obj))

函数的原型: 所有的函数都有一个 prototype 属性,并且只有函数才有这个属性。前面提到了 new 操作符是如何在内存中创建一个对象,并给我们返回创建出来的对象,其中第二步这个对象内部的 [[prototype]] 属性会被赋值为该构造函数的 prototype 属性。将代码与图结合,来看一下具体的过程。

function Person(name, age) {this.name = namethis.age = age
}const p1 = new Person('amo', 18)
const p2 = new Person('jerry', 20)
// 验证:对象(p1\p2)内部的[[prototype]]属性(__proto__)会被赋值为该构造函数(Person)的prototype属性;
console.log(p1.__proto__ === Person.prototype) // true
console.log(p2.__proto__ === Person.prototype) // true
console.log(Person.prototype.constructor) //[Function: Person]

内存表现:

  1. p1 和 p2 的原型都指向 Person 函数的 prototype 原型;
  2. 其中还有一个 constructor 属性,默认原型上都会有这个属性,并且指向当前的函数对象;
    在这里插入图片描述

结合对象和函数的原型,让所有的对象去共享这些函数,创建对象:

function Person(name, age) {this.name = namethis.age = age
}Person.prototype.sayHello = function () {console.log(`My name is ${this.name}, I'm ${this.age} years old.`)
}const p1 = new Person('amo', 18)
const p2 = new Person('jerry', 24)console.log(p1.sayHello === p2.sayHello) // true

如果我们需要在原型上添加过多的属性,通常我们会重新整个原型对象:
在这里插入图片描述
前面我们说过,每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获取 constructor 属性;而我们这里相当于给 prototype 重新赋值了一个对象,那么这个新对象的 constructor 属性,会指向 Object 构造函数,而不是 Person 构造函数了。如果希望 constructor 指向 Person,那么可以手动添加,如下:
在这里插入图片描述
手动添加的方式固然可以,但是也会造成 constructor 的 [[Enumerable]] 特性被设置了 true。默认情况下,原生的 constructor 属性是不可枚举的,如果希望解决这个问题,就可以使用我们前面介绍的 Object.defineProperty() 函数了,如下:
在这里插入图片描述

三、继承

面向对象的三大特性:封装、继承和多态。在 二、对象的创建和操作 一节中我们简单的了解了封装的过程,也就是把对象的属性和方法封装到一个函数中,本小节讲一下 JavaScript 中继承的实现,继承是面向对象中非常重要的特性,它可以帮助我们提高代码的复用性。继承主要的思想就是将重复的代码逻辑抽取到父类中,子类只需要通过继承父类,就可以使用父类中的方法,但是在实现 JavaScript 继承之前,需要先了解一个重要的知识点 原型链 当我们从一个对象上获取属性时,如果在当前对象自身没有找到该属性的话,就会去它原型上面获取,如果原型中也没有找到就会去它原型的原型上找,沿着这么一条线进行查找,那么这条线就是我们所说的原型链了。示例代码:

对应的内存中的查找过程:

当通过原型链查找某个属性时,一直找不到的话会一直查找下去么?肯定是不会的,JavaScript 的原型链也是有尽头的,这个尽头就是 Object 的原型。

Object 的原型: 事实上,不管是对象还是函数,它们原型链的尽头都是 Object 的原型,也称之为顶层原型,我们可以打印看看这个顶层原型长什么样。node 环境中:

console.log(Object.prototype) //[Object: null prototype] {}

在浏览器中:
在这里插入图片描述
Object.prototype 上有很多默认的属性和方法,像 toString、hasOwnProperty 等;如果我们再次打印 Object.prototype 的原型,这个原型属性已经指向了 null,如下:
在这里插入图片描述
当使用 new 操作符调用构造函数时,其对象的 [[prototype]] 会指向该构造函数的原型 prototype,其实 Object 也是一个构造函数,因为我们可以使用 new 操作符来调用它,创建一个空对象。

const obj = new Object()obj.name = 'amo'
obj.age = 30console.log(obj.__proto__ === Object.prototype) // true
console.log(obj.__proto__) // [Object: null prototype] {}
console.log(obj.__proto__.__proto__) // null

内存表现:
在这里插入图片描述
从 Object 的原型可以得出一个结论 原型链最顶层的原型对象就是 Object 的原型对象, 这也就是为什么所有的对象都可以调用 toString 方法了,如下:


从继承的角度来讲就是 Object是所有类的父类。 更为复杂的原型链关系内存图:

3.1 通过原型链实现继承

定义一个父类 Person 和子类 Student;父类中存放公共的属性和方法供子类使用;核心:将父类的实例化对象赋值给子类的原型; 示例代码:

// 定义Person父类公共的属性
function Person() {this.name = 'curry'this.age = 30
}// 定义Person父类的公共方法
Person.prototype.say = function () {console.log('I am ' + this.name)
}// 定义Student子类特有的属性
function Student() {this.sno = 101111
}// 实现继承的核心:将父类的实例化对象赋值给子类的原型
Student.prototype = new Person()// 定义Student子类特有的方法
Student.prototype.studying = function () {console.log(this.name + ' studying')
}// 实例化Student
const stu = new Student()
console.log(stu.name) // curry
console.log(stu.age) // 30
console.log(stu.sno) // 101111
stu.say() // I am curry
stu.studying() // curry studying

内存表现:

缺点:

  1. 从内存表现图中就可以看出,当打印 stu 对象时,name 和 age 属性是看不到的,因为不会打印原型上的东西;
  2. 当父类中的属性为引用类型时,子类的多个实例对象会共用这个引用类型,如果进行修改,会相互影响;
  3. 在使用该方案实现继承时,属性都是写死的,不支持动态传入参数来定制化属性值;

3.2 借用构造函数实现继承

针对 3.1 通过原型链实现继承 的缺点,可以借用构造函数来进行优化。在子类中通过 call 调用父类,这样在实例化子类时,每个实例就可以创建自己单独属性了;示例代码:

// 定义Person父类公共的属性
function Person(name, age) {this.name = namethis.age = age
}// 定义Person父类的公共方法
Person.prototype.say = function () {console.log('I am ' + this.name)
}// 定义Student子类特有的属性
function Student(name, age, sno) {// 通过call调用Person父类,创建自己的name和age属性Person.call(this, name, age)this.sno = sno
}// 实现继承的核心:将父类的实例化对象赋值给子类的原型
Student.prototype = new Person()// 定义Student子类特有的方法
Student.prototype.studying = function () {console.log(this.name + ' studying')
}// 实例化Student
const stu1 = new Student('curry', 30, 101111)
const stu2 = new Student('kobe', 24, 101112)
console.log(stu1) // Person { name: 'curry', age: 30, sno: 101111 }
console.log(stu2) // Person { name: 'kobe', age: 24, sno: 101112 }

内存表现:

缺点: 在实现继承的过程中,Person 构造函数被调用了两次,一次在 new Person(),一次在 Person.call();在 Person 的实例化对象上,也就是 stu1 和 stu2 的原型上,多出来了没有使用的属性 name 和 age;

3.3 寄生组合式继承

通过上面的两种方案,我们想实现继承的目的是重复利用另外一个对象的属性和方法,如果想解决方案二中的缺点,那么就要减少 Person 的调用次数,避免去执行 new Person(),而解决的办法就是可以新增一个对象,让该对象的原型指向 Person 的原型即可。

3.3.1 对象的原型式继承

将对象的原型指向构造函数的原型的过程就叫做对象的原型式继承,主要可以通过以下三种方式实现:

封装一个函数,将传入的对象赋值给构造函数的原型,最后将构造函数的实例化对象返回;

function createObj(o) {// 定义一个Fn构造函数function Fn() {}// 将传入的对象赋值给Fn的原型Fn.prototype = o// 返回Fn的实例化对象return new Fn()
}const protoObj = {name: 'curry',age: 30
}const obj = createObj(protoObj) // 得到的obj对象的原型已经指向了protoObj
console.log(obj.name) // curry
console.log(obj.age) // 30
console.log(obj.__proto__ === protoObj) // true

改变上面方法中的函数体实现,使用 Object.setPrototypeOf() 方法来实现,该方法设置一个指定的对象的原型到另一个对象或null;

function createObj(o) {// 定义一个空对象const newObj = {}// 将传入的对象赋值给该空对象的原型Object.setPrototypeOf(newObj, o)// 返回该空对象return newObj
}

直接使用 Object.create() 方法,该方法可以创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__

const protoObj = {name: 'curry',age: 30
}const obj = Object.create(protoObj)
console.log(obj.name) // curry
console.log(obj.age) // 30
console.log(obj.__proto__ === protoObj) // true
3.3.2 寄生组合式继承的实现

寄生式继承就是将对象的原型式继承和工厂模式进行结合,即封装一个函数来实现继承的过程。而这样结合起来实现的继承,又可以称之为寄生组合式继承。下面就看看具体的实现过程吧。

  1. 创建一个原型指向 Person 父类的对象,将其赋值给 Student 子类的原型;

  2. 在上面的实现方案中,Student 子类的实例对象的类型都是 Person,可以通过重新定义 constructor 来优化;

    // 定义Person父类公共的属性
    function Person(name, age) {this.name = namethis.age = age
    }// 定义Person父类的公共方法
    Person.prototype.say = function () {console.log('I am ' + this.name)
    }// 定义Student子类特有的属性
    function Student(name, age, sno) {// 通过call调用Person父类,创建自己的name和age属性Person.call(this, name, age)this.sno = sno
    }// 调用Object.create方法生成一个原型指向Person原型的对象,并将这个对象赋值给Student的原型
    Student.prototype = Object.create(Person.prototype)
    // 定义Student原型上constructor的值为Student
    Object.defineProperty(Student.prototype, 'constructor', {configurable: true,enumerable: false,writable: true,value: Student
    })// 定义Student子类特有的方法
    Student.prototype.studying = function () {console.log(this.name + ' studying')
    }// 实例化Student
    const stu1 = new Student('curry', 30, 101111)
    const stu2 = new Student('kobe', 24, 101112)
    console.log(stu1) // Student { name: 'curry', age: 30, sno: 101111 }
    console.log(stu2) // Student { name: 'kobe', age: 24, sno: 101112 }
    

内存表现:

总结:多个地方用到了继承,可以将上面的核心代码赋值在一个函数里面,如果不想用 Object.create(),也可以使用上面封装的 createObj 函数;

function createObj(o) {function Fn() {}Fn.prototype = oreturn new Fn()
}/*** @param {function} SubClass* @param {function} SuperClass*/
function inherit(SubClass, SuperClass) {SubClass.prototype = createObj(SuperClass.prototype)Object.defineProperty(SubClass.prototype, 'constructor', {configurable: true,enumerable: false,writable: true,value: SubClass})
}

寄生组合式实现继承的原理其实就是创建一个空对象用于存放子类原型上的方法,并且这个对象的原型指向父类的原型。

3.4 对象的方法补充

Object.hasOwnProperty(): 对象是否有某一个属于自己的属性(不是在原型上的属性)
in/for in 操作符: 判断某个属性是否在某个对象或者对象的原型上
instanceof: 用于检测构造函数的 pototype,是否出现在某个实例对象的原型链上
Object.isPrototypeOf(): 用于检测某个对象,是否出现在某个实例对象的原型链上
在这里插入图片描述

3.5 原型继承关系

在这里插入图片描述
在这里插入图片描述

说明

文章转发自 博主@Amo Xiang的2024最新版JavaScript逆向爬虫教程-------基础篇之面向对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/12668.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

网络编程示例之网络socket程序编程

注意:学习资料可在ElfBoard官方网站“资料”专区获取。 本节用到的源码路径:ELF 1开发板资料包->03-例程源码->03-1 命令行例程源码->05_elf1_cmd_net tcp协议程序 tcp_server.c 服务端仍然是按照如下顺序进行编写: socket()//创…

标准的渠道治理方法

在当今竞争激烈的市场环境中,品牌的渠道管理犹如一座大厦的基石,至关重要。而其中,对渠道价格的治理更是关键环节,直接关系到品牌的生死存亡与长远发展。 当品牌渠道中不幸出现低价、窜货链接时,一场关乎品牌未来走向…

双指针算法的妙用:提高代码效率的秘密(3)

双指针算法的妙用:提高代码效率的秘密(3) 前言: 小编在昨日讲述了关于双指针算法的两个题目,今日继续分享两个题目的解析,我相信,我只要坚持每天啥刷题,算法能力终究会提高的&…

动力商城-03 Idea集成apifox Mybatis-Plus字段策略

1.Idea下载apifox插件 2.新建令牌放入Idea 3.右键上传到对应接口 4.设置前置url 插件能够自动识别swagger注解 Mybatis-Plus字段策略 1、FieldStrategy作用 Mybatis-Plus字段策略FieldStrategy的作用主要是在进行新增、更新时,根据配置的策略判断是否对实体对…

11.11--final关键字和抽象类

一 java 1.final 关键字-----放在 访问修饰符后面 1)防止被继承 2)防止 父类方法 被重写 3)防止 类中的 属性 被修改 4)防止 局部属性 被修改 1.2.细节 1)final 修饰属性 必须赋初值 ------------------------------…

IntelliJ+SpringBoot项目实战(三)---基于源代码直接生成漂亮的接口文档

在SpringBoot中可以集成代码插件自动生成接口文档,而且生成的文档很漂亮,除了接口功能介绍、传入参数、响应参数,还具体类似postman的功能,可调用接口进行测试,另外还可以下单WORD版、.md,html格式的文档。下面我们先看…

TemplatesImpl 在Shiro中的利用链学习1

一、前言 在前面的学习中,我们学习了CC1、CC6链,其中CC1链受限于Java8u71版本,而CC6则是通杀的利用链;后来又将 TemplateImpl 融入到 CommonsCollections 利用链中,绕过了 InvokerTransformer 不能使用的限制&#xf…

中仕公考:2025年省考请注意!

打算参加25年省考的考生们注意啦!如果打算参加2025年公务员省考,从这个时间点开始备考刚刚好,如果还不知道怎么备考的,看这篇就够了! 省考流程: 网上报名——资格审查——确认缴费——查看报名序号——准考证打印——笔试——成…

开发RAG应用,你必须知道的7个Embedding模型

在自然语言处理(NLP)领域,Embedding模型是将文本数据转化为数值向量的核心技术,从而让计算机能够便捷地衡量文本间的语义关联,这种表示法已成为多种基础NLP任务的核心,如文本相似度判定、语义搜索、信息检索…

基于Java+SpringBoot学生成绩管理系统

一、作品包含 源码数据库设计文档全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA/eclipse 数据库&…

Kong API网关,微服务架构中,你看到就不想错过的选型

今天,很多公司都采用微服务架构来处理复杂业务,但随着服务数量增加,API管理成了一项繁重任务。Kong API网关,作为一款高性能的开源API网关,给开发者带来了极大便利。它不仅可以简化API的调用和管理,还拥有丰…

计算机毕业设计 | springboot+vue汽车修理管理系统 汽修厂系统(附源码)

1,项目背景 在如今这个信息时代,“汽车维修管理系统” 这种维修方式已经为越来越多的人所接受。在这种背景之下,一个安全稳定并且强大的网络预约平台不可或缺,在这种成熟的市场需求的推动下,在先进的信息技术的支持下…

使用京东API接口进行支付结算有哪些注意事项?

用京东API接口进行支付结算时,需要注意以下几个事项: 遵守京东开放平台规定:在使用京东API接口时,必须遵守京东开放平台的相关规定,不得滥用接口或进行非法操作。 保护用户隐私:为了保护用户隐私&#xff…

全国宪法宣传周答题活动怎么做

在12月4日全国宪法宣传周即将到来之际,越来越多的企业单位开始举办线上知识竞赛答题活动,以下是一个知识竞赛答题小程序的基本功能: 一、了解活动信息:确定答题活动的开始时间、结束时间以及是否分阶段进行等。不同的答题活动时…

【debug】QT 相关问题error汇总 QT运行闪退 QT5升级到QT6注意要点

总结一下碰到过的所有问题error以及解决方案 如果这个文档未帮助到你,仍有bug未解决,可以在下方评论留言,有偿解决。 qt的UI更新之后构建后发现没有变化 取消项目中的Shadow build的勾选,作用是取消影子构建,此后构建目…

信捷 PLC C语言 POU 指示灯交替灭1秒亮1秒

1.在全局变量表中定义2个定时器变量timer1,timer2 名称 类型 timer1 TMR_FB False -- False False timer2 TMR_FB False -- False False ot BOOL False -- False False ot表示指示灯 2.新建pou…

【Linux进程篇3】说白了,Linux创建进程(fork父子进程)也就那样!!!

--------------------------------------------------------------------------------------------------------------------------------- 每日鸡汤:没人可以好运一生,只有努力才是一生的护身符,不放弃、不辜负。 -----------------------…

使用服务器时进行深度学习训练时,本地必须一直保持连接状态吗?

可以直接查看方法,不看背景 1.使用背景2. 方法2.1 screen命令介绍2.2 为什么要使用screen命令2.3 安装screen2.4 创建session2.5 查看session是否创建成功2.6 跳转进入session2.7 退出跑代码的session2.8 删除session 1.使用背景 我们在进行深度学习训练的时候&…

防火墙笔记地十二天

1.IPSEC协议簇 IPSEC协议簇 --- 基于网络层的,应用密码学的安全通信协议组 IPV6中,IPSEC是要求强制使用的,但是,IPV4中作为可选项使用 IPSEC可以提供的安全服务 机密性 --- 数据加密 完整性 --- 防篡改 可用性 数据源鉴别 -…

即时设计:Sketch的云端版本控制

设计师们经常面临的一个挑战是设计软件的频繁更新,尤其是Sketch这类流行工具。每次更新可能会修复一些旧bug并增加新功能,但同时也可能导致与旧版本的不兼容问题,尤其是在不同工作环境中的电脑性能差异可能导致文件兼容性问题。那么&#xff…