一文梳理JavaScript中常见的七大继承方案

封面

阐述JavaScript中常见的七大继承方案

  • 📖序言
  • 📔文章内容抢先看
  • 📝一、基础知识预备
    • 1. 继承的定义
    • 2. 继承的方式
  • 📚二、6大常见继承方式
    • 1. 原型链继承 💡
      • (1)构造函数、原型和实例的关系
      • (2)基本思想
      • (3)实现原型链继承
      • (4)图例阐述
      • (5)破坏原型链
      • (6)优缺点
    • 2. 盗用构造函数继承💡
      • (1)基本思想
      • (2)使用方式
      • (3)实现原型链继承
      • (4)优缺点
    • 3. 组合继承💡
      • (1)基本思想
      • (2)实现组合继承
      • (3)图例
      • (4)优缺点
    • 4. 原型式继承💡
      • (1)基本思想
      • (2)实现原型式继承
      • (3)优缺点
    • 5. 寄生式继承💡
      • (1)基本思想
      • (2)实现寄生式继承
      • (3)优缺点
    • 6. 寄生式组合继承💡
      • (1)基本思想
      • (2)实现寄生式组合继承
      • (3)图例
      • (4)优缺点
  • 🗞️三、Class的继承
    • 1. 基本概念
    • 2. Object.getPrototypeOf()
    • 3. super关键字
      • (1)作为函数使用
      • (2)作为对象使用
        • 1)在普通方法中
        • 2)在静态方法中
    • 4. 类的prototype属性和 __ proto __ 属性
      • (1)class的继承链
      • (2)特殊情况继承
      • (3)实例的 __ proto __ 属性
  • 📑四、结束语
  • 🐣彩蛋 One More Thing
    • (:参考资料
    • (:番外篇

📖序言

在前端的面试中,继承是一道很常考的题目,面试官总会变着法来问你。比如说,你知道哪几种继承方式?这几种继承方式有什么区别?这几种继承方式的优缺点是啥?又各有什么特点呢?

一招致命,就像周一第一次遇到这道题的时候,只记得有那么几种继承方式,但是我也说不上个所以然,知识还是太浮动于表面了。

因此,写下这篇文章,来总结各种继承方式的知识点。一起来学习叭~✨

📔文章内容抢先看

在真正进入本文的讲解之前,我们先用一张思维导图来了解本文所涉及到的内容。详情见下图👇

思维导图

下面开始进入本文的讲解~

📝一、基础知识预备

1. 继承的定义

引用 Javascript 高级语言程序设计中的一句话:

继承是面向对象编程 (即 object-oriented language ,下面简称OO语言) 中讨论的最多的一个话题。很多 OO语言 都支持两种继承:接口继承实现继承前者只能继承方法签名,而后者可以继承实际的方法

然后,接口继承在 ECMAScript 中是不太可能存在的,原因在于函数没有签名

因此呢,实现继承是 ECMAScript 唯一支持的继承方式,且这主要通过原型链来实现。

2. 继承的方式

了解完定义,我们来看一下,继承有多少种方法。详情见下图👇

继承的方式

📚二、6大常见继承方式

1. 原型链继承 💡

(1)构造函数、原型和实例的关系

对于构造函数、原型和实例三者之间,有以下关系:每个构造函数都有一个原型对象,原型上有一个属性指回构造函数,而实例又有一个内部指针指向原型。

(2)基本思想

原型链继承的基本思想是通过原型来继承多个引用类型的属性和方法,其核心在于将父类的实例作为子类的原型

(3)实现原型链继承

上面的内容可能看起来有点抽象,我们先用一个例子来体验一下原型链继承。具体代码如下:

// 父类函数
function SuperType() {// 父类定义属性this.property = true;
}// 父类定义方法
SuperType.prototype.getSuperValue = function() {return this.property;
}// 子类函数
function SubType() {this.subproperty = false;
}/*** 关键点:* 通过创建父类SuperType的实例,* 并将该实例赋值给子类SubType.prototype*/
SubType.prototype = new SuperType();
// 子类定义新方法
SubType.prototype.getSubValue = function() {return this.subproperty;
}// 创建一个子类的实例
let instance = new SubType();
// 子类调用父类的方法
console.log(instance.getSuperValue()); // true

(4)图例阐述

依据以上代码,我们来用一张图表示这段代码的继承关系。如下图所示:

原型链继承

大家定位到图片的最右上角区域,同时我们也将由右上到左下进行分析。

首先,我们会创建构造函数 SuperType 的实例,并将其作为子类的原型,也就是 SuperType.prototype 。之后,实例创建完了,该实例上有一个内容指针指向父类的原型 SuperType.prototype 。完成之后,来到了第三步。该原型上有一个属性将指回构造函数 SuperType 。第四步,对于构造函数来说,每个构造函数又有它自己的原型,所以它又会指回 SuperType.prototype

依据上面这段描述,大家再看下(1)和(2)的关系和基本思想,是不是就清晰许多了呢。

同样地,下面的⑤~⑧步也是和上面一样的步骤,大家可以自行再理解对应,这里不再细述。

(5)破坏原型链

还有一个要理解的重点是,以对象字面量的方式去创建原型方法回破坏之前的原型链,因为这相当于重写了原型链如下例子所示:

function SuperType() {this.property = true;
}SuperType.prototype.getSuperValue = function() {return this.property;
};function SubType() {this.subproperty = false;
}// 继承SuperType
SubType.prototpe = new SuperType();
// 通过对象字面量添加新方法,这将导致上一行无效
SubType.prototype = {getSubValue() {return this.subproperty;},someOtherMoethod(){return false;}
}let instance = new SubType();
console.log(instance.getSuperValue()); 
// TypeError: instance.getSuperValue is not a function

在上面这段代码中,子类 SubType 的原型在被赋值为 SuperType 的实例后,又被一个对象字面量覆盖了。而覆盖后的原型其实已经变成了一个 Object 的实例,而不再是 SuperType 的实例,因此在赋值为对象字面量之后,原型链就被断掉了。这个时候 SubTypeSuperType 也就不再有任何关系。

(6)优缺点

1)优点:

  • 父类的方法可以复用。

2)缺点:

  • 父类的所有引用类型数据将会被所有子类共享,也就是说,一旦有一个子类的引用类型数据更改了,那么其他子类也会受到影响
  • 子类型实例不能给父类型构造函数传参。原因在于,一旦传参的话,由于第一个问题的因素,会导致当传相同的参数时会覆盖之前的。

