JavaScript 进阶3

80 阅读5分钟

JavaScript 进阶3

image.png

image.png

一、编程思想

  • 面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
  • 面向对象是以对象功能来划分问题,而不是步骤。 面向对象编程是一种程序设计思想,它具有 3 个显著的特征:封装、继承、多态。

image.png

二、构造函数

  1. 构造函数体现了面向对象的封装特性
  2. 构造函数实例创建的对象彼此独立、互不影响
  3. 命名空间式的封装无法保证数据的独立性
  4. 构造函数存在浪费内存的问题
  • 封装

封装的本质是将具有关联的代码组合在一起,其优势是能够保证代码复用且易于维护,函数是最典型也是最基础的代码封装形式,面向对象思想中的封装仍以函数为基础,但提供了更高级的封装形式。

<script>

<!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>
</head>

<body>
  <script>
    // 构造函数  公共的属性和方法 封装到 Star 构造函数里面了
    // 1.公共的属性写到 构造函数里面
    function Star(uname, age) {
      this.uname = uname
      this.age = age
      //命名空间--以往以普通对象(命名空间)形式封装的代码只是单纯把一系列的变量或函数组合到一起,所有的数据变量都被用来共享(使用 this 访问)。
      // this.sing = function () {
      //   console.log('唱歌')
      // }
    }
    
//通过面向对象的构造函数实现的封装:
// 2. 公共的方法写到原型对象身上   节约了内存
    Star.prototype.sing = function () {
      console.log('唱歌')
    }
    const ldh = new Star('刘德华', 55)
    const zxy = new Star('张学友', 58)
    ldh.sing() //调用
    zxy.sing() //调用
    // console.log(ldh === zxy)  // false
    console.log(ldh.sing === zxy.sing)

    // console.dir(Star.prototype)
  </script>
</body>

</html> 
</script>
  • 构造函数和原型的this指向

<!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>构造函数和原型的this指向</title>
</head>

<body>
  <script>
    let that
    function Star(uname) {
      // that = this
      // console.log(this)
      this.uname = uname
    }
    // 原型对象里面的函数this指向的还是 实例对象 ldh
    Star.prototype.sing = function () {
      that = this
      console.log('唱歌')
    }
    // 实例对象 ldh   
    // 构造函数里面的 this 就是  实例对象  ldh
    const ldh = new Star('刘德华')
    ldh.sing()
    console.log(that === ldh)
  </script>
</body>

</html>
  • 同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的。

🍉:

  1. Js 实现面向对象需要借助于谁来实现? 构造函数

  2. 构造函数存在什么问题? 浪费内存

三、 原型对象

image.png

3.1 原型

  1. 构造函数通过原型分配的函数是所有对象所共享的。

  2. JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象

  3. 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存

  4. 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

image.png

了解了 JavaScript 中构造函数与原型对象的关系后,再来看原型对象具体的作用,如下代码所示:

<script>
  function Person() {
    // 此处未定义任何方法
  }
​
  // 为构造函数的原型对象添加方法
  Person.prototype.sayHi = function () {
    console.log('Hi~');
  }
    
  // 实例化
  let p1 = new Person();
  p1.sayHi(); // 输出结果为 Hi~
</script>

构造函数 Person 中未定义任何方法,这时实例对象调用了原型对象中的方法 sayHi,接下来改动一下代码:

<script>
  function Person() {
    // 此处定义同名方法 sayHi
    this.sayHi = function () {
      console.log('嗨!');
    }
  }
​
  // 为构造函数的原型对象添加方法
  Person.prototype.sayHi = function () {
    console.log('Hi~');
  }
​
  let p1 = new Person();
  p1.sayHi(); // 输出结果为 嗨!
</script>

构造函数 Person 中定义与原型对象中相同名称的方法,这时实例对象调用则是构造函数中的方法 sayHi

通过以上两个简单示例不难发现 JavaScript 中对象的工作机制:当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享。

<script>
    function Person() {
    // 此处定义同名方法 sayHi
    this.sayHi = function () {
      console.log('嗨!' + this.name);
    }
  }
​
  // 为构造函数的原型对象添加方法
  Person.prototype.sayHi = function () {
    console.log('Hi~' + this.name);
  }
  // 在构造函数的原型对象上添加属性
  Person.prototype.name = '小明';
​
  let p1 = new Person();
  p1.sayHi(); // 输出结果为 嗨!
  
  let p2 = new Person();
  p2.sayHi();
</script>
  1. 原型是什么 ?
    一个对象,我们也称为 prototype 为原型对象
  2. 原型的作用是什么 ?
    共享方法
    可以把那些不变的方法,直接定义在 prototype 对象上

数组扩展最大值和求和方法


<!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>
</head>

<body>
  <script>
    // 自己定义 数组扩展方法  求和 和 最大值 
    // 1. 我们定义的这个方法,任何一个数组实例对象都可以使用
    // 2. 自定义的方法写到  数组.prototype 身上
    // 1. 最大值
    const arr = [1, 2, 3]
    Array.prototype.max = function () {
      // 展开运算符
      return Math.max(...this)
      // 原型函数里面的this 指向谁? 实例对象 arr
    }
    // 2. 最小值
    Array.prototype.min = function () {
      // 展开运算符
      return Math.min(...this)
      // 原型函数里面的this 指向谁? 实例对象 arr
    }
    console.log(arr.max())
    console.log([2, 5, 9].max())
    console.log(arr.min())
    // const arr = new Array(1, 2)
    // console.log(arr)
    // 3. 求和 方法 
    Array.prototype.sum = function () {
      return this.reduce((prev, item) => prev + item, 0)
    }
    console.log([1, 2, 3].sum())
    console.log([11, 21, 31].sum())
  </script>
