原型与原型链与继承

原型、原型链与继承

构造函数

构造函数创建实例的过程

1.创建一个新对象

2.将空对象的__proto__指向构造函数的原型

3.修改构造函数中this指向,将构造函数中的this指向实例对象,执行构造函数中的代码,给这个新对象添加属性和方法(通过call/apply)

4.返回新对象(实例对象)

手写new

  function Person(name, age) {
    this.name = name;
    this.age = age;
  }
  //   const p = new Person("张三", 40);
  //   console.log(p);
  function myNew(Fn, args) {
    let obj = {};
    obj.__proto__ = Fn.prototype;
    Fn.apply(obj, args);
    return obj;
  }
  const p = myNew(Person, ["张三", 40]);
  console.log(p);

原型

原型与原型链都都源于对象并服务于对象,他们是js实现继承的一种模型

原型:每个构造函数都有一个prototype属性,它就是通过构造函数创建(new)的对象的原型,在他上面定义的属性和方法都可以被对象实例所共享

  function Fn(name) {
    this.name = name;
    this.speak = function () {
      console.log("Chinese");
    };
  }
  Fn.saySomething = function () {
    console.log("i love you");
  };
  const fn = new Fn("张三");
  fn.speak();    // Chinese
  fn.saySomething();  // 报错 saySomething is not a function
  // 但是如果我将saySomething放到Fn的prototype中,所有实例都可以使用这个方法
  function Fn(name) {
    this.name = name;
  }
  Fn.prototype.saySomething = function () {
    console.log("i love you");
  };
  const fn = new Fn("张三");
  const fn2 = new Fn("李四");
  fn.saySomething();  // i love you
  fn2.saySomething();  // i love you

protype对象

1>proto属性

js中,除去null外任何对象内部都会自带__proto__属性;prototype是一个对象,所以存在__proto__属性

fn.__proto__==>Fn.prototype

2>constructor属性

对象的的prototype里面有个constructor属性,指向当前对象所属的构造函数

Fn.prototype.constructor==>Fn构造函数

在这里插入图片描述

每个构造函数都有一个prototype属性,指向原型对象,原型对象上有个constructor属性指回构造函数,每个实例对象都有一个__proto__属性,指向构造函数的prototype

原型链

原型链:每个对象都有一个__proto__属性,指向他的原型,也就是构造函数的prototype;当访问一个对象的属性时,它首先会在自己身上找,如果没有找到就会往原型上面找,如果还是没找到,他会继续往上,直到找到为止,如果查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined,这样就会形成一条链,就是原型链

原型链的终点是null,Object.prototype .__proto__指向null

在这里插入图片描述

继承

继承的本质是重写原型对象

原型链继承

可以继承属性和方法

      function Boy() {
        this.gender = "male";
      }
      function Girl() {
        this.gender = "female";
        this.color = "pink";
      }
      Girl.prototype.getColor = function () {
        return this.gender;
      };
      Girl.prototype.getGender = function () {
        return this.gender;
      };
      // 创建Girl实例,并加那个该实例赋值给Boy原型
      Boy.prototype = new Girl();
      const baby = new Boy();
      console.log(baby.getColor()); // pink
      console.log(baby.getGender()); // male

缺点:

  1. 多个实例对引用类型的操作会被篡改
  2. 在创建子类型是不能向超类型的构造函数中传参
      function Boy() {
        this.colors = ["blue", "green"];
      }
      function Girl() {}
      Girl.prototype = new Boy();
      const baby1 = new Girl();
      baby1.colors.push("black");
      console.log(baby1.colors); //["blue", "green","black"]
      // baby2和baby1的构造函数一样,都到原型上找,指向一致,color是引用类型,所以baby2也跟着变了
      const baby2 = new Girl();
      console.log(baby2.colors); //["blue", "green","black"]

构造函数继承

通过call()、apply()来实现继承

call

apply

缺点:只能继承父类的实例属性和方法,无法继承原型属性、方法

      function Boy() {
        this.gender = "male";
        // 继承Boy
        Girl.call(this);
      }
      function Girl() {
        this.gender = "female";
        this.color = "pink";
      }
      Girl.prototype.getGender = function () {
        return this.gender;
      };
      const baby = new Boy();
      console.log(baby); // {gender:"female",color:"pink"}
      console.log(baby.getGender()); //报错: baby.getGender is not a function

组合继承

使用原型链实现原型属性和方法的继承,通过构造函数实现对实例属性的及继承

      function Boy(name) {
        this.name = name;
        this.colors = ["blue"];
      }
      Boy.prototype.getName = function () {
        return this.name;
      };
      function Girl(name, age) {
        // 先利用构造函数继承来继承实例对象的属性和方法
        Boy.call(this, name);
        this.age = age;
      }
      // 在利用原型继承来继承原型
      Girl.prototype = new Boy();
      const baby1 = new Girl("baby1", 0);
      const baby2 = new Girl("baby2", 1);
      console.log(baby1);
      console.log(baby1.getName()); // baby1
      console.log(baby2.getName()); // baby2
      baby1.colors.push("pink");
      // 实例自身已经有colors属性,就不会到原型上找,所以不会相互影响
      console.log(baby1.colors); // ['blue','pink']
      console.log(baby2.colors); // ['blue'] 

