JavaScript 语言中,生成实例对象的传统方法是通过构造函数。相对于传统的面向对象语言,语义化等方面差强人意。ES6引入了 Class概念,作为对象的模板,一定程度上可以将class视为构造函数的语法糖。
|
|
| 传统构造函数 |
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.toString=function(){
return `My name is ${this.name} , i am ${this.age} years old.`
}
var Joe=new Person('Joe',24);
| Class |
class Person{
constructor(name,age) {
this.name=name;
this.age=age;
}
toString(){
return `My name is ${this.name} , i am ${this.age} years old.`
}
}
const Joe=new Person('Joe',24)
ES6 的类,完全可以看作构造函数的另一种写法。
class Person{}
console.log(typeof Person);//function
console.log(Person===Person.prototype.constructor);//true
| 特性 |
1.class 内定义的成员方法都是在类的prototype上的 ,如果是箭头函数则是定义在类实例上。
2.class 内声明的方法直接写方法名,使用function声明报错 。也无法在class内显示声明变量。
3.class 内部所有定义的方法,都是不可枚举的(non-enumerable)。
class Teacher{
say(){}
do(){}
run=()=>{}
}
Object.keys(Teacher.prototype);//[]
Object.getOwnPropertyNames(Teacher.prototype);//["constructor", "say","do"]
4.通过Object.assign()方法可以向class批量添加方法。
Object.assign(Person.prototype, {
toString(){},
toValue(){}
});
5.类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
| constructor() |
1.constructor()是类的默认方法,通过new命令生成对象实例时,自动调用该方法。如果没有显式定义,一个空的constructor()会被默认添加。
2.constructor()默认返回实例对象(this),因此完全可以显示return obj; 以此改变默认返回的对象。
class Foo {
constructor() {
return Object.create(null);
}
}
console.log(new Foo() );//{}
console.log(new Foo() instanceof Foo);//false null没有原型对象
3.创建实例的时候constructor()等同于构造函数,代码块内this指向的属性都会作为实例自身的属性。(没有修改默返回对象情况下)
4.实例属性除了定义在constructor()里面的this上面,也可以定义在类的最顶层。
class Foo{
bar = 1;
baz = 2;
fun(){}
}
new Foo();// Foo {bar: 1, baz: 2}
可见定义在顶层的属性实则还是放进了constructor()中执行的。
| 取值函数 getter 和 设值函数 setter |
取值函数和设值函数,就是get set属性描述符。因为是方法的形式,所以也是定义在prototype上的。
class Teacher{
constructor(name) {
this._name=name;
}
get privateName(){
return this._name;
}
set privateName(name){
this.privateName=_name;
}
}
const Joe=new Teacher('Joe');
console.log(Joe.privateName);//Joe
console.log(Object.getOwnPropertyDescriptor(Teacher.prototype,"privateName"));
//{enumerable: false, configurable: true, get: ƒ, set: ƒ}
| class表达式 |
| 声明 |
const MyClass = class { /* ... */ };
| 立即执行 |
const MyClass = new class { /* ... */ }();
| 特殊 |
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
//let in = new Me();//报错
let inst = new MyClass();
console.log(inst.getClassName());
这个类的名字是Me,但是Me只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass引用。
| class需要注意的地方 |
| 严格模式 |
类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。
| class不能提升 |
class 声明的类名不存在变量提升。
new Foo(); // ReferenceError
class Foo {}
| this 的指向 |
类的方法内部如果含有this,它默认指向类的实例。但是必须非常小心,一旦单独使用该方法,很可能因为this指向丢失报错。
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
//logger.printName(); //Hello there
const { printName } = logger;//传递了引用
printName();//报错 Uncaught TypeError: Cannot read property 'print' of undefined
logger.printName()是正常打印的,因为printName()使用的this指向的就是logger实例对象。
const printName =logger.printName 建立一个指向logger.printName方法体的引用。printName()执行的时候,依赖的上下文对象是全局对象。相当于要把全局对象传递进去当成this,然而,严格模式下全局环境禁止 this 指向全局对象,而是指向undefined的。
📌构造函数模拟上述class
function Logger(){
}
Logger.prototype.printName=function(name = 'there'){
console.log(this);//window
this.print(`Hello ${name}`);
}
Logger.prototype.print=function(text){
print(text);
}
const logger = new Logger();
const { printName } = logger;
printName();//执行了window.print() 触发页面打印功能
📌解决class 方法调用失去上下文(this)问题
一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
创建实例的时候,调用构造方法时候已经绑定了printName 方法的this就是实例对象了。
另一种解决方法是使用箭头函数。
|
|
类的普通的方法都是定义在类的原型上的,方法会被继承,需要通过类的实例对象来调用。
| 特征 |
1.类的静态方法则是定义在类上的,方法不会被继承,直接通过类调用。
class Foo{
static fun(){console.log('static');}
bar(){}
}
console.dir(Foo)//class Foo
Foo.fun();//static

