目录
面向对象编程
有两大编程思想:面向过程和面向对象;
面向过程编程POP(Process-oriented programming)
面向过程即分析出解决问题所需要的步骤,然后用函数将这些步骤一步步实现,使用的时候再一个个的一次调用就可以了;
即将大象装进冰箱,从面向过程来看,需要打开冰箱门、装进去大象、关上冰箱门
面向对象编程OOP(Object Oriented Programming)
面向对象即把事务分解成为一个个对象,然后由对象之间分工与合作;是以对象功能来划分问题,而不是步骤;在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工;
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目;
具有封装性、继承性、多态性等特性;
即将大象装进冰箱,从面向对象来看,要先找出对象,并写出 这些对象的功能;如大象对象,冰箱对象;
面向过程与面向对象编程思想两种方式对比:
面向过程 | 面向对象 | |
优点 | 性能比面向对象高,适合跟硬件联系很紧密的东西,如单片机就采用的面向过程编程 | 易维护、易复用、易拓展,可设计出低耦合的系统,使系统更加灵活、更加易于维护 |
缺点 | 没有面向对象易维护、易复用、易拓展 | 性能比面向对象低 |
面向对象的思维特点:
1、抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板);
2、对类进行实例化,获取类的对象
面向对象编程我们考虑的是有哪些对象,按照面向对象的思维特点,不断的创建对象,使用对象,指挥对象做事情;
ES6中的类和对象
- 对象
在JavaScript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,如字符串、数值、数组、函数等;
对象是由属性和方法组成的:
属性:事物的特征,在对象中用属性来表示(名词);
方法:事物的行为,在对象中用方法来表示(动词);
- 类(class):
在ES6中新增了类的概念,可用class关键字声明一个类,之后用该类实例化对象;
类和对象关系:
类抽象了对象的公共部分,它泛指某一大类(class);
对象特指某一个,通过类实例化一个具体的对象;
创建类
class 类名 { // 此处类名不要加括号
// 类中的函数体
}
创建实例
var xx = new 类名(); // 类必须使用new实例化对象
类constructor构造函数
constructor()方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new命令生成对象实例,自动调用该方法。若没有显示定义,类内部会自动给我们创建一个constructor()。
<script>
// 创建类
class Person {
constructor(uname, sex, age) {
this.uname = uname;
this.sex = sex;
this.age = age;
}
}
// 创建实例对象
var xm = new Person('小明', 'female', 19);
var xs = new Person('小衫', 'male', 18);
console.log(xm, xs);
</script>
输出结果如下:
类中添加方法
类中所有函数均不需要写function,且多个函数方法之间不需要添加符号分隔;
<script>
// 创建类
class Person {
constructor(uname, sex, age) {
this.uname = uname;
this.sex = sex;
this.age = age;
}
// 类中添加方法
Say() {
console.log('我叫' + this.uname, '性别' + this.sex, '今年' + this.age);
}
}
// 创建实例对象
var xm = new Person('小明', '女', 19);
var xs = new Person('小衫', '男', 18);
console.log(xm, xs);
// 通过实例调用方法
xm.Say(); // 我叫小明 性别女 今年19
xs.Say(); // 我叫小衫 性别男 今年18
</script>
继承
继承是指子类可以继承父类的一些属性和方法,语法如下;
class Father {
// 父类
}
class Son extends Father {
// 子类继承父类
}
看下面一个错误示例:
<script>
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) {
this.x = x;
this.y = y;
}
}
var son = new Son(1, 2);
son.sum();
</script>
运行该代码,会报如下错误:
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
at new Son (ObjTsting.html:24:17)
这是因为子类中无法使用父类的sum方法,在父类方法中this.x和this.y中的this指代的是父类,而子类中传过来的参数中,this.x和this.y指代的是子类,所有会报错;
那要如何继承父类中的参数和方法呢?此时则需要super关键字~
super关键字
它用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可以调用父类的普通函数,上述例子就可以改造成:
<script>
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(x, y); // 调用父类的构造函数
}
}
var son = new Son(1, 2);
son.sum(); // 3
</script>
继承是指若子类有属性和方法,则用子类的属性和方法,若无,则继承父类方法;
子类在构造函数中使用super,必须放到this前面(即必须先调用父类的构造方法,再使用子类的构造方法);
<script>
class Father {
constructor(uname, sex) {
this.uname = uname;
this.sex = sex;
}
}
class Son extends Father {
constructor(uname, age) {
super(uname); // 调用父类的构造函数(uname)
this.age = age; // 定义子类独有的属性
}
}
</script>
类里面共有的属性和方法一定要加this使用;构造函数中的this 指向的是创建的实例对象;谁调用类中的方法,this就指向谁;
在ES6中类没有变量提升,所以必须要先定义类,才能通过类实例化对象;
静态属性
用static表示,静态属性只能通过类来使用;
class Phone{
// 静态属性
static name = '手机'
static change() {
console.log('手机改变人类生活');
}
}
let phone = new Phone();
console.log(phone.name); // undefined
console.log(Phone.name); // 手机
phone.change(); // Uncaught TypeError: phone.change is not a function
Phone.change(); // 手机改变人类生活
构造函数和原型
构造函数和原型是在ES6出来之前存在的,后被类取代;在ES6之前,对象不是基于类创建的,而是用一种称为构造函数的特殊函数来定义对象和它们的特征。
创建对象可通过如下三种方式:
1、对象字面量
2、new Object()
3、自定义构造函数
<script>
var obj1 = {}; // 通过字面量创建对象
var obj2 = new Object(); // 利用new创建对象
function Obj3(uname, age) { // 利用构造函数创建对象
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('这是构造函数中包含的方法');
}
}
var dog = new Obj3('小狗', 1);
var cat = new Obj3('小猫', 1.5);
</script>
构造函数是一种特殊的函数,主要用于初始化对象,即为对象成员变量赋初始值,它总与new一起使用,他们可把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面;
在JS中,使用构造函数时要注意以下两点:
1、构造函数用于创建某一类对象,其首字母要大写;
2、构造函数要和new一起使用才有意义
new 在执行时会做四件事情:
1、在内存中创建一个新的空对象;
2、让this指向这个新的对象;
3、执行构造函数里面的代码,给这个新对象添加属性和方法;
4、返回这个新对象(所以构造函数里面不需要return)
实例成员与静态成员
构造函数中的属性和方法我们称为成员,成员可以添加;
实例成员就是构造函数内部通过this添加的成员;实例成员只能通过实例化的对象来访问;不可以通过构造函数来访问实例成员;
静态成员即在构造函数本身上添加的成员;静态成员只能通过构造函数来访问
<script>
function Obj3(uname, age) { // 利用构造函数创建对象
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('这是构造函数中包含的方法');
}
}
var dog = new Obj3('小狗', 1);
console.log(dog.uname); // 小狗 // 只能通过实例化的对象来访问实例成员
console.log(Obj3.uname); // undefined 不可以通过构造函数访问实例成员
// 静态成员是指在构造函数本身上添加的成员,静态成员只能通过构造函数来访问
Obj3.sex = 'male';
console.log(Obj3.sex); // male
</script>
构造函数的问题
构造函数方法很好用,但是存在浪费内存的问题;
如多个实例对象上的方法往往是相同的,但是若new出很多实例对象,则不同实例对象会开辟多个空间用于存放同一个函数,这样就会造成内存浪费;
既然使用同一个对象构建出来的实例对象,那么该函数和属性应该是所有对象所共享的,
构造函数原型对象prototype
构造函数通过原型分配的函数是所有对象所共享的~
JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象。注意,这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。因此可以将那些不变的方法,直接定义在prototype上,这样所有对象的实例就可以共享这些方法。
<script>
function Obj3(uname, age) { // 利用构造函数创建对象
this.uname = uname;
this.age = age;
}
Obj3.prototype.sing = function() {
console.log('wangwang');
}
var dog = new Obj3('小狗', 1);
var cat = new Obj3('小猫', 2);
dog.sing(); // wangwang
cat.sing(); // wangwang
</script>
对象原型__proto__
对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在。
- __proto__对象原型和原型对象prototype是等价的
- __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype,即如下示意图:
<script>
function Obj3(uname, age) { // 利用构造函数创建对象
this.uname = uname;
this.age = age;
}
Obj3.prototype.sing = function() {
console.log('wangwang');
}
var dog = new Obj3('小狗', 1);
console.log(Obj3.prototype);
console.log(dog.__proto__);
console.log(Obj3.prototype === dog.__proto__); // true
</script>
constructor构造函数
对象原型(__proto__)和构造函数(prototype)原型对象里面都有一个属性constructor属性,constructor我们称为构造函数,因为它指回构造函数本身。
他主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
如果我们修改了原来的原型对象prototype,给原型对象赋值的是一个对象(而不是像前面一样使用构造函数.prototype.方法的形式赋值),则必须手动的利用constructor指回原来的构造函数;
如下方代码,直接利用了对象对prototype进行了赋值,则
<script>
function Obj3(uname, age) { // 利用构造函数创建对象
this.uname = uname;
this.age = age;
}
Obj3.prototype = {
constructor: Obj3, // 将构造函数重新指回Obj3
sing: function() {
console.log('wangwang');
}
}
var dog = new Obj3('小狗', 1);
console.log(Obj3.prototype.constructor); // Obj3
console.log(dog.__proto__.constructor); // Obj3
</script>
否则, 会破坏指向,相当于将原来的构造函数直接重写了,会丢失原有构造函数中的方法~
<script>
function Obj3(uname, age) { // 利用构造函数创建对象
this.uname = uname;
this.age = age;
}
Obj3.prototype = {
sing: function() {
console.log('wangwang');
}
}
var dog = new Obj3('小狗', 1);
console.log(Obj3.prototype.constructor); // 变成指向了Object,改变了原本的构造函数
console.log(dog.__proto__.constructor); // 变成指向了Object,改变了原本的构造函数
</script>
构造函数、实例、原型对象三者之间的关系
原型链
(图来自黑马程序员pink老师前端教程~)
只要是对象,就有原型对;
当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性;
若没有则查找它的原型(即__proto__指向的prototype对象);
如果还没有就查找原型对象的原型,以此类推一直找到Object为止;
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说是一条路线;
在构造函数中,this指向的是实例对象;
在原型对象函数里面的this指向的也是实例对象(谁调用谁就是this)
扩展内置对象
可以通过原型对象,对原来的内置对象进行扩展自定义的方法,比如给数组增加自定义求偶数和的功能;
<script>
console.log(Array.prototype);
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
var arr = [1, 2, 3, 4];
console.log(arr.sum()); // 10
</script>
注意:数组和字符串内置对象不能给原型对象覆盖操作Array.prototype = {},只能是Array.prototype.xxx = function() {}的方法
<script>
console.log(Array.prototype); // 没有sum方法
Array.prototype = {
constructor: Array,
sum: function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
}
var arr = [1, 2, 3, 4];
console.log(arr.sum()); // arr.sum is not a function
</script>
继承
同理,ES6之前亦没有extends继承,我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承;
借用构造函数继承父类型属性
核心原理:通过call()方法将父类型的this指向子类型的this,即可实现子类型继承父类型的属性;
<script>
// 父构造函数
function Father(uname, age) {
// this 指向父构造函数的实例对象
this.uname = uname;
this.age = age;
}
// 子构造函数
function Son(uname, age, score) {
// 此处this指向子构造函数的实例对象,通过call改变this指向,并传入值
Father.call(this, uname, age);
this.score = score
}
var son = new Son('小头儿子', 12, 95);
console.log(son); // { "uname": "小头儿子", "age": 12, score: 95 }
</script>
借用原型对象继承父类型方法
核心原理:将子构造函数的原型对象指向父构造函数的实例对象,因为父构造函数的实例对象能够共享父构造函数中的方法,此时再对子构造函数或父构造函数的原型对象添加操作也不会对彼此有影响~(但是要记得手动改变下子构造函数的原型对象的构造函数指向)
<script>
// 父构造函数
function Father(uname, age) {
// this 指向父构造函数的实例对象
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log('我能挣钱!');
}
// 子构造函数
function Son(uname, age, score) {
// 此处this指向子构造函数的实例对象,通过call改变this指向,并传入值
Father.call(this, uname, age);
this.score = score
};
// 子构造函数的原型对象为Father的一个实例对象,实例对象共享Father构造函数中的方法
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor指回原来的构造函数
Son.prototype.constructor = Son;
// 并不影响子构造函数的原型对象使用
Son.prototype.sing = function() {
console.log('我还会唱歌~');
}
var son = new Son('小头儿子', 12, 95);
console.log(son); // { "uname": "小头儿子", "age": 12, score: 95 }
console.log(Father.prototype); // { "uname": "小头儿子", "age": 12, score: 95 }
console.log(Son.prototype.constructor);
</script>
- 使用call()方法
调用call()方法可以修改函数运行时的this指向,语法如下:
函数名.call(thisArg, arg1, arg2,....)
其中,thisArg为当前调用函数this的指向对象,args等为传递的其它参数;
<script>
function fn(x, y) {
console.log('看看这个数是多少');
console.log(this);
}
var o = {
sum: 12
}
fn.call(o, 1, 2); // this指向了o,里面含有sum
</script>
扩充:
call方法有两个作用:
- call()可以调用函数;
- call()可以改变这个函数的this指向(上述)
function fn() {
console.log('这是个函数');
}
fn(); // 这是个函数
fn.call(); // 这是个函数
function fn() {
console.log('这是个功能');
console.log(this); // 指向了obj
};
var obj = {
name: 'xiaoming'
};
fn.call(obj);
类的本质
- class本质还是function,即函数;
- 类的所有方法都定义在类的prototype属性上;
- 类创建的实例,里面也有__proto__指向类的prototype原型对象;
- ES6中类的绝大多数功能,ES5都能做到,新的class方法只是让对象原型的写法更加清晰,更像面向对象编程的语法而已;即ES6中的类更像是个语法糖。
<script>
class Obj3 { // 利用构造函数创建对象
constructor(uname, age) {
this.uname = uname;
this.age = age;
}
sing() {
console.log('wangwang');
}
}
Obj3.prototype.eat = function() {
console.log('饿了,要吃饭饭');
}
var dog = new Obj3('小狗', 1);
console.log(typeof Obj3);
console.log(Obj3.prototype);
console.log(dog.__proto__);
console.log(Obj3.prototype === dog.__proto__); // true
console.log(Obj3.prototype.constructor);
console.log(dog.__proto__.constructor);
</script>
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/4771.html