JS笔记(12): 对象的继承

666 阅读6分钟

继承:子类继承父类的属性和方法:

  • 1.原型继承
  • 2.call继承
  • 3.寄生组合继承
  • 4.ES6中的class类实现继承
  • 5.扩展式继承 B.prototype = {...A.prototype}
  • 6.deepclone深克隆 B.prototype = deepclone(A.prototype)
  • 继承的目的:就是为了代码能够更好复用,组合起来生成一个新的类别

### 一、原型继承

让子类的原型指向父类的实例

  • 1.方式:B.prototype = new A(); A的实例本身具备父类A的私有属性和公有方法,子类B的原型指向它,name子类B的实例就可以找到这些属性和方法了
  • 2.和传统后台语言继承为:子类继承父类,是把父类复制一份副本给子类(这样处理子类和父类就没有直接关系了)。JS中的原型继承是让子类和父类建立原型链接的机制,子类是实例调取父类原型上的方法,都是基于原型链的查找机制完成的
原型继承存在的问题:
  • 1.父类实例私有属性和公有属性,都变为子类实例的公有属性
  • 2.如果子类B的原型上之前有属性方法,重新执行A的实例后,之前的方法都没用了

function A() {
    this.x = 100;
};
A.prototype = {
    consyructor: A,
    getX: function () {
        console.log(this.x);
    }
};
function B() {
    this.y = 200;
};
// function AA() {
//     function x() {

//     } //先创建一个空对象
//     x.prototype = B.prototype; //把父类的原型地址赋值给空对象(即空对象的原型指向父类的原型)
//     return new x;  // 返回实例
// }
B.prototype = new A; //核心
let f = new B();

二、call继承(属性继承):

  • call继承:把父类A作为普通函数执行,让A中的this变为B的实例,相当于给B的实例增加一个属性和方法
  • 弊端:把父类A当做普通函数执行,和原型无关了,仅仅是把A中的私有属性变为子类B实例的私有属性,A原型中的公有属性方法和B及B的实例无关
  • new A() 把A作为类创建它的实例 this:实例
  • A() 把A作为普通函数执行 this:window
function A(name) {
    // this:f 
    this.x = name; // f.x=100
};
A.prototype = {
    consyructor: A,
    getX: function () {
        console.log(this.x);//100
    }
};
function B(name) {
    // this: 实例f
    A.call(this,name); //核心 把A执行,让A中的this变为f
    this.y = name;
};
let f  = new B('abc');
console.log(f.x)

三、寄生组合式继承

  • 子类的私有空间里继承父类的私有属性,子类的公有空间里继承父类的公有属性
  • 寄生组合式和原型继承的唯一区别:
    • B.prototype = new A(); 创建的A四位实例虽然指向了A的原型,但是实例中不是空的,存放了A的私有属性,这些属性变为B的公有属性
    • B.prototype = Object.create(A.prototype); 好处在于我们是创建一个没有任何私有属性的空对象,指向A的原型,这样B的公有中不会存在A四位私有属性
  • Object.create(): 内置Object类天生自带的方法,
    • 1.目的为创建一个空对象
    • 2.让新创建的空对象的__proto__指向第一个传递进来的对象(把obj作为新创建空对象的原型)
// 原理:
function create(obj){
    function AA(){}
    let o = new AA;
    o.__proto__ = obj;
    return o;
}

关于Object.create()

let obj = {
    name: 'Tom'
};
console.log(Object.create(obj)); //{}
 function A() {
    // this:f 
    this.x = 100; // f.x=100
};
A.prototype = {
    consyructor: A,
    getX: function () {
        console.log(this.x);//100
    }
};
function B() {
    A.call(this); //核心 基于call继承,把A的私有变为B的私有 f.x=100
    this.y = 200;
};
// B.prototype = A.prototype; //一般不这样处理,因为这种模式可以轻易修改父类A原型上的东西(重写),这样会导致A的其他实例受到影响
B.prototype = Object.create(A.prototype);
let f = new B();
console.log(f.x)

