JavaScript初学必备 之 面向对象

今日歌曲推荐:

comethru

一、什么是面向对象

1、对象

什么是对象?

——万物皆对象

理解(1)

对象是对单个事物的抽象:一瓶水、一张桌子、一个小孩等等都是对象,一个字符串、一个方法、一个数据库等等也是对象。

理解(2)

是JS中数据类型的一种对象,所谓对象数据类型,就像是一种无序的数据集合,由属性(对象的状态)和方法(对象的行为)构成

理解(3)

对象数据类型作为复杂数据类型,它的值保存在堆内存中,变量 obj 内存储的是对象的引用,也就是对象在堆内存中存储的地址。

二、面向对象

首先

  • 要明确面向对象不是语法    ,而是编程思想,一种编程模式
  • 面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。
  • 面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比由一系列函数或指令组成的传统的面向过程编程,更适合多人合作的大型软件项目。

举例说明

我们想吃鱼香肉丝、红烧肉等,我们可以根据自己的喜好,选择食物、准备原来、按照做法做菜,一步步把食物做好——这就类似面向过程思想,亲力亲为,步步紧跟,有条不紊,一步步把需要的功能做出来。


我们也可以选择一个饭店,根据我们的喜好进行点菜——面向对象思想就是找一个对象,我们无需知道怎么做的菜,直接吃到我们想吃的菜。

总结

面向对象将执行者转变成指挥者。

面向对象不是面向过程的替代,而是面向过程的封装。

三、创建对象的方式

对象(一种数据类型) 不是 面向对象(一种编程思想、模式)

方法一:字面量方式创建

let obj  = {...}

    let obj1 = {
        name:"鱼香肉丝",    //菜名
        material:["肉丝","胡萝卜","木耳","等等"],  //原料
        cook(){
            //console.log("这道菜的做法等等")   //做法
        }

    }
    console.log(obj1)

方法二:通过内置构造函数创建

let obj = new Object()

    let obj = new Object()
    obj.name = "东坡肉"
    obj.material = ["猪肉","栗子","等等"]
    obj.cook = function (){
        //console.log("做法")
    }
    console.log(obj)


讨论:

我们想要创建一个饭店,该饭店可以根据不同的口味提供不同的菜品,而上面两种创建方式都只能固定的产出一道菜,难道一个饭店只做一两道菜?当然,我们可以按照上面的方法,把所有菜品都一个一个创建出来,但毫无疑问,太过冗余,复杂度太高,并不合适。


方法三:工厂函数

写一个函数,通过调用该函数解决上面代码重复冗余的问题:

    function createOBJ(name,material){
        var obj = {}
        obj.name = name
        obj.material= material
        return obj
    }
    //有批量生产对象的感觉了,大大减少了代码量
    let obj1 = createOBJ("鱼香肉丝",["肉丝","胡萝卜","木耳","等等"])
    let obj2 = createOBJ("东坡肉",["111","222","333"])
    console.log(obj1)
    console.log(obj2)

到这一步,似乎有了面向对象编程的感觉了——找一个对象(函数也是对象),我们无需知道怎么做的菜,直接吃到我们想吃的菜(通过调用createObj(),生产想要的食物对象),而不是像前两种方式事事亲为,自己一步一步做好。

虽然工厂函数解决了代码冗余的问题,可无法解决对象识别的问题。

方法四:自定义构造函数

这才是JS开发中做面向对象开发的方法。我们先看一下内置构造函数:

  • new Object()
  • new String()
  • new Array()等等

Object() 、String()、Array()等等都是别人做好的构造函数,我们 new 这些构造函数可以批量创建对象结构、字符串结构、数组结构等等。


    function CreateObj(name){
        //自动创建对象
        this.name = name
        this.material = []
        this.cook = function (){
        }
        //自动返回
    }C

    //加了 new 之后就叫构造函数,特性:在函数内部自动创建一个对象,自动返回
    let obj1 = new CreatCeObj()    
    //console.log(obj1)  //实例化对象

    //传入想要的值C
    let obj2 = new CreateObj("鱼香肉丝")  //实例化对象
    console.log(obj2)

使用这种方法,很直接的发现创建变量十分容易,大大减少了代码冗余,同时识别对象的具体类型。

总结:

  • 构造函数是根据具体的事物抽象出来的抽象模板。
  • 实例对象是根据 new 构造函数得到的具体实例对象。

自定义构造函数 CreateObj() 与工厂函数 createOBj() 的不同之处:

  • 没显示创建对象
  • 直接将属性和方法赋给this
  • 没有return
  • 开头字母C是大写

针对这些问题,下面进行逐一探讨。

四、构造函数需要注意的问题

注意点1

函数名首字母大写(君子协定:不大写也没事,但大家默认这种写法)

function CreateObj(){}

注意点2

构造函数不需要 return ,会自动返回对象

注意点3

构造函数能当成普通函数调用吗?

