阅读 461

原型 原型链 继承方式 优缺点

原型

在javascript中每个对象(除null外)创建的时候,就会关联另一个对象,这个关联对象就是原型,每一个对象都会从原型中继承属性。

1.prototype

    function Person(age){
    	this.age = age;
    }
    Person.prototype.Name = 'test';
    var person = new Person();
    console.log(person.Name)    // 'test'
复制代码

从这里可以看到,每个函数都有一个prototype属性,这个属性指向的是函数的原型对象。函数的原型可以通过.prototype属性获取到,可以读取或设置其原型对象的属性。

2.__ proto__

    function Person(){}
    var person = new Person();
    console.log(person.__proto__ === Person.prototype);   // true
复制代码

每个对象(除null外)都会有一个__proto__属性,这个属性可以获取到该对象的原型

3.constructor

    function Person(){}
    console.log(Person === Person.prototype.constructor);  //true
复制代码

每个原型都有constructor属性,指向关联它的构造函数。

所以原型在函数和对象之间的关系如下:

原型链

4.拓展 原型的原型

由上面几点已经得知,原型是一个对象,那么原型的原型是什么呢?

    function Person(){}
    console.log(typeof(Person.prototype.__proto__))   //Object
复制代码

从这里可以看出,原型的原型也是一个对象,那么套娃一下,原型对象的原型的原型又是啥呢

    var obj = new Object()
    console.log(obj.__proto__.__proto__);    //null
复制代码

所以对象的原型的原型是null,到这里为止这个关联就结束了。所以整个原型链的关系图如下

几种继承方式和优缺点

假设一个公共父类Person,其属性方法如下:

    function Person(name){
    	this.name = name || 'noName';
        this.sleep = function(){
        	console.log(this.name + '好想睡觉');
        }
    }
    Person.prototype.eat = function(food){
    	console.log(this.name + '好想干饭,特别是' + food);
    }
复制代码

1.原型链继承

    function Woman(){}
    Woman.prototype = new Person();
    Woman.prototype.Name = "美少女";
    let woman = new Woman();
复制代码
  • 优点:
    • 这样将父类的示例作为子类的原型,容易实现。
    • 父类后续新增的属性或方法,子类中都可以访问。
    • 操作子类即可修改原型中的属性。
  • 缺点:
    • 要新增原型中属性或方法,必须要先new一个实例。
    • 无法多继承
    • 创建子类实例时,无法向父类构造函数传参

2.构造函数继承

    function Woman(name){
    	Person.call(this,name);
       this.name = name || 'No Name';
    }
    let woman = new Woman('xixi');
   console.log(woman.name, woman.sleep(), woman.eat());  // xixi, undefined, 报错(women.eat is not a function)
复制代码
  • 优点
    • 解决了原型链继承中不可传递参数的缺点。
    • 子类可以继承多个父类(使用多个call或apply)
  • 缺点
    • 这里的woman是Woman的实例,但不是Person的实例,因为Woman的原型并没有指向Person。
    • 父类Person原型中的属性和方法(eat)无法被继承。
    • 子类实例woman虽然可以继承父类Person的属性和方法,但是父类Person中的函数无法被复用,如上代码中的sleep()。

3.实例继承

   function Woman(name){
   		let instance = new Person();
        instance.name = name || 'no name';
        return instance
   }
   var women = new Woman();
复制代码
  • 优点
    • 不限制调用方式,无论是new Woman()还是Woman()都可以实现继承。
  • 缺点
    • woman是Person的实例,而不是Woman的实例
    • 不支持多继承

4.组合继承

	function Woman(name){
    	Person.call(this,name);
        this.name = name || 'no name';
    }
    
    // 将整个原型链更改
    Woman.prototype = new Person();
    Woman.prototype.constructor = Woman;
    let woman = new Woman();
复制代码
  • 优点
    • woman实例既能获取Woman的属性和方法,也能获取到Person的原型对象的属性和方法(eat)
    • woman即是子类Woman的实例,也是父类Person的实例
    • 弥补了单纯的构造继承的缺点,可以向父类Person传值
    • 可以复用所有对象的函数
  • 缺点
    • 调用了两次父类构造函数,生成了两份实例,多了一些些内存消耗。

6.寄生组合继承

	function Woman(name){
    	Person.call(this,name);
        this.name = name ||'No Name';
    }(function(){
    	// Super无实例方法,避免了上一种继承方式的创建两次实例
    	var Super = function(){};
        Super.prototype = Person.prototype;
        Woman.prototype = new Super();
    })
    Woman.prototype.constructor = Woman;
    
    let woman = new Woman();
复制代码
  • 优点
    • 在组合继承方式优点上,解决了创建两次实例的问题
  • 缺点
    • 实现复杂
文章分类
前端
文章标签