邂逅JavaScript逆向爬虫-------基础篇之面向对象

news2024/11/16 20:24:35

目录

  • 一、概念
  • 二、对象的创建和操作
    • 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属性的值,默认undefined
    writable: false,// age属性是否可以写入(修改),默认false
    enumerable: true, // age属性是否可以枚举,默认false
    configurable: 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 = age
    obj.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 = name
    this.age = age

    this.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 = name
    this.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 = name
    this.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 = 30

console.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 = name
    this.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 = name
        this.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 = o
    return 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.coloradmin.cn/o/1620063.html

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

相关文章

zabbix6.4告警配置(短信告警和邮件告警),脚本触发

目录 一、前提二、告警配置1.邮件告警脚本配置2.短信告警脚本配置3.zabbix添加报警媒介4.zabbix创建动作4.给用户添加报警媒介 一、前提 已经搭建好zabbix-server 在需要监控的mysql服务器上安装zabbix-agent2 上述安装步骤参考我的上篇文章:通过docker容器安装za…

软考-系统集成项目管理中级--合同管理

本章历年考题分值统计(16年11月及以后按新教材考的) 本章重点常考知识点汇总清单(学握部分可直接理解记忆) 8、合同签订管理(掌握)10下53,14上53,15上53 考题 签订合同的前期调查,每一项合同在签订之前,应当做好以下几…

Python蜘蛛侠

目录 写在前面 蜘蛛侠 编写代码 代码分析 更多精彩 写在后面 写在前面 本期小编给大家推荐一个酷酷的Python蜘蛛侠,一起来看看叭~ 蜘蛛侠 蜘蛛侠(Spider-Man)是美国漫威漫画宇宙中的一位标志性人物,由传奇创作者斯坦李与艺…

MySQL主从结构搭建

说明:本文介绍如何搭建MySQL主从结构; 原理 主从复制原理如下: (1)master数据写入,更新binlog; (2)master创建一个dump线程向slave推送binlog; &#xff…

javaScript中的作用域和作用域链

