js面向对象编程

246 阅读6分钟

本文出自 MDN

1.命名空间(Namespace)

使用命名空间也最大程度地减少应用程序的名称冲突的可能性。

命名空间是一个容器,它允许开发人员在一个独特的,特定于应用程序的名称下捆绑所有的功能。 在JavaScript中,命名空间只是另一个包含方法,属性,对象的对象。

// 给普通方法和属性创建一个叫做MYAPP.commonMethod的容器
MYAPP.commonMethod = {
  regExForName: "", // 定义名字的正则验证
  regExForPhone: "/\d{3}-\d{8}|\d{4}-\d{7}/", // 定义电话的正则验证
  validateName: function(name){
    // 对名字name做些操作,你可以通过使用“this.regExForname”
    // 访问regExForName变量
  },
 
  validatePhoneNo: function(phoneNo){
    if(!this.regExForPhone.test(phoneNo)) {
        throw new Error("验证失败");
    }
  }
}

// 对象和方法一起申明
MYAPP.event = {
    addListener: function(el, type, fn) {
        el.addEventListener(type, fn);
    },
   removeListener: function(el, type, fn) {
        el.removeEventListener(type,fn);
   },
   getEvent: function(el) {
       return getEventListeners(el);
   }
  
   // 还可以添加其他的属性和方法
}

//使用addListener方法的写法:
MYAPP.event.addListener(div, "click", callback);

2.标准内置对象(全局的对象)

挂载在javaScript全局上的对象,JavaScript 中的每个对象都是 Object 对象的实例且继承它所有的属性和方法。

MDN JavaScript 标准内置对象

3.自定义对象(类)

JavaScript 面对对象编程,不是基于“类”的实现,而是基于构造函数(constructor)和原型链(prototype)去实现的。

3.1 类的实例

在下面的示例中,我们定义了一个名为Person的类,然后我们创建了两个Person的实例

function Person() { }
var person1 = new Person();
var person2 = new Person();

注意:有一种新增的创建未初始化实例的实例化方法 Object.create

3.2 构造函数

构造函数名字的第一个字母通常大写, 函数体内部使用了this关键字,代表了所要生成的对象实例,生成对象的时候,必须使用new命令。

对象实例被创建时,构造器就会被调用,构造器是对象中的一个方法。 在JavaScript中函数就可以作为构造器使用,因此不需要特别地定义一个构造器方法,每个声明的函数都可以在实例化后被调用执行,构造器常用于给对象的属性赋值或者为调用函数做准备。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。

function Person() {
    alert('Person instantiated');
}
var person1 = new Person();

3.3 属性(对象属性)

属性就是 类中包含的变量;每一个对象实例有若干个属性. 为了正确的继承,属性应该被定义在类的原型属性 (函数)中。

function Person(firstName) {
  this.firstName = firstName;
  alert('Person instantiated');
}

var person1 = new Person('Alice');
var person2 = new Person('Bob');

// Show the firstName properties of the objects
alert('person1 is ' + person1.firstName); // alerts "person1 is Alice"
alert('person2 is ' + person2.firstName); // alerts "person2 is Bob"

3.4 方法(对象方法)

定义一个方法, 需要将一个函数赋值给类的 prototype 属性; 这个赋值给函数的名称就是用来给对象在外部调用它使用的。

function Person(firstName) {
  this.firstName = firstName;
}

Person.prototype.sayHello = function() {
  alert("Hello, I'm " + this.firstName);
};

var person1 = new Person("Alice");
var person2 = new Person("Bob");

// call the Person sayHello method.
person1.sayHello(); // alerts "Hello, I'm Alice"
person2.sayHello(); // alerts "Hello, I'm Bob"

3.5 继承

创建一个或多个类的专门版本类方式称为继承(Javascript只支持单继承)。 创建的专门版本的类通常叫做子类,另外的类通常叫做父类。 在Javascript中,继承通过赋予子类一个父类的实例并专门化子类来实现。在现代浏览器中你可以使用 Object.create 实现继承。

// 定义Person构造器
function Person(firstName) {
  this.firstName = firstName;
}

