JS 继承、深浅拷贝

74 阅读4分钟

主要写一些关于继承和拷贝相关的知识点,准确来说,ES5的继承方法不算方法,是前辈们的经验之谈,ES6的方法是JS推出的,关于深浅拷贝的话,深拷贝在近段时间的面试中出现的频率应该还行。

ES5继承

1,原型继承:

利用自定义原型的方法去继承,当Stu这个类继承了Person这个类的时候,我们把Stu叫做Person的子类。

核心:子类的原型指向父类的实例化对象

优点:实现了继承; 缺点:继承了属性,但不是在自己身上,失去了自己的原型。

(代码解析:先去函数内部查找name属性,然后发现对象内部没有这个属性,再去对象内部的 _ proto __上查找,对象的 _ proto __指向了自己的构造函数的原型,也就是Stu.prototype;因为我们手动修改了Stu.prototype ,给它赋值为Person构造函数的实例化对象,也就是说我们Stu.prototype指向了Person的实例化对象,从而达到继承的效果)。

function Person(name) {
    this.name = name
}
Person.prototype.sayName = () => {
    console.log('name')
}
​
function Stu(age) {
    this.age = age
}
Stu.prototype.abc = () => {
    console.log(123)
}
Stu.prototype = new Person('QF001')
Stu.prototype.abc2 = () => {
    console.log(456)
}
const s = new Stu(18)
// 需求 使用 Person 内部的 name 何 sayName 方法
console.log(s)
console.log(s.age)
console.log(s.name) // QF001
2,借用构造函数的继承:

核心:借用call方法修改,父类构造函数内部的this指向。

优点:将属性继承在自己身上,保留了自己的原型。

缺点:只能继承父类的属性,不能继承父类原型上的方法。

(代码解析:通过new关键字调用Stu这个构造函数,new关键词会在这个函数内部创建一个空对象,并且会把这个函数内部的this指定刚才创建的空对象;给这个对象添加一个age属性,并给他赋值为参数 age;调用Person函数,并通过call方法改变这个函数的this指向,this指向刚才new关键词创建的对象;Person函数开始执行,给这个对象太内疚一个abc属性,并赋值为name,函数执行完毕,new关键词会将刚创建的对象return,将这个对象保存在变量s中)

function Person(name) {
    this.abc = name
    this.name = name
}
Person.prototype.sayName = () => {
    console.log('name')
}
​
function Stu(age) {
    this.age = age
    Person.call(this, 'QF002')
}
​
const s = new Stu(18)
s.sayName()
3,组合继承:

*利用原型继承 继承到父类的 原型上的方法;

*利用借用继承 继承到父类的 构造函数上的方法;

优点:能够继承到属性,并且是在自己对象内部;能够继承到父类原型上的方法.

缺点:在原型上有一套多余的属性

function Person(name) {
    this.abc = name
    this.name = name
}
Person.prototype.sayName = () => {
    console.log('name')
}
function Stu(age) {
    this.age = age
    // 2. 利用 借用继承 继承到 父类的 构造函数内部的 属性
    Person.call(this, 'QF001')
}
// 1. 利用 原型继承 继承到 父类的 原型上的方法
Stu.prototype = new Person()
const s = new Stu(18)
console.log(s)
console.log(s.name)
//查找对象内部属性的时候, 会先在对象内部查找, 找到直接使用, 并停止查找
s.sayName()
4,拷贝继承:

核心:通过for in 遍历父类实例化对象上的所有属性,拷贝到子类上;能够遍历对象内部的属性,以及原型上。

function Person(name) {
    this.name = name
}
Person.prototype.sayName = () => {
    console.log('name')
}
​
function Stu(age) {
    this.age = age
    const p = new Person('QF001')
    for (let key in p) {
        // console.log(key, p[key])
        Stu.prototype[key] = p[key]
    }
}
Stu.prototype.abc = () => {
    console.log(123)
}

ES6类的继承

在书写子类的时候,语法:class 子类类名 extends 父类类名 {};

在书写子类constructor 需要在内部书写super() //两个语法必须同时存在,super()必须在constructor的开始位置。除了能够继承class类,还能继承ES5的构造函数。

function Person (name) {
    this.name = name
}
Person.prototype.sayName = function () {
    console.log('name')
}
​
class Stu extends Person {
    constructor(age) {
        super('QF001')
        this.age = age
    }
}
const s = new Stu(18)

深浅拷贝:

一定是引用数据类型,对象,数组,函数(基本只有对象何数组);

赋值:只要是引用数据类型,那么在赋值时,就是引用地址的传递;

1,浅拷贝:

遍历对象拿到没一个key与value,然后赋值给另外一个对象,如果所有的value都是基本数据类型,那么浅拷贝完成之后,修改新的对象不会影响到老对象,如果value中有引用数据类型(出现了多层数据结构),那么浅拷贝一层只能拷走第一层数据结构,多层的没有办法处理。

JS提供的方法:Object.assign(新对象,原始对象) : 将原始对象中所有的key全部浅拷贝到新对象中,然后返回一个对象。

let o1 = {
    a: 1,
    b: 2,
    c: 3,
}
let o2 = {}
for (let key in o1) {
    o2[key] = o1[key]
}
console.log(o1) // {a: 1, b: 2, c: 3}
console.log(o2) // {a: 1, b: 2, c: 3}
console.log(o1 === o2)  // false//JS提供的方法
let o2 = Object.assign({}, o1)
2,深拷贝:

不管数据有多少层数据结构,百分百复制出一份,一模一样但是毫不相关的数据。

let o1 = {
    a: 1,
    b: 2,
    c: 3,
    d: {
        d1: '001',
        d2: '002',
        d3: '003'
    }
}
let o2 = {}
function deepClone(target,origin) {
    for(let k in origin) {
        if(origin[k].constructor === Object){
            target[k] = {}
            deepClone(target[k],origin[k])
        }else if(origin[k].constructor === Array){
            target[k] = []
            deepClone(target[k],origin[k])
        }else {
            target[k] = origin[k]
        }
    }
}
deepClone(o2,o1)