2. 盗用构造函数继承💡

(1)基本思想

对于以上原型链继承存在的两个问题,所以导致原型链基本不会被单独使用。因此,为了解决原型包含引用值导致的问题,我们引入一种新的继承方式,为 “盗用构造函数” 。这种技术也被称为对象伪装或者经典继承

其基本思路为: 使用父类的构造函数来增强子类实例,等同于赋值父类的实例给子类(不使用原型)。

(2)使用方式

函数是在特定上下文中执行代码的简单对象,因此,可以使用 apply()call() 方法,以新创建的对象为上下文来执行构造函数。

(3)实现原型链继承

我们用一个例子来实现原型链继承。具体代码如下:

function SuperType(name) {this.name = name;this.colors = ["red", "blue", "green"];
}function SubType(name, age) {// 继承SuperType并传参,这里是核心SuperType.call(this, 'monday');// 实例属性this.age = age;
}let instance1 = new SubType("monday", 18);
instance1.colors.push("gray");
console.log(instance1.name); // monday
console.log(instance1.age); // 18
console.log(instance1.colors); // [ 'red', 'blue', 'green', 'gray' ]let instance2 = new SubType();
console.log(instance2.colors); // [ 'red', 'blue', 'green' ]

大家可以看到,在上面的这个例子中, 通过使用 call() 方法,借用了 SuperType 构造函数,这样,在创建 SubType 的实例时,都会将 SuperType 中的属性复制一份出来。