注意:不用new就是当成普通函数调用,加了new就是构造函数(先记住)

    function CreateObj(name){
        this.name = name
    }
    //当成普通函数调用:
    let obj1  = CreateObj("1111")
    console.log(obj1)   //返回undefined

输出 undefined,因为普通函数不会自动返回,函数中并没有 return。

思考一下,现在this指向谁???

该函数挂在window下调用,this自然指向window,那么上面的程序会给window对象增加一个name属性。我们打印一下window.name,看一下是否正确:

console.log(window.name)  //1111

所以可以当作普通函数调用,但这不是我们的目的,我们写构造函数是为了批量创建需要的对象。

注意点4

构造函数中的this指向:指向new完后产生的实例对象

 // 一个函数没被调用之前,谁都不知道this指向谁
    function CreateObj(name){
        console.log(this)
        this.name = name
    }
    let obj1 = new CreateObj("11111111") //new过程===实例化过程
    //实例对象已经生产,this指向它
    //调用之后,this指向我们创建的对象,我们将该对象赋值给变量 obj1 ,所以也可以认为指向该变量
    console.log(obj1)
  • 一个函数没被调用前,谁都不知道其中的this指向谁
  • new过程===实例化过程
  • 当调用后,实例对象产生,this指向它
  • 后我们又将该对象赋值给变量 obj1 ,所以也可以认为指向该变量

五、原型

1、回顾之前的问题

我们先再看看上面写的自定义构造函数:

     function CreateObj(name){
        this.name = name
        this.material = material
        this.cook = function (){
            
        }
    }

    let obj2 = new CreateObj("鱼香肉丝")  //实例化对象
    let obj2 = new CreateObj("东坡肉") 

我们知道,复杂对象类型存放在堆区,赋值给变量 obj ,简单数据类型存放在栈区,obj指向其存放的地址。从上面的代码中,看起来似乎没什么问题,但实际上存在一个很大的弊端,那就是空间浪费问题。为什么呢?先看下面的图:

我们每调用一次构造函数,都会在堆区存放一个实例对象,在这些实例对象中,有些变量是私有的(比如name、material),需要我们在生产实例对象时各自创建;而对于公有的方法cook(),因为方法的功能是相同的,我们没有必要每次生产一个实例对象都在去创建一个新的cook(),每个方法都占有一定内存,当生产很多对象时,无疑是对资源的巨大浪费。

不妨看看下面这张图:

我们把共享函数 cook() 定义到构造对象外部,在对象中创建一个变量 cook 接收,这样只需要创建一个 cook() 函数就可以实现功能。一个是小的简单数据类型,一个是大的复杂数据类型,毫无疑问,节省了内存空间,解决了资源浪费问题。

    function cook(){
        console.log("做法")
    }    
    
    function createObj(name){
        this.name = name
        this.material = material
        this.cook = cook
    }

以上例子仅供参考,其实还存在问题:当构造函数中函数不止一个,有很多共享函数时,会造成全局变量命名冲突问题。而且多个共享函数存放在不同位置,调用时效率会低很多

下面就要介绍原型。

2、原型

prototype:原型对象


在Javascript中, 每一个构造函数都有一个 prototype属性,指向另一个对象。 这个对象的所有属性和方法,都会被构造函数的实例继承。也就是说,我们直接把需要共享的属性和方法直接在原型对象中定义就行了

    function createObj(name,material){
        this.name = name
        this.material = material
    }
    //把共享函数放在 prototype 原型对象中
    createObj.prototype.cook = function (){}

    let obj = new createObj("鱼香肉丝",["原料111","原料222","原料333"])
    console.log(obj) //我们发现:在实例对象中自动生成 原型对象 ,对象中存在我们存放的 共享函数。

如果像之前那样把多个共享的方法分别写在外面,存在不同的地方,在调用时效率会低,而使用原型对象,所以属性和方法存放在同一处,在开发调用时无疑会提高效率。

3、总结

如果你翻看上面任何一个函数的运行结果,其实不难发现:生产的实例对象中都有 prototype 属性。

  • 任何函数都有一个  prototype 属性,该属性是一个对象。
  • 构造函数的对象  prototype 默认都有一个 constructor 属性,指向  prototype 对象所在函数
  • 通过构造函数得到的实例对象内部会包含一个指向构造函数的  prototype 对象的指针 _proto_

六、原型链

1、方法

  • 每个对象都有一个内部属性[[Prototype]]__proto__,它指向另一个对象,形成了原型链。
  • 当访问一个对象的属性或方法时,如果该对象本身没有这个属性,JavaScript会在它的原型上查找;
  • 如果原型也没有,则继续往上溯,直到找到或者到达原型链的顶端(即Object.prototype),这时会返回undefined

原型链允许动态共享属性,减少了内存消耗,并支持函数作为构造函数创建实例时的继承。比如,当我们通过new关键字创建一个对象时,实际上是在原型链上寻找并继承构造函数的属性和方法。常见的操作如hasOwnPropertyisPrototypeOf等都依赖于原型链的工作原理。ps:来自AI总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值