目录
- 前言
- 一,面向对象编程
- 1.1 面向过程与面向对象
- 1.2 JS创建类和对象
- 1.3 类的继承
- 1.4 Super关键词
- 1.5 几个注意点
- 二,构造函数原型
- 2.1 创建对象的三种方法
- 2.2 静态成员和实例成员
- 2.3 构造函数的弊端
- 2.4 函数的共享-原型prototype
- 2.5 对象原型___proto__
- 2.6 constructor构造函数
- 2.7 构造函数、实例、原型对象三者之间的关系
- 2.8 原型链
- 2.9 对象成员查找规则
- 2.10 this的指向问题
- 2.11 原型对象的运用
- 后记
前言
在学习了javascript基础后,我们应当去深层次的学习ES6。ES6有许多新特性,都将广泛运用于项目中。并且在企业的面试中,对ES6的考察也非常细致。
而es6中,笔者认为最难的部分是js的原型。本节就从面向对象开始,为大家逐渐深入原型讲解。原型方面的知识会涉及到后面对框架的理解,希望大家重视,也希望本节内容能帮助到大家!
一,面向对象编程
1.1 面向过程与面向对象
学过java,c++的同学应该能够理解面向对象。这里用通俗的语言对面向过程与面向对象做一个简单的介绍:
什么是面向过程?
面向过程的编程是以过程为中心进行编程。面向过程更注重因果关系,比如我要解决一个问题,我应该怎么做,我给出具体解法,让程序跑起来,最后解决问题。典型的是C语言。
什么是面向对象?
面向对象的编程是以对象为中心进行编程。它把很多个问题分成类,在写出一类问题的解法。比如我今天有一个问题需要解决,于是这个问题就是‘对象’,我去查找这一类问题怎么解决,这一类问题就是‘类’。面向对象就是把每个问题看成对象,调用这个对象对应的类下的方法来解决问题。典型是java语言,c++语言。
想要知道更多,读者可以去搜集更多面向对象的文档学习。
1.2 JS创建类和对象
创建类:
class 类名{
方法1() {}
方法2() {}
}
实例对象:
var 对象名 = new 类名()
于是,该对象可以调用该类中的方法,访问类中的数据。
具体参见下面实例:
class Star {
constructor(uname) {
this.uname = uname;
}
sing() {
console.log('我唱歌');
}
}
//创建对象new
var ldh = new Star('ldh');
console.log(ldh.uname);
ldh.sing();
这里需要注意,一般类名需要大写。
1.3 类的继承
这里举一个简单的例子。你的爷爷是一个百万富翁,你的爸爸继承了你爷爷的钱,并且他用这笔钱拿去赚钱翻倍成了千万富翁。你爸爸继承了你爷爷的东西,这是继承;他用这些东西,做了别的事情,这是继承的重要意义。
现在我们来学习具体定义:
父类/子类:已有类为父类,新建类为子类。
继承:一个类从另一个已有的类获得其特性,称为继承。
简单理解:某个类A具有某些特征,另一个类B,也具有A类的所有特征,并且还可能具有自己的更多的一些特征,此时,我们就可以实现:B类使用A的特征信息并继续添加自己的一些特有特征信息。
现规定,父类为A,子类为B,子类继承父类的格式:
class B extends A;
子类继承父类,可以使用父类中的方法,例子如下:
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
super(1, 2);//调用了父类中的构造函数
}
}
var son = new Son(1,2);
son.sum();
1.4 Super关键词
子类在继承父类时可以调用父类的方法,也可以给自己新增方法。倘若父类中有一个方法名字为say,子类也有一个方法名为say,会如何呢?
请看如下代码:
最后的结果是子类中的方法覆盖了父类中的同名方法:
此时有一个需求,我们需要调用父类中的say,该如何调用?这里就需要关键词Super了。使用supper可调用父类中的原型方法:
super.方法名
结果:
1.5 几个注意点
点一:在ES6中没有变量提升,所以要先定义类再实例化对象;
点二:类里面的共有属性与方法一定加this使用;
点三:实例对象中的this指向的是this对象,方法中的this指向的是方法的调用者。
二,构造函数原型
在ES6类与对象之前,通过构造函数和原型实现类的机制。
2.1 创建对象的三种方法
法一:利用new object()创建:
var obj = new object():
法二:利用对象字面量创建:
var obj1 = {}
法三:构造函数创建对象:
构造函数创建类,类创建对象:
function Start(uname, age) {
this.uname = uname;
this.age = age;
this.call = function() {
console.log(this)
}
}
var ldh = new Start('Ann', 12);
Obj.sex = '男'//静态成员
构造函数创建对象一共四步,首先,系统会在内存中创建一个空对象;让this指向这个对象;然后执行构造函数内部代码给新对象提供属性和方法;最后返回这个新对象。
2.2 静态成员和实例成员
实例成员是构造函数内部通过this添加的成员,上述代码中uname,age,call就是实例成员;
静态成员在构造函数本身上添加的成员,sex就是静态成员,静态成员只能通过构造函数访问,不能通过对象访问。
2.3 构造函数的弊端
构造函数方法好用,但是存在消费内存的行为:我们用构造函数的方法创建了一个类,但是在创建对象的时候,实际上是开辟了多个空间去存放多个对象的,如下图,比较的是两者的地址,地址是不一样的。
2.4 函数的共享-原型prototype
构造函数通过原型分配的函数时所有对象所共享的。
JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个prototype本身就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
那么我们现在来打印一下Star构造函数,看看它有没有prototype:
注意,console.dir能够显示一个对象所有的属性和方法。
我们可以把那些不变的方法,直接定义在prototype对象上,这样所有的对象实例就可以共享这些方法。
所以,我们通常不把这些方法放在构造函数里面,而是放在原型对象上。
依旧可以正常输出,我们成功的实现了对象的共享。
2.5 对象原型___proto__
对象为什么能访问原型上的方法?
因为对象都有一个__proto__属性指向构造函数的prototype原型对象。
如何证明:
这里需要了解方法的查找规则:先看看对象上有没有本身的方法,如果有就执行,如果没有,因为有__propo__的存在,就去构造函数原型对象prototype身上查找该方法。
proto只读不写。(这里就好像vue中的prop,只读不写)
2.6 constructor构造函数
对象原型(proto) 和构造函数(prototype)原型对象里面都有一个属性,constructor属性,我们称为构造函数,因为它指回构造函数本身。
目的:记录用哪个构造函数创建出来的。
现在我们来查看下constructor是什么:
都指向了它们的构造函数。
这里注意,方法的添加不能用这种格式:
因为这样做会覆盖掉prototype,所以我们如果要修改原型对象,只能手动去添加。
2.7 构造函数、实例、原型对象三者之间的关系
2.8 原型链
从上面的讲述可知,只要是对象都有__proto__原型的存在,指向原型对象。原型对象也是对象,我们可以看看原型对象中有没有__proto__的存在:
原型对象:
里面依旧存在__proto__。并且,它等于Object.prototype:
那么,Object的原型对象的原型对象又是谁呢:
答案为空,已经到了最顶层。
所以,我们可以划出完整的原型链:
2.9 对象成员查找规则
当访问一个对象的属性(包括方法),首先查找这个对象自身有没有该属性;
如果没有就查找它的原型(__proto__指向的==prototype原型对象);
如果还没有就查找原型对象的原型(Object原型对象);
以此类推一直找到Object为止(null);
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
2.10 this的指向问题
在构造函数中this指的是对象实例;
原型对象函数里面的thi指向的是实例对象:
2.11 原型对象的运用
扩展内置对象,可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给主族增加自定义求和属性:
Array.prototype.sum = function() {
for(var i = 0; i < this.length; i++) {
sum = sum + this[i];
}
return sum;
}
数组和字符串内置对象不能给原型对象覆盖操作Array.prototype = {},只能是Array.prototype.xxx = function(){}的方式。
后记
本节主要讲述js原型的相关知识,逻辑可能稍微有些混乱,如果有问题希望大家能帮我指出,欢迎关注!