同时,相对于原型链来说,盗用构造函数的一个优点就是可以在子类构造函数中向父类构造函数传递参数。大家可以看到上面代码,我们在使用 SuperType.call() 时,就可以将参数直接传递给父类 SuperType

(4)优缺点

1)优点:

  • 子类的构造函数可以向父类的构造函数传递参数。

2)缺点:

  • 只能继承父类的实例属性和方法,不能继承父类的原型属性和方法
  • 无法实现复用,每新创建一个子类,都会产生父类实例函数的副本,非常影响性能。

3. 组合继承💡

(1)基本思想

由于原型链和盗用构造函数继承的方式都存在一定的缺陷,所以它们俩基本上不能单独使用。为此呢,我们引入了一种新的继承方式,组合继承。组合继承也叫做伪经典继承,它综合了原型链和盗用构造函数,将两者的优点结合了起来。

其实现的基本思路为:使用原型链来继承原型上的属性和方法,然后呢,通过盗用构造函数来继承实例上的属性。

这样,通过在原型上定义方法实现了函数的复用,又能保证每个实例都有它自己的属性

(2)实现组合继承

下面我们用一个例子来实现组合继承。具体代码如下:

function SuperType(name) {this.name = name;this.colors = ['red', 'blue', 'green'];
}SuperType.prototype.sayName = function() {console.log(this.name);
}function SubType(name, age) {// 继承属性→借用构造函数继承实例上的属性SuperType.call(this, name);this.age = age;
}// 继承方法→通过原型的方式继承原型的属性和方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){console.log(this.age);
}let instance1 = new SubType('Monday', 18);
instance1.colors.push('gray');
console.log(instance1.colors); // [ 'red', 'blue', 'green', 'gray' ]
instance1.sayName(); // Monday
instance1.sayAge(); // 18let instance2 = new SubType('Tuesday', 24);
console.log(instance2.colors); // [ 'red', 'blue', 'green' ]
instance2.sayName(); // Tuesday
instance2.sayAge(); // 24

大家可以看到,在以上的代码中,通过盗用构造函数,继承了父类 SuperType 实例上的属性。同时,通过原型的方式,也成功继承了父类 SuperType 原型上的属性和方法。

(3)图例

我们来用一张图展示一下上述的结果。具体如下:

组合继承

(4)优缺点

1)优点:

  • 避开原型链和盗用构造函数继承的缺陷,实现对实例和原型的继承。
  • 组合继承是 javascript 常用的一种继承方式,且保留了 instanceof 操作符和 isPrototypeOf() 方法识别合成对象的能力。

2)缺点:

  • 父类中的实例属性和方法,既存在于子类的实例中,又存在于子类的原型中,这将会占用更多的内存
  • 所以,在使用子类创建实例对象时,其原型中会存在两份相同的属性和方法。

4. 原型式继承💡

(1)基本思想

原型式继承实现的基本思路是,将某个对象直接赋值给构造函数的原型。如下代码所示:

function object(obj) {function F(){}F.prototype = obj;return new F();
}

以上这段代码是 Douglas Crockford 在2006年写的一篇文章中所谈及。这篇文章介绍了一种不涉及严格意义上构造函数的继承方法,且他的出发点是即使不自定义类型,也可以通过原型实现对象之间的信息共享

在上述代码中, object() 对传入其中的对象执行了一次,并将 F 的原型直接指向传入的对象。

(2)实现原型式继承

下面我们用一个例子来实现原型式继承。具体代码如下:

