• JavaScript的原型、原型链、继承


    1.原型和原型链的概念

    js在创建一个对象时,比如叫 obj,都会给他偷偷的加上一个引用,这个引用指向的是一个对象,比如叫 yuanxing
    这个对象可以给引用它的对象提供属性共享,比如:yuanxing上有个属性name,可以被 obj.name访问到,
    这个可以提供属性共享的对象,就称为前面对象的原型

    而原型本身也是一个对象,所以它也会有一个自己的原型,这一层一层的延续下去,直到最后指向 null,这就形成的 原型链

    那js的这一种是原型机制是怎么实现的呢?

    2.原型的实现

    我们先从一个例子来看:

    //code-01
    let obj = new Object({name:'xiaomin'})
    console.log(obj.name)
    console.log(obj.toString())
    
    // xiaomin
    // [object Object]
    

    我们首先创建了一个对象obj,它有一个属性name
    属性name是我们创建的,但是在创建obj的时候,并没有给它创建toString属性,为什么obj.toString()可以访问的到呢?

    prototype 属性
    我们先来看一下Object.prototype属性

    我们发现这里有toString属性,所以其实Object.prototype就是obj的原型,按照原型的概念,它能够为obj提供属性共享
    所以当obj.toString()的时候,先在obj的属性里找,没找到,就在obj的原型Object.prototype的属性上找,可以找到,所以调用成功

    proto 属性
    那么obj是怎么找到原型的呢?我们打印obj属性看一下:

    我们发现obj除了我们创建的name以外,还有一个__proto__属性,它的值是一个对象,那么它等于什么呢?

    我们发现obj.__proto__指向的就是Object.prototype

    到此为止,我们可以简单总结一下js语言实现原型的基本机制了

    • 在创建一个对象obj时,会给它加上一个__proto__属性
    • __proto__属性 指向obj构造函数prototype对象
    • 当访问obj的某个属性时,会先在它自己的属性上找,如果没找到,就在它的原型(其实就是__proto__指向的对象)的属性上找

    构造函数
    这个有一个构造函数的概念,其实构造函数也就是普通函数,当这个函数被用来 new一个新对象时,它就被称为新对象的 构造函数
    就上面的例子而言,Objec就是obj的构造函数,
    这里要区别一下Objectobject的区别,前者是一个js内置的一个函数,后者是js的基本数据类型(number,string,function,object,undefined)

    3.new 实际上做了什么

    上面有说到new关键字,那么在它实际上做了什么呢?
    上面代码code-01使用系统内置的函数Object来创建对象的,那么我们现在用自己创建的函数来创建一个新对象看看:

    //code-02
    
    function human(name){
      this.name = name
    }
    human.prototype.say = function(){
      alert('我叫'+this.name)
    }
    let xiaomin = new human('xiaoming')
    
    console.log(xiaomin.name)
    xiaomin.say()
    

    这里的human就是新对象xiaoming的构造函数
    我们把新创建的对象xiaoming打印出来看看:

    我们看到xiaoming有一个属性name,并且xiaoming.__proto__完全等于构造函数的human.prototype,这就是它的原型
    从这里我们可以总结一下new的基本功能:

    • 构造函数的this指向新创建的对象xiaoming
    • 为新对象创建原型,其实就是把新对象的__proto__指向构造函数的prototype

    手写new
    上面我们了解了new具体做了什么事情,那么它是怎么实现这些功能的呢?下面我们手写一个函数myNew来模拟一下new的效果:

    //code-03
        function human(name, age) {
          this.name = name;
          this.age = age;
        }
        human.prototype.say = function () {
          console.log("my name is " + this.name);
        };
    
        xiaoming = myNew(human, "xiaoming", 27);
    
        function myNew() {
          let obj = new Object();
          //取出函数的第一个参数,其实就是 human函数
          let argArr = Array.from(arguments);
          const constructor = argArr.shift();
          // 指定原型
          obj.__proto__ = constructor.prototype;
          //改变函数执行环境
          constructor.apply(obj, argArr);
          return obj;
        }
    
        xiaoming.say();
    

    我们先把新对象xiaoming打印出来看一下:

    我们发现这和上面的代码code-02的效果是一样的
    上面代码code-03里面如果对apply的作用不太熟悉的,可以另外了解一下,其实也很简单,意思就是:在obj的环境下,执行constructor函数,argArr是函数执行时的参数,也就是指定了函数的this

    4.继承的实现

    其实就上面的内容,就可以对js的原型机制有个基本的了解,但是一般面试的时候,如果有问到原型,接下来就会问 能不能实现 继承的功能,所以我们来手写一下 原型的继承,其实所用到的知识点都是上面有提到的

    继承的概念
    我们先来说下继承的概念:
    继承其实就是 一个构造函数(子类)可以使用另一个构造函数(父类)的属性和方法,这里有几点注意的:

    • 继承是 构造函数 对 另一个构造函数而言
    • 需要实现属性的继承,即 this的转换
    • 需要实现方法的继承,一般就是指 原型链的构建

    继承的实现
    基于上面的3点要素,我们先直接来看代码:

    // code-04
       // 父级 函数
        function human(name) {
          this.name = name;
        }
        human.prototype.sayName = function () {
          console.log("我的名字是:", this.name);
        };
    
        // 子级 函数
        function user(args) {
          this.age = args.age;
          //1.私有属性的继承
          human.call(this, args.name); 
          //2.原型的继承
          Object.setPrototypeOf(user.prototype, human.prototype); //原型继承-方法1
          // user.prototype.__proto__ = human.prototype; // 原型继承-方法2
        }
        // 因为重新赋值了prototype,所以放置 user 外部
        // user.prototype = new human();//原型继承-方法3
        // user.prototype = Object.create(human.prototype);//原型继承-方法4
    
        user.prototype.sayAge = function () {
          console.log("我的年龄是:", this.age);
        };
        let person = new human("人类");
        let xiaoming = new user({ name: "xiaoming", age: 27 });
    
        console.log("----父类-----");
        console.log(person);
        person.sayName();
    
        console.log("----子类-----");
        console.log(xiaoming);
        xiaoming.sayName();
        xiaoming.sayAge();
    
    

    我们先来看下打印的结果:

    从打印结果,我们可以看到xiaoming拥有person的属性和方法(name,sayName),又有自己私有的属性方法(age,sayAge),这是因为构造函数user实现了对human的继承。
    其实实现的方法无非也就是我们前面有说到的 作用域的改变和原型链的构造,其中作用域的改变(this指向的改变)主要是两个方法:call和apply,原型链的构造原理只有一个,就是对象的原型等于其构造函数的prototype属性,但是实现方法有多种,代码code-04中有列出4种。
    从上面的例子来看原型链的指向是:xiaoming->user.prototype->human.prototype

    5.class和extends

    我们可能有看到一些代码直接用 class 和 extends关键字来实现类和继承,其实这是ES6的语法,其实是一种语法糖,本质上的原理也是相同的。我们先来看看基本用法:
    用法

    //code-05
       class human {
          //1.必须要有构造函数
          constructor(name) {
            this.name = name;
          }//2.不能有逗号`,`
          sayName() {
            console.log("sayName:", this.name);
          }
        }
    
        class user extends human {
          constructor(params) {
            //3.子类必须用`super`,调用父类的构造函数
            super(params.name);
            this.age = params.age;
          }
          sayAge() {
            console.log("sayAge:", this.age);
          }
        }
    
        let person = new human("人类");
        let xiaoming = new user({ name: "xiaoming", age: 27 });
    
        console.log("----<human> person-----");
        console.log(person);
        person.sayName();
    
        console.log("----<user> xiaoming-----");
        console.log(xiaoming);
        xiaoming.sayName();
        xiaoming.sayAge();
    

    执行结果:

    我们看到执行的结果和上面的代码code-04是一样的,但是代码明显清晰了很多。几个注意的地方:

    • class类中必须要有构造函数constructor,
    • class类中的函数不能用 ,分开
    • 如果要继承父类的话,在子类的构造函数中,必须先执行 super来调用的父类的构造函数

    相同
    上面有说class的写法其实原理上和上面是一样的,我们来验证一下

    1. 首先看看userhuman是什么类型

      这里看出来了,所以虽然被class修饰,本质上还是函数,和代码code-04中的user,human函数是一样的

    2. 再来看看prototype属性

      这里看出来sayName,sayAge都是定义在human.prototypeuser.prototype上,和代码code-04中也是一样的

    3. 我们再来看看原型链

      这与代码code-04中的原型链的指向也是一样:xiaoming->user.prototype->human.prototype

    差异
    看完相同点,现在我们来看看不同点:

    1. 首先写法上的不同
    2. class声明的函数,必须要用new调用
    3. class内部的成员函数没有prototype属性,不可以用new调用
    4. class 内的代码自动是严格模式
    5. class声明不存在变量提升,这一点和 let一样,比如:
      //code-06
      console.log(name_var);
      var name_var = "xiaoming";
      //undefined,不会报错,var声明存在变量提升
      
      console.log(name_let);
      let name_let = "xiaoming";
      // Uncaught ReferenceError: Cannot access 'name_let' before initialization
      //报错,let声明不存在变量提升
      
      new user();
      class user {}
      // Uncaught ReferenceError: Cannot access 'user' before initialization
      //报错,class声明不存在变量提升
      
      
    6. class内的方法都是不可枚举的,比如:
        //code-07
        class human_class {
        constructor(name) {
          this.name = name;
        }
        sayName() {
          console.log("sayName:", this.name);
        }
      }
      function human_fun(name) {
        this.name = name;
      }
      human_fun.prototype.sayName = function () {
        console.log("sayName:", this.name);
      };
      console.log("----------human_class-----------");
      console.log("prototype属性", human_class.prototype);
      console.log("prototype 枚举", Object.keys(human_class.prototype));
      
      console.log("----------human_fun-----------");
      console.log("prototype属性", human_fun.prototype);
      console.log("prototype 枚举", Object.keys(human_fun.prototype));
      
      运行结果:

    6.总结

    简单总结一下:

      • 每个对象在创建的时候,会被赋予一个__proto__属性,它指向创建这个对象的构造函数的prototype,而prototype本身也是对象,所以也有自己的__proto__,这就形成了原型链,最终的指向是 Object.prototype.__proto__ == null
      • 可以通过new,Object.create(),Object.setPrototypeOf(),直接赋值__proto__等方法为一个对象指定原型
      • new操作符实际做的工作是:创建一个对象,把这个对象作为构造函数的this环境,并把这个对象的原型(proto)指向构造函数的prototype,最后返回这个对象
      • 继承主要实现的功能是:this指向的绑定,原型链的构建
      • ES6的语法classextends可以提供更为清晰简洁的写法,但是本质上的原理大致相同
  • 相关阅读:
    【转】36个经典的JQuery导航菜单演示+下载
    【转】ASP.NET 3.5 开发范例精讲精析读书笔记
    【转】js弹出框详解
    【转】谈谈三层架构中MODEL的作用
    【转】制作.net实体类生成器(1)
    ASP.NET开发实战宝典.pdf配套视频源码iso| 开发实战宝典pdf和配套视频源码iso
    【转】if (!IsPostBack)
    jquery
    取web.config 连接字符串
    js接收传递过来的参数
  • 原文地址:https://www.cnblogs.com/ma13461749958/p/14248405.html
Copyright © 2020-2023  润新知