高级JS

76 阅读9分钟

2022.11.04

一.原型继承

继承
+ 出现在两个构造函数之间的关系
+ 当 构造函数A 的实例 可以使用 构造函数B 的属性和方法
+ 就说 构造函数A 继承自 构造函数B
+ 构造函数A 是 构造函数B 的子类
+ 构造函数B 是 构造函数A 的父类
+ 市场上常见的继承方案
=> 原型 / 借用 / 组合 / 寄生 / 冒充 / 拷贝 / ...

原型继承
+ 原理: 让子类的原型指向父类的实例
+ 代码: 子类.prototype = new 父类
+ 依据: 依赖原型链的对象访问机制实现的
+ 优点:
=> 可以继承父类构造函数体内的属性, 也可以继承父类构造函数原型上的方法
+ 缺点:
=> 继承来的属性不在自己身上
=> 子类的所有实例, 继承来的属性值是一样的

/*
      原型链: 提供对象访问机制
        + 当你访问一个对象成员的时候
        + 首先在自己身上查找, 如果有直接使用
        + 如果没有, 去到自己的 __proto__ 查找, 如果有直接使用
        + 如果还没有, 就再去 __proto__ 查找, 如果有直接使用
        + 以此类推, 直到 Object.prototype 都没有, 返回 undefined

      例子: 假设有一个对象是 o
        + o.name
        + 如果 o 对象自己本身就有, 直接使用
        + 如果 o 对象没有, 就查找 o.__proto__ 是否存在, 有就直接使用
        + 如果 o.__proto__ 也没有, 那么就自动再去 o.__proto__.__proto__ 查找, 如果有就直接使用
        + 以此类推, 如果走到了 Object.prototype 上都没有, 那么返回 undefined
    */
  // 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
    
        function Student(gender) {
      this.gender = gender
    }
    // 得到一个 Person 的实例
    const p = new Person('张三', 18)
    // 修改原型对象
    Student.prototype = p
    const s = new Student('男')
    // 当你方法 s 的 gender 属性的时候
    // 自己有直接使用
    console.log(s.gender)
    // 当你访问 s 的 name 属性的时候
    // 自己没有, 去到自己的 __proto__ 查找(s.__proto__)
    // 因为 s.__proto__ === Student.prototype
    // 又因为 Student.prototype === p
    // 所以 s.__proto__ === p
    // p 是 Person 的实例对象, 身上有 name 和 age 属性
    // s.__proto__ 身上就有 name 和 age 属性
    console.log(s.name)
    // 当你访问 s 的 sayHi 方法的时候
    // 自己没有, 去到自己的 __proto__ 查找(s.__proto__ === p), 发现也没有
    // 再次去 __proto__ 查找(s.__proto__.__proto__)
    // 因为 s.__proto__ === p
    // 所以 s.__proto__.__proto__ === p.__proto__
    // 因为 p 是 Person 构造函数的实例
    // 所以 p.__proto__ === Person.prototype
    // 所以 s.__proto__.__proto__ === Person.prototype
    s.sayHi()
    const s2 = new Student('女')
    console.log(s2.gender)
    console.log(s2.name)

二.借用继承

借用继承(借用构造函数继承 / call继承)
+ 原理: 把父类构造函数当做普通函数使用
+ 代码: 父类.call(this)
+ 实现: 利用 call 方法改变函数内的 this 指向

  • 优点:
    => 继承来的属性直接出现在自己身上, 并且每一个子类的实例继承来的属性都可以保证值不一样

  • 缺点:
    => 只能继承构造函数体内的属性
    => 不能继承构造函数原型上的方法

    // 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
    // const pp = new Person()
    // console.log(pp);
    // 子类
    function Student(gender,...ab) {
      this.gender = gender
    
      // 这里的 this 是谁 ? Student 的实例对象(s)
      // 在这里把 父类构造函数 当做普通函数调用一下, 并且利用 call 来改变 this 指向
      // Person 函数内的 this 指向了 Student 的实例对象
      // Person 函数执行的时候, 函数体内的代码就在想 Student 的实例上添加了若干成员
      Person.call(this,...ab)
    }
    
    const s = new Student('男', '李四', 22)
    console.log(s)
    const s2 = new Student('女', '王五', 18)
    console.log(s2)
    
    // Person 是一个构造函数
    // 调用的时候和 new 关键字连用, 函数内的 this 指向 本次被自动创建的对象(当前实例对象)
    // 函数体内的代码执行的时候, 就是想 实例对象 上添加成员
    
    // 但是 Person 的本质是一个函数, 就可以不和 new 连用
    // 不会报错, 也能执行代码, 只是没有了创建实例的能力
    // 因为直接调用的时候 this 指向 window, 所以 name 和 age 成员添加到 window 身上了
    // 只要 this 指向哪里, 这些成员就添加到哪里
    // Person('张三', 18)
    
    // call 方法只要是函数就能使用
    // 作用: 改变函数内的 this 指向
    // 语法: 函数名.call()
    // 第一个参数: 该函数内的 this 指向
    // 第二个参数开始, 依次给函数进行形参赋值
    // const obj = { a: 100, b: 200 }
    // 利用 call 调用 Person 函数
    // Person 函数内的 this 指向 obj, 那么函数内的代码执行的时候
    // 向 obj 上添加成员
    // Person.call(obj, '李四', 20)
    // console.log(obj)
    

