Javascript教程八——面向对象编程

本文详细介绍了JavaScript中的面向对象编程,包括this的概念及其在不同情况下的指代,call、apply和bind方法的使用,以及创建对象的各种方式如对象字面量、构造函数、原型和组合模式。还探讨了继承的不同实现,如原型继承、构造函数继承、组合继承和ES6的class继承,总结了各种继承方式的优缺点。
摘要由CSDN通过智能技术生成

8、面向对象编程

this

this,用于指代环境对象的,这里先讲解一下,方便之后理解。但是在不同情形下,this的指代是不同的:

1、this在一个对象的method出现,指代的是method所属的对象;
2、this在函数中出现,注意是普通函数,指代全局对象window;
3、this在事件中出现,指代事件绑定的对象;
4、this在函数中出现,在严格模式下,this 是未定义的(undefined);

以上就是this的指代了,特别交代一句,无论何时,匿名函数中的this全部指代全局window。

但是这里的this和接下来要讲的创建对象就不能一概而论,特殊情况特殊对待吧。

call apply bind

this的重定向

首先来说明一下这三个函数的作用,简单来讲就是:

把一个对象的方法,绑定给另外一个对象去使用。

call apply bind 相当于把this重新绑定了,直接替换了this

举个🌰:

var a = {
	name = 'Leo';
	age = 20;
	getName() {
		console.log(this.name+this.age);
	}
}
var b = {
	name = 'ruby';
	age = 30;
}
a.getName(); // Leo, 20

a.getName.call(b); // ruby, 30  getName里面的this就绑定上b了,直接!!!
a.getName.apply(b);
a.getName.bind(b)();    

注意,bind和上面的两个函数不太一样,bind返回的是一个函数,并不能自己去执行,需要加上()去执行它。

函数传参

var a = {
	getName(name,age) {
		console.log(name+age);
	}
}
var b = {}
a.getName.call(b,'Lily',10); // Lily, 10
a.geyName.apply(b,['Lily',10]); // Lily 10    传参传递的是一个数组
a.getName.bind(b,'Lily',10)(); // Lily 10

call与apply实现bind

首先了解一下bind的特性,bind本身作用在一个函数上面,并且把函数的作用域this进行替换,替换成第一个参数的this,然后把新参数传进去:

首先了解一下重新定义this

Function.prototype.bind = function bind_new(obj_new){
	var func = this; // 这是函数本来的this
	return function(){
		func.apply(obj_new); // 更换this
	}
}

之后就是添加新参数了 这里需要讲一下,传递参数是有一个arguments的,这是实参。

Function.prototype.bind = function bind_new(obj_new){
	var arg = arguments.slice(1); // 这是数组的原定参数
	var func = this; // 这是函数本来的this
	var bound = function(arg1){   // 所谓的函数柯里化
		return func.apply(obj_new, [...arg, ...arg1]); // 更换this,并添加传递的参数
	}
	// 函数需要设置自己的原型对象
	var fn = new Function() {};  // 搞一个父类
	fn.prototype = func.prototype;  // bind自己的原型应该是bind_new函数的原型
	bound.prototype = new fn();  // 父类的实例是子类的原型
	return bound;
}

创建对象

1、直接利用对象字面量{…}创建

var a = {
	name: 'ruby',
	age: 20,
	getName(){
		return this.name;
	}
}

但是如果创建多个对象,就需要一遍一遍的写,希望能够批量创建对象。

2、Object构造函数

var Person = new Object();  // 直接继承自Object对象
Person.name = 'ruby';   // 因为对象的属性可以随时增加
Person.age = 20;

这样也是需要大量复写工作。

3、单例模式

单例模式,核心结构中值包含一个被称为单例的特殊类。一个类只有一个实例,即一个类只有一个对象实例

简单来讲,便是如果我们用 new 去创建实例,每次创建的都是同一个实例:

1、如果实例不存在,那么便需要利用构造函数创建一个实例
2、如果实例已经存在,那么就需要直接返回这个实例

看出来了,有点全局变量的意思,那么将全局变量转换成局部变量,就需要闭包,让我们来试一试。

首先,我们先来想一想构造函数,构造函数就是一个函数, 然后里面写一些属性和方法:

function fn(name){
	this.name = name;
	this.getName = function(){
		console.log(this.name);
	}
}

好了,有了构造函数,下面就需要放入闭包里面:

var CreateOne = (() => {
	var instance = null;    // 实例
	return function(name){ // 利用了函数柯里化
		if(instance != null){
			return instance;   // 实例存在就需要直接返回
		}
		instance = new fn(name);  // 实例不存在,那么就创建一个并返回
		return instance;
	} 
})();  // 函数让它执行

