写在前面:

JS 面向对象 在前端学习来说 一直是个重点和难点,不过在前端学习中 是很重要的思想 在es6 还有框架中 都有广泛的使用,所以是前端开发者必须要学会的知识
我在工作中用面向对象的时候比较少 所以基本学完之后就废弃了 基本忘的差不多了 最近开始学习react了 发现react上来就开始用es6的class定义类(class是一个语法糖 能简化构造函数原型的写法更加清晰)里面就涉及到了面向对象的知识,发现自己已经忘的差不多了 构造函数 原型 实例的关系也搞不明白了 所以我就抽时间又查阅了一些资料 自己用我的理解总结一下 希望对大家能有所帮助

面向对象的概念:

在js的世界里一般会把构造函数看成一个面向对象,其实面向对象从表面意思理解是面向全局的一个对象,构造函数就是那个面向全局的对象(函数本身就是一个对象)

说到构造函数 不得不先说下创建对象的方式:

ps:万物皆对象

1. 最简单的方式就是通过new Object()或者直接用简写形式 对象字面量 直接创建就好:
var person = new Object()
person.name = 'Jack'
person.age = 18

person.sayName = function () {
  console.log(this.name)
}

或者直接 ↓ (推荐)

var person = {
  name: 'Jack',
  age: 18,
  sayName: function () {
    console.log(this.name)
  }
}

对于上面的写法固然没有问题,但是假如我们要生成两个 person 实例对象呢?

var person1 = {
  name: 'Jack',
  age: 18,
  sayName: function () {
    console.log(this.name)
  }
}

var person2 = {
  name: 'Mike',
  age: 16,
  sayName: function () {
    console.log(this.name)
  }
}

通过上面的代码我们不难看出,这样写的代码太过冗余,重复性太高。

2. 简单方式的改进——工厂模式:

我们可以写一个函数,解决代码重复问题:

function createPerson (name, age) {
  return {
    name: name,
    age: age,
    sayName: function () {
      console.log(this.name)
    }
  }
}

然后生成实例对象:

var p1 = createPerson('Jack', 18)
var p2 = createPerson('Mike', 18)

这样封装确实爽多了,通过工厂模式我们解决了创建多个相似对象代码冗余的问题。

3. 更优雅的工厂函数:构造函数

一种更优雅的工厂函数就是下面这样,构造函数:

function Person (name, age) {
  this.name = name
  this.age = age
  this.sayName = function () {
    console.log(this.name)
  }
}

var p1 = new Person('Jack', 18)
p1.sayName() // => Jack

var p2 = new Person('Mike', 23)
p2.sayName() // => Mike

构造函数、原型、实例之间的关系

在这里插入图片描述

下面的话浓缩的都是精华

  • 这里的 function Person(){} 就是构造函数
  • 构造函数 new 出来的 person就是它的实例(对象)
  • 构造函数都有一个 prototype 属性,指向另一个对象(就是它的原型(对象)),这个对象的所有属性和方法,都会被构造函数的实例继承——就是说构造函数的原型和构造函数的实例 属性和方法可以共享
  • 这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上

拿Person举例

function Person(name, age) {
    this.age = age;
    this.getName = function() {
        console.log(`this is ${name}.`);
    }
}

var p1 = new Person('lokka', 5);
p1.getName(); // this is lokka.
console.log(p1.age); // 5
构造函数
1. Person 就是构造函数(其实就是一个函数)
实例
1. 构造函数Person通过 var person = new Person(); 生成的person就是构造函数的实例 实例继承原型上的属性和方法

2. 实例上有个属性__proto__ (双下划线) 指向原型
person.__proto__指向的就是构造函数Person的原型,即: person.__proto__ === Person.prototype

