JS高级第三天

219 阅读11分钟

1.面向对象编程

面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作
面向对象的特性:
 1.封装性 2.继承性 3.多态性
面向过程编程
 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。
 缺点:没有面向对象易维护、易复用、易扩展
面向对象编程
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系   统,使系统 更加灵活、更加易于维护

 缺点:性能比面向过程低
案例说明
    <body>
         <div>div1</div>
         <div>div2</div>
         <div>div3</div>
      <p>p1</p>
      <p>p2</p>
      <p>p3</p>
      <p>p4</p>
<script>
  /* 
    1. 面向对象 : 是 一种注重结果的解决问题 思维方式
        面向过程 : 注重的是过程
        面向对象 : 注重的是结果

    2. 面向对象 本质 是对面向过程的封装
    */

  //需求: 给页面每一个div标签 和 p标签 设置颜色 和 边框

  /* 1.面向过程 : 注重过程 
    特点:代码冗余
    */

  //1.1 获取元素
  let divList = document.querySelectorAll("div")
  let pList = document.querySelectorAll("p")
  //1.2 遍历数组
  //   for (let i = 0; i < divList.length; i++) {
  //     divList[i].style.backgroundColor = "red"
  //     divList[i].style.border = "2px solid cyan"
  //   }

  //   for (let i = 0; i < pList.length; i++) {
  //     pList[i].style.backgroundColor = "blue"
  //     pList[i].style.border = "5px solid purple"
  //   }

  /* 2.面向对象
    函数封装 : 把冗余代码放入函数中(弊端:全局变量污染)
        全局变量污染 : 变量名太多,导致增加重名的风险,代码会被覆盖
    对象封装: 把函数作为对象的方法
  */
  let obj = {
    setColor: function(list, color) {
      for (let i = 0; i < list.length; i++) {
        list[i].style.backgroundColor = color
      }
    },
    setBorder: function(list, border) {
      for (let i = 0; i < list.length; i++) {
        list[i].style.backgroundColor = border
      }
    }
  }

  obj.setColor( divList, "red")
  obj.setColor(pList, "cyan")
</script>
</body>

image.png

2.原型

 构造函数通过原型分配的函数是所有对象所 共享的
 JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象

image.png

3.给数组扩展求最大值方法,最小值方法和求和方法

 <script>
     const arr = [1, 2, 3]
      Array.prototype.max = function () {
      //原型函数的this指向实例对象arr
      return Math.max(...this)
    }
   Array.prototype.min = function () {
    //原型函数的this指向实例对象arr
     return Math.min(...this)
    }
     console.log(arr.max()) // 3
     console.log(arr.min()) //  1
     console.log([2, 5, 9].max()) //  9
//求和
    Array.prototype.sum = function () {
       //原型函数的this指向实例对象arr
    return this.reduce((prev, item) => prev + item, 0)
  }
   console.log([2, 5, 9].sum()) //  16
</script>
求最大值方法,最小值方法和求和方法
  <script>
//在原型对象上添加一个getMax()方法
//求最大值 
Array.prototype.getMax = function () {
  //console.log(this); this就调用的是arr
  return Math.max(...this)
}
let arr = [10, 20, 30] //等效于 let arr = new Array(10,20,30)
let ret = arr.getMax()
console.log(ret); //30
//求最小值
Array.prototype.getMin = function () {
  return Math.min(...this)
}
let arr2 = [10, 20, 90]
let ret2 = arr2.getMin()
console.log(ret2); // 10
//求和
Array.prototype.getSum = function () {
  return this.reduce(function (pre, item) {
    return item + pre
  }, 0)
}
let arr1 = [10, 20, 90]
let ret1 = arr1.getSum()
console.log(ret1); // 120
</script>

4.对象原型

 __proto__ 是JS非标准属性
 [[prototype]]和__proto__意义相同
用来表明当前实例对象指向哪个原型对象prototype
__proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype
 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在
 

image.png

5.原型对象

语法:
  function Person() {

 }

 // 每个函数都有 prototype 属性
 console.log(Person.prototype);
原型对象具体的作用,如下代码所示:
 function Person() {
// 此处未定义任何方法
 }

 // 为构造函数的原型对象添加方法
 Person.prototype.sayHi = function () {
 console.log('Hi~');
  }

 // 实例化
 let p1 = new Person();
  p1.sayHi(); // 输出结果为 Hi~