2.该方法没被继承,隶属于类对象的,无法被实例调用。
new Foo().fun();//Uncaught TypeError: (intermediate value).fun is not a function
3.如果静态方法包含this关键字,这个this指的是类,而不是实例。
4.静态方法可以与非静态方法重名。
class Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.bar() // hello
| 静态属性 |
静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
两种方式:1.class 内static声明 2.class 外像普通对象一样加属性
class Foo{
bar = 1;
static baz=2
}
Foo.bac=3
console.log(new Foo())// Foo {bar: 1}
console.log(Foo.bac,Foo.baz);//3 2
|
|
ES6 不提供私有方法和私有属性,只能通过变通方法模拟实现。
私有方法和私有属性,指的是只能在类的内部访问的方法和属性,外部不能访问。
| 声明式的私有 |
变量名方法名前面加下划线,告诉别人只是私有的,一种约定俗成的做法!明显,这样做并不真正的私有,掩人耳目罢了!
class Foo{
_bar = 1;
_baz(){}
}
| 使用Symbol类型的属性名 |
这个只要编码者不透露信息给使用者捕捉并解读到,就相当于私有了
const bar=Symbol()
const baz=Symbol()
class Foo{
[baz] = 1;
[bar](){}
}
let foo=new Foo()
console.log(foo);//Foo {Symbol(): 1}
console.log(Reflect.ownKeys(foo))//[Symbol()]

|
|
Class 可以通过extends关键字实现继承, ES5 则通过修改原型链实现继承。
| 继承机制 |
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面Parent.apply(this)。
ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到子类的this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
1.子类必须在constructor()中调用super(),否则新建实例时会报错。
因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super(),子类就得不到this对象。
class Parent{
constructor(name){
this.name=name
}
}
class Son extends Parent{
constructor(name,age) {
super(name);
this.age=age
}
}
let s=new Son('Joe',25);
2.子类没有显示定义constructor方法,会在默认构造方法调用super方法。
class Son extends Parent{
}
// 等同于
class Son extends Parent{
constructor(...args) {
super(...args);
}
}
3.在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。
class Son extends Parent{
constructor(name,age) {
console.log(this.name);//Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
super(name);
this.age=age
}
}
4.Object.getPrototypeOf()判断一个类是否继承了另一个类。
console.log(Object.getPrototypeOf(Son)===Parent);//true
| super |
super这个关键字,既可以当作函数使用,也可以当作对象使用。
| super作为函数 |
代表父类的构造函数,只能用在子类的构造方法中。
class A {}
class B extends A {
constructor() {
super();
}
}
super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)。
| super作为对象 |
在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class Father{
constructor(){
this.other=1;
}
}
class Son extends Father{
constructor() {
super();
console.log(super.other);//undefined
}
}
console.log(new Son());
other属性是定义在父类的实例对象上的(constructor方法是用来创建实例的,this指向实例对象),而在普通方法中super是指向原型对象的。super指向Father.prototype。
在子类普通方法中通过super调用父类的方法时,父类方法内部的this也指向当前的子类实例。`super.say.call(this)`
class Father{
x=1;
say(){
console.log(this.x);
}
}
class Son extends Father{
x=2;
speak(){
super.say()
}
}
let boy=new Son();
boy.speak();//2
子类对super的属性的读取操作:读取得是父类原型属性,写入的是子类实例
class Father{
x=1;
say(){
console.log(this.x);
}
}
class Son extends Father{
constructor(arg) {
super();
this.x=2;
super.x=3;
console.log(this.x);//3
console.log(super.x);//undefined
}
}
let boy=new Son();
super在静态方法之中指向父类自身,在普通方法之中指向父类的原型对象。
class Father{
x=1;
static x=2;
static say(){
console.log('c',this.x);
}
}
class Son extends Father{
x=3
static speak(){
console.log('a',super.x);//读取父类自身属性x
console.log('b',this.x);//读取子类自身属性x
super.say()//父类静态方法中的this是指向子类自身
}
}
Son.speak();
console.dir(Son)

b 2 原因是Son类自身没有x属性,会往原型链查找该变量,找到父类身上的x,就是2
改一下:
class Son extends Father{
static x=3
static speak(){
console.log('a',super.x);//读取父类自身属性x
console.log('b',this.x);//读取子类自身属性x
super.say()//父类静态方法中的this是指向子类自身
}
}
Son.speak();
console.dir(Son)

| 原生构造函数的继承 |
- Boolean()
- Number()
- String()
- Array()
- Date()
- Function()
- RegExp()
- Error()
- Object()
class myArray extends Array{
constructor(...args) {
super(...args)
}
//自定义方法 获取对象数组特定key对应的值数组
getMappingValueArrayOfKey(keyName){
if(Object.prototype.toString.call(this)=='[object Array]'){
return this.map((item,index)=>{
return item[keyName]
})
}
return 'null(参数一应为对象数组)';//不是数组
}
}
const arr=new myArray({name:'dog',sound:'汪汪'},{name:'cat',sound:'喵喵'})
arr.push({name:'pig',sound:'哼哼'});
console.log(arr);//[{…}, {…}, {…}]
console.log(arr.getMappingValueArrayOfKey('name'));//["dog", "cat", "pig"]
参考文档:
Class 的基本语法
2265

被折叠的 条评论
为什么被折叠?



