JS继承

Posted 海客无心x

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS继承相关的知识,希望对你有一定的参考价值。

1. 原型链继承

它是下面这个形式的。

function F() {
  this.f_age = 8;
  this.f_name = 'father';
}
F.prototype.getFAge = function() {
  return this.f_age;
}
function S() {
  this.s_age = 3;
}
S.prototype = new F();
S.prototype.getSAge = function() {
  return this.s_age;
}
let s = new S();
console.log(s.getFAge()); // 8
console.log(s.getSAge()); // 3
console.log(s.constructor);  // [Function: F]
console.log(s instanceof S); // true
console.log(s instanceof F); // true

它的主要过程:

  • 实例化父类 F -> f
  • 将子类的原型对象 S.prototype 指向 f
  • 实例化子类 S -> s

加深理解:
s.__proto__ -> S.prototype -> f.__proto__ -> F.prototype

需要注意的是:

  • 本来 S 的 constructor 是 S,但是因为 S.prototype 重写了,所以现在 constructor 是 F.
  • 查找机制: s -> S.prototype -> F实例 -> F.prototype

    function F() {
      this.name = 'F';
    }
    F.prototype.name = 'F-prototype';
    function S() {}
    S.prototype = new F();
    let s = new S();
    console.log(s.name); // F

原型继承中 子类 重写 父类的方法

function F() {
  this.x = true;
}
F.prototype.getF = function() {
  return this.x;
}
function S() {
  this.y = false;
}
S.prototype = new F();
S.prototype.getS = function() {
  return this.y;
}
// 重写父类型中的 getF
S.prototype.getF = function() {
  return '33';
}
let s = new S();
console.log(s.getS()); // false 
console.log(s.getF()); // 33,   原来的 父类中 返回 true
delete(S.prototype.getF);
console.log(s.getF()); // true
如果你想要改变(覆盖)父类的方法,必须在子类的 原型对象 被 父类的实例 赋值之后 覆盖。后来居上,你懂的。
后来居上的 字面量方法 也一样,如下
function F() {}
function S() {}
S.prototype = new F();
// 下面的这句话重写了上面这句话。
S.prototype = {
  getX: function() {
    return '33';
  }
}

原型链继承的特点和示例代码:

  • a. 原来的父类实例属性 变为了 子类的原型属性。共享性。

    function F() {
      this.colors = ['red', 'blue'];
    }
    function S() {}
    S.prototype = new F();
    let s1 = new S();
    let s2 = new S();
    s1.colors.push('yellow');
    console.log(s1.colors); // ['red', 'blue', 'yellow']
    console.log(s2.colors); // ['red', 'blue', 'yellow'] 
  • b. 创建子类型的实例的时候,不能像超类型的构造函数传递参数。


2. 借用构造函数 继承

真是骚操作啊。

原理:

  • A.谁调用了函数,函数中的 this 就指向谁。
  • B.利用 apply 和 call 在 子类 内部调用 函数。
function F() {
  this.colors = ['red', 'blue'];
}
function S() {
  F.call(this);
}
let s1 = new S();
let s2 = new S();
s1.colors.push('yellow');
console.log(s1.colors);  // ['red', 'blue', 'yellow']
console.log(s2.colors);  // ['red', 'blue']

特点:

  • 属性不会共享。(上面说了)
  • 可以传参

    function F(name) {
      this.name = name;
      this.colors = ['red', 'blue'];
    }
    function S(name) {
      F.call(this, name);
    }
    let s1 = new S('ccc');
  • 缺点在于函数复用。

    其实和 JS构造模式 是一个道理。在原型链 中的 共享的函数还是很有必要的。一些属性也是应该共享的。


3. 组合继承: 跟JS模式中的 组合构造模式 很像。

  • 可以传参。
  • 可以 选择 是否共享属性和方法。(非战争的年代,人们有权选择过自己的生活)