function object(obj){function F(){}F.prototype = obj;return new F();}let person = {name: 'Monday',friends: ['January', 'February', 'March']
};let otherPerson = object(person);
otherPerson.name = 'Friday';
otherPerson.friends.push('April');let anotherPerson = object(person);
anotherPerson.name = 'Sunday';
anotherPerson.friends.push('May');console.log(person.friends); // [ 'January', 'February', 'March', 'April', 'May' ]

大家可以看到,最终所有的 friend 都拷贝到了 person 对象上。但是这种方法其实用的比较少,有点类似于原型链继承的模式,所以一般也不会单独使用。

(3)优缺点

1)优点:

  • 适用于不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。

2)缺点:

  • 当继承多个实例的引用类型数据时,指向是相同的,所以存在篡改的可能。
  • 无法传递参数。
  • ES5 中已经存在 Object.create() 的方法,能够代替上面的 object 方法。

5. 寄生式继承💡

(1)基本思想

寄生式继承的基本思想是,在上面原型式继承的基础上,以某种方式来增强对象,并返回这个对象。

(2)实现寄生式继承

下面我们用一个例子来实现寄生式继承,具体代码如下:

// object函数
function object(obj){function F(){}F.prototype = obj;return new F();
}// 函数的主要作用是为构造函数新增属性和方法,以增强函数
function createAnother(original) {// 通过调用函数来创建一个新对象let clone = object(original);// 以某种方式增强这个对象clone.sayHello = function() {console.log('hello');}// 返回对象return clone; 
}let person = {name: 'Monday',friends: ['January', 'February', 'March']
};let anotherPerson = createAnother(person);
anotherPerson.sayHello(); // hello

大家可以看到,通过创建一个新的构造函数 createAnother ,来增强对象的内容。并在之后对这个对象进行返回,供给我们使用。

(3)优缺点

1)优点:

  • 适合只关注对象本身,而不在乎数据类型和构造函数的场景。

2)缺点:

  • 给对象添加函数会导致函数难以重用,与上面第2中的盗用构造函数继承类似。
  • 当继承多个实例的引用类型数据时,指向是相同的,所以存在篡改的可能。
  • 无法传递参数。

6. 寄生式组合继承💡

(1)基本思想

寄生式组合继承的基本思想是,结合盗用构造函数寄生式模式来实现继承。

(2)实现寄生式组合继承

我们用一个例子,来展示寄生式组合继承。具体代码如下:

function inheritPrototype(subType, superType) {// 创建对象let prototype = Object.create(superType.prototype);// 增强对象prototype.constructor = subType;// 指定对象subType.prototype = prototype;
}// 父类对实例属性和原型属性进行初始化
function SuperType(name) {this.name = name;this.friends = ['January', 'February', 'March'];
}
SuperType.prototype.sayName = function() {console.log(this.name);
}// 借用构造函数对子类实例属性进行传参(支持传参和避免篡改)
function SubType(name, age) {SuperType.call(this, name);this.age = age;
}// 运用寄生式继承的特点,将父类的原型指向子类
inheritPrototype(SubType, SuperType);// 新增子类的原型属性
SubType.prototype.sayAge = function() {console.log(this.age);
}let instance1 = new SubType('Ida', 18);
let instance2 = new SubType('Hunter', 23);instance1.friends.push('April');
console.log(instance1.friends); // [ 'January', 'February', 'March', 'April' ]
instance1.friends.push('May');
console.log(instance2.friends); // [ 'January', 'February', 'March' ]

大家可以看到,对于寄生式组合继承来说,它借用了构造函数来对子类的实例属性进行传参,同时通过运用寄生式继承的特点,使用 inheritPrototype 构造函数来将父类的原型指向子类,这样,子类就继承了父类的原型 。

同时,子类还可以在自己的原型上新增自己想要的原型属性,达到继承自己的原型方法的效果。

(3)图例

我们再来用一张图展示一下上述的结果。具体如下:

寄生式组合继承

(4)优缺点

