JavaScript对象

JavaScript对象

OOP似乎是编程领域绝对避不开的话题😄。我个人的理解面向对象就是对问题的抽象,将问题抽象成一个个小的模块分而治之,模块之间的关系尽量单一清晰,这样代码在扩展和维护的时候所花费的精力最小,也就是所谓的高内聚,低耦合。之所以它并不容易掌握,就是这个抽象能力是需要通过各种复杂的问题不断训练的。很多时候我们只是想着将写出来的代码进行复用,不要写出重复的代码,但其实最重要的是对问题的抽象,划分出合理的模块和设计模块之间的耦合关系,代码复用只是这个过程中自然而然产生的现象,我们应该明白问题的本质。

JavaScript并不像一些强类型语言那样使用class继承,而是基于原型的方式继承。从ES6开始,出现了class关键字,这让JavaScript看看起来更像面向对象的编程语言。但是,实际上ES6的class关键字不过是原型继承的语法糖而已。

创建对象

① 工厂模式(Factory Pattern)创建类对象

1
2
3
4
5
6
7
8
9
10
11
12
function Car(color,doors,oil) {
let tempCar = new Object();
tempCar.color = 'red';
tempCar.doors = 4;
tempCar.oil = '10%';
tempCar.showOil = function () {
alert(this.oil);
};
return tempCar;
}

let car = Car('red',4,75%);
  • 工厂模式创建对象非常容易理解,将创建对象,添加属性的过程封装为一个函数,属性值作为函数的参数传入,当我们想要一个对象的时候,执行这个函数即可。
  • 但是工厂模式有一个缺点,就是所有有其创建的实例都是使用用Object构造的,不易区分。

② 构造函数(constructor)创建类

  • 构造函数相比工厂函数的区别在于:构造函数方法的目的是创建模版,使用了this关键字。

与工厂模式相比,构造函数方法优点没有显式地创建对象,而是直接将属性和方法赋值给了this对象,在构造函数内部,this指向构造出来的内部对象。此外,这种方法没有return语句,构造函数默认返回this,即新实例对象 。其次构造函数创建的对象可识别

调用函数时,构造函数创建类一定会使用new操作符。

  • 构造函数没有返回值,这一点类似于一些语言创建类的方法(例如Java、C++和Python都以 class 关键字来创建类,都没有return关键字)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function Vehicle(vCholor,vDoors,vOil){
    this.color = vCholor;
    this.doors = vDoors;
    this.oil = vOil;
    this.showOil = function () {
    alert(this.oil);
    };
    }

    let vehicle1 = new Vehicle('red',4,'75%');

    console.log(vehicle1.constructor === Vehicle); //true
    console.log(vehicle1 instanceof Vehicle); //true

当然,构造函数也有缺点:会将方法在每个实例上都创建一遍。

③ 基于原型+构造函数创建类对象

这种方法完全基于JavaScript原型机制创建,体现出原型继承思想的精髓。

  • 每创建一个函数就有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那么 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function Person(name,age) {
    this.name = name;
    this.age = age;
    this.frients = ['JackieChan', 'EasonChan'];
    };
    Person.prototype = {
    constructor: Person,
    job: "Software Engineer",
    sayName () {
    return this.name;
    }
    };
    let person1 = new Person("andy8421", 18);
    let person2 = new Person("googleplex", 25);
    // 也可以使用Object.create()方法创建实例
    使用这种方法是有一个关键点:引用类型不要放到原型上,例如上代码中this.frients = ['JackieChan', 'EasonChan'];不能放到Person.prototype中,否则会出现修改冲突:当person1修改friends时,person2的friends也会被修改。这也是为什么一般不单独使用原型模式创建对象,而是使用原型+构造函数组合模式创建的原因。

④ class关键字

从ES6开始出现class关键字,它使得JavaScript看起来更像面向对象的编程语言。然而实质上,class关键字是基于原型+构造函数创建类对象的一个语法糖而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person{
constructor(name,age){
this.name = name;
this.age = age;
this.frients = ['JackieChan', 'EasonChan'];
}
job = "Software Engineer";
sayName () {
return this.name;
}
}
let person1 = new Person("andy8421", 18);
let person2 = new Person("googleplex", 25);
// 也可以使用Object.create()方法创建实例

总结

对象的创建模式

  • Object构造函数模式

    1
    2
    3
    var obj = {};
    obj.name = 'Tom'
    obj.setName = function(name){this.name=name}
  • 对象字面量模式

    1
    2
    3
    4
    var obj = {
    name : 'Tom',
    setName : function(name){this.name = name}
    }
  • 构造函数模式

    1
    2
    3
    4
    5
    6
    function Person(name, age) {
    this.name = name;
    this.age = age;
    this.setName = function(name){this.name=name;};
    }
    new Person('tom', 12);
  • 构造函数+原型的组合模式

    1
    2
    3
    4
    5
    6
    function Person(name, age) {
    this.name = name;
    this.age = age;
    }
    Person.prototype.setName = function(name){this.name=name;};
    new Person('tom', 12);
  • class关键字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Foo {}

    // 构造函数的类
    class Bar {
    constructor() {}
    }

    // 获取函数的类
    class Baz {
    get myBaz() {}
    }

    // 静态方法的类
    class Qux {
    static myQux() {}
    }

    OOP的特点

  • 面向对象(OOP)的三大基本特性

封装:隐藏对象的属性和实现细节,仅对外公开接口,控制程序中属性读和修改的访问级。将数据与操作数据的源代码进行有机结合,形成‘类’。其中的数据和函数都是类的成员。
继承:使子类具有父类的属性。
多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

  • 面向对象语言的四个基本准则:

抽象:忽略主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。
继承: 使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。
封装: 隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别
多态: 接口可以有多种不同的实现方式


版权声明:本文作者为「Andy8421」.本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!