function F(name) {
  this.name = name;
  this.colors = ['red'];
}
F.prototype.getF = function() {
  console.log(this.name);
}
function S(name, age) {
  F.call(this, name);
  this.age = age;
}
S.prototype = new F();
S.prototype.constructor = S;
S.prototype.getS = function() {
  console.log(this.age);
}
// 强行让 constructor 为子类,缺点是 constructor 变为可枚举。
let s1 = new S('s1', 18);
let s2 = new S('s2', 20);
s1.colors.push('yellow');
console.log(s1);    // S {name: 's1', colors: ['red', 'yellow'], age: 18}
console.log(s2);    // S {name: 's2', colors: ['red'], age: 20}
delete(s1.colors);      // 删掉了 子类实例中,借用父类构造函数继承的 colors
console.log(s1.colors); // 子类实例中:父类原型中的 colors 还是存在的。
// ---------------------------------------------------------------
console.log(s1.constructor); // [Function: S]
console.log(Object.keys(S.prototype)); // 其中包含 constructor 属性

4. 原型式继承: 可以说是 ES3中对 ES5中 Object.create 的实现了。

缺点很明显:共享属性。

function Create(o) {
  function F(){};
  F.prototype = o;
  return new F();
}
let person = {
  name: '123',
  colors: ['red']
}
let f1 = Create(person);
let f2 = Create(person);
f1.colors.push('yellow');
console.log(f2.colors);   // ['red', 'yellow']
ES5中的 Object.create(用作新对象原型的对象,可选的定义额外属性的对象)
  • 若是没有第二个参数,和我们上方自己写的 Create 方法相同。

    let person = {
      name: '123',
      colors: ['red']
    }
    let f1 = Object.create(person);
    let f2 = Object.create(person);
    f1.colors.push('yellow');
    console.log(f2.colors);   // ['red', 'yellow']
  • 若是写了第二个参数,则会覆盖掉原型中的 属性。

    let person = {
      name: '123',
      colors: ['red']
    }
    let f1 = Object.create(person, {
      colors: {
    value: ['red']
      }
    });
    let f2 = Object.create(person, {
      colors: {
    value: ['red']
      }
    });
    f1.colors.push('yellow');
    console.log(f2.colors);   // ['red']

在没有必要兴师动众地 创建构造函数,只是想让 一个对象 与 另外一个对象保持 相似的情况下,使用 原型式继承 就可以了。


5. 寄生组合式继承。

产生的原因是组合继承 的缺点:

  • 调用两次父类。
    • 1.创建子类原型的时候
    • 2.子类型构造函数内部的调用
  • 最后造成的结果。子类会包含 父类的全部 实例属性。并且在调用子类构造函数的时候会重写(覆盖)一些属性。
function Create(o) {
  function F() {};
  F.prototype = o;
  return new F();
}
// 1. 这一步为了 子类继承 父类的原型
function Inherit(subType, superType) {
  let prototype = Create(superType.prototype);
  // 只承包了 父类的原型。
  prototype.constructor = subType;
  // 增强对象,重写 被重写的 constructor
  subType.prototype = prototype;
}

function F(name) {
  this.name = name;
  this.colors = ['blue'];
}
F.prototype.say = function() {
  console.log(this.name);
}
// 2. 这一步为了 子类继承 父类的一些构造函数内部的东西
function S(name, age) {
  F.call(this, name);
  this.age = age;
}
Inherit(S, F);

疑问:其实本质上没有啥差别。主要的骚操作就要在于:子类继承的类别变化了。最后其实我有一个疑问。。

组合继承 是这样的:调用了一次构造函数。

function S(name) {
  F.call(this, name);
}                      // 调用了一次
S.prototype = new F(); // 调用了一次

于是我想,下面这个难道不对???

function F(name) {
  this.name = name;
}
F.prototype.getName = function() {
  console.log(this.name);
}
function S(name) {
  F.call(this, name);
}
S.prototype = F.prototype;
S.prototype.constructor = S;
let s1 = new S('csn');
s1.getName();                  // csn
console.log(s1.constructor);   // [Function: S]

F如果没有实例化,貌似我这样写没有什么错??还更简单?

以上是关于JS继承的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段9——JS中的面向对象编程

js代码片段: utils/lcoalStorage/cookie

JS代码片段:一个日期离现在多久了

js常用代码片段(更新中)

js常用代码片段

Chrome-Devtools代码片段中的多个JS库