十七、面向对象

17.1 简介

“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想。

  1. 我们之前写的所有代码都是面向过程的

“面向对象”(Object Oriented)是软件开发方法,一种编程范式。把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。

  1. 我们不再重复书写过程,而是将过程封装成函数,每一次调用这个函数时,就会得到一个对象,该对象所具备功能与之前面向过程的功能是一模一样的。

17.2 面向过程

  • 如果是面向过程 去描述一个人 每一条属性都要自己定义 书写的是定义的过程 叫做面向过程
  1. // 如果是面向过程 去描述一个人 每一条属性都要自己定义 书写的是定义的过程 叫做面向过程
  2. var obj = {
  3. name: "zhangsan",
  4. age: 13,
  5. sex: '男'
  6. }
  7. // 如果要再定义一个人 需要再写一遍
  8. var obj1 = {
  9. name: "lisi",
  10. age: 14,
  11. sex: "男"
  12. }
  13. // 如果要再定义一个人 需要再写一遍
  14. // ……
  15. // 这么写代码 可以 但是比较繁琐

17.3 面向对象

17.3.1 工厂函数

  • 如果能够有一种方式 能够快速的生成一个对象 我们就不用每次都从头到尾敲一遍
  1. function createPerson() {
  2. return {
  3. name: "lisi",
  4. age: 14,
  5. sex: "男"
  6. }
  7. }
  8. // 自从有了createPerson函数之后,就每调用一次 就可以得到一个对象
  9. var obj = createPerson();
  10. var obj1 = createPerson();
  11. console.log(obj)
  12. console.log(obj1)

提示: 这种可以返回内容的函数,我们称之为“工厂”。这种书写代码的模式,叫做工厂模式。属于设计模式的一种。 问题: 虽然可以创建 但是内容完全一致 我们希望内容可变

思考:函数中的什么内容可以发生变化?
答案:参数是可以变化的
于是 我们将可变的部分提取成参数 由外部传入

  1. function createPerson(name, age, sex) {
  2. return {
  3. name: name,
  4. age: age,
  5. sex: sex
  6. }
  7. }
  8. var obj = createPerson("张三", 13, "男");
  9. var obj1 = createPerson("李四", 14, "男");
  10. console.log(obj);
  11. console.log(obj1);

定义”人”的工厂创建完毕 现在再定义一个”车”的工厂

  1. function createCar(type, color, price) {
  2. return {
  3. type: type,
  4. color: color,
  5. price: price
  6. }
  7. }
  8. function createPerson(name, age, sex) {
  9. return {
  10. name: name,
  11. age: age,
  12. sex: sex
  13. }
  14. }
  15. // 调用函数创建一个人
  16. var p = createPerson("张三", 13, "男");
  17. // 调用函数创建一辆车
  18. var c = createCar("五菱宏光", "blue", 108000);
  19. console.log(p);
  20. console.log(c);
  21. // 想要分辨两个对象各自的类型
  22. console.log(Object.prototype.toString.call(p)); // [object Object]
  23. console.log(Object.prototype.toString.call(c)); // [object Object]

结论:想要分辨两个对象各自的类型 无法分辨 注: Object.prototype.toString.call()这种方式虽然可以检测到JS内置的构造函数名称,但是无法分辨自定义的构造函数名称

17.4 构造函数

用于构造对象的函数,叫做构造函数。

  1. 用new调用
  2. 首字母要大写
  3. 内部要通过this给实例添加属性
  4. 内部最好不要出现return
    1. 如果出现了return 返回的如果是值类型 则忽略return
    2. 如果出现了return 返回的如果是引用类型 则以return为准
  5. 对象的类型是构造函数名称

引导

  1. function createPerson() {
  2. }
  3. function createCar() {
  4. }
  5. // 构造函数: 此时 createPerson和createCar的作用就是在构造对象 这样的函数叫做构造函数
  6. // 构造函数其实就是普通函数
  7. // 如果使用new调用 就是构造函数 会返回对象
  8. var p = new createPerson();
  9. var p1 = new createPerson();
  10. var c = new createCar();
  11. // 如果不使用new调用 就是普通函数 是否返回内容要取决于是否有return
  12. var d = createPerson();
  13. var e = createCar();

new 是一个关键字 它的作用类似于运算符 是一种调用函数的方式 new 的英文意思: 新的 也就是说 一旦new了 就希望得到一个新对象 注:构造函数其实就是普通函数 如果使用new调用 就是构造函数 会返回对象 如果不使用new 就是当做普通函数在调用 是否有返回内容要取决于是否有return

构造函数

构造函数在执行的时候:

  • 开辟一个内存空间(空对象)
  • 将空对象与this绑定
  • 然后让这个空对象的 proto 指向函数的原型prototype,继承了该函数的原型。
  • 执行函数体中的代码
  • 返回这个this地址

