高级JS 详解面向对象、构造函数、原型、实例与原型链、原型链继承
写在前面:
JS 面向对象 在前端学习来说 一直是个重点和难点,不过在前端学习中 是很重要的思想 在es6 还有框架中 都有广泛的使用,所以是前端开发者必须要学会的知识
我在工作中用面向对象的时候比较少 所以基本学完之后就废弃了 基本忘的差不多了 最近开始学习react了 发现react上来就开始用es6的class定义类(class是一个语法糖 能简化构造函数原型的写法更加清晰)里面就涉及到了面向对象的知识,发现自己已经忘的差不多了 构造函数 原型 实例的关系也搞不明白了 所以我就抽时间又查阅了一些资料 自己用我的理解总结一下 希望对大家能有所帮助
面向对象的概念:
在js的j世界里一般会把构造函数看成一个面向对象,其实面向对象从表面意思理解是面向全局的一个对象,构造函数就是那个面向全局的对象(函数本身就是一个对象)
说到构造函数 不得不先说下创建对象的方式:
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.name = name;
this.age = age;
this.getName = function() {
console.log('this is lokka.');
}
}
var p1 = new Person('tom', 5);
console.log(p1.name); // tom
console.log(p1.age); // 5
p1.getName(); // this is lokka.
构造函数
1. Person 就是构造函数(其实就是一个函数)
实例
1. 构造函数Person通过 var person = new Person(); 生成的person就是构造函数的实例 实例继承原型上的属性和方法
2. 实例上有个属性__proto__ (双下划线) 指向原型
person.__proto__指向的就是构造函数Person的原型,即: person.__proto__ === Person.prototype
原型
1. 每写一个构造函数 就会同时生成个原型(没有构造函数中的属性或者方法,可以理解为空对象)
2. 构造函数都有一个 `prototype` 属性,指向另一个对象(就是它的原型),这个对象的所有属性和方法,都会被`构造函数的实例继承`。——`就是说构造函数的原型和构造函数的实例 属性和方法可以共享`
3. 原型上有constructor属性指向构造函数 原型.constructor === 构造函数
构造函数和原型的关系:
- 每个构造函数都有一个
prototype
属性,指向另一个对象(就是它的原型),用白话讲就是每写一个构造函数 就会同时生成个原型(没有构造函数中的属性或者方法,可以理解为空对象)
通过 构造函数.prototype可以设置和访问原型中的属性和方法 - 同时原型中也有个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 比较特殊
-
我们要知道所有函数都是 Function 的实例,所有函数的原型都指向 Function.prototype,
Function 即是构造函数 同时也是 Function 的实例
,就是 Function 实例化了自己本身,所以我们可以得出:
Function.__proto == Function.prototype
- 因为 Object 也是函数,所以我们可以得出:
Object.__proto == Function.prototype
- 一切对象都最终继承自Object对象,Object对象直接继承自根源对象null
其中 person
是 Person
构造函数对象的实例。而 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 继承自己
原型链继承
有关原型链继承的内容可以参考我的这篇文档——ES5中的继承和ES6中的类继承模式详解
写在后面:
面向对象中的知识点一直都是很难理解的,这篇文章是我很用心写的一篇文章,用我自己能理解的话术来解释构造函数 原型 实例 原型链 继承等 可能涉及到的底层原理没有那么深刻 不过通过仔细理解文章中的重点 就应该可以很好地使用面向对象了 大家都加油 文章中有什么不对的 也请大家帮我指正出来 大家共同进步!ღ( ´・ᴗ・` )比心