// 在Person.prototype中加入方法
Person.prototype.walk = function(){
  alert("I am walking!");
};
Person.prototype.sayHello = function(){
  alert("Hello, I'm " + this.firstName);
};

// 定义Student构造器
function Student(firstName, subject) {
  // 1.调用父类构造器, 指向当前子类的this指向,这样父类的属性和原型方法中的this都指向子类。
  Person.call(this, firstName);

  // 初始化Student类特有属性
  this.subject = subject;
};

// 2.把子类的原型对象赋值成一个继承了父类的原型对象的对象。
// 注意: 常见的错误是使用 "new Person()"来建立Student.prototype.
// 这样做的错误之处有很多, 最重要的一点是我们在实例化时
// 不能赋予Person类任何的FirstName参数
// 调用Person的正确位置如下,我们从Student中来调用它
Student.prototype = Object.create(Person.prototype); // See note below

// 3.设置"constructor" 属性指向Student
Student.prototype.constructor = Student;

// 更换"sayHello" 方法
Student.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + ".");
};

// 加入"sayGoodBye" 方法
Student.prototype.sayGoodBye = function(){
  console.log("Goodbye!");
};

// 测试实例:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello();   // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk();       // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"

// Check that instanceof works correctly
console.log(student1 instanceof Person);  // true 
console.log(student1 instanceof Student); // true

3.6 继承的演变

1.借用构造函数继承

缺点: 只能继承父类构造器的属性和方法,无法继承父类的原型属性和原型方法。

function Person() {
    this.name = "父类";
}

Person.prototype.say = function() {console.log("我是" + this.name)}

function Student() {
    Person.call(this, "子类");
    this.age = 20;
}

var student1 = new Student();
student1.name // "父类"
student1.say() // Uncaught TypeError: student1.say is not a function at <anonymous>:1:8

2.借用原型链继承(解决了上述没有继承父类原型的问题)

缺点:其中一个实例改变了引用类型的值,会影响到另外一个实例的值。

function Person() {
    this.name = "父类";
    this.stature = {
        height: 1700,
        width: 1000,
    }
}
Person.prototype.say = function () {
    console.log("这是父类的方法");
}
function Student() {}
Student.prototype = new Person();

var student1 = new Student();
var student2 = new Student();

student1.stature.height = 1800;
student2.stature.height // 1800

3.组合式继承(通过上面两种方式的组合,解决了引用同一个对象的问题)

缺点:每次创建实例的时候,都执行了父类的构造函数。

function Person() {
    this.name = "父类";
    this.stature = {
        height: 1700,
        width: 1000,
    }
}
Person.prototype.say = function () {
    console.log("这是父类的方法");
}
function Student() {
    Person.call(this);
}
Student.prototype = new Person();

var student1 = new Student();
var student2 = new Student();

student1.stature.height = 1800;
student2.stature.height // 1700
  1. 组合式继承优化(解决了上一个例子的重复执行父类的构造函数的问题)

缺点:实例的构造函数指向了父类的构造函数。

function Person() {
    this.name = "父类";
    this.stature = {
        height: 1700,
        width: 1000,
    }
}
Person.prototype.say = function () {
    console.log("这是父类的方法");
}
function Student() {
    Person.call(this);
}
Student.prototype = Person.prototype;

var student1 = new Student();
var student2 = new Student();

5. 组合式继承继续优化 (解决了上述的构造函数指向父类的问题)

下图一:Student.prototype = Person.prototype 会导致父类的构造函数也指向Student。
下图二:使用Object.create,会生成一个全新的对象, 完美解决了同一个对象引用的问题。

function Person() {
    this.name = "父类";
    this.stature = {
        height: 1700,
        width: 1000,
    }
}
Person.prototype.say = function () {
    console.log("这是父类的方法");
}
function Student() {
    Person.call(this);
}

// Student.prototype = Person.prototype;
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

var student1 = new Student();
var student2 = new Student();

图一:

图二:

3.7 封装

在上一个例子中,Student类虽然不需要知道Person类的walk()方法是如何实现的,但是仍然可以使用这个方法;Student类不需要明确地定义这个方法,除非我们想改变它。 这就叫做封装,对于所有继承自父类的方法,只需要在子类中定义那些你想改变的即可。