类
讲到类,有其它编程语言经验的同学应该是非常熟悉了。类是面向对象编程中的一个重要概念,它是对事物的一种抽象,实例化是对抽象的具体化操作。类与类之间可以有继承关系,也就是我们常说的父类和子类。在继承的过程中又存在多态性这个概念,基类的方法可以在子类中有不同的表现行为。我上面讲到的实例化、继承和多态性都是类中最基本、也是最具代表性的概念了。
那么我们会问道:JavaScript中存在类吗?
其实是没有的,虽然我们经常会看到 new、 instance of 以及ES6中的class关键字,但实际上这些都是为了模拟类的行为和方法设计的一种模式而已,并不是真正意义上的类。
构造函数和constructor
我们知道使用类的第一步就是要实例化对象,那么类是如何被实例化的呢?这里就用到构造函数了,这个方法的任务就是初始化实例所需要的所有信息。
接下来我们首先看一下传统的实例化方法
function Point(x,y){
this.x = x;
this.y = y;
}
let pt1 = new Point(1,2);
console.log(pt1); //Point
通过new操作构造了一个Point实例,这中间经历了这么几个步骤需要读者朋友知道:
1)创建一个新的对象;
2)将函数的作用域赋值给新对象,也就是this指向了这个新对象;
3)执行函数中的内容,将属性方法赋值给我新对象;
4)返回新对象。
接着再看下ES6中对于类的定义
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
}
let pt1 = new Point(1,2);
console.log(pt1);//Point
console.log(typeof Point); //function
console.log(Point === Point.prototype.constructor);//true
从上面的代码就可以发现,其实ES6中的类就是一个语法糖的效果,pt1同样都是指向Point,Point本身也是函数。每个函数都有一个原型指针,这个指针指向原型对象,而对象中的congstructor是指向函数本身的。这句话涉及到原型的知识,后面会讲到,放到这里是为了帮助大家对上述代码更好的理解。
所以说构造函数的prototype是继续在ES6类中存在的,事实上,类的所有方法都定义在类的原型属性上面。
class Point{
constructor(){
}
toString(){
}
toValue(){
}
}
//等同于
Point.prototype = {
constructor(){},
toString(){},
toValue(){}
}
所以对于类中方法的实现完全是可以通过原型来实现的,如果下次面试官让你利用原型将类中的方法实现以下,希望你能轻松应对哦。
constructor方法是类的默认方法,通过new实例化对象时会自动调用该方法。一个类必须有一个constructor,如果没有引擎会自动为其创建一个。
class Point{
}
let pt1 = new Point();
console.log(pt1);//Pont {}
constructor默认返回的是this,但是你也可以返回自己创建的对象。
class Point{
constructor(){
return Object.create(null);
}
}
let pt1 = new Point();
console.log(pt1);//{ }
类的实例化
前面已经讲过,类的实例化化是通过new 实现的。与ES5一样,类的属性除非是显示定义在自身(this),否则都是定义在类实例的原型上。看下面的例子
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
return ("x:"+this.x+"y:"+this.y);
}
}
let pt1 = new Point(1,2);
console.log(Point.hasOwnProperty('x')); //false
console.log(Point.hasOwnProperty('y'));//false
console.log(pt1.hasOwnProperty('x')); //true
console.log(pt1.hasOwnProperty('y'));//true
console.log(Point.hasOwnProperty('toString'));//false
console.log(pt1.__proto__.hasOwnProperty('toString'));//true
从上面代码输出结果可以看出,this指向的类的实例,具有显式属性x和y,而toString既不属于Point,也不属于实例对象,而是属于Point的原型对象,不过实例对象是可以调用的。
私有方法和私有属性
私有方法是很常见的需求,但是ES6中是不提供的,不过可以通过变通的方式实现
class Student{
constructor(age){
this.age = age;
}
//公有方法
myInfo(){
return this._myAge(this.age);
}
//私有方法
_myAge(){
return this.age;
}
}
let xiaoming = new Student('12');
console.log(xiaoming.myInfo());//12
console.log(xiaoming._myAge());//12
虽然可以用下划线暗示该方法是私有方法,但是外部还是可以调用的,这样的私有方法并不安全,那么如何操作呢?同学们可以发动你聪明的小脑袋思考一下。
这里有两种方法:
方法一:将私有方法移出模块之外,因为模块内部的内容外部都可以访问到。
class Student{
constructor(age){
this.age = age;
}
//公有方法
myInfo(){
return myAge.call(this);
}
}
//私有方法
function myAge(){
return this.age;
}
let xiaoming = new Student('12');
console.log(xiaoming.myInfo());//12
方法二:利用Symbol值,这个是ES6中的语法,它的作用就是保证属性和方法的唯一性。
const myAge = Symbol('age func');
const realAge = Symbol('realage');
class Student{
constructor(age){
this.age = age;
}
//公有方法
myInfo(){
return this[myAge](this.age);
}
//私有方法
[myAge](age){
return this[realAge] = age;
}
}
let xiaoming = new Student('12');
console.log(xiaoming.myInfo());//12
类的静态属性
静态属性是类本身的属性,而不是实例的属性,是如下的写法
class Teacher{
}
Teacher.level = 1;
console.log(Teacher.level);//1
这里注意的是ES6要求静态属性只能写在类的外部,写在里面的话会报错,也就是说类的内部没有静态属性,只有静态方法。
类的静态方法
类相当于实例的原型,所以类中定义的方法会被实例继承。而如果在类中的方法前加上static关键字的话,这个方法就属于类了,实例去调用就会出错。
class Teacher{
static speak(){
console.log('I am a teacher');
}
}
let laowang = new Teacher();
console.log( Teacher.speak() );//I am a teacher
console.log( laowang.speak() );//undefined
加上static后,speak方法是属于类而不属于实例 laowang 了。
关于类的基本概念就讲的这里,希望同学们能对js中的类有一个系统的认识,接下来我会讲到类的继承的有关知识。