原型链相关介绍

784 阅读3分钟

一.原型链

1.什么是原型链:

每一个对象都有自己的原型,而原型也是对象,也有自己的原型,以此类推而形成的链式结构就叫做原型链

由于每个对象都有原型,这样就形成了一个关联一个,层层相互依赖的从属关系,我们把它们叫做原型链;通过这种机制,让对象得以使用原型中的属性和方法,并凭借原型链一层一层的按顺序继承,让对象能拥有原型链上所有原型的功能,这就是JavaScript对象背后的运作机制。

补充: 在JavaScript中,几乎每个原型链的末端都会是Object,并最后指向到null

2.原型指针

  • prototype:

prototype属性,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象; 这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象);

  • proto:

proto 是原型链查询中实际用到的,它总是指向 prototype,换句话说就是指向构造函数的原型对象,它是对象独有的。 注意,为什么Foo构造也有这个属性呢,因为在js的宇宙里万物皆对象,包括函数;

3.代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <script>
        /* 
        1.原型链 : 每一个对象都有自己的原型, 而原型也是对象,
        也会有自己的原型,此次类推形成链式结构。称之为原型链。(原型链的终点是null)
        2.对象访问原型链规则 : 就近原则
        * 对象先访问自己的,自己没有就找原型的,原型没有就找原型的原型,
        一直到原型链终点null.如果还找不到。  属性则获取undefined, 
        方法则会报错 xxx is not a function
        */

        //1.构造函数
        /* function Person(name, age) {
            this.name = name
            this.age = age
            //    this.type = '学生'//如果自己有type,优先访问自己的
        }

        //2.原型对象 : 存储具有共同特征的数据
        Person.prototype.type = '哺乳动物'
        Person.prototype.country = '中国'
        Person.prototype.eat = function () {
            console.log(this.name + '吃东西')
        }

        //3.实例对象
        let p1 = new Person('张三', 20)
        let p2 = new Person('李四', 20)
        console.log(p1)


        /* 小测试 */
        /* console.log(p1.name)//张三  p1自己有name属性
        console.log(p1.age)//20   p1自己有age
        console.log(p1.type)//哺乳动物  p1自己没有type,但是p1的原型有
        console.log(p1.girlFriend)//undefined  p1自己没有girlFriend, 
        p1的原型也没有girlFriend

        p1.eat()// 吃东西 */
        //    p1.learn()//报错   undefined is not a function
        /* 思考:  p1自己没有toString, p1的原型也没有toString, 但是为什么不报错呢?
        原因:  p1的原型的原型有toString
        */
        /*   p1.toString()
  
          /* 如何查看实例对象原型 : 两行 */

        //查看p1的原型
        // console.log(p1.__proto__.constructor)//Person
        // console.log(Person.prototype === p1.__proto__)//true
        // //查看p1的原型的原型
        // console.log(p1.__proto__.__proto__.constructor)//Object
        // console.log(Object.prototype === p1.__proto__.__proto__)//true
        // //查看p1的原型的原型的原型
        // console.log(p1.__proto__.__proto__.__proto__)//null */ */


       /* 1.原型链:每一个实例对象都有自己的原型,而原型也是对象,
           也有自己的原型,以此类推形成链式结构,称之为原型链
          2.对象访问原型链规则:就近原则
           对象优先访问自己的属性,自己没有才会访问原型,
           原型也没有就访问原型的原型,以此类推直到原型链终点(null),
           如果还没有,属性则获取undefined,方法则报错 xxx is not a function */
        
        // 构造函数
        function Person(name, age) {
            this.name = name
            this.age = age
        }
        // 原型
        Person.prototype.type = '哺乳动物'
        Person.prototype.country = '中国'
        Person.prototype.eat = function () {
            console.log('吃东西')
        }
        // 实例对象
        let p1 = new Person('张三', 20)
        console.log(p1)//Person {name: '张三', age: 20}
        // 请说出下列代码运行结果
        console.log(p1.name)//张三,p1有name属性
        console.log(p1.type)//哺乳动物,p1没有type,但是p1的原型有
        console.log(p1.girlFriend)//undefined,p1没有这个属性,
        而且p1的原型也没有这个属性
        p1.eat()//吃东西,p1自己没有eat,但是p1的原型有
        // p1.learn()//报错,报错原因,p1没有learn,取出来undefined
        // 查找p1的原型
        console.log(p1.__proto__.constructor)//person
        console.log(p1.__proto__=== Person.prototype)//true

         // 查找p1的原型的原型
         console.log(p1.__proto__.__proto__.constructor)//object
        console.log(p1.__proto__.__proto__=== Object.prototype)//true
    </script>
</body>

</html>

效果如下:

image.png

二.内置对象原型链

1.内置对象:js语言自带的一些对象,提供了一些简单或者是基本的必要功能(属性和方法)
2.特点:方便快速开发;
js提供多个内置对象:math、data、array、string等等

