邂逅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://xiahunao.cn/news/2981951.html

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

相关文章

Java | Leetcode Java题解之第48题旋转图像

题目&#xff1a; 题解&#xff1a; class Solution {public void rotate(int[][] matrix) {int n matrix.length;// 水平翻转for (int i 0; i < n / 2; i) {for (int j 0; j < n; j) {int temp matrix[i][j];matrix[i][j] matrix[n - i - 1][j];matrix[n - i - 1]…

YOLOv8 训练自己的数据集(20240423)

环境搭建请参考&#xff1a;Win10 搭建 YOLOv8 运行环境&#xff08;20240423&#xff09;-CSDN博客 环境测试请参考&#xff1a;本地运行测试 YOLOv8&#xff08;20240423&#xff09;-CSDN博客 一、使用 YOLOv8 的 coco128 数据集熟悉一下如何训练和预测 1.1、在项目根目录…

二手车交易平台搭建重点,会用到哪些三方服务?

在搭建二手车交易平台时&#xff0c;有几个重点方面需要关注&#xff0c;并且会涉及到一些第三方服务的使用。以下是关键点和可能用到的第三方服务&#xff1a; 一、二手车交易平台搭建重点 用户友好与界面设计&#xff1a;一个成功的二手车交易平台首先需要一个直观、易用且吸…

【软件安装】(十六)双系统Ubuntu22.04引导启动菜单的默认项

一个愿意伫立在巨人肩膀上的农民...... 好学的人总是喜欢在电脑上安装双系统&#xff0c;可是安装好系统之后&#xff0c;就会出现默认启动优先级的苦恼&#xff0c;如果在Bios中设置Windows引导启动为优先启动&#xff0c;那么每次想要进如Ubuntu系统就都需要重新设置Bios。如…

LAMMPS单层石墨烯建模

本文主要介绍两种晶胞建模方式。 一、Z形晶胞 晶胞分析&#xff1a;a1沿水平x轴方向&#xff0c;a2沿垂直y轴方向。石墨烯是二维结构&#xff0c;a3取小于单层石墨烯厚度。假设石墨烯键长L1.421&#xff0c;则a13L&#xff0c;a21.732L&#xff0c;a32L&#xff08;低于3.35即…

CSAPP | Lab2-Bomb Lab详细解析

预备阶段 1.Lab要求 邪恶的邪恶博士在我们班的机器上安放了大量的“二元炸弹”。二进制炸弹是一个由一系列阶段组成的程序。每个阶段都要求你在 stdin 上键入一个特定的字符串。如果你键入了正确的字符串&#xff0c;那么这个阶段就会被拆除&#xff0c;炸弹就会进入下一个阶…

如何利用美国站群服务器通过CN2线路优化中美之间的数据传输?

如何利用美国站群服务器通过CN2线路优化中美之间的数据传输? 随着全球化进程的不断推进&#xff0c;跨国企业和国际市场的拓展对数据传输速度和稳定性提出了更高的要求。特别是对于中美之间的数据传输&#xff0c;由于地理位置遥远和网络环境不同&#xff0c;优化数据传输变得…

数据类型总结

1 引言 在计算机的世界里&#xff0c;数据类型是被人类定义出来的&#xff0c;方便人去更好地理解、辨别数据。计算机只能识别二进制数&#xff0c;不可能要求写代码时&#xff0c;只是输入一些0/1的东西。通过定义数据类型&#xff0c;可以让人和计算机更好地“沟通”&#x…

制氢机远程监控运维方案

制氢机远程监控运维方案 在当今能源转型的大背景下&#xff0c;氢能作为清洁、高效且可再生的能源载体&#xff0c;其重要性日益凸显。而制氢机作为氢能产业链中的关键设备&#xff0c;其稳定运行与高效运维对于保障氢气供应、推动氢能产业健康发展至关重要。在此背景下&#…

spring boot 基础案例【2】对多环境配置的支持更改

教程1 案例教程 案例仓库 在线编程 教程2 基础教程 教程仓库 在线编程 本案例所在的仓库 本案例所在的文档 进入正文 1.文件目录 1. Chapter12Application.java 地址&#xff1a;/chapter1-2/src/main/java/com/didispace/chapter12/Chapter12Application.java package com.…