1)优点:

  • 寄生式组合继承可以算是引用类型继承的最佳方式。
  • 几乎避免了上述所有继承方式中所存在的缺陷,也是执行效率最高且应用面最广的。

2)缺点:

  • 实现的过程相对较繁琐,要捋好父子之间的关系,避免跳入原型的漩涡中。这其实也不算啥缺点,因为它值得!

🗞️三、Class的继承

1. 基本概念

在上述中我们讲到了各种类型的继承方式,但这似乎都逃不开原型链的闭圈中。到了 2015 年,ES6 的出现解决了这个问题。 ES6class 可以通过 extends 关键字来实现继承,这间接地,比 ES5 通过修改原型链实现继承的方式更加清晰和方便了许多。

我们用一个例子来先看一下 class 是如何实现继承的。具体代码如下:

class Point{constructor(x, y) {this.x = x;this.y = y;}
}class ColorPoint extends Point {constructor(x, y, color) {// 调用父类的 constructor(x, y)// 只有super方法才能返回父类的实例super(x, y); this.color = color;}toString() {return this.color + ' ' + super.toString() {// 调用父类的toString()}}
}

大家可以看到,通过 extends 关键字,实现了对父类的属性和方法进行继承,这似乎看起来就比上面的寄生式组合继承要方便的多了。

接下来,我们继续看其他用法。

2. Object.getPrototypeOf()

ES6 中, Object.getPrototypeof 方法可以用来从子类上获取父类比如:

Object.getPrototypeof(ColorPoint) === Point
// true

所以,可以使用这一方法来判断一个类是否继承了另外一个类。

3. super关键字

上面我们有看到 super 这个关键字,它主要是用来返回父类的实例。那在实际的应用中, super 可以当作函数使用,也可以当作对象使用。接下来我们来谈谈这两种情况。

(1)作为函数使用

第一种情况,当 super 作为函数调用时,代表父类的构造函数ES6 要求,子类的构造函数必须执行一次 super 函数。如下例子:

class A {}class B extends A {constructor() {super();}
}

上述代码中的 super() 代表调用父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B 。因此,这里的 super() 相当于 A.prototype.constructor.call(this)


值得注意的是, 作为函数时, super() 只能用在子类的构造函数之中,用在其他地方会报错。比如:

class A {}class B extends A {m() {super(); // 报错}
}

大家可以看到,像上面这种情况, super() 用在 B 类的 m 方法之中就会造成句法错误。

(2)作为对象使用

第二种情况,当 super 作为对象时,在普通方法中指向父类的原型对象,在静态方法中指向父类。我们来一一分析这两种情况。

1)在普通方法中

先来看一段代码,具体如下:

class A {p() {return 2;}
}class B extends A {constructor() {super();console.log(super.p()); // 2}
}let b = new B();

在上面的代码中,子类 B 中的 super.p() 就是super 当作一个对象来使用。依据上面所描述的, super 在普通方法中指向父类的原型对象,因此,这里的 super.p() 就等于 A.prototype.p() 。所以最终输出为 2


继续,由于 super 指向父类的原型对象,所以,定义在父类实例上的方法或属性时无法通过 super 进行调用的。比如:

class A {constructor() {this.p = 2;}
}class B extends A {get m() {return super.p; // 定义在普通方法上,所以super指向父类的原型对象}
}let b = new B();
b.m // undefined

在上述代码中, super 在普通方法中调用,所以也就意味着 super 指向父类的原型对象。而 b.m 却想要直接指向父类 A 实例的属性上,自然就是搜索不到它的。


我们可以对代码进行修改,将属性定义在父类的原型对象上,让 super 可以找到具体的属性。具体如下:

class A {}
A.prototype.x = 2;class B extends A {constructor() {super();console.log(super.x); // 2}
}let b = new B();

大家可以看到,现在属性 x 是定义在 A.prototype 上面的,所以 super.x 就可以顺利的取到它的值了。

2)在静态方法中

上面我们谈到了 super 作为对象时在普通方法中的调用,现在我们来看一下在静态方法中的的调用是什么样的。

首先,我们谈到的一个点就是, super 在静态方法中被调用时,指向的是其父类。我们来看一段代码:

class Parent {static myMethod(msg) {console.log('static', msg);}myMethod(msg) {console.log('instance', msg);}
}class Child extends Parent {static myMethod(msg) {super.myMethod(msg);}myMethods(msg) {super.myMethods(msg);}
}Child.myMethod(1); // static 1
let child = new Child();
child.myMethod(2); // instance 2

大家可以看到,当直接使用 Child.myMethod(1) 来进行调用时,表明直接调用静态方法。而当 super 在静态方法中被调用时,指向的就是其父类中的静态方法,所以打印 static 1

继续,下面的 child ,通过 new 的方式实例化了一个对象,之后对实例进行调用。所以,此时调用的是普通方法,所以最终打印出 instance 1


在使用 super 的时候,必须要注意的一个点是,必须显式地指定是作为函数还是作为对象使用,否则就会出现报错的情况。比如:

class A {}
class B extends A {constructor() {super();console.log(super); // 报错}
}

大家可以看到,如果像上面这样引用的话,根本无法看出是作为函数还是作为对象来引用,此时 js 引擎解析代码的时候,就会报错。

那么我们就得通过清晰的表明 super 的数据类型,来判断我们的结果。比如:

class A {}
class B extends A {constructor() {super();// object.valueOf() 表示返回对象的原始值console.log(super.valueOf() instanceof B); // true}
}

上述代码中,我们通过 object.valueOf() 来指明 super 就是一个对象,之后 js 引擎识别到了,最后也就成功打印出来了。

4. 类的prototype属性和 __ proto __ 属性

(1)class的继承链

在大多数浏览器的 ES5 实现之中,每一个对象都有 __proto__ 属性,指向对应的构造函数的 prototype 属性。

class 作为构造函数的语法糖,同时有 prototype 属性和 __proto__ 属性,因此同时存在两条继承链。分别是:

  • 子类的 __proto__ 属性表示构造函数的继承,总是指向父类。
  • 子类 prototype 属性的 __proto__ 属性表示方法的继承,总是指向父类的 prototype 属性。

我们用一段代码来演示一下:

class A {}class B extends A {}B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

在上面的代码中,子类 B__proto__ 属性总是指向父类 A ,且子类 Bprototype 属性的 __proto__ 属性指向父类 Aprototype 属性。

这两条原型链可以理解为:

  • 作为一个对象时,子类 B 的原型( __proto__ 属性 )是父类 A
  • 作为一个构造函数时,子类 B 的原型( prototype 属性 )是父类的实例。

(2)特殊情况继承

下面讨论三种特殊的继承情况。具体如下:

第一种情况: 子类继承 Object 类。先来看一段代码:

class A extends Object {}A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true

在这种情况下, A 其实就是构造函数 Object 的复制, A 的实例就是 Object 的实例。

第二种情况: 不存在任何继承。先来看一段代码:

class A {}A.__proto__ === Function.prototype // true
B.prototype.__proto__ === Object.prototype // true

在以上这种情况下, A 作为一个基类,它不存在任何继承关系。且 class 是构造函数的语法糖,所以这个时候可以说 A 就是一个普通函数。所以, A 直接继承 Function.prototype

值得注意的是, A 调用后返回的是一个空对象(即 Object 实例),所以 A.prototype.__proto__ 指向构造函数 Objectprototype 属性。

第三种情况: 子类继承 null先来看一段代码:

class A extends null {}A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true

以上这种情况与第二种情况非常相似。 A 也是一个普通函数,所以直接继承 Function.prototype

值得注意的是, A 调用后返回的对象不继承任何方法,所以它的 __proto__ 指向 Function.prototype ,即实际上是执行了下面这段代码:

class C extends null {constructor() {return Object.create(null);}
}

(3)实例的 __ proto __ 属性

对于子类来说,其实例的 __proto__ 属性的 __proto__ 属性总是指向父类实例的 __proto__ 属性。也就是说,子类的原型的原型是父类的原型。

我们来看一段代码,具体如下:

let p1 = new Point(2, 3);
let p2 = new ColorPoint(2, 3, 'green');p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true

在上面的代码中, 子类 ColorPoint 继承了 Point ,所以导致了前者原型的原型是后者的原型。

因此,我们可以通过子类实例的 __proto__.__proto__ 属性来修改父类实例的行为具体如下:

p2.__proto__.__proto__.printName = function() {console.log('Monday');
};p1.printName(); // 'Monday'

大家可以看到,通过在 ColorPoint 的实例 p2 上向 Point 类中添加方法,从而影响到了 Point 实例 p1

📑四、结束语

上文我们讲到了6大继承方式以及 class 的继承,在现如今的开发场景中,基本上也是寄生式组合继承和 class 的继承用的比较多。相信通过上文的了解,大家对 javascript 的继承又有了一个新的认识。

到这里,关于 js 的继承讲解就结束啦!希望对大家有帮助!

如文章有误或有不理解的地方,欢迎小伙伴们评论区留言~💬

🐣彩蛋 One More Thing

(:参考资料

书籍👉ES6书籍《ES6标准入门》

书籍👉红宝书《JavaScript高级程序设计》第四版

同梦奇缘👉《javascript高级程序设计》笔记:继承

(:番外篇

  • 关注公众号星期一研究室,第一时间关注优质文章,更多精选专栏待你解锁~
  • 如果这篇文章对你有用,记得留个脚印jio再走哦~
  • 以上就是本文的全部内容!我们下期见!👋👋👋

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

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

相关文章

微软发布 Microsoft Edge 85 稳定版

喜欢就关注我们吧!微软推出了 Microsoft Edge 85 稳定版(85.0.564.41),现在正逐步向用户推送。此版本带来了以下新特性:收藏夹和设置的本地同步。现在可以在自己的环境中的 Active Directory 配置文件之间同步浏览器收…

leetcode94. 二叉树的中序遍历(左中右)

二:上码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…

浅谈前端路由原理hash和history

浅谈前端路由原理hash和history🎹序言🎸一、前端路由原理1、SPA2、什么时候需要路由🎷二、Hash模式1、定义2、网页url组成部分(1)了解几个url的属性(2)演示3、hash的特点🎺三、Histo…

leetcode145. 二叉树的后序遍历

一:题目 二:上码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*…

.NET Core API文档管理组件 Swagger

Swagger这个优秀的开源项目相信大家都用过,不多介绍了,这里简单记录一下使用过程。开源地址:https://github.com/domaindrivendev/Swashbuckle.AspNetCore在项目中添加组件Install-Package Swashbuckle.AspNetCore下面用最少的代码完成接入&a…

「3.4w字」超保姆级教程带你实现Promise的核心功能

保姆级详解promise的核心功能📚序言📋文章内容抢先看📰一、js的同步模式和异步模式1. 单线程💡2. 同步模式💡(1)定义(2)图例3. 异步模式💡(1&…

leetcode199. 二叉树的右视图(层序遍历03)

一:题目 二:上码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(n…

如何做好一个开源项目之徽章(二)

在上一篇【如何做好一个开源项目(一)】,笔者已经介绍过开源项目运作和维护的一些理念了,本篇开始,笔者将着重于介绍一些开源项目维护过程中的一些细节,比如徽章、构建等等。由于最近经常出差,所…

值得关注的HTML基础

值得关注的HTML基础🥳序言😋一、网页三大元素😜二、HTML简介1. 定义2. 发展历史😝三、HTML结构1. 引例阐述2. 特点3. HTML页面结构(1)DOCTYPE(2)html(3)head&…

leetcode637. 二叉树的层平均值(层序遍历04)

一:题目 二:上码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(n…

leetcode429. N 叉树的层序遍历(层序遍历05)

一:题目 二&#xff1a;上码 /* // Definition for a Node. class Node { public:int val;vector<Node*> children;Node() {}Node(int _val) {val _val;}Node(int _val, vector<Node*> _children) {val _val;children _children;} }; */class Solution { publi…

10分钟带你探索css中更为奇妙的奥秘

10分钟带你探索css中更为奇妙的奥秘&#x1f4d6;序言&#x1f4c3;一、css是啥1. CSS是什么2. 诞生背景3. 基础规则&#xff08;1&#xff09;一些基础规则&#xff08;2&#xff09;其他重要的语法&#xff08;3&#xff09;选择器&#xff08;4&#xff09;层叠与继承1&…

将微服务部署到 Azure Kubernetes 服务 (AKS) 实践

介绍本文的目的是&#xff1a;通过使用 DockerHub 和 Azure Kubernetes Service (AKS) 将之前 使用 .NET 和 Docker 构建的微服务 部署到微软 Azure 云上&#xff0c;来介绍微服务的基本部署过程。推送到 Docker HubDocker Hub 是世界上最大的容器镜像库和社区。许多产品&#…

leetcode 515. 在每个树行中找最大值(层序遍历06)

一:题目 二&#xff1a;上码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(n…

前端只是切图仔?来学学给开发人看的UI设计

给开发人看的UI设计&#x1f5bc;️序言&#x1f3a8; 一、背景1. 想做一个好的作品2. 没有专业UI&#x1f9f5;二、功能导向1. 设计中最重要的事2. 例子阐述2. 简约设计3. 设计简单的、完整的功能&#x1f9f6;三、设计原则1. 层级&#xff08;1&#xff09;层级是什么&#x…

Debian 新负责人发表演讲:Debian 的现状与面临的一些问题

喜欢就关注我们吧&#xff01;Debian GNU/Linux 年度主要的会议 DebConf20 已于近期举办&#xff0c;4 月份新当选的 Debian 项目负责人 Jonathan Carter 在会上发表了演讲&#xff0c;概述了 Debian 的现状与面临的一些问题。Debian 的财务稳定在 $896065 美元左右&#xff0c…

leetcode116. 填充每个节点的下一个右侧节点指针(层序遍历07)

一:题目 二:上码 /* // Definition for a Node. class Node { public:int val;Node* left;Node* right;Node* next;Node() : val(0), left(NULL), right(NULL), next(NULL) {}Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}Node(int _val, Node* _left,…

程序员修神之路--略懂数据库集群读写分离而已

“灵魂拷问&#xff1a;解决数据库读写瓶颈有哪些解决方案呢&#xff1f;这些方案解决了什么问题呢&#xff1f;这些方案有那些优势和劣势呢&#xff1f;一个可以抵抗高并发流量系统的背后必定有一个高性能的数据库集群&#xff0c;就像每一个成功的男人背后总有一个强势的女人…

一张网页带你了解中秋节的前世今生

一张网页带你了解中秋节的前世今生&#x1f317;序言一、&#x1f319;题材选取1. 诗词赏析2. 原型图抢先看3. 界面设计二、&#x1f31b;编码阶段1. 项目目录结构2. html设计&#xff08;1&#xff09;nav结构设计&#xff08;2&#xff09;banner结构设计&#xff08;3&#…

leetcode117. 填充每个节点的下一个右侧节点指针 II(层序遍历08)

一&#xff1a;题目 二&#xff1a;上码 /* // Definition for a Node. class Node { public:int val;Node* left;Node* right;Node* next;Node() : val(0), left(NULL), right(NULL), next(NULL) {}Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}Node(…