var instance1 = new CreateOne(‘ruby’); // ruby
var instance2 = new CreateOne(‘ruby2’); // ruby

4、使用工厂模式创建对象

通俗地讲,工厂模式就是将创建对象的语句放在一个函数里,通过传入参数来创建特定对象,最后返回创建的对象。这样子后面创建相同类型的对象可以直接调用函数,不用一遍遍复写。

function Person(name, age) {
	var a = new Object();   
	a.name = name;
	a.age = age;
	a.opetion = funtion(){
		console.log(this.name + this.age);
	}
	return a;
}
var ruby = Person('ruby', 20);

在Person函数中,返回的是一个对象,对象类型全是Object,这就很粗糙了。

5、构造函数

JavaScript不存在类的概念,那么稍微复杂的就是函数了。
构造函数的方法来创建对象:首先编写一个函数,在里面声明、定义局部变量以及内部函数,通过new 便可以将函数里面的this指向的是生成的对象

function Person(name, age) {
	this.name = name;
	this.age = age;
	this.opetion = funtion(){
		console.log(this.name + this.age);
	}
}
var ruby = new Person('ruby', 20);

利用了 new ,下面看一下new的操作:

1.创建一个新对象
2.将构造函数的作用域赋给新对象(因此this就指向了这个对象)
3.执行构造函数中的代码(为这个新对象添加属性)
4.返回新对象

然后就会发现,创建的实例的类型变成了Person,哦,有详细的类型了,完美。

对比工厂模式,我们可以发现以下区别:

1.没有显示地创建对象
2.直接将属性和方法赋给了this对象
3.没有return语句
4.终于可以识别的对象的类型。

new Person() 创建的对象会获得了一个constructor属性,它指向构造函数Person

但是虽然没有复写的麻烦了,但是实际上每次创建对象,都会把Person函数跑一遍,里面每次运行到的属性和方法都只是属于实际对象的。举个🌰:

function Person(name, age) {
	this.name = name;
	this.age = age;
	this.opetion = funtion(){
		console.log(this.name + this.age);
	}
}
var ruby = new Person('ruby', 20); // 构造函数帮助创建了两个属性和一个方法
var ruby2 = new Person('ruby2', 22); // 构造函数也帮忙创建了两个属性和一个方法

看出来了吧,因为this指向的是创建的对象,两次this分别指向ruby和ruby2,确实不用重写,也能找到对象类型,但是占了4个变量的空间和两个函数的空间,实际资源一点都没少占!!!!!!!
所以就有了最后一种方法。

6、原型创建

先来了解一下关于原型与原型链。

原型与原型链
1、原型

当我们创建一个函数时,函数会生成一个属性,prototype ,注意:只有函数才会有这个属性。然后这个 prototype 会指向这个函数的原型对象 Object,同时 prototype 下会生成一个 constructor 指针,这个指针会指向创建的函数,举个🌰:

function Hello(){
}
Hello.prototype; // Object
Hello.prototype.constroctor; // Hello()

在这里插入图片描述
上面构造函数创建对象的🌰:对象ruby是来自Person()这个构造函数,这个对象ruby就会生成两个属性 proto 和constructor , __proto__这个属性会指向构造函数Person的 prototype 指针指向的 Object;而 constructor 就指向Person()这个构造函数。

ruby.__proto__; // Object 原型对象
ruby.constructor; // Person()  构造函数

在这里插入图片描述

2、原型链

所以这种继承链接的基础就是对象的__proto__;
ruby对象的__proto__会找到构造函数的prototype,从而指向原型对象Object,然后原型对象会继续查看__proto__找自己的构造函数和原型对象,直到null,所以一连串的原型对象就构成了原型链。

ruby ————> Person.prototype(Object) ————> Object.prototype(Object的原型对象)————>null;

在这里插入图片描述
那么如何才能够减少空间的浪费呢,有一个办法,就是属性和方法不要写到函数里面,想办法写到函数的原型对象里面,没错,就是 Person.prototype里面,这样子每次使用都是同一个资源,还可以达到共享的效果。

function Person(){}
Person.prototype.name = 'Leo';
Person.prototype.age = 30;
Person.prototype.getName(){
	return this.name;
}

var ruby = new Person(); // Leo,30  每次创建对象共享的同一份资源,内存省了
ruby.name = 'ruby';
ruby.age = 20; // ruby, 20

7、组合模式

所谓的组合模式,便是将构造函数创建对象实例和原型创建混合在一起使用:

function Person(name, age) {
	this.name = name;
	this.age = age;
	}
} // 构造函数
Person.prototype.opetion = funtion(){
		console.log(this.name + this.age);
} // 写在原型链上的函数,因此多个对象可以公用同一个函数

