在ES6之前,class是一个保留字,我们不能使用/创建class,在这之前JS使用的是原型去模拟类的使用。为何要使用原型?原型和类在写法上有哪些区别?请接着往下看。
何为原型
原型:用JavaScript中最复杂的数据类型——Object去创建的对象,每个对象都存在 __proto__属性,该属性的属性值是一个地址,与其构造函数的prototype属性存放的地址一致,这两个地址指向的那一块内容,就是原型对象。
继承:当我们创建了一个空对象,并没有创建它的toString()方法,但是依然可以调用该方法,并不会报错,这是因为obj对象是由Object构造函数创建的,根据公式:obj.__proto__===Object.prototype,在Object.prototype对象里面有所有对象的共有属性,里面就有toString()方法,就可以说obj对象继承了Object这个对象的toString()方法。
原型链:当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined。这样一条条指向原型的链条,就是原型链,如下所示:
- obj->Object
- person->Person->Object
- arr->Array->Object
何为类
以下代码可以明显看出,obj和obj2对象是同一类的对象,obj3和它们不是同一类的。
let obj={
name:'lucy',
age:18
}
let obj2={
name:'Jack',
age:19
}
let obj3={
xxx:1,
yyy:2
}
如何把这种区分用代码表示出来?可以写一个说明文档:
如果要创建一个“人类对象”,请一定要这样做:
let xxx={
name:'____',
age:'____'
}
但是肯定会有人不遵守这种方式,更保险一点的方法,我们可以设定规则,提供一个函数,来创建这一类的对象,这就是构造函数:
function createPerson(name,age){
let obj={}
obj.name= name || ''
obj.age=age || 18
return obj
}
createPerson() //{name:'',age:18}
createPerson('lucy') //{name:'lucy',age:18}
createPerson('Jack',19) //{name:'Jack',age:19}
函数createPerson()就是创建了拥有name和age这两种属性的对象的函数,属性之间也有区分,例如人和人之间有一些自有属性:name,age,还有一些公有属性:例如走路。这些该如何区分呢?
可以把所有自有属性放到构造函数createPerson()里,把公有属性放到原型里,这就形成了类的雏形。
let 人类共有属性={
waik(){ console.log('走两步') },
species:'人类'
}
function createPerson(name,age){
let obj={}
obj.name= name || ''
obj.age= age || 18
obj.__proto__=人类共有属性
return obj
}
createPerson('Jack',19)
总结
- 什么是类:类就是拥有相同属性的对象。
- 如何创建类:构造函数,用来创建某个类的对象的函数。上例中
createPerson就创建了四种属性(共有/自有) - 共有属性:在原型里的属性,species,waik
- 自有属性:对象自身的属性,name,age
用JS实现类
方法一:使用原型(prototype)
将自身属性写到构造函数里,共有属性写到原型上,不造成内存的浪费。
function Dog(name){
this.name = name //自身属性
this.legsNumber = 4
}
Dog.prototype.kind = '狗' //共有属性
Dog.prototype.say = function(){
console.log(`我是${this.name},我有${this.legsNumber}条腿。`)
}
Dog.prototype.run = function(){
console.log(`${this.legsNumber}条腿跑起来。`)
}
const d1 = new Dog('贵宾犬')
方法二:使用 ES6 推出的 class
在类的参数传递中我们用constructor( )进行传参。传递参数后可以直接使用this.xxx进行调用。
自身属性写到constructor里面,共有方法写在constructor外面:
class Dog {
kind = '狗' // 等价于在 constructor 里写 this.kind = '狗',没有办法写在原型上
constructor(name) { //自身属性
this.name = name
this.legsNumber = 4
}
say(){ //共有方法,挂在原型上
console.log(`我是${this.name},我有${this.legsNumber}条腿。`)
}
run(){
console.log(`${this.legsNumber}条腿跑起来。`)
}
}
const d1 = new Dog('贵宾犬')
用JS实现继承
基于 原型 的继承
首先定义一个名为Animal的构造函数,然后用Dog函数继承它的属性和方法:
/* 父类 */
function Animal(legsNumber){
this.legsNumber = legsNumber //自身属性:腿
}
Animal.prototype.kind = '动物' //共有属性:动物
/* 子类 */
function Dog(name){
this.name = name //自身属性
Animal.call(this, 4) //关键代码1 继承父类自有属性
}
function f(){ } //关键代码2
f.prototype = Animal.prototype
Dog.prototype = new f()
Dog.prototype.kind = '狗' //覆盖父类原型上的属性kind为自己的共有属性
Dog.prototype.say = function(){ //创建子类的共有属性say
console.log(`我是 ${this.name},我有${this.legsNumber}条腿。`)
}
const d1 = new Dog('贵宾犬')
console.dir(d1)
基于 类 的继承
首先定义一个名为Animal的父类,然后创建一个名为Dog的类继承它的属性和方法。
class Animal{
constructor(legsNumber){
this.legsNumber = legsNumber //自身属性
}
run(){ //共有属性
console.log('我要跑')
}
}
class Dog extends Animal{ //关键代码1:extends自动继承原型上的属性
constructor(name) {
super(4) //关键代码2:继承父类constructor里面的自身属性
this.name = name //创建子类的自有属性
}
say(){ //创建子类共有属性
console.log(`我是${this.name},我有 ${this.legsNumber}条腿。`)
}
}
使用class实现继承的写法更优雅,更容易让人理解。