JS面向对象的重要知识点

一、闭包

闭包是连接函数外与函数内的桥梁,它是通过在函数内部再定义子函数来实现的,用户可以在函数外通过闭包访问函数的局部变量

// 外层函数
function outside() {
	// 外层函数的局部变量
	var a = 250;
	// 内层函数
	function inside() {
		return a;
	}
	// 将内层函数作为返回值
	return inside;
}

var fn = outside();
console.log(fn()); // 打印输出250

由上述代码所示,全局变量fn指向了outside()执行后返回的inside函数,而inside函数又依赖ouside函数的局部变量a,所以outside函数的局部变量将一直驻于内存中,用户随时可以在函数外通过闭包来进行访问,直到fn的引用解除后。

二、封装

封装是面向对象的一个重要特性,它指将对象内部的数据进行隐藏,只提供相应的接口供用户调用
JS语言中没有类似private的访问限制符,只能通过闭包来实现封装。

/**
 * 定义一个构造函数,内部实现闭包操作
 */
function Person(name, age, sex) {
	this.name = name;
	this.age = age;
	this.sex = sex;

	this.getName = function () {
		return this.name;
	};
	this.getAge = function () {
		return this.age;
	};
	this.getSex = function () {
		return this.sex;
	}
}

/**
 * 实例两个Person类对象
 */
var obj1 = new Person("张三", 18, "男");
var obj2 = new Person("李四", 16, "女");

console.log(obj1.getName()); // 成功打印"张三"
console.log(obj2.getAge()); // 成功打印16

三、工厂模式

将对象创建的实现细节都封装在一个函数内,让用户通过该函数进行对象的生成。

/**
 * 用于创建对象的工厂方法
 */
function createObj(name, age, sex) {
	var obj = new Object();
	obj.name = name;
	obj.age = age;
	obj.sex = sex;
	return obj;
}

var person = createObj("小明", 18, "男"); // 利用工厂方法创建了一个对象,并赋值给了变量person
console.log(person); // 成功打印person对象的信息

四、原型模式

JS语言中没有类(Class)的概念,它是通过构造函数(constructor)以及原型链(prototype chain)来实现面向对象的。
构造函数中定义的属性和方法是无法被实例所共享的,每一个实例都会重新开辟内存,再将构造函数的属性和方法“复制”进去。

/**
 * 定义一个构造函数,定义Person类拥有speak方法
 */
function Person() {
	speak = function () {
		console.log("我会说话");
	};
}

/**
 * 实例两个Person类对象
 */
var obj1 = new Person();
var obj2 = new Person();

obj1.speak(); // 成功输出
obj2.speak(); // 成功输出
console.log(obj1.speak === obj2.speak); // 输出false

上述代码中,虽然obj1和obj2的speak方法行为和特征完全相同,但并不是同一个方法,它们分别占用了不同的内存空间,彼此互不关联。
在这里插入图片描述


为了解决对象数据共享的问题,JS中每一个函数都有一个prototype属性,这个属性对应着一个原型对象;每一个对象实例都有一个__proto__属性,它指向了构造函数的prototype属性。所以,在同一构造函数下生成的实例都会共享原型对象上的属性和方法

function Person() {
	
}

/**
 * 调用Person构造器的prototype属性,并向其添加一个speak方法
 */
Person.prototype.speak = function () {
	console.log("我会说话");
};

var obj1 = new Person();
var obj2 = new Person();

obj1.speak(); // 成功输出
obj2.speak(); // 成功输出
console.log(obj1.speak === obj2.speak); // 输出true

因为obj1和obj2都直接引用了Person.prototype上的speak方法,所以两个speak方法是同一个对象,本质完全相同。
在这里插入图片描述

四、原型继承链

在JS中,每个对象都继承自另一个原型对象,而原型对象自身作为一个对象也继承了它的原型对象,以此不断传递下去,形成了原型链。
一切继承关系的顶端是Object.prototype(实际上Object也有自己的原型对象——null,但通常忽略不谈)
如果父对象与子对象有重复的属性或方法,将以子对象的为准(即覆写)

/**
 * 定义父类Person的构造函数
 */
function Person(name) {
	this.name = name;
	this.objNum = 1;
}

/**
 * 向Person的原型对象添加speak方法
 */
Person.prototype.speak = function () {
	console.log(this.name + "在说话");
}

/**
 * 定义子类Student的构造函数
 */
function Student(name, grade) {
	this.name = name;
	this.grade = grade;
	this.objNum = 2;
}

/**
 * 将Student的prototype属性引向Person的一个实例,此时该实例将成为原型对象
 * 再向原型对象添加一个study方法
 */
Student.prototype = new Person(this.name);
Student.prototype.study = function () {
	console.log(this.grade + "年级的" + this.name + "在学习");
}

/**
 * 实例两个Student类对象
 */
var stu1 = new Student("小明", 1);
var stu2 = new Student("小红", 2);
		
stu1.study(); // 1年级的小明在学习
stu1.speak(); // 小明在说话
console.log(stu1.objNum); // 2
stu2.study(); // 2年级的小红在学习
stu2.speak(); // 小红在说话
console.log(stu2.objNum); // 2

上述代码中,Person设计为父对象,Student设计为子对象,为了完成继承关系,我们将Person的对象实例赋给了Student.prototype
如此一来,Student的所有实例在读取属性和方法时,会先查找自身,如果自身没有找到的话,会继续查找Student.prototype,即new Person()
如果在Person中也没有找到目标,会继续向上查找Person.prototype,以此不断向上搜寻,直到抵达Object.prototype,如果还是没找到则返回undefined。
在这里插入图片描述
父类Person的原型属性中定义了speak方法,而子类Student没有speak方法,stu1和stu2在自身和原型对象上找不到speak方法时,就会向上到父类中去读取。(属性继承)
父类Person和子类Student都定义了一个objNum的属性,但stu1和stu2都只识别子类Student的属性值。(属性覆写)

五、对象冒充

通过原型链实现的继承关系,子类可以共享其链条上端所有类的的属性和方法,包括prototype内的属性和方法。
对象冒充也是一种继承的实现,它将父类的构造器作为子类构造器的属性,然后在子类构造器内调用该属性进行实例化,子类就能冒充父类对象,以此获得父类的属性和方法。(也可以使用call和apply方法完成)
要注意的是:在对象冒充中,子类只能共享父类自有的属性和方法(简称特权属性),无法共享prototype域(简称公有属性

/**
 * 定义父类Person的构造函数,添加一个speak方法
 */
function Person() {
	this.speak = function () {
		console.log("我会说话");
	};
}

/**
 * 向Person的prototype属性添加一个eat方法
 */
Person.prototype.eat = function () {
	console.log("我会吃饭");
};

/**
 * 定义子类Student的构造函数
 * 将Person构造器当作属性进行存储,调用以完成对象冒充操作
 * 此处亦可用call或apply方法
 */
function Student() {
	this.father = Person;
	this.father();
}

var stu = new Student();

stu.speak(); // 成功运行
stu.eat(); // 无法运行,提示eat未定义

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值