JS面向对象编程 OOP

面向对象编程OOP

​ 面向对象是把事务分解成一个个*对象*,以对象功能来划分问题,而不是步骤,然后由对象之间分工合作。

​ 具有灵活、代码可复用、容易维护,更适合多人合作的大型项目。

​ 面向对象编程的特性:封装性、继承性、多态性

​ 面向对象的两个概念:

对象
class关键字声明类一个具体的事物,由属性和方法组成
抽取对象公共部分封装成一个类,泛指某一大类(class)特指某一个,通过类实例化一个具体的对象
  • 封装 – 构造函数和原型

    1. 构造函数创建对象

      构造函数是Person() ;person1是实例对象;原型对象是Person.prototype;

      function Person(name) {
      	this.name = name;
      }
      var person1 = new Person('xiaoming');
      var person2 = new Person('xiaoli');
      
    2. 构造函数中的属性和方法成为成员,成员可以添加
    静态成员实例成员
    在构造函数本身上添加的成员构造函数内部通过this添加的成员
    只能通过构造函数来访问只能通过实例化对象来访问
    function Star(uname,age) {
        this.uname = uname;
        this.age = age; //通过this添加的实例成员
        this.sing = function() {
            console.log('我会唱歌');
        }
    }
    var xzq = new Star('薛之谦', 18);
    console.log(Star.uname) //undefined
    console.log(xzq.uname);//只能通过实例化对象来访问uname实例成员
    Star.sex = '男'; //在构造函数本身上添加,sex 静态成员 
    console.log(xzq.sex) //undefined 不能通过对象访问
    console.log(Star.sex) //男 只能通过构造函数来访问
    
    1. 构造函数存在浪费内存问题
      function Star(uname,age) {
          this.uname = uname;
          this.age = age;
          //方法放在构造函数里面,每一个实例对象就会为这个方法单独开辟内存空间,造成内存浪费
          this.sing = function() {
              console.log('我会唱歌'); 
          }
      }
      var xzq = new Star('薛之谦',19);
      var zls = new Star('赵露思',18);
      console.log(xzq.sing === zls.sing)//false 如下图,同一个函数存放的地址不一样
      

    2. 原型对象prototype(共享方法)
      原型对象作用
      prototype共享方法,减少内存开辟
      • 每一个构造函数都有一个prototype对象,这个对象的所有属性和方法都被构造函数所拥有
      • 把那些不变的方法,直接定义在prototype对象上,这样所有的实例对象都可以共享此方法,减少占用内存
      • 一般情况,公共 属性 定义到 构造函数 里面,公共 方法 定义在 原型对象 身上!!!
      function Star(uname,age) {
          this.uname = uname;
          this.age = age;
          //this.sing = function() {
              //console.log('我会唱歌');
          //}
      }
      //sing()定义在原型对象上,每个实例对象都共享这个方法
      Star.prototype.sing = function() {
          console.log('我会唱歌');
      }
      var xzq = new Star('薛之谦',19);
      var zls = new Star('赵露思',18);
      console.log(xzq.sing === zls.sing) //true 都通过原型对象查找sing,指向同一个地址
      
      • 扩展内置对象方法

        为Array函数添加自定义方法属性,通过在原型对象上.xxx添加
        Array.prototype.sum = function() {
            var sum = 0;
            for(var i = 0;i<this.length; i++) {
                sum += this[i];
            }
        	return sum;
        }
        var arr = new Array(1,2,3) //或者=[1,2,3]
        console.log(arr.sum())//6
        
    3. 对象原型 __ proto __
      • 为什么实例对象可以访问构造函数原型对象的属性方法呢?

        因为每个对象都有一个 proto 属性指向创建它的构造函数的原型,如下:

      ldh1.__proto__ == Star.prototype;
      ldh2.__proto__ == Star.prototype;
      //如果实例对象没有这个方法,则去查找构造函数的原型对象
      
      //Star本身是一个构造"函数",那么创建它的构造函数就是一个Function,所以
      Star.__proto__ == Function.prototype;
      

    在这里插入图片描述

    1. constructor 属性
      • 每个对象都有一个constructor属性,指回构造函数本身,如下:
      实例对象:
      person1.constructor == Person;
      person2.constructor == Person;
      
      • Person.prototype 是Person的原型"对象",所以这个对象也有constructor属性,同样指向Person
      原型对象:
      Person.prototype.constructor == Person;
      
        • 作用:记录该对象的构造函数是哪一个,可以用person1.constructor.name 找到构造函数

          它可以让原型对象重新指向原来的构造函数

        function Star() {
            this.name = 'oo';
        }
        Star.prototype = { //修改原型对象
            sing: function () {
                console.log('唱歌');
            }
        }
        var a = new Star();
        var b = new Star();
        console.log(a.constructor == Star) // false
        //由于Star.prototype原型对象被重新赋值,原本的constructor属性没有了,无法指回构造函数
        

      在这里插入图片描述

      Star.prototype = {
          constructor: Star, //手动利用constructor 这个属性指回原来的构造函数
          sing: function () {
              console.log('唱歌');
          }
      }
      var a = new Star();
      console.log(a.constructor == Star) // true 指回构造函数
      

      在这里插入图片描述

      1. 构造函数、实例对象、原型对象 三者之间的关系