继承

创建完对象,来谈一谈关于继承吧,说到继承,看创建便知道JS是依靠原型创建,那么继承也是依靠原型,但是这里有必要说一下,原型继承需需要理解原型和原型链。

原型继承

所以原型继承的基础就是原型和原型链,核心是:将父类的实例作为子类的原型
因为不存在类的概念,那么函数就是老大了,先弄一个所谓的父类:

function PersonOne(name) {
	// 注意:这里的属性和方法都是给实例对象的
	this.name = name;
	this.opetion = funtion(){
		console.log(this.name);
	}
}
// 然后这个函数的原型对象就是 PersonOne.prototype
PersonOne.prototype.age = 10; // 给原型对象一个共享变量

function Person(name){
	// 这是给Person的实例对象的属性
	this.name = name;
	this.opetion = funtion(){
		console.log(this.name);
	}
}
Person.prototype = new PersonOne(); // 父类实例(右)-- 子类原型(左)

// 然后就创建新对象了
var ruby = new Person('ruby'); // ruby,10

原型链:
ruby ————> Person.prototype ————> PersonOne.prototype
name,opetion,age ————> age

优点:

1、实现起来比较简单
2、父类所有的属性方法可以被所有实例共享

缺点:

1、父类想要增加属性和方法不容易,只能这样 PersonOne.prototype.XXX = YYY; 
2、父类的属性方法可以被所有实例共享,也是一个缺点,因为相互影响
3、创建实例时,根本无法向PersonOne里面传递参数
4、继承链依赖于原型链,所以只能单一的继承原型对象的方法和属性

构造函数继承

其核心思想是:在子级构造函数中调用父级构造函数。
如何实现在一个构造函数中调用另一个函数?——call()和apply()

function PersonOne(age){
	this.age = 20;
}
function Person(name, age){
	PersonOne.call(this, age); // 注意,这个this是Person的实例,也就是把属性和方法绑定到Person上了
	this.name = name;
}
var ruby = new Person('ruby', 20); // ruby, 20
var ruby2 = new Person('ruby2', 30); // ruby2, 30

因为压根就没有共享一个资源,所以ruby和ruby2彼此之间互不干涉,而且可以传递参数了,还有一个优点就是可以继承很多父类,只要调用call更改this就可以;但是问题也是那个问题,浪费资源,无法共享,而且只能拿去父类的构造函数里面的属性和方法,原型链上的拿不到。

组合继承

组合继承就是把构造函数继承+原型继承,把他们的优点结合起来。

function PersonOne(name){
	this.name = name;
	this.arr =  [1,2,3]; // 这是实例属性,不会共享
}
PersonOne.prototype.age = 20; // 这是共享的属性
function Person(name){
	PersonOne.call(this,name); // 拷贝一份PersonOne的实例属性 name和arr
	this.getName(){
		return this.name; // 新的实例方法
	}
}
Person.prototype = new PersonOne(); // 原型链来一份
var ruby = new Person('ruby'); // getName,ruby,[1,2,3],20  其中20是共享的

class继承

上面的继承太复杂,不好理解,因此ES6推出了javascript的class继承,让类的定义更加简单,但其实实际上class只是一种另外一种原型继承的表达方式而已,它遵循的基础仍然是原型继承

下面是定义一个类,包括构造函数以及方法:

class Person{
	// 构造函数
	constructor(name,age){ // 构造函数
		this.name = name;
		this.age = age; // this 指向类的实例对象
	}
	// 下面是方法,但是注意,方法实际上是定义在Person 的原型对象上的
	hello(){
		console.log(this.name); // 方法里面的this也是指向实例对象的  
	}
}

看上面定义的Person,它其实是一种原型对象,然后constructor就是它的构造函数,类似于

function Person() { ... }

然后定义在原型对象的 hello 函数 也就是:

Person.prototype.hello = function ()
	{ 
		console.log('Hello!!!'); 
	}

class继承可以直接通过extends来实现:

class primaryPerson extends Person {
	constructor(name, age, grade){
		super(name, age); // super是用来调用父类的构造函数的,可以直接传参,不用重写
		this.grade = grade;
	}
	hey(){
		console.log('hey');
	}
}
var primaryPerson1 = new primaryPerson('ruby',20); // ruby,20,hello(),hey()

相对于上面的原型继承的写法,这种class继承方式的写法就简单很多。
本文来源廖雪峰老师教程的笔记,有疑惑可以直接访问廖雪峰老师教程:https://www.liaoxuefeng.com/wiki/1022910821149312/1023022043494624

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值