康谋分享 | aiSim5激光雷达LiDAR模型验证方法(二)

aiSim中的LiDAR是一种基于光线追踪的传感器&#xff0c;能够模拟真实LiDAR发射的激光束&#xff0c;将会生成LAS v1.4标准格式的3D点云&#xff0c;包含了方位角、俯仰角和距离等。 aiSim能够模拟LiDAR单态&#xff08;Monostatic&#xff09;和同轴&#xff08;Coaxial&#…

PC端微信软件如何多开【详细教程】

现在工作中&#xff0c;很多小伙伴会用到两个微信。如何在PC端同时登录多个微信呢&#xff1f;赶快跟着下面的教程学起来吧 1、创建一个txt文本文件 2、输入以下代码并保存 echo offstart "" "复制粘贴微信的目标地址" 需要开几个微信就复制几行exit示例…

UTONMOS:用区块链技术拓展商业边界在哪里?

引言 大约从 2021 年Web 3 这个新概念开始受到风险基金和科技圈的普遍关注。但如果你对过去几年区块链的发展历史足够了解&#xff0c;就应该已经意识到现在的 Web 3 并不是什么新技术&#xff0c;甚至不是旧技术的进步&#xff0c;它只是一个基于区块链技术的宏大构想。 我是…

Vue3+Vant开发:个人信息管理

🙈作者简介:练习时长两年半的Java up主 🙉个人主页:程序员老茶 🙊 ps:点赞👍是免费的,却可以让写博客的作者开心好久好久😎 📚系列专栏:Java全栈,计算机系列(火速更新中) 💭 格言:种一棵树最好的时间是十年前,其次是现在 🏡动动小手,点个关注不迷路,…

一个联合均值与方差模型的R包——dglm

目录 一、引言二、包的安装与载入三、模拟例子3.1 数据生成3.2 数据查看3.3 模型估计参数 一、引言 在 R 语言中&#xff0c;dglm 包是用于拟合双参数广义线性模型&#xff08;Double Generalized Linear Models&#xff0c;简称 DGLMs&#xff09;的一个工具。这类模型允许同…

C语言实现双人贪吃蛇项目(基于控制台界面)

一.贪吃蛇 贪吃蛇是一款简单而富有乐趣的游戏&#xff0c;它的规则易于理解&#xff0c;但挑战性也很高。它已经成为经典的游戏之一&#xff0c;并且在不同的平台上一直受到人们的喜爱和回忆。 二.贪吃蛇的功能 游戏控制&#xff1a;玩家可以使用键盘输入设备来控制蛇的移动方…

139GB,台北倾斜摄影OSGB数据V0.1版

本月初发布了谷歌倾斜摄影数据OSGB转换工具V0.2版(更新&#xff01;谷歌倾斜摄影转换生成OSGB瓦片V0.2版),并免费分享了基于V0.2版转换工具生产的澳门地区OSGB数据(首发&#xff01;澳门地区OSGB数据V0.2版免费分享),V0.2版本在生产速度、显示效率和OSGB数据轻量化方面进行了优…

NVIDIA Jetson jtop查看资源信息

sudo -H pip install -U jetson-stats 安装好之后可能需要reboot 执行jtop&#xff1a; 时间久了可能会退出&#xff0c;可参考如下再次启动。 nvidiategra-ubuntu:~$ jtop The jtop.service is not active. Please run: sudo systemctl restart jtop.service nvidiategra-ub…

修改docker镜像版本,容器大小缩小10%!

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen 是的&#xff0c;你看的没错&#xff1a;修改docker镜像的版本&#xff0c;我的…

最新知识付费小程序源码独立版 支持多终端合一+源码开源可二开+含完整代码包和安装教程

在当今信息爆炸的时代&#xff0c;知识付费逐渐成为了一种新型的商业模式。为了满足广大开发者和运营者的需求&#xff0c;分享一个最新版的知识付费小程序源码独立版。该版本不仅支持多终端合一&#xff0c;还具备源码开源可二开的特性&#xff0c;同时提供了完整的代码包和安…