三.组合继承

/*
  组合继承
    + 把 原型继承 和 借用继承 组合在一起使用
*/

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }

// 子类
function Student(gender, name, age) {
  this.gender = gender

  // 借用继承(目的: 把属性继承在子类身上)
  Person.call(this, name, age)
}

// 原型继承(目的: 继承父类原型上的方法)
Student.prototype = new Person()

Student.prototype.play = function () { console.log('Student 原型上的 play 方法') }

const s = new Student('男', '张三', 18)
console.log(s)

四.拷贝继承

拷贝继承
+ 一种继承方案
+ 利用 for in 循环来实现继承
+ for in 循环作用: 遍历对象

/*
  in 关键字
    + 语法: 字符串 in 对象
    + 结果: 一个布尔值
      => true: 表示该对象能访问到这个 键名
      => false: 表示该对象访问不到这个 键名
    + 判断的就是该对象能不能访问到这个 key
      => 不管是在自己身上, 还是在原型上, 都为 true
*/

/*
  hasOwnProperty() 方法
    + 对象可以使用的方法
    + 语法: 对象.hasOwnProperty('键名')
    + 返回值: 一个布尔值
      => true: 说明该对象自身有这个 key
      => false: 说明该对象自身没有这个 key
    + 注意: 原型上的不算
*/

拷贝继承 - 方式1
+ 创建一个父类的实例
+ 遍历父类的实例, 把每一项依次添加到子类的原型上
父类

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }

// 创建一个父类的实例
const p = new Person('张三', 18)

//此时 p 是一个对象
//自己身上: name age
//原型上: sayHi
//如果你使用 for in 循环遍历的时候, 能访问到 name age sayHi 三个成员
for (let k in p) {
  console.log(k)
}

//子类
function Student(gender) {
  this.gender = gender
}

// 实现继承
// 把 p 的所有内容拷贝一份到 子类的原型
for (let k in p) {
  Student.prototype[k] = p[k]
}

Student.prototype.play = function () { console.log('Student 原型上的 play 方法') }
console.log(Student.prototype)

// 创建子类的实例
const s = new Student('男')
console.log(s)
console.log(s.name)
console.log(s.age)
s.sayHi()
s.play()

拷贝继承 - 方式2
+ 把 拷贝继承 的一部分 和 借用继承 组合在一起使用
+ 拷贝继承的一部分
=> 之前的拷贝继承, 拷贝的是 父类的实例
-> 会复制的内容是 实例的成员 和 原型的成员
=> 现在只拷贝 父类的原型
=> 一部分: 拷贝父类的原型复制到子类的原型

 // 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
Person.prototype.sayHi2 = function () { console.log('Person 原型上的 sayHi2 方法') }
Person.prototype.sayHi3 = function () { console.log('Person 原型上的 sayHi3 方法') }

// 子类
function Student(gender, name, age) {
  this.gender = gender

  // 利用借用继承, 继承父类的属性
  Person.call(this, name, age)
}

// 实现继承
// 利用拷贝继承继承父类的原型
for (let k in Person.prototype) {
  Student.prototype[k] = Person.prototype[k]
}

Student.prototype.play = function () { console.log('Student 原型上的 play 方法') }
console.log(Student.prototype)

const s = new Student('男', '李四', 20)
console.log(s)

五.冒充继承

冒充继承
+ 利用 Object.assign() 进行数据浅拷贝
+ 原理:
=> 创建一个父类的实例
=> 直接把父类的实例的内容浅拷贝一份到子类的实例上

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
Person.prototype.sayHi2 = function () { console.log('Person 原型上的 sayHi2 方法') }
Person.prototype.sayHi3 = function () { console.log('Person 原型上的 sayHi3 方法') }
// const p = new Person(name, age)

// 子类
function Student(gender, name, age) {
  this.gender = gender

  const p = new Person(name, age)

  // 实现浅拷贝
  // 这里的 this 是 Student 的实例对象
  // p 是父类的实例对象
  // 把父类实例对象的所有内容拷贝到子类的实例身上
  Object.assign(this, p)
}

// 问题: 子类其实没有自己的独立原型
Student.prototype = Person.prototype

Student.prototype.play = function () { console.log('Student 原型上的 play 方法') }

