深入理解 JavaScript 中的 class(类)
学习内容:
1、 掌握ES6中创建类和对象的方式 2、 掌握ES6中类的继承 3、 掌握ES6中静态方法的使用和调用 4、 理解ES6中实例化对象的原理一、ES5:function
1、构造函数
封装、继承、多态是大多OOP语言都支持的特性,而JavaScript在ES5中没有提出真正意义上的类、继承的概念。它通过函数首字母大写的方式告知开发者这是一个构造函数或者类(有些人会将它理解为类),但从严格意义上来讲,它是构造函数。这对于初学者来说,看起来是非常脑溢血的。比如下方code:
// 构造函数
function Person(name, age) {
// 实例属性
this.name = name;
this.age = age;
// 实例方法
this.speak = function() {
console.log(`你好,我的名字叫${this.name},今年${this.age}岁了。`)
}
}
// 实例化对象,并传入参数
let father = new Person("张三", 20);
// 调用实例属性
console.log(father.name);
// 调用实例方法
father.speak();
如同上方code所示,我们通过function
关键字+首字母大写的方式告知开发者,这不是普通的函数,而是一个特殊的函数—构造函数。如果首字母小写则将变成一个普通函数,意义就立马改变了。并且它们的调用方式也不同,普通函数就是以函数的方式调用函数名();
,而构造函数可以以这样的方式调用,同时也可以使用new
关键字来实例化对象。
2、静态方法
在大多数OOP语言中,静态基本上都是为了共享数据,以减少实例对象所造成的内存消耗。但是ES5中并没有提出相对明确的静态方法。但是,我们可以通过原型对象来实现静态方法。比如下方code:
// 构造函数
function Father(name) {
// 实例属性
this.name = name;
// 实例方法
this.run = function() {
console.log(`我叫${this.name},我会跑!`)
}
}
// 静态属性
Father.age = 20;
// 给Father构造函数的原型中添加方法
Father.prototype = {
// 重新指向构造函数
constructor: Father,
// 原型方法
speak: function() {
console.log("我会说话!");
},
run: function() {
console.log("我会跑!");
}
};
// 实例化对象
var father = new Father("张三");
//调用静态属性
consloe.log(Father.age);
// 调用原型方法
father.speak();
在上述code中,我们通过给构造函数的原型以对象的赋值,并将指针重新指向构造函数的方式来给构造函数添加静态方法,而静态属性我们使用构造函数名.属性名=属性值;
的方式实现,调用的时候使用构造函数名.属性名;
的方式调用。
3、继承
在大多数OOP语言中,继承基本上都是为了减少code冗余,提高code的可读性和灵活性以及可维护性而存在的。但是JavaScript在ES5中并没有提出相对明确的继承概念。但是在ES5中,我们可以通过原型链和一些方法来实现真正意义上的继承。比如下方code:
// 基类构造函数
function Father(name, age) {
// 实例属性定义在构造函数内部
this.name = name;
this.age = age;
// 判断原型中的该属性是否是函数类型的,也就是判断原型中是否有该方法
if ((typeof Father.prototype.speak) !== "function") {
// 方法定义在原型中
Father.prototype.speak = function() {
console.log(`我叫${this.name},今年${this.age}岁了`)
}
}
}
// 派生类构造函数
function Son(name, age, sex) {
// 调用基类的构造函数,相当于把基类中的属性添加到未来的派生来实例对象中
// 将Father基类中的this指向改变为son的实例,并将基类所需要的参数传递
Father.call(this, name, age);
// 实例对象属性
this.sex = sex;
}
// 修改派生类的原型,这样就可以继承基类原型中的方法
Son.prototype = new Father();
// 实例化son,并将基类所需要的参数一并传递
var son = new Son("小明", 20, "男");
console.log(son.name);
console.log(son.age)
son.speak();
在上述code中,我们可以看出派生类通过Son.prototype = new Father();
更改派生类原型为基类实例的方式来继承基类中的属性和方法,而通过Father.call(this, name, age);
修改this指向来调用基类的实例属性和方法,将其变成派生类实例的属性和方法,假如实例对象中有与原型同名的属性和方法则默认替换。在ES5中它是以这样的原理来实现继承的。
但是在实际开发中,以这样的方式实现静态、继承确实是有些麻烦,于是乎JavaScript在ES6中正式提出了class(类)、constructor(构造函数)、extends(继承)、static(静态)的概念。
二、ES6:class
2015年6月JavaScript正式发布ES6版本,并且最重要的特点之一就是,引入了class
的概念,使全球js开发者收益,使得js开发者终于告别了,直接使用原型对象模仿面向对象中的类和类继承的时代。
但是js中并没有一个真正的class
原始类型,class
仅仅只是对原型对象运用的语法糖。所以,只有理解如何使用原型对象实现类和类继承,才能真正地用好class
。那么先上code让大家体验一下:
class Person {
// 构造函数
constructor(age) {
this.age = age;
};
// 静态方法
static speak = function(age) {
console.log(`我的名字叫${Person.uName},今年${age}岁了`)
};
}
// 静态属性
Person.uName = "你好";
// 实例化对象
var father = new Person(20);
// 调用属性和方法
console.log(Person.uName);
Person.speak(father.age);
// 试图修改静态属性,但是不可以
Person.name = "小明";
console.log(Person.uName);
我们看到出现了一个个新的关键字,关键字class
用于声明一个类,关键字constructor
用于声明构造函数,关键字static
用于声明静态方法,而静态属性则于ES5中一样,同样只能写在类的外边。并且静态属性和方法不能被实例对象所调用,只能被类所调用。
ES6的语法很简单,并且也很容易理解。但是在实例化的背后,究竟是什么在起作用呢?
三、class
实例化背后的原理
使用class
的语法,让开发者告别了使用prototype
原型对象模仿面向对象的时代。但是,class
并不是ES6引入的全新概念,它的原理依旧是原型对象和原型链。
我们前边不是学习了测试数据类型的知识嘛,那么我们是不是就可以使用typeof
来检测类的类型,请看下方code:
// 声明一个类
class Person {
// 构造函数
constructor(age) {
this.age = age;
};
// 静态方法
static speak = function(age) {
console.log(`我的名字叫${Person.uName},今年${age}岁了`)
};
}
// 静态属性
Person.uName = "你好";
// 实例化对象
var father = new Person(20);
// 测试Person的类型
console.log(typeof Person);
通过typeof
数据类型测试,我们惊奇的发现,class
并非什么全新的数据类型,它实际上只是function
函数类型。
为了能够更加直观的了解Person
类,我们可以将它的实例对象在控制台上输出,结果如下:
我们发现Person实例对象的属性并不多,除去内置属性外,大部分属性根据名字都能明白它的作用。其中需要我们关注的两个属性是prototype原型对象
和__proto__实例对象的原型
。相信大家肯定会问:不是ES5中的原型和原型链嘛?没错,这就是原型对象。也就是说使用class创建对象的方式就是隐式的使用原型对象和原型链。
分析:
1. ES6中`声明类和构造函数`其实就等于ES5中使用`function`声明的构造函数
2. 而静态方法和静态属性其实就是在原型对象中设置的属性和方法
3. 在实例化对象的时候,浏览器自动创建了一个对象(原型对象),并且将该对象所属类的构造函数与原型对象绑定,或者说原型对象记录了构造函数的引用并指向了构造函数,而构造函数的`prototype`属性指向了原型对象
4. 当通过实例属性调用静态属性和方法的时候,实例属性的`__proto__`属性又指向了原型对象
5. 那么这无疑是ES5中构造函数、实例对象与原型对象的三角关系
**温馨提示:**如果有看不懂的朋友,请看博主上两篇博文,详细讲解了原型对象和基与原型对象的继承。
四、继承
在传统面向对象中(ES5面向对象),类是可以继承类的,这样派生类就可以使用基类中的属性和方法,来达到code复用的目的。
当然,ES6中也可以实现继承。ES6中明确提出了类的继承语法,即使用extends
关键字,并使用super()
方法调用基类的构造函数。其实super()
类似于call()
中的借调继承。请看下方code:
// 基类
class Anima {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
console.log(`我的名字叫${this.name},今年${this.age}岁了`);
}
}
// 派生类继承基类
class Dog extends Anima {
constructor(name, age, sex) {
// 调用基类的构造
super(name, age)
// 实例成员
this.sex = sex;
// 静态成员
Dog.color = "红色";
// 静态方法
Dog.run = function() {
console.log("我想睡觉");
}
}
// 方法重写
say() {
console.log(`我的名字叫${this.name},今年${this.age}岁了,颜色是${Dog.color}`);
}
}
var dog = new Dog("大黄", 18, "男");
dog.nm = "哈哈"
dog.say()
Dog.run();
console.log(dog);
console.log(Dog);
实际上ES6中的继承与ES5中的继承原理是一样的,其本质都是基与原型对象和原型链的继承。
五、对象常用方法
Object.key(对象)
:获取当前对象中的属性名,并返回一个包含所有属性名的数组。Object.defineProperty()
:该方法用于设置、修改、添加对象中的属性
Object.defineProperty(对象,修改或新增的属性名,{
value:修改或新增的属性的值,
writable:true/false,//如果值为false 不允许修改这个属性值
enumerable: false,//enumerable 如果值为false 则不允许遍历
configurable: false //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性
})
体验:
// 基类
class Anima {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
console.log(`我的名字叫${this.name},今年${this.age}岁了`);
}
}
// 派生类继承基类
class Dog extends Anima {
constructor(name, age, sex) {
// 调用基类的构造
super(name, age);
// 实例成员
this.sex = sex;
// 静态成员
Dog.color = "红色";
// 静态方法
Dog.run = function() {
console.log("我想睡觉");
}
}
// 方法重写
say() {
console.log(`我的名字叫${this.name},今年${this.age}岁了,颜色是${Dog.color}`);
}
}
// 实例化dog对象
var dog = new Dog("大黄", 18, "男");
// 设置dog对象中的sex属性
Object.defineProperty(dog, "sex", {
value: "小明",
writable: false,
enumerable: false,
configurable: false
});
// 试图修改,但是并不能修改
dog.sex = "女";
// 打印的还是 小明
console.log(dog.sex);
// 以数组的形式获取对象中所有对象的属性
// 注意:由于我们上边设置sex属性不可遍历,所以无法通过key()方法获取到sex属性,因为key()方法的原理是通过遍历查找的
console.log(Object.keys(dog));
六、总结
虽然ES5中,class(类)的继承和静态方法要比ES5中的语法简单,好理解。但是js中并没有一个真正的class
原始类型,class
仅仅只是对原型对象运用的语法糖。
简单来说,js中不管是ES5还是ES6中的继承和静态方法的实现都是基与原型对象和原型链实现的。所以,只有理解ES5中如何使用原型对象实现类和类继承,才能真正地用好ES6中的class
。希望大家不要因为ES6的语法简单而忽略ES5中的知识。
不管是ES6也好,ES7也罢,基本上都是基与ES5来封装实现的,所以希望各位博友能够先学好ES5。同时也希望大家能够广集意见,如果博主有说的不对的地方还请给位博友纠正。当然如果还有不懂的地方,博主也会给大家详细解释。最后感谢各位博友的支持,以及感谢CSDN能够提高这样好的一个平台,谢谢!