类
类是用于创建对象的模板。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(); // 报错,ReferenceErrorclass Point {}
constructor()
constructor()
办法是类的默认办法,new
生成实例对象时会主动调用该办法。
一个类必须有constructor()
办法,如果没有显式定义,引擎会默认增加一个空的constructor()
。
constructor()
办法默认返回实例对象(即this
)。
class Point {}// 主动增加class Point { constructor() {}}
getter和setter
与 ES5 一样,在类的外部能够应用get
和set
关键字,对某个属性设置存值函数和取值函数,拦挡该属性的存取行为。
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 jackconst { 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() // 10c.m() // 2
_proto_
和prototype
初学JavaScript时,_proto_
和prototype
很容易混同。首先咱们晓得每个JS对象都会对应一个原型对象,并从原型对象继承属性和办法。
prototype
一些内置对象和函数的属性,它是一个指针,指向一个对象,这个对象的用处就是蕴含所有实例共享的属性和办法(咱们把这个对象叫做原型对象)。_proto_
每个对象都有这个属性,个别指向对应的构造函数的prototype
属性。
下图是一些领有prototype
内置对象。
依据下面形容,看上面代码
var obj = {} // 等同于 var obj = new Object()// obj.__proto__指向Object构造函数的prototypeobj.__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 // trueFoo.__proto__ === Function.prototype // true
继承中的__proto__
类作为构造函数的语法糖,也会同时有prototype
属性和__proto__
属性,因而同时存在两条继承链。
- 子类的
__proto__
属性,示意构造函数的继承,总是指向父类。 - 子类
prototype
属性的__proto__
属性,示意办法的继承,总是指向父类的prototype
属性。
class Parent {}class Child extends Parent {}Child.__proto__ === Parent // trueChild.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__ // falsec.__proto__ === Child.prototype // truec.__proto__.__proto__ === p.__proto__ // true
小结
JavaScript中的Class更多的还是语法糖,实质上绕不开原型链。欢送大家留言交换。