3.代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <script>
        // 数组对象  
        //实例化对象
        /*  let arr = [10,20,30]//new Array(10,20,30)
         console.log( arr )
         //1.1 查看arr的原型
         console.log( arr.__proto__.constructor )//Array
         console.log( arr.__proto__ === Array.prototype )//true
         //1.2 查看arr的原型的原型
         console.log( arr.__proto__.__proto__.constructor )//Object
         console.log( arr.__proto__.__proto__ === Object.prototype )//true
         
         // 字符串对象
         let str = new String('abc')
         console.log( str )
         //2.1 查看str的原型
         console.log( str.__proto__.constructor )//String
         console.log( str.__proto__ === String.prototype )//true
         //2.2 查看arr的原型的原型
         console.log( str.__proto__.__proto__.constructor )//Object
         console.log( str.__proto__.__proto__ === Object.prototype )//true
         
         // 日期对象
         let date = new Date()
         /* js有几个特殊的对象, 无法使用 log来打印的,
         需要用dir来打印: function date dom对象 */
        /*  console.dir( date )
         //3.1 查看date的原型
         console.log( date.__proto__.constructor )//Date
         console.log( date.__proto__ === Date.prototype )//true
         //3.2 查看date的原型的原型
         console.log( date.__proto__.__proto__.constructor )//Object
         console.log( date.__proto__.__proto__ === Object.prototype ) *///true */

        
        // 1.查看str的原型链
        let str = new String('abc')
        console.log(str)
        // 2.查看str的原型
        console.log(str.__proto__.constructor)//ƒ String() { [native code] }
        console.log(str.__proto__ === String.prototype)//true
        // 3. 查看str的原型的原型
        console.log(str.__proto__.__proto__.constructor)//ƒ Object() { [native code] }
        console.log(str.__proto__.__proto__ === Object.prototype)//true
        // 4.查看date的原型链
        // 细节:js中有部分对象无法用log来打印的:dom对象,函数,日期,
        如果想查看堆内存结构,就可以使用console.dir()
        let date = new Date()
        console.dir(date)
    </script>
</body>

</html>

效果如下:

image.png

三.instanceof运算符

1.概念:

对象运算符(instanceof)用来判断一个对象是否属于某个指定的类或其子类的实例,如果是,返回真(true),否则返回假(false)。

2.代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <script>
        /* 
        1. instanceof(关键字): 运算符。 
        用于检测 构造函数的prototype在不在实例对象的原型链中
            简单来说: 亲子鉴定,鉴定两个对象之间有没有血缘关系
        2.  实例对象 instanceof 构造函数
            检测 右边构造函数的prototype 在不在 左边实例对象的原型链中
        
        3. 应用 :  某些函数为了限制你的数据类型,
        在内部需要用instanceof进行判断是否是正确的数据类型
        */

        let arr = [10, 20, 30]
        // arr-> Array.prototype -> Object.prototype -> null
        console.log(arr instanceof Array)//true
        console.log(arr instanceof Object)//true
        console.log(arr instanceof String)//false

        //封装一个函数,要求这个函数必须要传数组类型、 传其他类型不可以
        function fn(arr) {
            if (arr instanceof Array) {
                console.log(arr.reverse())
            } else {
                console.log('数据类型错误')
            }
        }

        fn([10, 20, 30])
        fn('abc')



    </script>
</body>

</html>

效果如下:

image.png

四.原型链应用:封装模态框

代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>面向对象封装消息提示</title>
  <style>
    .modal {
      width: 300px;
      min-height: 100px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
      border-radius: 4px;
      position: fixed;
      z-index: 999;
      left: 50%;
      top: 50%;
      transform: translate3d(-50%, -50%, 0);
      background-color: #fff;
    }

    .modal .header {
      line-height: 40px;
      padding: 0 10px;
      position: relative;
      font-size: 20px;
    }

    .modal .header i {
      font-style: normal;
      color: #999;
      position: absolute;
      right: 15px;
      top: -2px;
      cursor: pointer;
    }

    .modal .body {
      text-align: center;
      padding: 10px;
    }

    .modal .footer {
      display: flex;
      justify-content: flex-end;
      padding: 10px;
    }

    .modal .footer a {
      padding: 3px 8px;
      background: #ccc;
      text-decoration: none;
      color: #fff;
      border-radius: 2px;
      margin-right: 10px;
      font-size: 14px;
    }

    .modal .footer a.submit {
      background-color: #369;
    }
  </style>
</head>