构造函数 Person 中定义与原型对象中相同名称的方法,这时实例对象调用则是构造函中的方法 `sayHi
当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享。
 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(); 

image.png

6.原型继承

 // 继续抽取   公共的部分放到原型上
// 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)

image.png

// Person 构造函数
 function Person() {
this.arms = 2;
this.walk = function () {}
 }

 // Person 原型对象
    Person.prototype.legs = 2;
    Person.prototype.eyes = 2;
    Person.prototype.sing = function () {}
    Person.prototype.sleep = function () {}

  // Chinese 构造函数
     function Chinese() {
     this.skin = 'yellow';
     this.language = '中文';
   }

   // Chinese 原型对象
      Chinese.prototype = new Person();
      Chinese.prototype.constructor = Chinese;

  // 实例化
    let c1 = new Chinese();
    console.log(c1);



 // 将Man与Woman共有设置在Human中
    function Human(){
        this.head = 1;
        this.eyes = 2;
        this.legs = 2;
        this.say = function(){console.log('hello');}
        this.eat = function(){}
    }
    // 对Human进行实例化,得到实例obj_M
    let obj_M = new Human()
    console.log(obj_M);
    function Man(){  
    }
    // 再使用obj_M对象,替换Man的默认原型
    Man.prototype = obj_M;
    let oM1 = new Man();
    oM1.say();
    let oM2 = new Man();
    oM2.say();
    let obj_W = new Human();
    function Woman(){
    }
    // 再使用obj_W对象,替换Woman的默认原型
    Woman.prototype = obj_W
    let oW1 = new Woman()
    oW1.say();
    /* 
    小结:
        用户解释:
        Human(父),Man与Woman(子)
        如何实例继承:
            使用父的实例对象,来替换子的默认原型
        使用指导:
            公共的保存到父上
            独有保存到子
###### 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('ikun',30)
        let s2 = new Son('班长',20)
        console.log(s1,s2)
    

7.原型链

 每一个对象都有自己的原型, 而原型也是对象,也会有自己的原型,此次类推形成链式结构。称之为原型链。(原型链的终点是null)
 

image.png

// 通过原型相关知识的学习,我们得知,当一个对象上访问某个成员时,如果没有则会向这个对象的原型上找。
// 当原型对象上没有时,则会继承找上一级的原型对象
// 原型链就是由原型对象形成的一个链条

    function Person(){

    }

    let p1 = new Person();

    // 当p1上访问的成员不存在是则会到p1的原型对象上找 p1.__proto__
    // 如果p1的原型对象上也没有,则会到p1的原型对象的原型对象上找
    console.log(p1.__proto__);      // 第1级原型对象
    console.log(p1.__proto__.__proto__);    // 第2级原型对象
    console.log(p1.__proto__.__proto__.__proto__);  // 第3级原型对象    null


    // 第2级原型对象 对应的是系统构造函数Object
    // 验证
    console.log(Object.prototype === p1.__proto__.__proto__);
    console.log(p1.__proto__.__proto__.constructor === Object);

    小结:
        原型链,就由原型对象形成的一个链条
        .prototype  这个属性只有函数有
        .__proto__  所有对象都有(实例对象,函数,原型对象)
原型链-查找规则
  原型链是找成员
        当一个对象访问某个成员时,如果成员不存在,则会向上一级原型对象上找,
        上一级原型对象上没有,再向上一级找,直到找到null
        并且具有就近原则
    function Person(){
        this.age = 20;
    }
    Person.prototype.age = 22;
    Person.prototype.__proto__.age = 24;
    let p1 = new Person(); 
    console.log(p1.age);
    查找规则:
        原型找成员
        作用域链,找变量
        
   //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('ikun',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.girlFrined )//undefined  p1自己没有girlFrined, p1的原型也没有girlFrined

   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       

image.png

8.instanceof 函数

  //语法: 对象 instanceof 函数
  //判断实例对象与构造函数之间的关系
  //通过的方式是,判断构造函数.prototype 是否在实例对象的原型链上
function Person() {}
let p1 = new Person()

function Mobile() {}
let m1 = new Mobile()

console.log(p1 instanceof Person); //true
console.log(p1 instanceof Mobile); //false
console.log(m1 instanceof Mobile); //true
案例
<script>
// function Object() {}
console.log(Object.prototype) // Object
console.log(Object.prototype.__proto__) //null

function Person() {

}
const ldh = new Person()
// console.log(ldh.__proto__ === Person.prototype)
// console.log(Person.prototype.__proto__ === Object.prototype)
console.log(ldh instanceof Person) // true
console.log(ldh instanceof Object) // true
console.log(ldh instanceof Array) // false
console.log([1, 2, 3] instanceof Array) // true
console.log(Array instanceof Object) // true

9. 构造函数

构造函数 公共的属性和方法 封装到Star构造函数里面
1.公共的函数写到构造函数里面
function Star(uname, age) {
  this.uname = uname
  this.age = age
}
//2.公共的方法写到原型对象上,节约了空间
Star.prototype.sing = function () {
  console.log('唱歌');
}
const ldh = new Star('刘德华', 55)
const zxy = new Star('张学友', 58)
ldh.sing() //调用
zxy.sing() //调用
console.log(ldh.sing === zxy.sing);

构造函数的this

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

*1. 构造函数内部方法: 浪费内存资源

思考: p1和p2都有eat方法,而是函数体相同。为什么他们不是同一个函数?
  function Person(name,age){
         this.name = name
         this.age = age
         this.eat = function(){
             console.log('eat');

         }
     }

     let p1 = new Person('张三',18)
     let p2 = new Person('李四',20)
     console.log( p1,p2)
     console.log( p1.eat == p2.eat )//false

 因为每一次调用构造函数, 内部都会执行一次function,就会在堆中开辟一个新的空间。
 虽然代码是一样的,但是地址不同。 就会导致每调用一次构造函数,多出一个函数堆空间。
 导致内存资源浪费

**2. 使用全局函数解决内存浪费

思考题:p1的eat和p2的eat是不是同一个?
    let eat = function() {
       console.log("吃东西")
     }
     
    let learn = function() {
       console.log("学习")
     }

     function Person(name, age) {
       this.name = name
       this.age = age
       this.eat = eat
       this.learn = learn
     }

     let p1 = new Person("张三", 18)
     let p2 = new Person("李四", 20)
     console.log(p1, p2)
     console.log( p1.eat == p2.eat )//true
     因为构造函数内部并没有重新function创建一个函数,而是拷贝eat的地址赋值。 
     无论你调用构造函数多少次,都是拷贝eat的地址
     

*** 3.使用对象 : 解决构造函数内存浪费 + 变量污染

   let obj = {
    eat: function() {
      console.log("吃东西")
    },
    learn: function() {
      console.log("学习")
    }
  }

  function Person(name, age) {
    this.name = name
    this.age = age
    this.eat = obj.eat
    this.learn = obj.learn
  }

  let p1 = new Person("张三", 18)
  let p2 = new Person("李四", 20)
  console.log(p1, p2)
  

image.png

****使用对象后,构造函数可以直接调用对象里面的函数

image.png

原型对象----prototype

1.原型对象是什么? :  任何函数在声明的时候,系统会自动帮你创建一个对象,称之为原型对象

2.原型对象作用? : 解决  内存浪费 + 变量污染

  //构造函数
  function Person(name, age) {
    this.name = name
    this.age = age
  }

  //原型对象
  console.log(Person.prototype)
  Person.prototype = {
    eat: function() {
      console.log("吃东西")
    },
    learn: function() {
      console.log("学习")
    }
  }

  //实例对象 : 用new调用构造函数,返回的那个对象(new创建的那个对象)
  let p1 = new Person('张三',20)
  console.log( p1 )

  let p2 = new Person('李四',22)
  console.log( p1.eat == p2.eat )//true
原型对象相关三个属性 : 描述 构造函数、原型对象、实例对象三者关系
prototype : 属于构造函数, 指向原型对象
   作用 : 解决  内存浪费 + 变量污染
proto : 属于实例对象,指向原型对象
   作用:  让实例对象访问原型中的成员
constructor : 属于原型对象,指向构造函数
   作用:   可以让任何实例对象知道自己是谁创建的
   例如:   console.log( p1.__proto__.constructor )//Person
    //1.构造函数
    function Person(name,age){
        this.name = name
        this.age = age
    }

    //2.原型对象
    Person.prototype.eat = function(){
        console.log('吃东西')
    }

    Person.prototype.learn = function(){
        console.log('学习')
    }
  
   console.log( Person.prototype.constructor )//Person
   
    //3.实例对象
    let p1 = new Person('张三',20)
    console.log(p1)

    /* __proto__ : 属于实例对象的,指向原型对象
    注意: 这个属性不是web标准,很多浏览器不会显示的。
          这个属性在开发中不能使用,只能用于学习研究
    */
    p1.eat()//p1.__proto__.eat()
    
    /* 验证 构造函数、原型对象、实例对象三者关系 */
    console.log( p1.__proto__.constructor )//Person
    console.log( Person.prototype === p1.__proto__ )//true
    

image.png

image.png