Loading

Js面向对象

程序就是对现实世界的抽象,一个事物抽象到程序中后就变成了对象,在程序的世界中,一切皆对象

面向对象的编程指,程序中的所有操作都是通过对象来完成

做任何事情之前都需要先找到它的对象,然后通过对象来完成各种操作

一个事物通常由两部分组成:数据和功能

  • 一个对象由两部分组成:属性和方法

  • 事物的数据到了对象中,体现为属性

  • 事物的功能到了对象中,体现为方法

const user = {
    // 添加属性
    name:"seven",
    age:18,
    weight:100,

    // 添加方法
    get_info(){
        console.log(`姓名:${this.name},年龄:${this.age},体重:${this.weight}`)
    },
    
    set_age(age){
        this.age = age
    }
}

类 calss

  • 使用Object创建对象:

    1. 无法区分不同类型的对象
    2. 不方便批量创建对象
  • 通过class创建对象

    1. 类是对象模板,可以将对象中的属性和方法直接定义在类中,定义后,就可以直接通过类来创建对象
    2. 通过同一个类创建的对象,我们称为同类对象
      • 可以使用instanceof来检查一个对象是否是由某个类创建
      • 如果某个对象是由某个类所创建,则我们称该对象是这个类的实例
    3. 语法:calss 类名{}const 类名 = claskk{}
    4. 通过类创建对象:new 类名()
            class User {
         
            }
    
    			  // User() : 构造函数
            const u1 = new User()
     
            console.log(u1 instanceof User) // true
    
类属性
        class User {
            name = "mickey" // 实例属性,只能通过实例访问 const u = new User() u.name
            static age = 18 // 静态属性(类属性),只能通过类去访问,User.age

        }
				
				// 使用new关键字调用构造函数User创建一个新的对象实例,即实例化对象
        const  user = new User()

        console.log(user.name) // mickey

        user.city = "Beijing"
        console.log(user.city) // Beijing
类方法
        class User {
            name = "mickey"
            static age = 18

            // 添加方法-实例方法(不建议使用)
            sayHello = function (){
                console.log("hello world")
            }

            // 实例方法,通过实例.方法名调用(建议使用)
            printName(){
                console.log(this.name) // 实例方法中this就是当前实例
            }

            // 静态方法(类方法),通过类.方法名调用
            static test(){
                console.log(this.age) // 静态方法中this指向的是当前类
            }

        }

        const  user = new User()
        user.sayHello()
        user.printName()
        User.test()
构造函数
   class User {
     				// 在类中直接指定实例属性的值时,创建的所有对象属性都是这个值
            name = "mickey"
      }
       class User {

            // 在类中可以添加一个特殊的方法constructor
            // 该方法我们称为构造函数(构造方法)
            // 构造函数会在我们调用类创建对象时执行
            // 即该类实例化的时候会直接执行
            constructor(name,age) {
                // 可以在构造函数给实例属性赋值,构造函数中this表示当前所创建的对象
                this.name = name
                this.age = age
            }

        }

        const  u1 = new User("l",18)
        console.log(u1) // {name: 'l', age: 18}

        const  u2 = new User("q",28)
        console.log(u2) // {name: 'q', age: 28}

封装、继承、多态

面向对象三大特性:封装、继承、多态

封装

数据安全

  • 对象就是一个用来存储不同属性的容器

  • 对象不仅存储属性,还要负责数据的安全

  • 直接添加到对象中的属性,并不安全,因为它们可以被任意的修改

  1. 私有化数据

    • 将需要保护的数据设置为私有,只能在类内部使用,属性私有化在属性前面加#表示私有属性, #name
  2. 提供setter和getter方法来开放对数据的操作

    • 通过类内部提供的方法获取、修改私有属性

模块化与可维护性

封装可以使代码更加模块化,将相关的数据和功能封装在一个对象或模块中。这样可以使代码结构更加清晰,易于理解和维护

可以提供统一的接口来访问和操作数据,确保外部代码以一致的方式与对象进行交互。

例如,一个封装良好的对象可以提供一组方法来执行特定的操作,外部代码不需要了解内部的实现细节,只需要调用这些方法即可

    
        class User {
            // 私有属性必须先声明

            // 声明赋值的私有属性
            #name = "q"

            // 声明未赋值的私有属性
            #age

            constructor(age) {
                  this.#age = age

            }

            getAge(){
                return this.#age
            }

            getName() {
                return this.#name  // 获取私有属性name
            }

            setName(name) {
                if (name != null){
                    this.#name = name // 修改私有属性name

                }
            }


        }


        const user = new User(67)
        console.log(user.getName())
        console.log(user.getAge())
        user.setName()


通过getter和setter获取和设置私有属性值

        class User {
            #name


            // getter 获取name属性值
            get name(){
                return this.#name
            }

            // setter 设置name属性值
            set name(name){
                this.#name = name

            }
        }
        
        const  user = new User()
        user.name = "l"
        console.log(user.name) 
多态

在JS中不会检查参数的类型,所以这就意味着任何数据都可以作为参数传递