四、ES6中的类和继承

  • 语法:ES6中创建类是有自己标准语法的(这种语法创建出来的类只能new执行,不能当做普通函数执行)
  • class继承的核心原理:
    • 1.子类.call(this) => 继承父类的私有属性
    • 2.子类.prototype = Object.create(父类.prototype) => 继承父类的公有方法
    • 3.子类.__proto__ 指向父类(原本,类.__proto__ = Function.prototype

 //ES6 class继承
class A {
    constructor() {
        console.log(arguments); //Tom 子类的实例把参数传递给子类的构造函数,
        this.name = arguments;
        this.x = 100;
    }
    getX() {
        console.log(this.x);
    }
}
class B extends A{//类似与实现了原型继承
    constructor(){
        super(...arguments)
        //super类似于call继承,在这里相当于把父类的constructor执行,并且让方法中的this是B的实例,super当中传递的实参都是给A的
        this.y = 200;
        console.log(...arguments)
    }
    getY (){
        console.log(this.y);
    }
}
let f = new B('Tom','Jerry');
console.dir(f);

关于class语法:

class Fn { //Fn是类名,没有小括号
    constructor(n, m) {
        // super();
        //=> 等价于传统ES5中类的构造体
        this.x = n;
        this.y = m;
    }
    //=> 给Fn的原型上设置方法(只能设置方法不能设置属性)
    getX() {
        console.log(this.x);
    }
    //设置静态方法:把Fn当做一个普通对象设置的私有方法(和实例没有关系),同样也只能设置方法不能写属性
    static AA(){
        console.log(3)
    }
}
let f = new Fn(10,20);
console.log(f.x); //10
f.getX(); //10
Fn.AA(); //3

五、扩展式继承:(浅克隆)

  • B.prototype = {...A.prototype}
  • 把父类的prototype复制一份给子类(把地址赋值给子类)
  function A (){
    this.name = 'Tom';
};
A.prototype.getX = function(){
    console.log(this.name);
}
function B(){
    A.call(this);
    this.age = 18
}
B.prototype = {...A.prototype};
let f = new B();
console.log(f); //B {name: "Tom", age: 18} age: 18 name: "Tom" __proto__:getX:  ƒ () __proto__: Object

六、deepclone:深克隆

  function A() {
    this.name = 'Tom';
};
A.prototype.getX = function () {
    console.log(this.name);
}
function B() {
    A.call(this);
    this.age = 18
}
B.prototype = deepClone(A.prototype); //将父类的原型上的属性和方法克隆一份 复制地址给子类
let f = new B();
console.log(f);

function deepClone(obj) {
//先声明一个数组,去存克隆出来的内容
//判断obj是否为数组,是数组就o就为[],否则为{}
let o = obj.push ? [] : {};
//循环传进来的对象
for (let attr in obj) {
//检测对象中各项是否为该对象的私有属性,true则执行下面的代码
//判断对象中的某个值是否为引用类型
//如果是,就继续调用deepClone把引用值传到函数中
if (obj.hasOwnProperty(attr)) {
    if (typeof obj[attr] === 'object') {
        o[attr] = deepClone(obj[attr])
    } else {
        //如果是简单类型就直接赋值
        o[attr] = obj[attr];
    }
}
}
return o;
}

====

个人认为,

  • 原型继承,call继承,寄生组合继承,class继承是真正的继承,因为这四种方式都是让子类和父类产生关系,父类的属性或方法发生变化,那么子类继承的属性或方法也会发生相应的变化
  • 而扩展式继承和深克隆继承不应算作是真正的继承,因为这两种方式的目的都是把父类复制一份 给子类,此时子类原型的地址和父类原型的地址是不一样的。如果父类的属性或方法发生改变,那么子类上的属性或方法不会改变,没有做到真正的继承