在这里插入图片描述
7.原型链 ( JS的成员查找机制:沿着原型链依次查找属性方法,直到Object为止null)

 - Person.prototype 是一个原型"对象",那么创建它的构建函数就是一个Object,所以

 ```javascript
 Person.prototype.__proto__ == Object.prototype;
 Object.prototype.constructor == Object
 ```

 - 原型链的终点是null :

 ```
 Object.prototype.__proto__ == null;
 ```

 - Math和JSON是以对象存在的,  null没有原型  :

 ```
 Math.__proto__ === Object.prototype;
 JSON.__proto__ === Object.prototype;
 null.__proto__  // Cannot read properties of null (reading '__proto__')
 ```

 - Function.prototype是唯一一个typeof Function.prototype == "function"的原型,其他构造器的原型都是object

 ```
 typeof Function.prototype == "function";
 typeof Person.prototype == "object";
 ```

 #####  *js*的*成员查找机制*(就近原则)

 ① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。

 ② 如果没有就查找它的原型(也就是_proto_指向的prototype原型对象)。

 ③ 如果还没有就查找原型对象的原型(Object的原型对象)。

 ④ 以此类推一直找到Object为止(null)。

 ⑤ _proto_对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

在这里插入图片描述

  1. 重写会导致constructor指向发生改变
function Person() {
}
Person.prototype.name = "小明";
 
person1 = new Person();
person1.constructor.prototype.name = "小花";
console.log(Person.prototype.name, person1.constructor === Person);// 小花 true
 
person1 = {//这里已经属于重写构造函数了
	getName: function(){
		console.log("获取姓名");
	}
}
console.log(Person.prototype.name);// 小花
console.log(Person) // ƒ Person() {} constructor不指向构造函数Person
console.log(person1.constructor == Person) //false
console.log(person1.constructor == Object); //true ƒ Object() { [native code] }
  • 继承

  1. 父构造函数继承属性(call)(ES6之前)

    通过call()把父的this指向子的this,实现子类型继承父类型的属性。

    function Father(uname,age) {
    	//this指向父构造函数的实例对象;
    	this.uname = uname;
    	this.age = age;
    }
    function Son(uname,age,sex) {
    	//this指向子构造函数的实例对象;
    	Father.call(this,uname,age);
    	this.sex = '男';
    }
    var son = new Son('薛之谦',18,'男');
    console.log(son);
    

    在这里插入图片描述

    Father.call(this,uname,age)调用之后把父亲的this换成了son的this:

    在这里插入图片描述

  2. 原型对象继承(ES6之前)
    function Super(){
    }
    Super.prototype.money = function() {
        console.log('我有钱');
    };
    function Sub(){};
    //将父类型的实例对象作为子类型的原型对象,继承父亲的方法
    Sub.prototype = new Super(); //不能用 Sub.prototype = Super.prototype直接赋值,如修改了子原型对象,父原型对象也回跟着变化!!!
    
    Sub.prototype.constructor = Sub; //由于修改了原型对象,constructor需要重新指向给Sub
    var instance = new Sub;
    console.log(instance.money())//我有钱
    
