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 原型继承关系

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

stm32HAL库-GPIO

一 什么是 GPIO: GPIO(general porpose intput output), 通用输入输出端口 . 二 我们先认识芯片控制 GPIO 输出控制。 2.1LED 硬件原理如图: 当电流从这根电线流通, LED 亮。当电流不通过这根电线, LED 灭。 上面 PF** ,芯片电…

MySQL面试——聚簇/非聚簇索引

存储引擎是针对表结构,不是数据库 引擎层:对数据层以何种方式进行组织 update:加索引:行级锁;不加索引:表级锁

固态继电器:推进可再生能源系统

随着可再生能源系统的发展,太阳能系统日益成为现代能源解决方案的先锋。在这种背景下,固态继电器(SSR),特别是光耦固态继电器的利用变得日益突出。本文旨在深入探讨SSR在可再生能源系统中的多方位应用,重点…

【学习笔记】Python 使用 matplotlib 画图

文章目录 安装中文显示折线图、点线图柱状图、堆积柱状图坐标轴断点参考资料 本文将介绍如何使用 Python 的 matplotlib 库画图,记录一些常用的画图 demo 代码 安装 # 建议先切换到虚拟环境中 pip install matplotlib中文显示 新版的 matplotlib 已经支持字体回退…

SD-WAN:灵活、低成本、便于管理

近年来,SD-WAN(软件定义广域网)技术成为企业网络领域的新趋势,其带来的变革性影响备受瞩目。凭借出色的灵活性、高效的可管理性以及显著的成本优势,SD-WAN技术为企业网络注入了新的活力。 首先,SD-WAN技术的…

如何利用diskpart命令界面在win10/win11上解除U盘写保护

背景 在把U盘作为系统盘装了一次后,惊讶的发现自己U盘的一个1M的小卷被写保护了。不能格式化,不能删除文件,在给用户拷文件的时候,小卷还会提示病毒告警,非常的尴尬,因此展开了研究。 失败的尝试 尝试了网…

58、回溯-组合总和

思路&#xff1a; 数组内的每一个元素都可以无线使用只要最后可以拼接成target就可以。那么如何限制呢&#xff1f; &#xff08;target-已经拼接的和 &#xff09;/当前元素 就是你可以利用的数量。代码如下&#xff1a; class Solution {public static List<List<I…

触发器的基本概念及分类

目录 触发器的基本概念 作用对象 触发事件 触发条件 触发时间 触发级别或者触发频率 触发器的分类 DML 触发器 INSTEAD OF 触发器 系统触发器 Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 触发器的基本概念 …

2024年电商视频号夏令营(第四期)零基础带你玩转微信视频号

教学内容&#xff1a; 下 载 地 址&#xff1a; laoa1.cn/1821.html 1.剪辑软件整套实例教程0基本一小时懂得视频编辑 1.上课前必看 1.如何获实拍视频的原创素材 2.怎样运送视频水印&#xff0c;提取图片文案脚本 2.如何发布爆款短视频 2.微信视频号基本功能解读 2.直播的时…

软件物料清单(SBOM)生成指南 .pdf

如今软件安全攻击技术手段不断升级&#xff0c;攻击数量显著增长。尤其是针对软件供应链的安全攻击&#xff0c;具有高隐秘性、追溯难的特点&#xff0c;对企业软件安全威胁极大。 同时&#xff0c;软件本身也在不断地更新迭代&#xff0c;软件内部成分安全性在持续变化浮动。…

第十二届蓝桥杯C/C++ B组 杨辉三角形(二分查找+思维)

3418. 杨辉三角形 - AcWing题库 题目描述: 思路&#xff1a; 从上图片中&#xff0c;我们可以看出来这是一个对称图形&#xff0c;所以我们只看左半部分就可以了&#xff0c;我们一行一列去做数据量是1e9这样会很麻烦&#xff0c;所以我们这里做一个思想转换&#xff0c;斜着…

WiTUnet:一种集成CNN和Transformer的u型架构,用于改进特征对齐和局部信息融合

WiTUnet:一种集成CNN和Transformer的u型架构&#xff0c;用于改进特征对齐和局部信息融合 摘要IntroductionRelated workMethod WiTUnet: A U-Shaped Architecture Integrating CNN and Transformer for Improved Feature Alignment and Local Information Fusion. 摘要 低剂量…

天锐绿盾 | 如何防止开发部门源代码泄露、外泄?

天锐绿盾是一款专为企业设计的数据防泄密解决方案&#xff0c;尤其针对软件开发部门的源代码保护提供了多维度、全方位的防护措施。 PC访问咨询地址&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 以下是如何利用天锐绿盾防止公司…

C++ 之 string类的模拟实现

这学习我有三不学 昨天不学&#xff0c;因为昨天是个过去 明天不学&#xff0c;因为明天还是个未知数 今天不学&#xff0c;因为我们要活在当下&#xff0c;我就是玩嘿嘿~ –❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–…

【Web】第三次

【Web】第三次 1.完成学校官方网站页面制作2.使用动画完成过渡变换效果 1.完成学校官方网站页面制作 2.使用动画完成过渡变换效果 1.完成学校官方网站页面制作 html&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://…

Kafka 3.x.x 入门到精通(03)——对标尚硅谷Kafka教程

Kafka 3.x.x 入门到精通&#xff08;03&#xff09;——对标尚硅谷Kafka教程 2. Kafka基础2.1 集群部署2.2 集群启动2.3 创建主题2.4 生产消息2.4.1 生产消息的基本步骤2.4.2 生产消息的基本代码2.4.3 发送消息2.4.3.1 拦截器2.4.3.1.1 增加拦截器类2.4.3.1.2 配置拦截器 2.4.3…

毕业答辩PPT怎么做?制作PPT必备的模板网站和AI工具来了!

临近毕业季&#xff0c;眼下应该有不少朋友忙着做论文答辩 PPT&#xff0c;但毕业前也有诸多事项要同时推进&#xff0c;如工作实习、毕业旅游、毕业照筹备等&#xff0c;能花在制作毕设答辩 PPT 上的时间较少&#xff0c;“时间紧任务重”&#xff0c;要想又快又好地搞定答辩 …

【Vision Pro应用】分享一个收集Apple Vision Pro 应用的网站

您是否也觉得 Vision Pro 应用程序商店经常一遍又一遍地展示相同的几个 VisionOS 应用程序?许多有趣、好玩的应用程序似乎消失得无影无踪,让人很难发现它们。为了帮助大家更轻松地探索和体验最新、最有趣的 Vision Pro 应用程序,这里分享一个网站https://www.findvisionapp.…

论文解读:(VPT)Visual Prompt Tuning

文章汇总 要解决的问题 大型模型应用于下游任务本身就存在挑战。最明显的(通常也是最有效的)适应策略是对预先训练好的模型进行全面的端到端微调。 动机 只微调参数的一个子集 解决的办法 只在输入空间中引入少量特定于任务的可学习参数&#xff0c;而在下游训练期间冻结…

Vitis AI 迁移学习并部署在DPU中

目录 1. 本文目的 2. ResNet18介绍 3. 迁移学习 4. 量化配置文件 5. 模型编译&#xff1a; 6. 总结 1. 本文目的 使用迁移学习的方法&#xff0c;将预训练的resnet18模型从原来的1000类分类任务&#xff0c;改造为适应自定义的30类分类任务。 2. ResNet18介绍 ResNet1…