js分享14-继承与深浅拷贝(小白必看)

63 阅读5分钟

01_认识继承

一个构造函数的实例使用另一个构造函数的属性和方法

function Person(name) {
    this.name = name
}
Person.prototype.sayHi = function () {
    console.log('hello world')
}

function Student() {}
Student.prototype.study = function () {
    console.log('好好学习')
}

/**
 *  如果 s1 内带有 name 属性
 *  并且 s1 还可使用 sayHi 这个方法
 *      那么我们就说 Studen 这个类继承自 Person
 *      Student 是 Person 的子类
 *      Person 是 Student 的父类
*/
const s1 = new Student()

02_原型继承

  • 利用自定义原型的方式来实现继承关系
  • 核心: 子类的原型指向父类的实例
  • 优点: 可以继承父类的 属性(构造函数体内) 和 方法(构造函数原型)
  • 缺点:
    • 没有自己的原型
    • 继承下来的属性不在自己身上, 在原型上
// 父类
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function () {
    console.log('hello world')
}

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

// 实现继承
Student.prototype = new Person('Jack', 18)

/**
 *  Student 的 prototype 指向了当前构造函数的原型对象
 *  原型链继承就是更改他的原型对象为另一个 构造函数的实例对象
 * 
 *  那么后续在查找的时候, 会先在 Student 的实例身上查找
 *  找到就是用, 没有的话会去 __proto__ 中查找
 *  也就是自己构造函数的原型, 但是现在原型已经被修改为了 Person 构造函数的实例化对象
 * 
 *      所以相当于可以在 Student 的实例上找到或使用 Person 的实例上的方法
*/

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

03_借用构造函数继承

  • 借用构造函数继承 / 借用继承 / call 继承
  • 核心:
    • 把父类构造函数当作普通函数来调用, 利用 call 修改 this 指向
  • 优点:
    • 把属性都继承在自己身上
  • 缺点:
    • 只能继承父类的属性(构造函数体内)不能继承方法(原型上的内容)
    • 可以有自己的原型
function Person(name, age) {
    this.name = name
    this.age = age
}

// Person 原型上的方法是给 Person 的实例对象使用
Person.prototype.sayHi = function () { console.log('hello world') }

function Student(gender, ...arg) {
    this.gender = gender

    // 实现继承
    Person.call(this, ...arg)
    /**
     *  Person 是一个构造函数
     *  本质上还是一个函数, 所以是可以不加 new 去调用的
     *  不加 new 内部无法自动创建对象, 所以调用时 内部 this 指向 window
     *  相当于是给 window 添加了 name 和 age 属性
     *  我们可以通过 修改 this 指向的方式, 去将构造函数体内的代码继承到函数体内部
    */
}

const s = new Student('男', 'Jack', 18)
console.log(s)
console.log(s.gender)
console.log(s.name)
console.log(s.age)
// s.sayHi()

const s2 = new Student('女', 'Rose', 20)
console.log(s2)

04_组合继承

  • 把原型继承 和 借用构造函数继承 放在一起使用
  • 优点:
    • 能继承 属性和方法
    • 继承的属性在自己身上
  • 缺点:
    • 原型上多了一套属性
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

function Student(gender, ...arg) {
    this.gender = gender

    // 组合借用继承 - 为了继承构造函数体内的属性
    Person.call(this, ...arg)
}

// 组合原型继承 - 为了继承原型上的方法
Student.prototype = new Person()

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

05_拷贝继承

  • 拷贝继承
    • 利用 for in 循环遍历对象
    • 把所有的内容复制一份放到子类的原型上

for in 循环的时候, 不光可以便利到对象自己身上的属性, 也可以遍历原型上的属性

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

function Student(gender, ...arg) {
    this.gender = gender
    const p = new Person(...arg)
    for (let k in p) {
        Student.prototype[k] = p[k]
    }
}

06_ES6的继承

  • 类继承的语法
    • 语法:
      • 书写子类的时候
        • class 子类 extends 父类 {}
      • 书写子类的 contructor 的时候
        • super(参数)
    • 注意:
      • extends 和 super 必须都出现才能正常继承
      • 在 constructor 内的时候, 必须先写 super 后写自己的
      • ES6 的类可以继承 ES5 的构造函数
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 创建一个类 - 创建一个继承自 Person 的类
// 语法: class 类名 extends 父类 {}
class Student extends Person {
    constructor(gender, ...arg) {
        // 语法: spuer()
        super(...arg)

        // 自己的属性
        this.gender = gender
    }

    // 自己的方法
    study() { console.log('好好学习') }
}

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

07_深浅拷贝

  • 把一个数据结构内的所有内容复制一份放在另一个一摸一样的数据结构内
  • 注意:
    • 表示拷贝的是数据结构, 不是方法(一般不考虑函数, 通常我们都是考虑的数组和对象)
  • 赋值
    const o1 = { name: 'jack' }
    const o2 = o1
    console.log(o1, o2)
    o2.name = 'Rose'
    console.log(o1, o2)
    
  • 浅拷贝
    const o1 = { name: 'Jack', age: 18, info: { weight: 180, height: 180 } }
    const o2 = {}
    for (let k in o1) {
        o2[k] = o1[k]
    }
    console.log(o1, o2)
    o2.name = 'Rose'
    console.log(o1, o2)
    // 修改第二层数据
    o2.info.weight = 200
    console.log(o1, o2)
    
    • Object 上有一个方法, 就是进行浅拷贝, 复制
      • 语法: Object.assign(新对象, 原始对象)
      • 返回值: 把原始对象内的成员浅拷贝的新对象内
    const o1 = { name: 'Jack', age: 18, info: { weight: 180, height: 180 } }
    const o2 = Object.assign({}, o1)
    console.log(o1, o2)
    o2.name = 'Rose'
    console.log(o1, o2)
    // 修改第二层数据
    o2.info.weight = 200
    console.log(o1, o2)
    
  • 深拷贝
    • 不管多少层数据结构, 都百分之百复制一份过来; 变成两个完全一模一样但是毫不相干的数据结构
    • JSON.parse(JSON.stringify(数据)) , 也可以完成深拷贝
const o1 = {
    name: 'Jack',
    age: 18,
    info: {
        weight: 180,
        height: 180
    },
    address: {
        city: '北京',
        desc: {
            scripts: 'xxx路xxx接到'
        }
    },
    hobby: ['篮球', '足球', '羽毛球']
}
function deepCopy(target, origin) {
    /**
     *  target 叫做目标对象
     *  origin 叫做原始对象
     * 
     *      目标: 把 origin 内的内容深拷贝一份到 target 内
    */

    // 循环 遍历 origin
    for (let k in origin) {
        // 如果 origin[k] 是一个对象或者数组, 不能直接转存
        if (origin[k].constructor === Object) {
            // origin[k] 是一个对象, target[k] 应该是一个什么数据类型
            target[k] = {}
            // 把origin[k] 里面的数据深拷贝一份到 target[k]
            deepCopy(target[k], origin[k])
        } else if (origin[k] === Array) {
            target[k] = []
            deepCopy(target[k], target[k])
        } else {
            // 既不是对象也不是数组, 直接转存
            target[k] = origin[k]
        }
    }
}