<body>
  <button id="btn1">消息提示1</button>
  <button id="btn2">消息提示2</button>
  <button id="btn3">消息提示3</button>
  <button id="btn4">消息提示4</button>

  <!-- <div class="modal">
      <div class="header">提示消息 <i>x</i></div>
      <div class="body">消息内容</div>
      <div class="footer">
        <a href="javascript:;" class="cancel">取消</a>
        <a href="javascript:;" class="submit">确认</a>
      </div>
    </div> -->


  <script>
    //1. 模态框构造函数
    function Modal(title, message) {
      this.title = title
      this.message = message
      this.modalBox = `<div class="modal">
        <div class="header">${this.title} <i>x</i></div>
        <div class="body">${this.message}</div>
        </div>`
    }

    //2. 模态框原型
    Modal.prototype.open = function () {
      //(1)创建空标签
      let div = document.createElement('div')
      //(2)设置标签内容
      div.innerHTML = this.modalBox
      //(3)添加到页面
      document.body.appendChild(div)
      //给删除按钮注册点击事件
      div.querySelector('.header i').onclick = function () {
        document.body.removeChild(div)
      }
    }

    //1.确认框构造函数
    function Confirm(title, message) {
      this.title = title
      this.message = message
      this.modalBox = ` <div class="modal">
        <div class="header">${this.title} <i>x</i></div>
        <div class="body">${this.message}</div>
        <div class="footer">
          <a href="javascript:;" class="cancel">取消</a>
          <a href="javascript:;" class="submit">确认</a>
        </div>
        </div>`
    }

    /* 为什么要继承: confirm确认框和 modal模态框 功能是一样的, 
    也要显示到页面,也要点击xx移除
    1.继承 :  一个对象 继承 另一个对象 所有的成员
    2.原型继承 :  把父对象 作为 子对象构造函数的原型
    */
    Confirm.prototype = Modal.prototype

    //继续给Confirm的原型添加自己的方法
    Confirm.prototype.addEvent = function (confirm) {
      let modal = document.querySelector('.modal')
      modal.querySelector('.submit').onclick = function () {
        //调用函数
        confirm()
        //移除模态框
        modal.parentNode.removeChild(modal)
      }
      modal.querySelector('.cancel').onclick = function () {
        //移除模态框
        modal.parentNode.removeChild(modal)
      }
    }

    //弹窗1
    document.querySelector('#btn1').onclick = function () {
        //创建模态框
        let del = new Modal('友情提示', '删除成功')
      del.open()
      console.log(del )
    }
    //弹窗2
    document.querySelector('#btn2').onclick = function () {
      //创建模态框
      let operate = new Modal('提示消息', '您没有权限操作')
      operate.open()
    }

    //弹窗3
    document.querySelector('#btn3').onclick = function () {
      //创建确认框
      let hint = new Confirm('友情提示', '您确定要删除吗?')
      console.log(hint)
      hint.open()
      //添加确认功能
      hint.addEvent(function () { alert('删除成功') })

    }
    //弹窗4
    document.querySelector('#btn4').onclick = function () {
      //创建确认框
      let hint = new Confirm('友情提示', '您确定要下单吗?')
      hint.open()
      //添加确认功能
      hint.addEvent(function () {
        alert('下单成功')
      })
    }

  </script>
</body>

</html>

五.原型继承(拓展)

1.原型继承的作用

可以解决原型中属性的复用问题,下级函数一旦继承上级函数,那么下级函数的实例,就可以使用自身原型中的属性,和上级函数原型中的属性

2.代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script>
        /* 
        1.继承 :  一个对象 拥有 另一个对象 所有的 成员
        2.原型继承 :把父对象 作为子对象构造函数的原型
        */

        //父对象
        let father = {
            house:{
                address : '深圳湾一号',
                price: 20000000
            },
            car:{
                brand : '劳斯莱斯幻影',
                price:15000000
            }
        }

        //子对象
        //构造函数
        function Son(name,age){
            this.name = name
            this.age = age
        }
        //原型继承: 把父对象 作为子对象构造函数的原型
        Son.prototype = father
        //可选 : 原型继承之后,由于父对象覆盖原来的 子对象构造函数原型, 
        就会导致constructor消失.
        //解决办法: 手动添加。(对开发几乎没有影响,也可以不加)
        Son.prototype.constructor = Son


        //实例对象
        let s1 = new Son('张三',30)
        let s2 = new Son('李四',20)
        console.log(s1,s2)
        
        
    </script>
</body>
</html>

效果如下:

image.png

六.总结

  • 原型链:从一个实例对象往上找构造这个实例的相关联的对象,然后这个关联的对象再往上找它又有创造它的上一级的原型对象,以此类推,一直到 Object.prototype 原型对象终止,这个链条就断了,也就是说 Object.prototype 是整个原型链的顶端。通过什么往上找?就是通过 prototype 和 (两个下划线)proto(两个下划线).
  • 原型:构造函数都有一个 prototype 属性,这是在声明一个函数时js 自动增加的,这个 prototype 指的就是原型对象。原型对象怎么区分被哪个构造函数引用?就是通过 constructor(构造器),原型对象中会有一个构造器,这个构造器会默认声明的那个函数。
  • 构造函数:任何一个函数只要被 new 去操作(使用)就是构造函数,构造函数也是函数
  • 实例:只要是对象就是一个实例
  • 构造函数可以使用new 生成实例