作用域(Scope) 什么是作用域 作用域是在运行时代码中的某些特定部分中变量、对象和函数的可访问性。 换句话说,作用域决定了代码区块中变量和其他资源的可见性。 示例: function outFun2() {var inVariable "内层变量2…

通过本机端口映射VMware中虚拟机应用(例如同一局域网别人想远程连接你虚拟机中的数据库)

需要 虚拟机中安装一下达梦数据库,并且以后大家都连接你虚拟机中达梦数据库进行开发。。。。。。在不改动自己虚拟机配置,以及本地网卡任何配置的情况下如何解决?本虚拟机网络一直使用的NAT模式。 解决 找到NAT设置添加端口转发即可解决。…

springboot+springsecurity+vue前后端分离权限管理系统

有任何问题联系本人QQ: 1205326040 1.介绍 优秀的权限管理系统,核心功能已经实现,采用springbootvue前后端分离开发,springsecurity实现权限控制,实现按钮级的权限管理,非常适合作为基础框架进行项目开发。 2.效果图…

AI创作系统Pro版,付费及流量主小程序源码搭建,抖音小程序项目分享,ai系统搭建运营。

AI技术可以激发新的创新思维和方法,在许多领域都能发挥重要作用。今天我们来讲述以下这款ai工具的作用以及可以延伸出什么互联网项目。 一、AI创作系统Pro系统功能 这款系统有AI写真,绘画,动 漫,音 乐,换 脸&#xf…

DSP 应用领域及内部结构

随着 DSP 性能的改善和成本的降低,DSP 在通用数字信号算法处理的基础上,其应用几乎遍及电子学每个领域。 (1)通信:网络通信,高速调制解调器,编/解码器,自适应均衡器,移动…

MES(生产管理系统)开发岗人才定向培养来啦

定向就业培养,职等你来 《中国制造2025》,是我国实施制造强国战略第一个十年的行动纲领,按照“四个全面”战略布局要求,实施制造强国战略,加强 统筹规划和前瞻部署。围绕重点行业转型升级和新一代信息技术、智能制造、…

小程序APP屏幕悬浮按钮可拖动可展开

示例&#xff1a; 插件地址&#xff1a;suspensionButton 悬浮按钮 - DCloud 插件市场 使用方式&#xff1a; import pxSuspenButton from /components/px-suspen-button/index components: { pxSuspenButton }, <pxSuspenButton :centerIcon"centerIcon" :bgCo…

【VSCode调试技巧】Pytorch分布式训练调试

最近遇到个头疼的问题&#xff0c;对于单机多卡的训练脚本&#xff0c;不知道如何使用VSCode进行Debug。 解决方案&#xff1a; 1、找到控制分布式训练的启动脚本&#xff0c;在自己的虚拟环境的/lib/python3.9/site-packages/torch/distributed/launch.py中 2、配置launch.…

【GitHub】2FA认证(双重身份验证)

GitHub 2FA认证&#xff08;双重身份验证&#xff09; 写在最前面一、使用 TOTP 应用程序配置双2FA&#xff08;双因素身份验证&#xff09;1. 介绍2. github3. 认证 官网介绍小结 & 补充 &#xff1a;权限不足or验证码错误问题 &#x1f308;你好呀&#xff01;我是 是Yu欸…

【DVadmin教程】新增一个app并且创建一个curd模型

下载dvadmin 在gitee上搜索 django-vue-admin 别选错了&#xff0c;选择那个星最多的 gitee上会推荐移步 django-vue3-admin 别上当&#xff0c;用那个会踩坑&#xff0c;此教程就不适用了&#xff1b; 运行后端 后端 创建APP python manage.py startapp rules调整位置 为了…

【Linux】详解信号的分类如何自定义信号的作用

一、信号的定义 Linux系统提供的让用户(进程)给其他进程发送异步信息的一种方式。在操作系统中&#xff0c;信号是一种进程间通讯的有限制的方式&#xff0c;主要用于提醒进程某个事件已经发生。信号在Unix、类Unix以及其他POSIX兼容的操作系统中广泛应用。它作为一种异步的通知…

Ubuntu系统开机长

Ubuntu系统开机长 1. 检查开机自启动软件的所占时间2. 将耗时最高的禁止开机自启动 1. 检查开机自启动软件的所占时间 systemd-analyze blame2. 将耗时最高的禁止开机自启动 sudo systemctl disable networking.service这个耗时是有阈值的&#xff0c;一般大于15s的算&#x…

增加PyQt5界面的交通流量预测(模型为CNN_GRU,CNN_BiGRU_ATTENTION,LSTM,Python代码)

1.效果视频&#xff1a;增加PyQt5界面的交通流量预测&#xff08;模型为CNN_GRU&#xff0c;CNN_BiGRU_ATTENTION&#xff0c;LSTM&#xff09;_哔哩哔哩_bilibili&#xff09; 2.三个模型和数据集的介绍 交通流量预测(python代码&#xff0c;压缩包中带有数据&#xff0c;CN…

基于pytorch hook机制,生成一次前后向的timeline

基于pytorch hook机制,生成一次前后向的timeline 一.效果图二.代码三.运行以上代码,生成timeline.json,打开https://ui.perfetto.dev/,导入该文件,可看到以上效果 本文通过pytorch hook机制,拦截所有子module的forward和backward,统计执行时间及内存使用情况 一.效果图 二.代码…

探索Sun-Panel:一站式服务器、NAS导航面板、Homepage、浏览器首页

随着数字化时代的持续演进&#xff0c;尤其是对于开发者而言&#xff0c;我们与浏览器的互动频率越来越高。尽管浏览器提供了书签功能&#xff0c;但总有一种感觉&#xff0c;似乎书签并不能完全满足我们的需求。我们期待着一个能够满足我们需求的可私有化部署的浏览器导航页。…