17.5 安全类

属于设计模式的一种 能够保证不论程序员使用new还是不使用new都可以得到实例对象。

  1. function Person() {
  2. if (this === window) {
  3. // 说明外面没有new 就是当普通函数来调用的 外部想要得到返回内容 内部就得有return
  4. return new Person();
  5. }
  6. }
  7. var p = new Person();
  8. var p1 = Person();
  9. // p 和 p1都是Person的实例

17.6 称呼问题

用于构造对象的函数叫做构造函数。
构造函数构造出来的对象叫做实例。

17.7 原型的推导过程

要搞明白原型是什么,先来看问题:

  1. // 定义构造函数
  2. var Animal = function(eye, weight) {
  3. this.eye = eye;
  4. this.weight = weight;
  5. this.eat = function() {
  6. console.log("吃东西");
  7. }
  8. this.sleep = function() {
  9. console.log("zzzzzzzzzzzzzzz...")
  10. }
  11. }
  12. var a1 = new Animal("眼睛", 100);
  13. a1.eat();
  14. a1.sleep();
  15. var a2 = new Animal("眼睛1", 200);
  16. a2.eat();
  17. a2.sleep();
  18. console.log(a1.eat === a2.eat); // false

问题: 既然是同样的方法 最好在内存中只保留一个 能够被每一个实例复用 思考: 之所以每一个实例的方法的地址不同 是因为每一次执行构造函数的时候 都定义了两个新函数

  1. // 定义构造函数
  2. var Animal = function(eye, weight) {
  3. this.eye = eye;
  4. this.weight = weight;
  5. this.eat = eat;
  6. this.sleep = sleep;
  7. }
  8. // 之所以每一次都定义两个新函数 是因为代码放在了里面
  9. // 现在将代码抽取到函数的外部 这样就只会定义一次 不论初始化多少Animal的实例 都共用同一个方法
  10. var eat = function() {
  11. console.log("吃东西");
  12. }
  13. var sleep = function() {
  14. console.log("zzzzzzzzzzzzzzz...")
  15. }
  16. var a1 = new Animal("眼睛", 100);
  17. a1.eat();
  18. a1.sleep();
  19. var a2 = new Animal("眼睛1", 200);
  20. a2.eat();
  21. a2.sleep();
  22. console.log(a1.eat === a2.eat); // true

结论:虽然将代码抽取到外部 可以解决复用的问题 但是污染了外部的作用域 也就意味着:定义一个构造函数 还要定义一堆变量 定义一堆函数 这也不合适

为了解决外部环境的污染问题 我们就定义一个变量 保存一个对象 将所有的函数都挂载在该对象身上

  1. var Animal = function (eye, weight) {
  2. this.eye = eye;
  3. this.weight = weight;
  4. this.eat = prototype.eat;
  5. this.sleep = prototype.sleep;
  6. }
  7. // 为了解决外部环境的污染问题 我们就定义一个变量 保存一个对象 将所有的函数都挂载在该对象身上
  8. var prototype = {
  9. eat: function () {
  10. console.log("吃东西");
  11. },
  12. sleep: function () {
  13. console.log("zzzzzzzzzzzzzzz...")
  14. }
  15. }
  16. var a1 = new Animal("眼睛", 100);
  17. a1.eat();
  18. a1.sleep();
  19. var a2 = new Animal("眼睛1", 200);
  20. a2.eat();
  21. a2.sleep();
  22. console.log(a1.eat === a2.eat); // true

现在只剩下一个变量 可是这一个对象我们也不想要 所以可以挂载到函数本身 => Constructor.prototype 我们能够考虑到这个问题 JS之父布兰登·艾奇也想到了 于是,他规定每一个函数都有一个属性prototype 而该对象 它身上的内容都可以被构造函数的实例访问到 用于共享方法和属性 默认有一个属性 constructor指向构造函数本身

17.8 原型查找机制

12-01-面向对象 - 图1

原型查找机制: 当一个对象调用属性时,会先查找自身是否具备,如果有,就用 如果没有 会查找自身的构造函数的原型(原型对象)是否具备 如果有就用 如果没有 就继续向上查找(因为原型对象也是对象 查它的属性又触发了它的原型查找机制) 直到Object.prototype为止 因为再往上就是null

17.9 对象类型的分辨

  • 方式 Object.prototype.toString.call(对象)
  • 学习了原型之后,我们就可以使用 实例.constructor 来获取对象的构造函数 进而判定对象的类型

17.10 instanceof

用来检测一个对象是否是函数的实例
返回值是布尔值

17.11 hasOwnProperty

该方法是ES5中新增的方法 用于检测一个对象的属性是否是该对象自身的属性
如果是自身属性 返回true 否则返回false