3. 类的继承(ES6)
3.1 创建类
  • class本质还是function
  • 类的所有方法定义在类的prototype属性上,类创建实例对象也有__ proto __指向类的原型对象
  • ES6其实就是语法糖,写法更加清晰、方便
//通过class关键字创建类,类名首字母大写,类名不用加小括号
class Star {
	constructor(uname,age) { //可以接收传递过来的参数,同时返回实例对象
        this.uname = uname;
        this.age = age; //this指向创建的实例对象
    }
}
//类中添加方法:
Star.prototype.sing = function(song) {
    console.log(this.uname + song); //this指向sing方法调用者,即实例对象
}
//利用类创建实例对象 new不能省略
var xzq = new Star('薛之谦', 40);
var zls = new Star('赵露思', 25);
//只要new生成实例,就会自动调用constructor(),如果我们不写这个函数,类也会自动生成
console.log(xzq.uname) //通过this拿到
//类里面所有函数不需要写function,不需要逗号隔开
xzq.sing('无数');//薛之谦无数
zls.sing('有你在');
3.2 类的extends继承
class Father {
    constructor(x,y) {
        this.x = x;
        this.y = y;
    }
}
Father.prototype.sum = function() {
        console.log(this.x + this.y); //this指向sum方法调用者,即实例对象
    }
1.super关键字 调用父类构造函数
class Son extends Father {
    constructor(x,y) { //接收到的参数是new Son的
        super(x,y); //在子类构造函数constructor中
        			//通过super调用父类的构造函数,把参数传递给父类的constructor
    }
}
var son = new Son(1,2);
son.sum();//3 通过super可把子类参数传给父类并调用方法
2.super关键字 调用父类普通函数
class Father {
	say() {
		return '我是爸爸'}
}
class Son extends Father {
	say() {
    //super.say()就是调用父类的普通函数say()
	console.log(super.say() + '的儿子'); 
	}
}
var son = new Son();
son.say(); //我是爸爸的儿子
-- 继承中的属性或方法查找,就近原则;实例化子类输出一个方法,先看子类没有再去查找父类
  • 多态性

    同一个方法,在不同的对象中,有不同的表现,就是多态。

    • 必须要有继承
    • 必须要有方法的重写
3.子类继承父类方法同时扩展自己的方法
class Father {
    constructor(x,y) {
        this.x = x;
        this.y = y;
    }
    sum() {
        console.log(this.x + this.y); //this指向的是类的constructor
    }
}
//子类继承父类加法 同时 扩展减法方法
class Son extends Father {
	constructor(x,y) {
		super(x,y); //子继承父类后,必须先调用父类构造函数才能使用子类构造函数constructor
        			// 即 super必须在子类this之前调用
		this.x = x;
		this.y = y;
	}
	subtract() {
		console.log(this.x - this.y);
	}
}
class Son2 extends Father {
	constructor(x,y) {
		super(x,y); //子继承父类后,必须先调用父类构造函数才能使用子类构造函数constructor
        			// 即 super必须在子类this之前调用
		this.x = x;
		this.y = y;
	}
	multiply() {
		console.log(this.x * this.y);
	}
}
var son = new Son(5,3);
var son2 = new Son2(1,3);
son.subtract(); //2
son.sum(); //8
son2.sum(); //4
son2.multiply()//3
3.3 使用类的注意事项
<button>点击<button>
var that;//声明一个全局变量
class Star {
	constructor(uname,age) { 
        //3.constructor里面的this指向的是 创建的实例对象
        that = this; //将this保存给全局变量that
        this.uname = uname;
        this.age = age; 
        //this.sing(); //2.类里面共有的属性和方法,一定要加this使用
        this.btn = document.querySelector('button');
        this.btn.onclick = this.sing;
    }
    sing() {
        //4.方法里面的this指向的是 这个方法的调用者
        console.log(this.uname);//sing方法里面的this指向的是btn,则是undefined
        console.log(that.uname);//薛之谦 that里面存储的是constructor里面的this
    }
    dance() {
        //这个dance方法里面的this 指向的是实例对象 因为实例对象xzq调用了这个函数
    }
}
var xzq = new Star('薛之谦');//1.ES6中类没有变量提升,必须先定义类,才能通过类实例化对象
xzq.dance();//实例对象调用dance
  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值