要调用某个函数,无需指定的类型,只要对象满足某些条件即可

多态为我们提供了灵活性

  class  User{
            constructor(name) {
                this.name = name
            }
        }



        const  u1 = new  User("l")
        const  u2 = new  User("q")

        function sayHello(obj){
            console.log("hello ",obj.name)

        }


        sayHello(u1)
        sayHello(u2)
继承

可以通过extends关键来完成继承

  • 当一个类继承另一个类时,就相当于将另一个类中的代码复制到了当前类中
  • 继承发生时,被继承的类称为 父类(超类),继承的类称为 子类
  • 通过继承可以减少重复的代码,并且可以在不修改一个类的前提对其进行扩展
   class  User{
            constructor(name) {
                this.name = name
            }
        getInfo() {
               console.log(this.name)
        }

        }


  // 使用extends关键字 Admin继承User类
  class Admin extends User{

  }

  const a = new Admin("l")
  a.getInfo()

通过继承可以在不修改一个类的情况下对其进行扩展

面向对象OCP开闭原则:程序应该对修改关闭,对扩展开放

        // 使用extends关键字 Admin继承User类

        // 在子类中,可以通过创建同名方法来重写父类的方法
        class Admin extends User{

            // 重写构造函数
            constructor(name) {
                // 重写构造函数时,构造函数的第一行代码必须为super()
                super(name);
                this.name = name
            }


            // 重写getInfo方法
            getInfo(){
                super.getInfo() // 通过super调用父类的getInfo方法
                console.log("名字是:",this.name)
            }

        }

        const a = new Admin("l")
        a.getInfo()

对象结构

对象中存储属性的区域实际有两个:

  1. 对象自身

    • 直接通过对象所添加的属性,位于对象自身中
    • 在类中通过key=value的形式添加属性、方法,位于对象自身中
  2. 原型对象(prototype)

    • 对象中还有一些内容,会存储到其他的对象里

    • 对象中会有一个属性用来存储原型对象,这个属性叫做__proto__

    • 原型对象也负责为对象存储属性

    • 我们访问对象中的属性时,会优先访问对象自身的属性,

    • 对象自身不包含该属性时,才会去原型对象中寻找

    • 会添加到原型对象中的情况:

      1. 在类中通过xxx(){}方式添加的方法,位于原型中

      2. 主动向原型中添加的属性或方法

      class  User {
            name = "l"  // 存储对象自身
            get = function (){}  // 存储对象自身

            set(){} // 存储原型对象

        }
        
        // 对象本身
        const user = new User() 


原型对象数据结构
        // 访问原型对象
        console.log(user.__proto__)
				// 访问原型对象
        console.log(Object.getPrototypeOf(user))

原型对象中的数据:

           1. 对象中的数据(属性、方法等),比如User.set()方法
           2. constructor (对象的构造函数)

原型对象也有对应的原型对象,构成一条原型链,根据对象的复杂程度不同,原型链的长度也不同

// 访问user对象的原型对象的原型对象       
console.log(user.__proto__.__proto__)

原型链:

  • 读取对象属性时,会优先对象自身属性,
  • 如果对象中有,则使用,没有则去对象的原型中寻找
  • 如果原型中有,则使用,没有则去原型的原型中寻找
  • 直到找到Object对象的原型(Object的原型没有原型(为null))
  • 如果依然没有找到,则返回undefined
  • 作用域链,是找变量的链,找不到会报错
  • 原型链,是找属性的链,找不到会返回undefined
原型的作用
// 所以的同类型对象的原型对象都是同一个,也就意味着同类型对象的原型链是一样的
var u1 = new  User()
var u2 = new  User()
console.log(u1.__proto__ === u2.__proto__) // true
  • 所有同类型对象的原型链都是一个,原型就相当于是一个公共的区域,只需要创建一个可以被所有该类实例访问

  • 在对象中有些值是对象独有的,比如属性,每个对象都应该有自己值,有些值对于每个对象都是一样的,像各种方法,对于一样的值没有必要重复创建,比如set()方法,在原型中,new100个对象只会创建1次,而不是创建100次

// 对于某些是每个对象独立的方法,可以使用x=y的方式存储在对象真实    
class  User {
            get = function (){}  // 存储对象自身,每个对象创建一个

            set(){} // 存储原型对象,原型链共用,多个对象不会重复创建

        }
  • JS中继承就是通过原型来实现的, 当继承时,子类的原型就是一个父类的实例
        // 继承User
        class  Admin extends User{

        }

        const  a = new Admin()
        // user实例 --> object --> Object原型 --> null
        console.log(a.__proto__.__proto__.__proto__.__proto__)
//Object.create()是 JavaScript 中用于创建一个新对象的方法,这个新对象的原型可以指定为某个已有的对象
/*

Object.create(proto, [propertiesObject])
proto:新创建对象的原型对象,可以是一个已有的对象,如果null,则新创建的对象没有原型,不会继承任何属性和方法
propertiesObject(可选):包含属性描述符的对象,用于定义新对象自身的属性

当将 proto 参数设置为 null 时,可以创建一个没有原型的对象。这种对象通常用于存储临时数据或实现特定的算法,以避免意外地访问到原型链上的属性

*/


   const parent = {
     sayHello() {
       console.log('Hello from parent')
     }
   }

   const child = Object.create(parent)
   child.sayHello()
