Javascript继承机制的设计思想(一)

971 阅读4分钟

构造函数继承

在之前我们有实现过new运算符,在其中也是做了原型的指向。但是,如果,你的构造函数没有定义原型对象的情况下。其实,也相当于没有使用原型链继承。


function A(x,y,z){
    this.x=x;
    this.y="共有属性";
    this.z=()=>{
        console.log("共有方法");
    };
};
var b = new A(1,2);//这里传了多个参数,但是我们只使用了1个参数
var c = new A(4,5);
b.y="修改了的y";
b.z={a:1,b:2};
console.log(b); // {x: 1, y: "修改了的y", z: {a:1,b:2}}
console.log(c,c.z()); // {x: 4, y: "共有属性", z: z: ()=>{ console.log("共有方法"); }}

在上面的例子中,我们修改了实例对象b中的属性y,实例对象c并没有受到影响。如果我们希望实例对象公有一些同样的属性和方法时。这样是可行的。但是,问题是,我们每new一个实例对象出来,无论是值类型,还是引用类型,都会开辟新的内存空间。

构造函数继承的缺点

  1. (耗内存) 构造函数中的属性过大的话,实例化时,将占用过多的内存。
  2. 实例对象的共有属性或方法无法,占用一个内存空间即可。没必要都开辟新的内存空间。(通过上方的例子可看出实例对象b和c不互相影响)

每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。

注意在写构造函数时,其中的this关键字,它就代表了新创建的实例对象。我们在回顾一下

new的实现, 分为四步:

  1. 创建一个空对象
  2. 把空对象的隐式原型指向构造函数的显示原型
  3. 通过call或apply调用构造函数,把构造函数中的this指向新创建的空对象。(记得给构造函数传入参数,这些参数将成为创建的空对象内的私有属性,比如{x:1,y:2})。
  4. 返回一个对象(当你使用new运算符的时候会隐式返回这个新创建的对象,如果你new构造函数的时候,传递了参数的话,那么这个对象将有这些属性)

new 小结

  • 输入是一个构造函数(可有参数可无参数,无参数时,你最终new 构造函数将得到一个空对象)
  • 输出是一个对象,比如:{x:1,y:2}。
  • 构造函数中不建议有返回值,这样的话,构造函数也就失去了意义。
  • 构造函数如果有原型对象的话,创建的实例对象将继承原型对象上的属性和方法。

function _new(/*constr,params*/){//...arg更方便,隐式取参即可
   let args=[...arguments];
   let constructor=args.shift();
   var newObj={};
   newObj.__proto__=constructor.prototype;
  //let newObj = Object.create(constructor);//issue
    let result =  constructor.apply(newObj ,args);
    return typeof result === "object" ? result: newObj 
}
var p = _new(A,1);  // {x: 1, y: "共有属性", z: ƒ}

具体可见:new的实现

原型链继承

function A(){};
A.prototype={
    y:"共有属性",
    z:()=>{}
}
var b = new A; // 没有参数,没必要写括号
var c = new A;
console.log(b); // {}
console.log(c); // {}
console.log(b.__proto__); // {y: "共有属性", z: ƒ}
console.log(c.__proto__); // {y: "共有属性", z: ƒ}

原型链继承缺点

  • 无法传参,也就无法实现私有属性和方法

构造函数+原型链继承(组合式继承)

js的作者,就是为了解决上面的耗费内存的问题,从而引入了原型对象。

function A(x){
    this.x=x;
};
A.prototype={
	y:"共有属性",
    z:()=>{
        console.log("共有方法");
    }
}

var b = new A(1);
var c = new A(2);

// 通过构造函数或者实例对象都可以修改原型对象上的属性/方法,并有都会直接影响到其他实例对象。(方便修改共有属性/方法)
//b.__proto__.y="111";
A.prototype.y="222";

console.log(b); // {x: 1}
console.log(c); // {x: 2}
console.log(b.__proto__); // {y: "222", z: ƒ}
console.log(c.__proto__); // {y: "222", z: ƒ}

组合式继承小结

  • 通过构造函数或者实例对象都可以修改原型对象上的属性/方法,并有都会直接影响到其他实例对象。(方便修改共有属性/方法)
  • 相比单纯的构造函数继承,或者是原型链继承。结合了两者的优点。既可以拥有私有属性,又可以拥有共有属性。
  • 节省系统资源

多重继承

以上通过

  • 构造函数继承
  • 原型链继承
  • 构造函数+原型链的组合继承 只是最简单的继承场景,并没有涉及到多重继承的情况。后续会继续更新~

其他继承方式

  1. es6的继承 class extends
  2. call,apply
  3. Object.create({})
  4. 浅拷贝
  5. 深拷贝

总结:

  1. 继承的实现,既要让实例对象有私有属性,又要拥有公有属性。
  2. 公有属性,修改一处,所有实例都可生效,比较方便。
  3. 要节省系统资源。

参考