文章目录
一、class
今后,只要在es6中创建一种新的类型,包含构造函数+原型对象方法,用class来创建。
1. 定义class:
class:
集中保存一种类型的所有子对象的统一属性结构和方法定义的程序结构。
步骤:
-
用
class{}
包裹原构造函数+原型对象方法 -
原构造函数名升级为整个class的名字,所有构造函数统一更名为"constructor"
-
原型对象中的方法,不用再加prototype前缀,也不用=function,直接简写为:
方法名(){ ... ...}
直接放在class{}内的方法定义,其实还是保存在原型对象中的
使用class:
var 对象名=new class名(属性值,...);
2. 本质
a. 构造函数中的属性,依然会成为子对象的自有属性
b. 直接定义在class中的方法,保存在子对象的原型对象中
c. 子对象依然使用_ _proto_ _
指向原型对象
示例:
// class{} 包裹原构造函数+原型对象方法 原构造函数名升级为整个class的名字
class Student{
//1. 构造函数统一更名为constructor
constructor(sname,sage) {
//仍然为自有属性
this.sname=sname;
this.sage=sage;
}
//2. 原型对象: 直接放在class{}内的方法,保存在原型对象中的方法
intr(){
console.log(`I'm ${this.sname}, I'm ${this.sage}`)
}
}
//创建两个学生:
var lilei=new Student("Li Lei",11);
var hmm=new Student("Han Meimei",12);
console.log(lilei);
console.log(hmm);
lilei.intr();
hmm.intr();
3.定义共用属性
直接在class中定义的方法,都默认保存在原型对象中。但是直接在class中定义的属性,却不会成为共有属性,不会保存在原型对象中,而是成为每个子对象的自有属性。
那么如果我们想在class中定义多个子对象的共用属性呢?
方法一:构造函数prototype方法
在构造函数中添加原型对象的属性:
类名.prototype.属性名=属性值
tip:但是要注意的是应在构造函数中设置该属性时加上一个判断,防止外界更改该共用属性后,再new新对象时,该共用属性又变回构造函数内的默认值!
if (类型名.prototype.共有属性名=== undefined) {
类型名.prototype.共有属性 = 属性值;
}
示例:
class Student{
//className="幼儿园小班" //自有属性
constructor(sname, sage) {
this.sname = sname;
this.sage = sage;
//如果当前类型的原型对象中不包含指定的共有属性
if (Student.prototype.className===undefined) {
//就自动向原型对象中添加共有属性
Student.prototype.className="幼儿园小班"
}
}
intr() {
console.log(`I'm ${this.sname}, I'm ${this.sage}`)
}
}
//创建两个学生:
var erya = new Student("erya", 5);
var yuanyuan = new Student("yuanyuan", 6);
console.log(erya, yuanyuan);
//过了一年,两人共同升了一级
Student.prototype.className="幼儿园中班"
console.log(erya.className, yuanyuan.className);
方法二:静态属性
静态属性: 不需要创建子对象,单靠类型名就可直接访问的属性
今后,在ES6中,如果希望所有子对象都可使用一个共同的属性值时,都要用静态属性代替原来的原型对象属性!
定义静态属性:
class 类型名{
static 共有属性名=属性值
...
...
}
访问静态属性:
错误: this.静态属性
正确: 类型名.静态属性
原理:
标有static的静态属性,都是保存在构造函数对象身上。
构造函数在程序中不会重复=>静态属性也不会重复!
任何时候,任何地点,访问一个类型的静态属性,永远访问的都是同一份!
示例:
class Student{
constructor(sname, sage) {
this.sname = sname;
this.sage = sage;
}
intr() {
console.log(`I'm ${this.sname}, I'm ${this.sage}, I'm from ${Student.className}`);
}
static className = '幼儿园小班';
}
//创建两个学生:
var erya = new Student("erya", 5);
var yuanyuan = new Student("yuanyuan", 6);
erya.intr();
yuanyuan.intr();
//过了一年,两个学生都升级
Student.className="幼儿园中班";
erya.intr();
yuanyuan.intr();
二、class中保护对象
2.1 保护单个属性
a.用开关保护
保护对象的代码要写在构造函数中,且要保护的对象用this代替!
"use strict";
class Emp{
constructor(eid, ename, salary) { //妈妈
this.eid=eid;//只读
this.ename=ename;//禁止删除
this.salary=salary;//禁止for in遍历
//当程序执行到这里时,正在创建子对象过程中
//如果在正在创建子对象过程中,想获得正在创建的子对象,保护正在创建的子对象
// 传值!
// ↓
Object.defineProperties(this, {
//哪些属性:
eid:{
writable:false,
configurable:false
},
ename:{
configurable:false
},
salary:{
enumerable:false,
configurable:false
}
})
}
}
var erya=new Emp(1001, "erya", 12000);
尝试做一些非法操作:
//尝试打开eid的writable开关
Object.defineProperty(erya,"eid",{
writable:true
});//报错: Cannot redefine property: eid
//尝试修改eid
erya.eid=-2;//报错: Cannot assign to read only property 'eid'
//尝试删除ename
delete erya.ename;//报错:Cannot delete property 'ename'
//尝试遍历所有属性(包括salary)
for (var key in erya) {
console.log(`属性名: ${key}, 属性值:${erya[key]}`)
}//虽然可以遍历,但是没有salary
b.访问器属性保护(ES6新做法)
- 在
class{}
中定义访问器属性 get 函数
和set函数
变为两个独立的函数,但是名称却是相同的,都是访问
器属性名。
class Emp{
constructor(eid, ename, eage) {
this.eid=eid;
this.ename=ename;
//先创建一个半隐藏的_eage属性,但是暂时不要给属性值
Object.defineProperty(this, "_eage", {
//value:undefined
writable:true,
enumerable:false,
configurable:false
})
//再修改Emp类型的原型对象中的访问器属性eage的enumerable为true
//Object.defineProperty(this,"eage",{...})//错误: eage是定义在class中的,保存在原型对象中。要想修改原型对象中的成员,都要用原型对象本身去修改!而不应该用某一个子对象去修改!
Object.defineProperty(Emp.prototype,"eage",{
enumerable:true
});
this.eage=eage;
}
//创建访问器属性eage,保护eage的值必须介于18~65之间
get eage() {
return this._eage;
}
set eage(value) {
if (value>=18&&value<=65) {
this._eage=value
} else {
throw Error(`年龄超范围`)
}
}
}
var erya=new Emp(1001, "erya", 25);
console.log(erya);
2.2 保护多个属性
三、 两种类型间的继承:
两种class之间包含部分相同的属性结构和方法定义时,可以使两种class类型间继承
3.1 步骤
1). 额外创建一个父级class
i. 父级class的构造函数中包含子类型class中相同部分的属性结构定义
ii. 父级class的原型对象中包含子类型class中相同部分的方法定义
iii. 既然父级class中保存了相同的属性结构和方法定义,则子类型class中可以删除所有重复的属性结构和方法定义
iv. 问题: 子类型class的子对象缺少必要的属性,而且有些共有方法也无法使用了!
2) 让子类型class继承父类型的class
i. extends 子类型的原型对象继承父类型的原型对象:
class 子类型 extends 父类型{
...
}
用extends 父类型
取代 Object.setPrototypeOf(xxx, xxx)
:
原理: 只是设置子类型的原型对象继承父类型的原型对象,只能保证孙子对象可以使用爷爷类型原型对象中的共有方法!暂时无法为孙子对象补全缺少的自有属性.
ii. 使用super,在子类型构造函数中调用父类型构造函数
extends附赠一个关键词super
,super
指向父类型的构造函数
调用super()
,就等效于调用父类型的构造函数了
d. 结果:
1). 孙子对象可使用3处保存的方法: 自己的,父级class的,爷爷class的
2). 孙子对象中拥有两处规定的属性:父类型构造函数+爷爷类型构造函数。
示例:
示例:
// 父类型class
class Enemy {
constructor(x,y) {
this.x=x;
this.y=y;
}
// 父类型class的原型对象
fly() {
console.log(`飞到:x=${this.x},y=${this.y}`)
}
}
// 子类型class plane继承父类型class enemy
class Plane extends Enemy{
constructor(x,y,score) {
super(x,y);//->父类型Enemy的constructor()
//相当于执行了
//Enemy的constructor(){
// this.x=x;
// this.y=y;
//}
this.score=score;
}
getScore() {
console.log(`击落敌机得${this.score}分`)
}
}
// 子类型class san继承父类型class enemy
class San extends Enemy{
constructor(x, y, award) {
super(x,y);
this.award=award;
}
getAward() {
console.log(`击落降落伞,得${this.award}奖励`)
}
}
var p1=new Plane(50,100,5);
console.log(p1);
p1.fly();//p1的爷爷Enemy给的
p1.getScore();//p1的爸爸Plane给的
var s1=new San(20,80,"1 life");
console.log(s1);
s1.fly();//s1的爷爷Enemy给的
s1.getAward();//s1的爸爸San给的