// 创建一个没有原型的新对象,定义了一个名为 name 的属性,属性值为 john,属性全保险是可写、可枚举、可配置
const newObject = Object.create(null, {
  name: {
    value: 'John',
    writable: true,
    enumerable: true,
    configurable: true
  }
});
修改原型

一般情况下,不需要修改原型对象

/*
不建议通过类的实例去修改原型:因为会通过一个对象影响所有同类对象
*/
    const  a = new Admin()
    const a1 = new Admin()
    const a2 = new Admin()
    
    // a、a1、a2对象的set方法都会被修改
    a.__proto__.set = () => {}
    
    a2.__proto__ = new Base() // 不会影响其他对象,相当于是给a2对象赋值了一个新的原型对象

通过prototype修改原型对象

User.proptotype // 访问User实例的原型对象
User.prototype.get = () => {} // 给所有实例添加一个get方法

原型一般不需要修改,如果要修改通过类.prototype去修改

instanceof和hasOwn
// instanceof 用来检查一个对象是否是一个类的实例
// 检查的是对象的原型链上是否有该类实例只要原型链上有该类实例,就会返回true
// Object是所有对象的原型,所以任何和对象和Object进行instanceof运算都会返回true


 class  User {

  }

  // 继承User
  class  Admin extends User{
    name = "l"
    get(){}
    set = function (){}

  }


  const  admin = new Admin()

  console.log(admin instanceof Admin) // true
  console.log(admin instanceof User) // true
  console.log(admin instanceof Object) // true 
// in用来检查一个对象的自身是否含有某个属性
// 使用in运算符检查属性时,无论属性在对象自身还是在原型中,都会返回true
console.log("get" in admin) // true


// 对象.hasOwnProperty检查一个对象的自身是否包含某个属性(不推荐使用)
// hasOwnProperty是在Object原型中的一个方法
console.log(admin.hasOwnProperty("name")) // true
console.log(admin.hasOwnProperty("set")) // true
console.log(admin.hasOwnProperty("get")) // false

// Object.hasOwn(对象,属性名) 用来检查一个对象的自身是否含有某个属性(推荐使用)
console.log(Object.hasOwn(admin,"name")) // true
console.log(Object.hasOwn(admin,"set")) // true
console.log(Object.hasOwn(admin,"get")) // false

旧类

早期js中通过函数定义类,如果直接调用就是一个普通函数,通过new调用就是一个构造函数

function User(name){
        // 在构造函数中,this表示新建的对象
        this.name = name

    }
    // 向原型中添加属性(方法)
    User.prototype.get = function(){}
    // 静态属性
    User.staicName = "a"
    // 静态方法
     User.staticGet = function(){}

    User() // 普通函数

    const user = new User() // 构造函数

通过旧的方式创建类,方法属性比较分散,可以将其放在一个立即执行函数里面

    var User = (function () {

            function User(name){
                // 在构造函数中,this表示新建的对象
                this.name = name
            }
                // 向原型中添加属性(方法)
                User.prototype.get = function () {
                }
                // 静态属性
                User.staicName = "a"
                // 静态方法
                User.staticGet = function () {
                }


                const user = new User() // 构造函数
                // 返回User
                return User

            }
        )()

        
        const u = new User("l")
        console.log(u.name)
// 旧类继承  
var User = (function () {
            function User() {

            }

            return User

        })()


        var Admin = (function () {
            function Admin() {

            }

            // 继承User,将User对象赋值给Admin的原型
            Admin.prototype = new User()

            return Admin

        })()

new运算符

当使用new 去调用一个函数时,这个函数将会作为构造函数调用

  1. 创建一个普通的JS对象(Object对象 {}), 称其为新对象
  2. 将构造函数的prototype属性设置为新对象的原型
  3. 使用实参来执行构造函数,并且将新对象设置为函数中的this
  4. 如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值返回(千万不要这么做)
    如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值返回
    通常不会为构造函数指定返回值
	// 1.创建一个普通的JS对象
	var newInstance = {}
	// 2.构造函数的prototype属性设置为新对象的原型
	newInstance.__proto__ = NewObject.prototype
	// 3.使用实参来执行构造函数,并且将新对象设置为函数中的this
	constructor() {
    
    	// 4.如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值返回(千万不要这么做)
      // 如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值返回
      // 通常不会为构造函数指定返回值
          }

对象的分类:

  1. 内建对象

    • 由ES标准所定义的对象
    • Object Function String Number ....
  2. 宿主对象

    • 由浏览器提供的对象
    • BOM、DOM
  3. 自定义对象

    • 由开发人员自己创建的对象
posted @ 2024-10-15 16:56  木子七  阅读(201)  评论(0编辑  收藏  举报