面向对象
一句话概括面向对象,是一些数据和操作这些数据的方法组成的一个对象,相同行为的对象可以抽象成一个类,通过封装隐藏内部的具体实现,通过继承实现泛化,通过多态实现方法的扩展;
面向对象的特征:
- 封装
- 继承
- 多态
创建对象的方式
- Object.create(原型)
const obj = Object.create(null)
- 字面量创建
const obj = {}
- 通过new的形式
const obj = new Object()
面向对象和面向过程
- 面向过程是一步一步的编写
- 面向对象需要提取一个类,封装其中的方法,通过类的实例来控制
- 面向对象是对面向过程的封装
类
es5之前的类是通过函数进行模拟,函数名的首字母大写,来表示类
function Person () {
}
类的属性放在实例上,方法放在原型上(什么是原型下文讲解)
function Person (name) {
// name属性
this.name = name
}
// 设置name的方法
Person.prototype.setName = function (name){
this.name = name
}
es6中实现了类的语法糖,本质上还是通过函数模拟的,其中的属性还是被放在实例上,方法放在原型上,静态方法放在构造器上
class Person {
// 构造函数
constructor (name){
this.name = name
}
setName(name){
this.name = name
}
}
什么是原型?
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。 这个对象的所有属性和方法,都会被构造函数的所拥有。(换句话说构造函数的prototype属性的值就是它的原型,原型上可以存放构造函数的实例共有的属性和方法)
function Person (name) {
// name属性
this.name = name
}
// 设置name的方法
Person.prototype.setName = function (name){
this.name = name
}
const p = new Person(1)
p.setName(2)
p.name // 2
p.setName(2)的时候,Person中其实并没有这个方法,因此js内部会从它的原型上查找是否有这个方法;
原型和构造函数和实例的关系
原型中有constructor属性,这个属性指向的就是原型所在的函数;
实例的__proto__指向构造函数的 prototype(就是它的原型)
function Person (name) {
// name属性
this.name = name
}
// 设置name的方法
Person.prototype.setName = function (name){
this.name = name
}
Person.prototype.constructor // Person (name) {...}
const p = new Person()
p.__proto__ === Person.prototype // true
总结
- 所有构造函数都有一个
prototype属性,它是一个对象,表示实例的原型 - 原型中有
constructor属性,指向原型所在的构造函数 - 所有实例中的__proto__,指向构造函数的prototype(它的原型)
- 所有构造函中的__proto__,指向Function的prototype(它的原型)
- 所有原型的__proto__,指向Object的prototype(除非手动修改)
- Object的原型的__proto__指向null(原型的顶层是null)
面试题:
- 下面输出什么
function Person(){}
const p = new Person()
p.__proto__ // ?
Person.__proto__ // ?
实例的__proto__指向构造函数的prototype;构造函数的__proto__指向Function的prototype;
2. 下面输出什么?
Person.prototype.name = 1
function Person(){}
Person.prototype.name = 2
const p1 = new Person()
p1.name // ?
Person.prototype = {
name: 3
}
const p2 = new Person()
p1.name // ?
p2.name // ?
解析:2, 2, 3;这道题不仅仅考原型,还考new的时候内部做了什么。第一个应该输出2;第二个为什么输出2,是因为下面虽然修改了原型的指向,但是p1是在修改之前就new出来了,new构造函数的时候内部隐士的创建了this对象,并且this.__proto__指向它的原型,后面再次修改原型的指向都不会影响到之前的实例;如果修改原型上的属性,会影响到之前;
Person.prototype.name = 1
function Person(){}
Person.prototype.name = 2
const p1 = new Person()
Person.prototype.name = 3
p1.name // 3
什么是原型链
每个原型内部也有一个__proto__属性,这个属性就指向原型的原型,这就是原型链;
在访问一个对象的属性的时候,首先会在对象本身上进行查找,如果没有找到就到它的原型上查找,如果也没有找到就沿着它的原型链向上进行查找,直到找到或查找到顶层原型null为止,如果没有找到就返回undefined;
私有属性放在构造函数内部,共有属性放在原型上;
继承
通过原型的特性可以实现类的继承
// 继承之圣杯模式
// 实现一个原型继承的方法
function extendsPro (Child, Father) {
function F(){}
F.prototype = Father.prototype
Child.prototype = new F()
Child.constructor = Child
Child.prototype.use = Father.prototype
}
// 父类
function Person(name){
this.name = name
}
Person.prototype = {
age:11
}
// 子类
function Children (name){
// 继承属性
Person.call(this, name)
}
extendsPro(Children, Person)
const c = new Children()
c.age // 11