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(); //我的名字是张三解读一下,上面的代码实现了面向对象中所谓“类”的封装。
- 类的模拟:构造函数People能够实例化一批人这种类型的基本对象,模拟的是“类”这一概念。
- 对象属性的模拟:构造函数中的this指向的是构造函数的“实例对象”,所有的实例对象,都具有name,age和sex三种在构造函数中的属性。值来自new运算时,传入的参数。
- 对象方法的模拟:构造函数有一个内置属性——prototype,指向的是所有实例对象的原型对象,将方法赋值给这个原型对象后,所有的实例对象都可以根据原型链的继承原理,来继承方法(例如sayName方法)。这些方法内部写的this,指向的是方法的调用者,也就是实例对象。
- 构造函数的内置属性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]() { // ... } }