原型
1. 每写一个构造函数 就会同时生成个原型(没有构造函数中的属性或者方法,可以理解为空对象)
2. 构造函数都有一个 `prototype` 属性,指向另一个对象(就是它的原型),这个对象的所有属性和方法,都会被`构造函数的实例继承`。——`就是说构造函数的原型和构造函数的实例 属性和方法可以共享`
3. 原型上有constructor属性指向构造函数 原型.constructor === 构造函数
构造函数和原型的关系:
  1. 每个构造函数都有一个 prototype 属性,指向另一个对象(就是它的原型),用白话讲就是每写一个构造函数 就会同时生成个原型(没有构造函数中的属性或者方法,可以理解为空对象) 通过 构造函数.prototype可以设置和访问原型中的属性和方法
  2. 同时原型中也有个constructor属性可以指向构造函数
构造函数和实例的关系:

用 new 关键字创建 Person 实例时,内部执行了4个操作:
1. 创建一个新对象
2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
3. 执行构造函数中的代码
4. 返回新对象

所以就把构造函数中的指针this 指向了新创建的实例中 实例中就可以调用构造函数中的属性和方法了

实例和原型的关系:

实例上有个属性__proto__ (双下划线) 指向原型
person.__proto__指向的就是构造函数Person的原型,即: person.proto === Person.prototype

实例继承原型上的属性和方法

更简单的原型写法

根据前面例子的写法,如果我们要在原型上添加更多的方法,可以这样写:

function Person() {}

Person.prototype.getName = function() {}
Person.prototype.getAge = function() {}
Person.prototype.sayHello = function() {}
... ...
function Person() {}

Person.prototype = {
    constructor: Person,
    getName: function() {},
    getAge: function() {},
    sayHello: function() {}
}

这种字面量的写法看上去简单很多,但是有一个需要特别注意的地方。Person.prototype = {}实际上是重新创建了一个{}对象并赋值给Person.prototype,这里的{}并不是最初的那个原型对象。因此它里面并不包含constructor属性。为了保证正确性,我们必须在新创建的{}对象中显示的设置constructor的指向。即上面的constructor: Person

完整的原型链:

在这里插入图片描述

1. 首先我们先看左上角的 Function ,Function 比较特殊

  1. 我们要知道所有函数都是 Function 的实例,所有函数的原型都指向 Function.prototype, Function 即是构造函数 同时也是 Function 的实例,就是 Function 实例化了自己本身,所以我们可以得出:
Function.__proto == Function.prototype
  1. 因为 Object 也是函数,所以我们可以得出:
Object.__proto == Function.prototype
  1. 一切对象都最终继承自Object对象,Object对象直接继承自根源对象null

其中 personPerson 构造函数对象的实例。而 Person 的原型对象同时又是构造函数对象 Object 的实例。这样就构成了一条原型链。原型链的访问,其实跟作用域链有很大的相似之处,他们都是一次单向的查找过程。因此实例对象能够通过原型链,访问到处于原型链上对象的所有属性与方法。这也是 person1 最终能够访问到处于 Object 构造函数对象上的方法的原因。

基于原型链的特性,我们可以很轻松的实现继承。

2. 再说一下 Funtion 和 Object 的关系

从图上我们可以知道 Function 和 Object 是互相继承关系,因为
Function 通过原型链继承了 Object.prototype,而 Object 又继承了 Function.prototype 所以说 Function 和 Object 是互相继承关系

3. 我们再看这个等式

Function.constructor == Function

刚开始看到这个等式我也很疑惑,看了 MDN 也没看明白,后来看了好多文档才理解
因为 .constructor 属性是原型上的属性,构造函数上没有就去原型链上查找,所以:

Function.constructor == Function.prototype.constructor

又由于

Function.prototype.constructor == Function

所以

Function.constructor == Function

所以我们可以得出结论 Function 继承自己

原型链继承

有关原型链继承的内容可以参考我的这篇文档——

写在后面:

面向对象中的知识点一直都是很难理解的,这篇文章是我很用心写的一篇文章,用我自己能理解的话术来解释构造函数 原型 实例 原型链 继承等 可能涉及到的底层原理没有那么深刻 不过通过仔细理解文章中的重点 就应该可以很好地使用面向对象了 大家都加油 文章中有什么不对的 也请大家帮我指正出来 大家共同进步!ღ( ´・ᴗ・` )比心