关于javascript:前端JavaScript中的class

类是用于创建对象的模板。JavaScript中生成对象实例的办法是通过构造函数,这跟支流面向对象语言(java,C#)写法上差别较大,如下:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 1);

ES6 提供了更靠近Java语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,能够定义类。

如下:constructor()是构造方法,而this代表实例对象:

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

类的数据类型就是函数,它自身就是指向函数的构造函数:

// ES5 函数申明
function Point() {
    //...
}

// ES6 类申明
class Point {
  //....
  constructor() {
  }
}
typeof Point // "function"
Point === Point.prototype.constructor // true

在类外面定义的办法是挂到Point.prototype,所以类只是提供了语法糖,实质还是原型链调用。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

Point.prototype = {
  //....
  toString()
}
var p = new Point(1, 1);
p.toString() // (1,1)

类的另一种定义形式类表达式

// 未命名/匿名类
let Point = class {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
};
Point.name // Point

函数申明和类申明有个重要区别,函数申明会晋升,类申明不会晋升。

let p = new Point(); // 被晋升不会报错
function Point() {}

let p = new Point(); // 报错,ReferenceError
class Point {}

constructor()

constructor()办法是类的默认办法,new生成实例对象时会主动调用该办法。

一个类必须有constructor()办法,如果没有显式定义,引擎会默认增加一个空的constructor()

constructor()办法默认返回实例对象(即this)。

class Point {
}

// 主动增加
class Point {
  constructor() {}
}

getter和setter

与 ES5 一样,在类的外部能够应用getset关键字,对某个属性设置存值函数和取值函数,拦挡该属性的存取行为。

class User {
  constructor(name) {
    this.name = name;
  }

  get name() {
    return this.name;
  }

  set name(value) {
    this.name = value;
  }
}

this

类的办法外部的this,它默认指向类的实例,在调用存在this的办法时,须要应用 obj.method()形式,否则会报错。

class User {
  constructor(name) {
    this.name = name;
  }
  printName(){
    console.log('Name is ' + this.name)
  }
}
const user = new User('jack')
user.printName() // Name is jack
const { printName } = user;
printName()     // 报错 Cannot read properties of undefined (reading 'name')

如果要独自调用又不报错,一种办法能够在构造方法里调用bind(this)

class User {
  constructor(name) {
    this.name = name;
    this.printName = this.printName.bind(this);
  }
  printName(){
    console.log('Name is ' + this.name)
  }
}
const user = new User('jack')
const { printName } = user;
printName()     // Name is jack

bind(this) 会创立一个新函数,并将传入的this作为该函数在调用时上下文指向。

另外能够应用箭头函数,因为箭头函数外部的this总是指向定义时所在的对象。

class User {
  constructor(name) {
    this.name = name;
  }
  printName = () => {
    console.log('Name is ' + this.name)
  }
}
const user = new User('jack')
const { printName } = user;
printName()     // Name is jack

动态属性

动态属性指的是类自身的属性,而不是定义在实例对象this上的属性。

class User {
}

User.prop = 1;
User.prop // 1

静态方法

能够在类外面定义静态方法,该办法不会被对象实例继承,而是间接通过类来调用。

静态方法里应用this是指向类。

class Utils {
  static printInfo() {
     this.info();
  }
  static info() {
     console.log('hello');
  }
}
Utils.printInfo() // hello

对于办法的调用范畴限度,比方:公有私有,ES6临时没有提供,个别是通过约定,比方:在办法后面加下划线_print()示意公有办法。

继承

Java中通过extends实现类的继承。ES6中类也能够通过extends实现继承。

继承时,子类必须在constructor办法中调用super办法,否则新建实例时会报错。

class Point3D extends Point {
  constructor(x, y, z) {
    super(x, y); // 调用父类的constructor(x, y)
    this.z = z;
  }

  toString() {
    return super.toString() + '  ' + this.z ; // 调用父类的toString()
  }
}

父类的静态方法,也会被子类继承。

class Parent {
  static info() {
    console.log('hello world');
  }
}

class Child extends Parent {
}

Child.info()  // hello world

super关键字

在子类的构造函数必须执行一次super函数,它代表了父类的构造函数。

class Parent {}

class Child extends Parent {
  constructor() {
    super();
  }
}

在子类一般办法中通过super调用父类的办法时,办法外部的this指向以后的子类实例。

class Parent {
  constructor() {
    this.x = 1;
    this.y = 10
  }
  printParent() {
    console.log(this.y);
  }
  print() {
    console.log(this.x);
  }
}

class Child extends Parent {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();
  }
}

let c = new Child();
c.printParent() // 10
c.m() // 2

_proto_prototype

初学JavaScript时,_proto_prototype 很容易混同。首先咱们晓得每个JS对象都会对应一个原型对象,并从原型对象继承属性和办法。

  • prototype 一些内置对象和函数的属性,它是一个指针,指向一个对象,这个对象的用处就是蕴含所有实例共享的属性和办法(咱们把这个对象叫做原型对象)。
  • _proto_ 每个对象都有这个属性,个别指向对应的构造函数的prototype属性。

下图是一些领有prototype内置对象。

依据下面形容,看上面代码

var obj = {} // 等同于 var obj = new Object()

// obj.__proto__指向Object构造函数的prototype
obj.__proto__ === Object.prototype // true 

// obj.toString 调用办法从Object.prototype继承
obj.toString === obj.__proto__.toString // true

// 数组
var arr = []
arr.__proto__ === Array.prototype // true 

对于function对象,申明的每个function同时领有prototype__proto__属性,创立的对象属性__proto__指向函数prototype,函数的__proto__又指向内置函数对象(Function)的prototype

function Foo(){}
var f = new Foo();
f.__proto__ === Foo.prototype // true
Foo.__proto__ === Function.prototype // true

继承中的__proto__

类作为构造函数的语法糖,也会同时有prototype属性和__proto__属性,因而同时存在两条继承链。

  1. 子类的__proto__属性,示意构造函数的继承,总是指向父类。
  2. 子类prototype属性的__proto__属性,示意办法的继承,总是指向父类的prototype属性。
class Parent {
}

class Child extends Parent {
}

Child.__proto__ === Parent // true
Child.prototype.__proto__ === Parent.prototype // true

继承实例中的__proto__

子类实例的__proto__属性,指向子类构造方法的prototype

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。

class Parent {
}

class Child extends Parent {
}

var p = new Parent();
var c = new Child();

c.__proto__ === p.__proto__ // false
c.__proto__ === Child.prototype // true
c.__proto__.__proto__ === p.__proto__ // true

小结

JavaScript中的Class更多的还是语法糖,实质上绕不开原型链。欢送大家留言交换。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理