组合继承融合了两者的有点,避免了他们的缺陷

原型式继承

object()对传入其中的对象执行了一次浅复制,将构造函数F的原型直接指向传入的对象

      function object(obj) {
        function F() {}
        F.prototype = obj;
        return new F();
      }
      const person = {
        name: "test",
        colors: ["blue"],
      };
      const p = object(person);
      p.name = "hello";
      p.colors.push("pink");
      console.log(p.colors); // ["blue","pink"]
      const p2 = object(person);
      p2.name = "world";
      p2.colors.push("white");
      console.log(p2.colors); // ["blue","pink",'white']
      console.log(p.colors); // ["blue","pink",'white']

缺点:

  1. 多个实例对引用类型的操作会被篡改
  2. 在创建子类型是不能向超类型的构造函数中传参

寄生式继承

在原型的基础上,增强对象,返回构造函数

      function object(obj) {
        function F() {}
        F.prototype = obj;
        return new F();
      }
      function objectAnother(origin) {
        const clone = object(origin);
        clone.greet = function () {
          alert("hello");
        };
        return clone;
      }

      const person = {
        name: "test",
        colors: ["blue"],
      };
      const p = objectAnother(person);
      p.colors.push("pink");
      p.greet();
      console.log(p.colors); // ['blue',pink]
      const p2 = objectAnother(person);
      p2.colors.push("white");
      console.log(p2.colors); // ["blue","pink",'white']
      console.log(p.colors); // ["blue","pink",'white']

寄生组合式继承

function inheritPrototype(subType, superType){
  const prototype = Object.create(superType.prototype);  // 创建对象,创建父类原型的一个副本
  prototype.constructor = subType;  // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  subType.prototype = prototype;  // 指定对象,将新创建的对象赋值给子类的原型
}

// 父类初始化实例属性和原型属性
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){
  SuperType.call(this, name);
  this.age = age;
}

// 将父类原型指向子类
inheritPrototype(SubType, SuperType);

// 新增子类原型属性
SubType.prototype.sayAge = function(){
  alert(this.age);
}

const instance1 = new SubType("xyc", 23);
const instance2 = new SubType("lxy", 23);

instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]

ES6类继承extends

ES6支持类的继承,它背后依旧是使用原型链

      class Person {
        static myStaticProp = 42; // 静态属性:class本身的属性,不是定义到实例对象(this)上面的属性
        constructor(name, ageNum) {
          this.name = name;
          this.age = ageNum;
        }
        getDoubleAge() {
          return 2 * this.age;
        }
      }
      const p = new Person("潘周聃", 29);
      console.log(p.getDoubleAge()); // 58
      class personalInfo extends Person {
        constructor(name, age, info) {
          super(name, age); // 不能在调用super之前引用this
          this.info = info;
        }
        getInfo() {
          return this.info;
        }
      }
      const p2 = new personalInfo("潘周聃", 29, "硕士毕业于苏黎世联邦理工大学");
      console.log(p2.getDoubleAge()); // 58
      console.log(p2.getInfo()); //"硕士毕业于苏黎世联邦理工大学"

扩展

instanceof

基本语法

返回布尔值

      const arr = [1];
      console.log(arr instanceof Array); // true
      console.log(arr instanceof Object); // true
      console.log(null instanceof Object); // false

原理

右侧的对象(构造函数)的原型对象prototype)是不是在左侧对象的原型链上

手写instanceof

      function myInstLnce(L, R) {
        if (typeof L !== "object" || L === null) return false;
        const origin = R.prototype;
        L = Object.getPrototypeOf(L);
        while (true) {
          if (L === origin) return true;
          L = Object.getPrototypeOf(L);
        }
      }

apply call bind

call、apply、bind的区别

都可以改变this的指向

1》call 和 apply 改变this指向的同时,会调用函数,bind改变函数的this指向,不会调用

2》call 和apply 的传参不同,第一个参数都是this指向的执行上文,后面的参数都是作为改变this指向的函数的参数;call 第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上,apply第二个参数必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上

      function fn(uname, age) {
        this.name = uname;
        this.age = age;
      }
      const obj = {
        name: "张三",
        age: 20,
      };
      fn.call(obj, "test", 22);
      console.log(obj);
      fn.apply(obj, ["测试", 33]);
      console.log(obj);

3》在使用上的区别:

call:对象的继承,在子构造函数这种调用父构造函数,但是改变this指向,就可以继承父的属性

function superClass () { 
 this.a = 1; 
 this.print = function () {  
 console.log(this.a);  
}
  }
function subClass () {  
 superClass.call(this);   
 this.print();
}
subClass(); // 1

apply的应用场景: Math.max,获取数组中最大、最小的一项

const max = Math.max.apply(null, array)  // 和Math.max(...array)效果一样

第一个参数,是一个对象。 函数的调用者,将会指向这个对象。如果不传,则默认为全局对象 window

注意点:

多次 bind 时只认第一次 bind 的值

箭头函数中this不会被改变

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值