JavaScript中面向对象——类的封装(一)

时间:2022-08-10 19:51:47

     JavaScript本身并不是一种“面向对象”的语言(它是“基于对象”的),但它可以凭借自身的灵活性,用开发技巧,模仿出“面向对象”语言的效果。在ES6中标准中,将这种“技巧”规范化成了class 关键字。

     之前我们谈过new运算符,知道它的作用是:“实例化构造函数,以提供‘批量’生成某‘类’对象的手段”。

//用构造函数,模拟"类"这一概念
function People(name = "默认姓名",age = 0 ,sex = "男"){
    this.name = (name instanceOf String)? name : "无名氏";  // 检查姓名是否是字符串类型,如果否则赋值默认姓名。
    this.age =  ((age instanceOf Number)&& (age > 0)) ? age : -0 ;  // 检查年龄是否大于0,如果否则赋值-0。
    this.sex = (sex === "男")? sex :"女";                    // 检查性别是否是男,如果否,则赋值女。
    //在构造函数上只保证,实例不会出现数据类型上的明显错误,其他验证在工程的其他代码处检查。
}
People.prototype.sayName = function(){
    console.log("我的名字是"+ this.name);
}
//实例化一个对象
var person1 = new People('张三',15,'男');
person1.sayName(); 
//我的名字是张三
     解读一下,上面的代码实现了面向对象中所谓“类”的封装。

  1. 类的模拟:构造函数People能够实例化一批人这种类型的基本对象,模拟的是“类”这一概念。
  2. 对象属性的模拟:构造函数中的this指向的是构造函数的“实例对象”,所有的实例对象,都具有name,age和sex三种在构造函数中的属性。值来自new运算时,传入的参数。
  3. 对象方法的模拟:构造函数有一个内置属性——prototype,指向的是所有实例对象的原型对象,将方法赋值给这个原型对象后,所有的实例对象都可以根据原型链的继承原理,来继承方法(例如sayName方法)。这些方法内部写的this,指向的是方法的调用者,也就是实例对象。
  4. 构造函数的内置属性prototype是一个对象,对象中有属性constructor,这个属性指向的是构造函数本身。所以在实例对象上,我们可以通过访问实例对象的原型对象的constructor属性,来访问实例的构造函数。但是要注意,这有一个前提是,该属性没有被更改。毕竟作为动态语言的JavaScript,是可以在运行时更改属性值的。

     在ES5的时代,上面介绍的这种封装类的手段十分常见,所以在ES6中ECMA将这种技巧标准化,并做出了相关规定。我们用ES6的Class 写法,将上面的例子,重新写一遍,代码如下:

// ES6中类的写法
class People{
   constructor(name="默认姓名",age=0,sex="男"){
      this.name = (name instanceOf String)? name : "无名氏";  // 检查姓名是否是字符串类型,如果否则赋值默认姓名。
      this.age =  ((age instanceOf Number)&& (age > 0)) ? age : -0 ;  // 检查年龄是否大于0,如果否则赋值-0。
      this.sex = (sex === "男")? sex :"女";                    // 检查性别是否是男,如果否,则赋值女。
      //在构造函数上只保证,实例不会出现数据类型上的明显错误,其他验证在工程的其他代码处检查。
   }
   // 实例方法
   sayName(){
     console.log("我的名字是:"+this.name);
   }
}
var person2 = new People('张三',15,'男');
      构造函数的 prototype 属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的 prototype 属性上面。其中构造函数对应constructor,这相当于ES5中构造函数的prototype.constructor。构造函数中的this依然指向实例对象。实例方法则是class内部除了构造函数之外的其他函数,它们中的this也指向实例对象。class中方法的写法类似于“ES6对象中方法的简化写法”。总结一句话,ES6中的class的实质是ES5中封装类的技巧的一种“语法糖”。

     由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。

Object.assign(People.prototype, {
  sayHello(){cosnole.log("hello");};
  sayHi(){console.log("Hi");}
});
    注意:另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。在这一点上,ES6与ES5不同

    

class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
var Point = function (x, y) {
  // ...
};

Point.prototype.toString = function() {
  // ...
};

Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
     另外,与对象的方法一样, 类的属性名,可以采用表达式。

let methodName = 'getArea';

class Square {
  constructor(length) {
    // ...
  }

  [methodName]() {
    // ...
  }
}