zoukankan      html  css  js  c++  java
  • JavaScript 原型与继承

    JavaScript 原型与继承

    JavaScript 中函数原型是实现继承的基础。prototype、construct、原型链以及基于原型链的继承是面向对象的重要内容

    prototype

    1. 原型即 prototype,是函数的一个属性,是一个对象

      function Car() {}
      console.log(typeof Car.prototype);
      console.log(Car.prototype);
      // object
      // {...}
      
    2. 所有被构造函数构造出的对象都能访问 prototype 上定义的属性和方法

      function Car() {
      	this.brand = "BMW";
      	this.color = "red";
      }
      Car.prototype.price = "100 万";
      var car1 = new Car();
      var car2 = new Car();
      console.log(car1.price);
      console.log(car2.price);
      // 100 万
      // 100 万
      
    3. 构造函数内部和 prototype 定义了同名属性,实例对象会优先调用构造函数中的属性

      function Car() {
      	this.price = "10 万";
      }
      Car.prototype.price = "100 万";
      var car1 = new Car();
      console.log(car1.price);
      // 10 万
      
    4. 通过实例对象不能更改 prototype 上的属性

      function Car() {}
      Car.prototype.price = "100 万";
      var car1 = new Car();
      car1.price = "10 万";
      console.log(Car.prototype.price);
      // 100 万 
      

    一般将不变化的内容或方法放在 prototype 下,需要动态变化的放在构造方法内,通过参数配置

    constructor

    1. constructor 指向构造函数本身

      实例对象的 constructor 属性指向构造函数

      function Car() {}
      var car = new Car();
      console.log(car.constructor);
      console.log(Car)
      // Car(){}
      // Car(){}
      
    2. constructor 可以被更改

      constructor 可以被修改,但是并不会影响实例化对象

      function Bike() {
      	this.name = "bike";
      }
      Bike.prototype.name = "Bike";
      function Car() {}
      Car.prototype = {
      	constructor: Bike
      }
      var car = new Car();
      console.log(Car.prototype);
      console.log(car.name);
      // {constructor: Bike(){}, ...} 
      // undefined
      

    __proto__

    1. 构造函数在实例化时,将其 prototype 挂载到函数内 this 的 __proto__

      function Car() {}
      Car.prototype.name = "Jett";
      var car = new Car();
      console.log(Car.prototype);
      console.log(car.__proto__);
      // Car.prototype ->
      // {
      //	name: "Jett", 
      //	construct: Car(){}
      //	_proto_: {...}
      //	}
      // car._proto_ ->
      // {
      //	name: "Jett",
      //	construct: Car(){}
      //	_proto_: {...}			
      // }
      // 
      

      可以看到,打印出的 Car.prototype 和 car.__proto__ 内容一致。因为在实例化对象时,Car.prototype 被挂载到函数内的 this.__proto__ 上,即实例对象的 __proto__ 属性上

      prototype 是构造函数的属性,__proto__ 属于每个实例对象的,是一个内部属性,它们指向相同的内容

    2. 可以通过实例对象访问 __proto__ 属性,并对其进行修改

      function Car() {}
      Car.prototype.name = 'BWM';
      var car = new Car();
      console.log(car.name);
      car.__proto__= {
      	name:"Benz"
      }
      console.log(car.name);
      // BWM
      // Benz
      

      也可以更改 prototype 的属性到达效果

      function Car() {}
      Car.prototype.name = 'BWM';
      var car = new Car();
      console.log(car.name);
      Car.prototype.name = 'Benz';
      console.log(car.name);
      // BWM
      // Benz
      

      但是,将 prototype 重新赋值并不能对之前实例化的对象造成影响

      function Car() {}
      Car.prototype.name = 'BWM';
      var car = new Car();
      console.log(car.name);
      Car.prototype = {
      	name: "Benz"
      }	
      console.log(car.name);
      // BWM
      // BWM
      

      这是因为重新赋值相当于创建新对象,使 prototype 指向的新的对象,而实例对象的 __proto__ 属性依然指向原来的内容,相当于一个对象的两个引用,其中一个不在指向该对象,而且指向了新对象

      这不能对已经实例化出的对象造成影响,但是后面再实例化对象则可以造成影响,因为实例化过程中将修改后的 prototype 挂载到了实例对象的 __proto__ 属性下,二者指向同一对象

    原型链

    1. prototype 中的 __proto__ 属性

      function Car() {}
      var car = new Car();
      console.log(Car.prototype);
      

      当我们打印构造函数的 prototype 属性时,可以看到

      {
      	constructor: Car(),
      	__proto__: {...}
      }
      

      prototype 中也有 __proto__ 属性,实例化过程 protorype 被挂载到实例对象的 __proto__ 下,这就意味着实例对象的 __proto__ 中也有一个 __proto__ 属性

      因为这里的 prototype 是一个非空对象,是由 new Object() 或者其他自定义构造方法实例化出的,自然也有 __proto__ 属性

    2. 链式的 __proto__

      原型链是由 __proto__ 组成的链接,原型链的顶端是 Object.prototype

      JuniorCoder.prototype.basicSkill = "html/css";
      function JuniorCoder() {
      	this.lowerSkill = "javascript"
      }
      var junior = new JuniorCoder();
      SeniorCoder.prototype = junior;
      function SeniorCoder() {
      	this.advancedSkill = "vue";
      }
      var senior = new SeniorCoder();
      console.log(senior);
      

      这里将 JuniorCoder() 的实例对象赋值给 SeniorCoder.prototype,打印出

      SeniorCoder {
      	advcedSkill: "vue",
      	__proto__: { // senior.__proto__ ,即 SeniorCoder.protoype
      		lowerSkill: "javascript",
      		__proto__: { // junior.__proto__ ,即 JuniorCoder.prototype
      			basicSkill: "html/css",
      			__proto__: { // Object.prototype
      				constructor: Object(),
      				toString: toString()
      				// ...
      			}
      		}
      	}
      }
      

      可以看出,senior 的 __proto__ 属性指向 JuniorCoder() 实例 junior,这是因为之前 将 junior 赋值给了 SeniorCoder.prototype

      此外,junior 的 __proto__ 也指向了一个对象,这个对象就是 JuniorCoder.porotype,相当于 new Object() 得出的,所以 junior 的 __proto__ 下的 __proto__ 就是 Object.prototype,这就是原型链的顶端,在里面我们还可以看到 toString 方法等等

    3. 访问原型链上属性

      JuniorCoder.prototype.basicSkill = "html/css";
      JuniorCoder.prototype.sex = "man";
      function JuniorCoder() {
      	this.lowerSkill = "javascript"
      	this.age = 22;
      }
      var junior = new JuniorCoder();
      SeniorCoder.prototype = junior;
      function SeniorCoder() {
      	this.advancedSkill = "vue";
      }
      var senior = new SeniorCoder();
      console.log(senior.age);
      console.log(senior.sex);
      // 22
      // man
      

      senior 可以访问 junior 本身的属性,也可以访问 JuniorCoder.prototype 上的属性,因为 junior 被挂载到了 SeniorCoder.prototype 上

      JuniorCoder.prototype.basicSkill = "html/css";
      function JuniorCoder() {
      	this.lowerSkill = "javascript";
      	this.years = 3;
      }
      var junior = new JuniorCoder();
      SeniorCoder.prototype = junior;
      function SeniorCoder() {
      	this.advancedSkill = "vue";
      }
      var senior = new SeniorCoder();
      senior.years++;
      // 等同于 senior.years = senior.years + 1;
      console.log(senior.years);
      console.log(junior.years);
      // 4
      // 3
      

      可以看到,通过 senior 试图改变 years 属性并不能真正影响 junior 的 years 属性,实际上只是在 senior 下创建了新的 years 属性,并将 junior.years 加一的结果赋值给它

    Object.creat()

    1. Object 的 creat 方法用于创建对象,参数指定 prototype,可以为对象或 null

      var test = {
      	name: "obj"
      }
      var obj = Object.create(test);
      console.log(obj.name);
      console.log(obj.__proto__ == test);
      // obj
      // true
      
    2. Object.creat(null)

      var obj = Object.create(null);
      console.log(obj);
      document.write(obj);
      // {}
      // 报错
      

      控制台显示 obj 是一个空对象,没有任何属性,包括 __proto__,如果使用 document.write(obj) 则会报错,因为 document.write 方法会把参数转成字符串再打印在页面,默认调用 toString() 方法,toString 方法需要从原型链上继承而来,而 obj 是一个完全的空对象,没有原型链,也没有 toString 方法,所以会报错

    基于原型的继承

    1. 利用原型链实现继承

      JuniorCoder.prototype.basicSkill = "html/css";
      function JuniorCoder() {
      	this.lowerSkill = "javascript"
      	this.age = 22;
      }
      var junior = new JuniorCoder();
      SeniorCoder.prototype = junior;
      function SeniorCoder() {
      	this.advancedSkill = "vue";
      }
      var senior = new SeniorCoder();
      

      senior 继承了 junior 的自身属性及原型链

    2. call/apply 实现继承

      function JuniorCoder(lowerSkill) {
      	this.lowerSkill = lowerSkill;
      }
      function SeniorCoder(lowerSkill, advancedSkill) {
      	JuniorCoder.apply(this, [lowerSkill]);
      	this.advancedSkill = advancedSkill;
      }
      var senior = new SeniorCoder("javascript", "vue");
      

      继承了 JuniorCoder 实例的自身属性,不能继承原型链

    3. 公共原型继承

      JuniorCoder.prototype.basicSkill = "html/css";
      function JuniorCoder() {
      	this.lowerSkill = "javascript"
      }
      SeniorCoder.prototype = JuniorCoder.prototype;
      function SeniorCoder() {
      	this.advancedSkill = "vue";
      }
      var senior = new SeniorCoder();
      

      senior 继承 JuniorCoder 实例的原型链,不继承自身属性,但是改动 SeniorCoder.prototype 会影响 JuniorCoder.prototype

    4. 中间对象继承(圣杯模式)

      JuniorCoder.prototype.basicSkill = "html/css";
      function JuniorCoder() {
      	this.lowerSkill = "javascript"
      }
      Buffer.prototype = JuniorCoder.prototype;
      function Buffer() {}
      SeniorCoder.prototype = new Buffer();
      function SeniorCoder() {
      	this.advancedSkill = "vue";
      }
      SeniorCoder.prototype.basicSkill = "markdown";
      console.log(SeniorCoder.prototype.basicSkill);
      console.log(JuniorCoder.prototype.basicSkill);
      // markdown
      // html/css
      

      继承原型链,不继承自身属性,prototype 不相互影响,这种继承方式更为实用

      进行封装以后,更适应企业级开发

      JuniorCoder.prototype.basicSkill = "html/css";
      
      function JuniorCoder() {
      	this.lowerSkill = "javascript"
      }
      
      function SeniorCoder() {
      	this.advancedSkill = "vue";
      }
      inherit(SeniorCoder, JuniorCoder);
      SeniorCoder.prototype.basicSkill = "markdown";
      console.log(new SeniorCoder());
      console.log(new JuniorCoder());
      
      function inherit(Target, Origin) {
      	Target.prototype = Object.create(Origin.prototype);
      	Target.prototype.constructor = Target;
      	Target.prototype.superClass = Origin;
      }
      

      使用 Object 的 creat 方法直接创建中间对象,将 construtor、superClass 属性设置好,便于分析和维护

    hasOwnProperty()

    判断属性是否是实例对象本身的,如果是则返回 true

    Car.prototype.brand = "BMW";
    function Car() {
    	this.color = "red";
    }
    var car = new Car();
    console.log(car.hasOwnProperty("brand"));
    console.log(car.hasOwnProperty("color"));
    // false
    // true
    

    instanceOf

    判断实例对象的原型链上是否有某个构造方法

    JuniorCoder.prototype.basicSkill = "html/css";
    
    function JuniorCoder() {
    	this.lowerSkill = "javascript"
    }
    
    function SeniorCoder() {
    	this.advancedSkill = "vue";
    }
    inherit(SeniorCoder, JuniorCoder);
    
    function inherit(Target, Origin) {
    	Target.prototype = Object.create(Origin.prototype);
    	Target.prototype.constructor = Target;
    	Target.prototype.superClass = Origin;
    }
    
    var senior = new SeniorCoder();
    
    console.log(senior instanceof SeniorCoder);
    console.log(senior instanceof JuniorCoder);
    console.log(senior instanceof Object);
    // true
    // true
    // true
    
  • 相关阅读:
    MySQL高性能优化规范建议,速度收藏
    基于 debian 操作系统的 docker 镜像,安装 vim
    Vue 开发经验总结
    DNS 负载均衡
    图解:从单个服务器扩展到百万用户的系统
    defer、return、返回值,这三者的执行逻辑
    goroutine 知识点
    一条SQL语句在MySQL中如何执行的
    架构设计的常用方法
    vue中直接修改props中的值并未给出警告,为啥?
  • 原文地址:https://www.cnblogs.com/pgjett/p/12537041.html
Copyright © 2011-2022 走看看