js通过三种方法实现面向对象的设计
- 构造函数
- ES6类(语法糖)
- Object.create()
举个例子:
构造函数以大写开头
const Person = function (firstName, birthYear) {
console.log(this);//Person {}
}
调用构造函数需要用new
关键字
const john = new Person("John", 1999)
console.log(person);//Person {}
这段时间发生了四步
1.这个对象被创建
2.这个构造函数被调用,此时出现this
,并且this
指向该对象
3.对象被链接到原型
4.函数自动返回这个Person
对象
此时在构造函数里加上两行代码
this.firstName = firstName;
this.birthYear = birthYear
此时打印john
就会发现是带有这两个参数的Person
对象,此时我们就可以通过这个构造函数创建各种不同的实例化对象。
js
并没有类,所以我们通过构造函数的方式来模拟类,用与实例化对象。
记住永远不要在构造函数里去声明一个方法,这样的话如果创建一千个对象,那么就会有一千个对象有这个方法的副本。
所以为了解决这个问题,我们会使用原型和原型继承。
原型继承和其他语言的继承是不一样的,OOP
的继承是一个类继承另外一个类,子类获取到的方法实际上是对父类方法的复制。而js
的原型继承是一个实例继承一个类的属性和方法。
而原型继承中,对象其实是通过行为(方法)链接到原型,委托原型来进行操纵。
举个例子:Array.prototype
其实是js
中创建的所有数组的原型,所以说当你使用map方法的时候,实际上是委托到原型上调用的。
在 JavaScript 中,对象有一个特殊的隐藏属性 [[Prototype]]
(如规范中所命名的),它要么为 null
,要么就是对另一个对象的引用。该对象被称为“原型”
只要是对象就必须有原型。
举个例子,我们先实例化一个对象,这个对象是通过Person
构造器实例化出来的
const Person = function (firstName, birthYear) {
this.firstName = firstName;
this.birthYear = birthYear
}
const jonas = new Person("jonas", 1999)
上面已经提到过实例化对象时会进行第四步操作,而第三步对象被链接到原型就与接下来要举的例子有关
请先记住__proto__
可以看作 是 [[Prototype]]
的 getter/setter
。
然后再加上这行代码
Person.prototype.calcAge = function () {
console.log(2037 - this.birthYear);
}
这行代码的意思是在Person
的prototype
属性上添加一个方法用于计算年龄。注意Person.prototype
不是Person
的原型
那么Person.prototype
是谁的原型呢,答案是jonas
的
首先让我们通过代码来分析
console.log(jonas.__proto__ === Person.prototype)//true
此时控制台打印为true,这说明可能看到这里会觉得疑惑,为什么jonas.__proto__
此时不是jonas
的属性而是jonas
的原型了呢,请回想我刚刚要求记住的那句话:__proto__
可以看作 是 [[Prototype]]
的 getter/setter
。
所以在这个地方jonas.__proto__
并不是原型的属性,而是原型本身。
此时可以看到打印jonas.__proto__
结果如下,出现了刚刚我们自己添加的calcAge
方法
还可以通过以下代码进行验证
console.log(Person.prototype.isPrototypeOf(jonas));//true
//isPrototypeOf()用于检查一个对象是否存在于另一个对象的原型链中。
console.log(Person.prototype.isPrototypeOf(new Person));//true 并返回Person对象
console.log(Person.prototype.isPrototypeOf(Person)) //false
第二行代码说明了一个结论,也就是说Person.prototype
是Person
的所有实例化对象的原型,第三行代码也说明了刚刚的结论Person.prototype
不是Person
的原型
说明了构造函数需要给new
出的对象定义原型,而这个原型就储存在构造函数的prototype
属性中。也就是此时的Person.prototype
,因为只要是对象就必须有原型
回看上面提到的第三步对象被链接到原型,此时jonas
的__proto__
属性就是在此时创建的,此时链接的原型就是Person.prototype
我们还可以在原型上设置属性
Person.prototype.species = 'human';
console.log(jonas.species, jack.species);//human human
但请注意,此时的species
并不是jonas
的属性,它是jonas
原型的属性
以下代码可以验证
console.log(jonas.hasOwnProperty('firstName'));//true
console.log(jonas.hasOwnProperty('species'));//false
//在这行代码中,其实jonas本身是没有hasOwnProperty() 但是js会往上找它的原型有没有
//它的原型是Person.prototype,Person.prototype也没有,就会再往Object.prototype上找
那为什么jonas
能够获取到species
属性呢,那是因为原型链的作用。当js
发现在jonas
身上找不到species
属性的时候,就会往上找它的原型,从而使用,如果它的原型上没有就再往上找,,直到指向null
。
所以原型链不是闭合的,它的最终指向是null
下面一张动图可以更清晰的看到,我们在控制台中打印出jonas
的__proto__
属性,也就是 jonas
的原型Person.prototype
也可以清楚的看到原型链的最终指向是null
值得注意的是
console.log(Person.prototype.constructor);//指向Person本身
并且this
的指向也不会受原型影响,依旧是哪个对象调用它,它就指向哪个对象
可以做一些相关的题巩固一下: 使用原型