js面向对象,原型,原型链

107 阅读4分钟

面向对象

一句话概括面向对象,是一些数据和操作这些数据的方法组成的一个对象,相同行为的对象可以抽象成一个类,通过封装隐藏内部的具体实现,通过继承实现泛化,通过多态实现方法的扩展;

面向对象的特征:
  1. 封装
  2. 继承
  3. 多态
创建对象的方式
  1. Object.create(原型)
const obj = Object.create(null)
  1. 字面量创建
const obj = {}
  1. 通过new的形式
const obj = new Object()
面向对象和面向过程
  1. 面向过程是一步一步的编写
  2. 面向对象需要提取一个类,封装其中的方法,通过类的实例来控制
  3. 面向对象是对面向过程的封装

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

总结

  1. 所有构造函数都有一个prototype属性,它是一个对象,表示实例的原型
  2. 原型中有constructor属性,指向原型所在的构造函数
  3. 所有实例中的__proto__,指向构造函数的prototype(它的原型)
  4. 所有构造函中的__proto__,指向Function的prototype(它的原型)
  5. 所有原型的__proto__,指向Object的prototype(除非手动修改)
  6. Object的原型的__proto__指向null(原型的顶层是null)

image.png 面试题:

  1. 下面输出什么
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