module4-01-面向对象编程、原型链、构造函数、原型对象

面向对象编程、原型链、构造函数、原型对象

一、面向对象的概念

1.1 什么是对象

  • 在实际开发中,对象就是一个抽象的概念,可以将其简单理解为:数据集或功能集

  • ECMAScript-262 把对象定义为:无需属性的集合,其属性可以包含基本值、对象或者函数

1.2 什么是面向对象

  • 面向对象编程 - OOP,是一种编程开发思想

  • 它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟

(1)面向对象的特性

  • 封装性

  • 继承性

  • [多态性]抽象性

(2)面向对象与面向过程对比

  • 面向过程就是亲历亲为,事无巨细,面面俱到,步步紧跟,有条不紊

  • 面向对象就是找一个对象,指挥得结果

  • 面向对象将执行者编程指挥者

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

(3)总结

  • 在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务

  • 因此,面向对象编程具有灵活、代码可复用、高度模块化等特点吗,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程,更适合多人合作的大型软件项目

(4)体验面向对象感想

  • 因为由多个类似的对象构成,可以利用构造函数的方法进行封装,然后创建单独的对象

    • 抽象出Class

    • 根据Class创建Instance

    • 只会Instance得出结果

  • 当数据代码越多的时候,使用面向对象则越优化

二、创建对象的几种方式

2.1 创建方法

  • new Object()

  • 对象字面量 {}

  • 工厂函数

  • 自定义构造函数

(1)newObject与字面量

  • 简单方法:使用new Object()创建然后用点语法进行赋值

  • 字面量方法:使用{}来创建对象,并在{}里面赋值

(2)工厂函数

  • 使用一个函数来,只需传入所需要的参数,然后函数会返回一个根据参数而得出来得对象

    • 直接返回字面量或者new Object()点语法

    • 一定要有返回值

  • 缺点:

    • 使用instanceof不能很好得检测出属于什么实例,因为函数内部是使用new Object()或者{},所以instanceof一个Object返回true

(3)构造函数

  • 用new使用构造函数来按照模板生成对象实例

  • new关键字的作用

    • ① 创建一个新对象

    • ② 将函数内部的this指向了这个对象

    • ③ 执行构造函数内部的代码

    • ④ 将新对象作为返回值

  • 可以使用instanceof来判断是否为某构造函数的实例

    • 判断一个对象的具体对象类型,需要使用instanceof来判断

    • 如果检测是否为Object的实例也会为true

(4)构造函数和实例对象的关系

  • 构造函数是根据具体的事物抽象出来的抽象模板

  • 实例对象是根据抽象的构造函数模板得到的具体实例对象

  • 每一个实例对象都通过一个constructor属性,指向创建该实例的构造函数

    • 注意:constructor是实例的属性的说法不严谨,因为存在于原型当中

  • 可以通过constructor属性判断实例和构造函数之间的关系

    • 注意:这种方法不严谨,推荐使用instanceof操作符,constructor也可以更改

2.2 静态成员和实例成员

  • 使用构造函数方法创建对象时,可以给构造函数和创建的实例对象添加属性和方法,这些属性和方法都叫做成员

  • 实例对象:在构造函数内部添加给this的成员,属于实例对象的成员,在创建实例对象后必须由对象调用

    • 实例生成的name与构造函数的name不一样,构造函数的name是构造函数名

  • 静态成员:添加给构造函数自身的成员,只能使用构造函数调用,不能使用生成的实例对象,不会传到实例中

    • function Person () {}
      Person.version = '1.0'
      var person1 = new Person()
      console.log(Person.version) // '1.0'
      console.log(person1.version) // undefined
    • 比如内置对象Math,它里面的属性和方法都是存在于Math中,其所有的属性和方法都是静态的

2.3 构造函数问题

  • 浪费内存

    • 每次调用一次构造函数,都会创建相同的函数,会造成内存浪费

    • 解决方法:

      • ① 把内部定义的函数抽到外面

      • ② 第一个方法可能会造成命名冲突问题,所以可以放在一个对象中定义

      • 但是这些方法都是在构造函数外部实现的,有没有一种方法在内部实现的呢?(看③)

三、原型对象与原型链

3.1 原型对象

