类和构造函数的区别
ES5中函数定义类(构造函数):
function Foo(x,y) {
this.x = x;
this.y = y;
}
Foo.prototype.toString = function(){
console.log(this.x, this.y)
}
var foo = new Foo(1,2)
foo.toString()
// 1 2
ES6中定义类:
调用类的方法也就是调用原型(prototype)上的方法
class Foo {
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
console.log(this.x, this.y)
}
}
var foo = new Foo(1,2)
foo.toString()
// 1 2
ES6中,class实际上也是一个function对象,其原型链与es6一致,但有几点需要注意
- 类里的
constructor()
方法表示构造函数,与原型链中的.constructor
无关 - 在class中声明的函数会直接添加到原型中,可以直接使用,不需要经过原型
- 通过new关键字生成实例时,只会执行
constructor
构造函数里的内容
class Foo {
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
console.log(this.x, this.y)
}
}
Foo.prototype.name = 'Jack'
var foo = new Foo(1,2)
console.log(Foo) // Function: Foo]
console.log(foo) // Foo { x: 1, y: 2 }
console.log(Foo === foo.constructor) // true 成员访问对象 foo.__proto__.constructor
console.log(Foo.prototype) // Foo { name: 'Jack' } 对象访问原型
console.log(foo.__proto__) // Foo { name: 'Jack' } 成员访问原型
es5的构造函数和静态方法
function Person(name, age) {
// 构造函数里的方法和属性
this.name = name
this.age = age
this.run = function() {
console.log(`${this.name}, ${this.age}`)
}
}
// 设置原型属性
Person.prototype.sex = '男'
Person.prototype.work = function() {
console.log('work')
}
// 设置静态方法
Person.setName = function() {
console.log('静态方法')
}
var person = new Person('张三', '23')
person.run() // 实例共享原型的属性
person.work()
Person.setName() // 用构造函数来执行静态方法
ES6里的类和静态方法
类里直接声明的函数实际上是在原型中进行声明,如果要特地指明静态方法,需要用static
关键字,这样实际上就是Person.xxx
直接添加类的一个属性
class Person {
say() {
console.log('实例方法')
}
static work() {
console.log('静态方法')
}
}
const person = new Person()
person.say() // 实例内部找不到该方法时,可以通过原型找到该方法并调用
Person.work() // 静态方法相当于类的一个属性,因此可以通过类来直接调用
类的继承
- 类中直接定义的方法,实际上是在原型中定义,但是它是隐式的
class Demo {
classFun() {
}
}
console.log(Demo.prototype) // Demo {}
console.log(Demo.prototype.classFun) // [Function: classFun] 实际上是有的
Demo.prototype.other = 'other' // 直接通过原型声明的话会显示
console.log(Demo.prototype) // Demo { other: 'other' }
- 使用
extentds
关键字实现继承时,实例化子类时,会执行父类的构造函数constructor()
方法
class Father {
constructor() {
console.log('执行父类构造函数')
this.fatherAttribute = '这里是父类的属性'
}
}
class Child extends Father{
}
const child = new Child() // 打印:'执行父类构造函数'
// 相当于视实例child去调用了父类的构造函数,this指向child,因此会在child创建 fatherAttribute
console.log(child) // Child { fatherAttribute: '这里是父类的属性' }
- 在一个使用
extentds
关键字实现继承的类中,如果不声明constructor()
方法,在实例化子类时会默认调用父类的构造方法,但是如果声明了constructor()
,必须要手动去调用父类构造函数(super()
),否则子类constructor()
构造函数会报错
class Father {
constructor() {
console.log('执行父类构造函数')
this.fatherAttribute = '这里是父类的属性'
}
}
class Child extends Father{
constructor() {
super() // super()表示调用父类的构造函数,声明构造函数而不调用super()会报错
}
}
- 可以在子类构造函数里使用
super.xxx
去调用父类在原型中声明的方法
class Father {
constructor() {
console.log('执行父类构造函数')
this.fatherAttribute = '这里是父类的属性'
}
implicitFunction() {
console.log('调用父类原型中的隐式方法')
}
}
Father.prototype.explicitFunction = () =>{
console.log('调用父类原型中的显式方法')
}
class Child extends Father{
constructor() {
super()
super.implicitFunction() // 调用父类原型中的隐式方法
super.explicitFunction() // 调用父类原型中的显式方法
}
}
-
继承在原型链中的关系:
和ES5原型链继承有所不同,类(类也是构造函数)在继承中,子类构造函数的原型不是父类的实例,且
__proto__
指向父类构造函数而非Function
的原型父类构造函数的
__proto__
仍指向Function
原型
class Father {
constructor(){ // 注明:如果父类中没有声明构造函数,则无法成功被继承
}
}
class Child extends Father{
}
Child.prototype.pro = '子类原型属性'
const child = new Child()
console.log(child.constructor == Child) // true
console.log(child.__proto__.constructor == Child) // ture
console.log(child.__proto__.__proto__ == Father.prototype) // true
console.log(child.constructor.__proto__ == Father) // ture
console.log(child.__proto__.__proto__.constructor == Father) // true
console.log(Child.__proto__ == Father) // true
console.log(Child.prototype) // Child { pro: '子类原型属性' }
ES5使用原型链继承中,子类和父类构造函数的__proto__
都是指向Function
原型的
function Father() {
this.num = 1
this.obj = {value:1}
}
function Child() {
Father.call(this)
}
Child.prototype = new Father()
const child = new Child()
Function.prototype.app = 'app'
console.log(Child.__proto__) // { [Function] app: 'app' }
console.log(Father.__proto__) // { [Function] app: 'app' }
类的单例设计
一个类可以生成多个实例,每次new
的实例都是互不干扰的,单例就是让类生成一个固定的实例
class Person {
constructor() {
this.name = 'lucy'
}
// 一个静态方法,用于生成一个单例
static getInstance() {
if(!Person.instance){ // 如果尚未创建实例,则进行创建,如果存在,则一直返回该实例
Person.instance = new Person()
}
return Person.instance
}
}
const person = Person.getInstance() // 注意,这里既然想用一个单例,则就不用new关键字去生成实例了,如果直接使用new还是会生成互不干扰的各个实例
console.log(person.name) // lucy
person.name = 'jack' // 对实例属性进行修改
const person_ = Person.getInstance()
console.log(person_.name) // jack,可以证明用该方法生成的实例,会返回同一个实例
const personA = new Person() // 使用new关键字可以创建其他实例,实例间互不干扰
console.log(personA.name) // lucy