</body>

</html>

3.2 constructor属性

  1. 在哪里? 每个原型对象里面都有个constructor 属性(constructor 构造函数)

  2. 作用:该属性指向该原型对象的构造函数, 简单理解,就是指向我的爸爸,我是有爸爸的孩子

  3. 使用场景: 如果有多个对象的方法,我们可以给原型对象采取对象形式赋值. 但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了 此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数

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>constructor属性</title>
</head>

<body>
  <script>
    // constructor  单词 构造函数

    // Star.prototype.sing = function () {
    //   console.log('唱歌')
    // }
    // Star.prototype.dance = function () {
    //   console.log('跳舞')
    // }
    function Star() {
    }
    // console.log(Star.prototype)
    Star.prototype = {
      // 从新指回创造这个原型对象的 构造函数
      constructor: Star,
      sing: function () {
        console.log('唱歌')
      },
      dance: function () {
        console.log('跳舞')
      },
    }
    console.log(Star.prototype)
    // console.log(Star.prototype.constructor)

    // const ldh = new Star()
    // console.log(Star.prototype.constructor === Star)
  </script>
</body>

</html>

3.2 对象原型

对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在。

注意:

  1. proto 是JS非标准属性
  2. [[prototype]]和__proto__意义相同
  3. 用来表明当前实例对象指向哪个原型对象prototype
  4. __proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数

如下图所示:

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>Document</title>
</head>

<body>
  <script>
    function Star() {

    }
    const ldh = new Star()
    // 对象原型__proto__ 指向 改构造函数的原型对象
    console.log(ldh.__proto__)
    // console.log(ldh.__proto__ === Star.prototype)
    // 对象原型里面有constructor 指向 构造函数 Star
    console.log(ldh.__proto__.constructor === Star)

  </script>
</body>

</html>
  1. prototype是什么?哪里来的? 原型(原型对象) 构造函数都自动有原型

  2. constructor属性在哪里?作用干啥的? prototype原型和对象原型__proto__里面都有 都指向创建实例对象/原型的构造函数

  3. __proto__属性在哪里?指向谁?在实例对象里面 指向原型 prototyp

3.3 原型继承


<!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>
</head>

<body>
  <script>
    // 继续抽取   公共的部分放到原型上
    // const Person1 = {
    //   eyes: 2,
    //   head: 1
    // }
    // const Person2 = {
    //   eyes: 2,
    //   head: 1
    // }
    // 构造函数  new 出来的对象 结构一样,但是对象不一样
    function Person() {
      this.eyes = 2
      this.head = 1
    }
    // console.log(new Person)
    // 女人  构造函数   继承  想要 继承 Person
    function Woman() {

    }
    // Woman 通过原型来继承 Person
    // 父构造函数(父类)   子构造函数(子类)
    // 子类的原型 =  new 父类  
    Woman.prototype = new Person()   // {eyes: 2, head: 1} 
    // 指回原来的构造函数
    Woman.prototype.constructor = Woman

    // 给女人添加一个方法  生孩子
    Woman.prototype.baby = function () {
      console.log('宝贝')
    }
    const red = new Woman()
    console.log(red)
    // console.log(Woman.prototype)
    // 男人 构造函数  继承  想要 继承 Person
    function Man() {

    }
    // 通过 原型继承 Person
    Man.prototype = new Person()
    Man.prototype.constructor = Man
    const pink = new Man()
    console.log(pink)
  </script>
</body>

</html>

3.4原型链

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  2. 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
  3. 如果还没有就查找原型对象的原型(Object的原型对象)
  4. 依此类推一直找到 Object 为止(null)
  5. __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路

基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对 象的链状结构关系称为原型链 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="delete">删除</button>
  <button id="login">登录</button>

  <!-- <div class="modal">
    <div class="header">温馨提示 <i>x</i></div>
    <div class="body">您没有删除权限操作</div>
  </div> -->


  <script>
//1.模态框的构造函数
function Modal(title='',message=''){
  this.title=title
  this.message=message
  this.modalBox=document.createElement('div') //会生成盒子,盒子是公共也要放进去
  this.modalBox.className='modal'
  this.modalBox.innerHTML=`
  <div class="header">${this.title} <i>x</i></div>//别忘记this
    <div class="body">${this.message}</div>
  `
}
// 打开与关闭方法
Modal.prototype.open=function(){
  document.body.appendChild(this.modalBox)
  this.modalBox.querySelector('i').addEventListener('click',()=>{   //这里要用箭头函数,才能指向m
  this.close()
  })
}
Modal.prototype.close=function(){
  document.body.removeChild(this.modalBox)
}
//2.按钮点击 
document.querySelector('#delete').addEventListener('click',()=>{
  const m=new Modal('温馨提示','您没有权限删除')
  m.open()
})
document.querySelector('#login').addEventListener('click',()=>{
  const n=new Modal('温馨提示','您登录成功啦')
  n.open()
})
  </script>
</body>

</html>