zoukankan      html  css  js  c++  java
  • 再说说__proto__和prototype以及js的继承

    1.__proto__和prototype

    JS中的原型链已经是一个老生常谈的问题,毕竟也是JS 这门语言的特色之一了。

    这里写图片描述
    首先“万物皆对象“,虽然这句话一直有争议,但是有它的道理的,null类型这些的争论这里就不说了。
    对象中有个属性__proto__,被称为隐式原型,这个隐式原型指向构造改对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。这个实例可能是如图中的new Foo()出来的实例。

    构造该对象的f1,f2构造函数是fuction Foo(),它的原型是Foo.prototype,那么f1,f2就指向了构造该对象的构造函数的原型,也就是Foo.prototype,那么构造函数Foo()它的proto指向哪里了,还是找它的构造函数,它的构造函数是Function(),那么它的proto就指向了Fuction.prototype,沿着proto这条路最上就是Object.prototype,Object的proto就是null了。

    刚刚说的对象有个proto属性,方法也是对象,方法中除了有proto之外(这个proto指向构造该函数/对象的构造原型,也就是上一层了),还有prototype,这个属性就是原型属性,他是一个指针,指向一个对象,这个对象就叫原型对象,这里放着包含所有实例共享的属性和方法,这个原型对象里面有一个属性constructor,这个属性也包含一个指针,指回了原构造函数。

    1.构造函数Foo()构造函数的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。

    2.原型对象Foo.prototypeFoo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。

    3.实例f1和f2是Foo这个对象的两个实例,这两个对象也有属性__proto__,指向构造函数的原型对象,这样子就可以像上面1所说的访问原型对象的所有方法。

    4.构造函数Foo()除了是方法,也是对象,它也有__proto__属性,指向谁呢?指向它的构造函数的原型对象。函数的构造函数不就是Function嘛,因此这里的__proto__指向了Function.prototype。其实除了Foo(),Function(), Object()也是一样的道理。原型对象也是对象,它的__proto__属性,又指向谁呢?同理,指向它的构造函数的原型对象。这里是Object.prototype.最后,Object.prototype的__proto__属性指向null。

    5.对象有属性__proto__,指向该对象的构造函数的原型对象。
    方法除了有属性__proto__,还有属性prototype,prototype指向该方法的原型对象。

    6.再看图。

    2.继承

    1.父类的实例作为子类的原型

    function Animal(name) {
      // 属性
      this.name =  name || "Animal";
      // 实例方法
      this.sleep = function() {
        console.log(this.name + '正在睡觉!');
      }
    }
    
    // 原型方法
    Animal.prototype.eat = function(food) {
      console.log(this.name + '正在吃' + food);
    };
    
    
    function Cat() { 
    }
    Cat.prototype = new Animal();
    Cat.prototype.name = 'cat';
    
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.eat('fish'));
    console.log(cat.sleep());
    console.log(cat instanceof Animal); //true 
    console.log(cat instanceof Cat); //true
    

    cat.proto === Cat.prototype //true

    构造cat对象的构造函数是Cat(),它的原型是Cat.prototype,那么隐式原型__proto__就指向构造该对象的构造函数的原型对象,那么cat的__proto__就指向的是Cat.prototype。

    Cat.prototype.proto === Animal.prototype //true
    原型对象也是对象,是对象就有__proto__,Animal的实例返回给了这个原型对象,那么这个原型对象的隐式原型__proto__就指向的是构造该对象的构造函数的原型,我们看看这个原型对象的构造函数是谁
    这里写图片描述

    那么Animal()构造函数的原型对象就是Animal.prototype了。自然就有上面true的结果了。

    这种方法的继承的缺点:

    1. 父类的引用属性和原型对象的引用属性是所有实例共享的
    2. 创建子类实例时,无法向父类构造函数传参
    3. 不能多继承
      第一个致命缺点,因为我们每个实例各自的属性互不干扰才对:
      这里写图片描述

    注意原型上的方法/属性是共享的
    这里写图片描述

    2.构造继承

    没有用到原型,使用父类的构造函数来增强子类实例,等于直接是复制父类的实例属性给子类。

    经典继承也叫做 “借用构造函数” 或 “伪造对象” 。其基本思想是:在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,因此可以通过使用apply() 和call() 方法也可以在新创建的对象上执行构造函数。(JS高程)

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // false
    console.log(cat instanceof Cat); // true
    

    特点:

    解决了1中,子类实例共享父类引用属性的问题
    创建子类实例时,可以向父类传递参数(通过call的后面参数)
    可以实现多继承(call多个父类对象)

    缺点:

    实例并不是父类的实例,只是子类的实例
    只能继承父类的实例属性和方法,不能继承原型属性/方法
    无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

    3.组合继承

    通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    Cat.prototype = new Animal();
    
    //组合继承也是需要修复构造函数指向的。
    
    Cat.prototype.constructor = Cat;
    
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // true
    console.log(cat instanceof Cat); // true
    

    这种方式看似是原型继承和构造继承的组合,弥补了构造继承只能继承实例属性/方法,不能继承原型属性/方法的缺点,也弥补了原型继承引用属性共享的问题,可向父类传参,函数可复用,即是子类的实例,也是父类的实例。

    缺点就是调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
    这里写图片描述

  • 相关阅读:
    深拷贝与浅拷贝
    图片旋转插件
    promise 小抄
    github fork项目更改后与原作者同步更新
    eslint 的配置
    css规范
    Object类
    BigIntager
    System类
    Math类和Random类
  • 原文地址:https://www.cnblogs.com/zhangmingzhao/p/9339118.html
Copyright © 2011-2022 走看看