const s = new Student('男', '张三', 18)
console.log(s)
s.name = 'lala'
console.log(s);

六.寄生继承

寄生继承
+ 从 组合继承 一点一点演化过来
+ 需要利用到第三个没用的构造函数

    // 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
Person.prototype.sayHi2 = function () { console.log('Person 原型上的 sayHi2 方法') }
Person.prototype.sayHi3 = function () { console.log('Person 原型上的 sayHi3 方法') }


// 准备第三个构造函数, 当做桥梁使用
function Third() {}
// 需求: 把 Person.prototype 上的所有内容放在 t 对象身上
// 方案1: 浅拷贝
// 方案2: for in 循环
// 把 Person 的 prototype 上的所有内容放在 Third 的 prototype 上
//   因为放在 Third.prototype 上, t 也能用
// 又不想使用 for in 循环
// 私人: 让 Third 的原型 寄生 Person 的 原型
Third.prototype = Person.prototype
// Third 就是作为一个桥梁出现
// 我不需要自己的原型, 我自己也不需要任何属性
const t = new Third()
console.log(t)

// 子类
function Student(gender, name, age) {
  this.gender = gender

  // 利用借用继承继承下父类的属性
  Person.call(this, name, age)
}

// 实现继承
// 让子类的原型继承自 桥的 实例
Student.prototype = t

// Student 也相当于没有自己的原型了, Student 的原型其实就是 桥的实例对象
// 实际上就相当于添加在桥上了
Student.prototype.play = function () { console.log('Student 原型上的 play 方法') }

const s = new Student('男', '张三', 18)
console.log(s)

七.寄生继承升级

  • 利用闭包的形式把所有的内容包裹起来

       // 父类
     function Person(name, age) {
       this.name = name
       this.age = age
     }
     Person.prototype.sayHi = function () { console.log('Person 原型上的 sayHi 方法') }
     Person.prototype.sayHi2 = function () { console.log('Person 原型上的 sayHi2 方法') }
     Person.prototype.sayHi3 = function () { console.log('Person 原型上的 sayHi3 方法') }
    
     // 书写一个子类
     const Student = (function () {
       // 在这个自执行函数内, 实现继承逻辑
       function Student(gender, name, age) {
         this.gender = gender
    
         Person.call(this, name, age)
       }
    
       // 桥梁 构造函数
       function Third() {}
       Third.prototype = Person.prototype
       const t = new Third()
       // Student 继承 桥梁
       Student.prototype = t
    
       // 子类自己的原型上的方法
       Student.prototype.play = function () { console.log('play 方法') }
       return Student
     })()
    
     // 全局还有 Student 变量吗 ? 没有了
     // 所以把全局的变量直接定义为 子类的 构造函数名即可
     const s = new Student('男', '张三', 18)
     console.log(s)
     
    

八.类的继承

类的继承
+ 专门对应 class 语法实现的继承
+ 实现:
=> 步骤1: 在创建子类的时候, 直接使用 extends 关键字继承自父类
-> 语法: class 子类名 extends 父类名 {}
=> 步骤2: 在子类的 constructor 内使用 super 来继承父类的属性
-> 语法: super(参数)
-> 注意: 在子类的 constructor 内, super 必须写在最前面

在继承的过程中
+ 父类有一些内容不希望被继承
+ 我们管不希望被继承的属性和方法叫做 类的静态成员
+ 添加一个 static 关键字
=> 一旦加了 static 关键字
=> 就是给 类 本身使用的
=> 不在给 类 的实例使用
=> 所以就继承不了了

// 父类写法一
class Person {
  constructor (name, age) {
    this.name = name
    this.age = age
  }

  // 写出来可能会被继承个子类使用
  sayHi1 () { console.log('sayHi1') }
  sayHi2 () { console.log('sayHi2') }

  // 不想被继承给子类使用
  // 不能被 Person 的实例使用了
  // 是给 Person 这个类自己使用的
  static sayHi3 () { console.log('sayHi3') }
}

// 父类写法二
// function Person(name, age) {
//   this.name = name
//   this.age = age
// }

// Person.prototype.sayHi1 = function () { console.log('Person 原型上的 sayHi 方法') }
// Person.prototype.sayHi2 = function () { console.log('Person 原型上的 sayHi2 方法') }
// Person.prototype.sayHi3 = function () { console.log('Person 原型上的 sayHi3 方法') }


// 子类
// 一个继承自 Person 的 Student 子类
// extends 关键字就等价于 继承父类原型上的方法
class Student extends Person {
  constructor (gender, name, age) {
    // 相当于在继承父类的属性
    // 等价于原先的 借用继承(目的: 继承构造函数体内的属性)
    super(name, age)
    this.gender = gender
  }

  play () { console.log('play') }
}

const s = new Student('男', '张三', 18)
console.log(s)
s.sayHi3()

2022.11.07

一.单例模式核心