(1)任何函数都具有一个prototype

  • 即构造函数的prototype也是实例的__protp__所指向的对象

    • person.__proto__ === Person.prototype // true
    • 同时任何函数都有一个__proto__,这个代表创建函数实例的构造函数Function,而Functionprototype又是Object构造函数创造的一个实例(因为函数的__proto__中也包含一个__proto__)。

  • 构造函数的prototype对象默认有一个constructor属性,指向prototype对象所在构造函数

  • 通过构造函数得到的实例对象内部包含一个指向构造函数的prototype对象的指针__proto__

(2)实例对象可以直接访问原型对象成员

  • 比如实例对象可以打点访问到constructor,其实是在__proto__中的,中间那步可以省略

  • person1.__proto__.constructor === person1.constructor
  • 所以尽量不要使用constructor进行判断对象类型,constructor是可以更改的

(3)解决上述构造函数问题

  • 把方法存在构造函数的prototype之中,只用创建一个函数空间,而且每个实例对象都可以访问 / 使用这个方法

3.2 原型链

思考

  • 为什么实例对象可以调用构造函数的prototype原型对象的属性和方法?

  • __proto__用来表示实例对象的继承关系,他的__proto__指向的是new这个实例对象出来的构造函数的prototype原型对象

(1)创建构造函数并生成一个实例对象有关联的所有对象

  • 在最底层的实例对象中,假设实例对象有一个sayHi方法,Student.prototype中有一个sayHi方法,Object.prototype中也有一个sayHi方法,其中最底层的实例对象都可以访问到上述的方法,但是如果调用的话,会采用就近原则一层层在__proto__中查找,这里首先调用的是自身的方法

  • 其中Object.prototype.__proto__指向的是null代表最末端

  • 平时使用数组的方法也是都像这样封装在Array.prototype当中的

3.3 实例对象读写原型对象成员

(1)原型链查找机制

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性:

  • ① 搜索首先从对象实例本身开始

  • ② 如果在实例中找到了具有给定名字的属性,则返回该属性的值

  • ③ 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性

  • ④ 如果在原型对象中找到了这个属性,则返回该属性的值

(2)实例对象读写原型对象成员

  • 读取

    • 先在自己身上找,找到即返回

    • 自己身上找不到,则沿着原型链向上查找,找到即返回

    • 如果一直到原型链的末端还没找到,则返回undefined,如果是调用方法且没找到则看成是undefined() 会报错

  • 值类型/引用类型成员写入

    • 当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上

    • 也就是说该行为实际上会屏蔽掉对原型成员的访问

    • // 值类型
      Student.prototype.city = 'gz'
      stu1.city = 'dg'
      stu1 // { city: 'dg', __proto__: { 'city': 'gz' } }
    • // 引用类型
      Student.prototype.city = { a: 'a' }
      stu1.city = { b: 'b' }
      stu1 // { city: { b: 'b' }, __proto__: { city: { a: 'a' } } }
  • 复杂类型成员修改

    • 同样会先在自己身上找该成员,如果自己身上找到则直接修改

    • 如果自己身上找不到,则沿着原型链继续查找,如果找到则修改

    • 如果一直到原型链的末端还没有找到该成员,则报错

    • // 复杂类型成员修改
      Student.prototype.city = { city: 'gz' }
      stu1.city.city = 'dg'
      stu1 // { __proto__: { city: { city: 'dg' } } }

3.4 更简单的原型语法

(1)重置prototype

  • 可以不用每次添加一个方法都用.prototype来赋值,直接将prototype重新指向一个自己定义的对象,里面封装好方法。

  • 这样的话会使原有的constructor重置掉,这时候需要重新定义construcotr在新的prototype上

(2)原型对象使用建议

在定义构造函数的时候,可以根据成员的功能不同,分别进行设置

  • 私有成员(一般就是非函数成员放到构造函数

  • 共享成员(一般就是函数放到原型对象

  • 如果重置了prototype记得修正constructor的指向

3.5 内置构造函数的原型对象

所有函数都有prototype属性对象

JS中内置构造函数也有prototype原型对象属性:

  • Object、Function、Array、String、Number...都有prototyoe

不可以更改内置构造函数的原型对象

  • 如果想在数组的原型对象中增加一个自定义方

  • Array.prototype = { ... }

  • 改方法不会奏效,因为js默认保护机制中内置原型对象不能被修改

  • 可以Array.prototyoe.方法名 = ...但是也不推荐

原文地址:https://www.cnblogs.com/lezaizhu/p/14236137.html