前面学习的JavaScript的内容,属于初学JavaScript必经之路,为了更有效率的编写代码,我们必须理解JavaScript面向对象的精髓。
什么是对象
老师用了一句话:万物皆对象。
- 对象是对单个事物的抽象。
- 对象是一个容器,封装了属性和方法。属性-对象的状态。方法:对象的行为
- 数据集或功能集
- ECMAScript-262把对象定义为:无序属性的集合,其属性可以包含基本值,对象或者函数。
什么是面向对象
简而言之,把相关数据和方法组织为一个整体来看待,从更高层次来进行系统建模,更贴近事物的自然运行模式。网上看到一句话觉得很逗(你个土豪,你们全家都是土豪)。
面向对象有三大要素:封装,继承,多态
- 封装:不管你昨天做了什么,别人都不知道,除非你自己告诉他们
- 继承:冰冻三尺非一日之寒,想想那些富几代就明白了吧!
- 多态:就算双胞胎也不可能完全一模一样。即使都是从父亲那里继承来的,也会有些差别。
面向对象不是面向过程的替代,而是面向过程的封装。
优点:灵活,代码可复用,高度模块化。容易维护和开发。更适合多人合作的大型软件项目。
面向对象的设计思想
- 抽象出class(构造函数)
- 根据class(构造函数)创建instance(实例)
- 指挥instance得到结果
创建对象的几种方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 创建对象的几种方式
//new Object方法
// new Object()
// var person=new Object();
// person.name="Bob";
// person.age="18";
// person.sayName=function(){
// console.log(this.name);
// };
// person.sayName();
//对象字面量化简方式
// var person1={
// name:"bob",
// age:"18",
// sayName:function(){
// console.log(this.name);
// }
// };
// var person2 = {
// name: "May",
// age: "16",
// sayName: function () {
// console.log(this.name);
// }
// };
// person1.sayName();
// person2.sayName();
//工厂函数
// function createPerson(name,age){
// //添加一个新对象
// var person=new Object();
// person.name=name;
// person.age=age;
// person.sayName=function(){
// console.log(this.name);
// };
// //必须有返回值
// return person;
// }
// //定义一个新对象
// var person1=createPerson("bob","18");
// person1.sayName();
//自定义函数
function createPerson(name,age){
return{
name:name,
age:age,
sayName:function(){
console.log(this.name);
}
};
}
var person1=createPerson("May","18");
person1.sayName();
</script>
</body>
</html>
构造函数,实例对象,原型对象三者的关系
老师讲的那堆专业名词我真的有点晕圈,所以去浏览了一下百度百科
- 构造函数:一种特殊的方法,主要用来在创建对象时初始化对象,即为对象成员变量赋初始值,总与new运算符一起使用。
- 实例对象:多数语言中,实例化一个对象就是为对象开辟一个内存空间,或者不声明,直接使用new构造函数名,建立一个临时对象。
- 原型对象:每创建一个函数,该函数都会有一个自带的prototype属性,该属性指向一个对象,该对象称之为原型对象。JS中一切继承都是用原型对象实现的!
原型对象上默认有一个constructor属性,指向相关联的构造函数。通过调用构造函数产生的实例对象,拥有一个内部属性_proto_,指向了原型对象。实例对象能够访问原型对象上的所有属性和方法。
故此:所有构造函数都有一个原型对象,原型对象上包含一个指向构造函数的指针,而实例对象内部有一个指向原型的指针。实例可以通过内部指针访问原型对象,原型对象可以通过constructor找到构造函数。
静态成员和实例成员
- 使用构造函数创建对象时,可以给构造函数和创建的实例对象添加属性和方法,这些属性和方法称为成员。
- 实例成员:在构造函数内部添加给this的成员,属于实例对象的成员,在创建后,必须由对象调用
- 静态成员:添加给构造函数自身的成员,只能使用构造函数调用,不能使用生成的实例对象调用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//自定义构造函数
function createPerson(name,age){
//实例成员,添加在函数内部
this.name=name;
this.age=age;
this.sayName=function(){
console.log(this.name);
};
}
//直接给构造函数添加成员
createPerson.version="1.0";
// //生成对象实例
// var person1=new createPerson("bob","18");
// //调用实例成员
// person1.sayName();
//调用静态成员
console.log(createPerson.version);
</script>
</body>
</html>
构造函数的问题(浪费内存)
解决方法:使用原型对象可以更好的解决浪费内存的问题。根据前面说明的三者关系,将所有对象实例需要共享的属性和方法直接定义在prototype对象上。
<script>
//将多个公共函数封装到一个对象
var fnc={
sayName:function(){
console.log(this.name);
},
sayAge:function(){
console.log(this.age);
}
};
function person(name,age){
this.name=name;
this.age=age;
this.sayAge=fnc.sayAge;
this.sayName=fnc.sayName;
}
//生成实例对象
var person1=new person("Bob","13");
var person2= new person("May", "18");
</script>
原型链
在JavaScript中,每一个函数都有一个原型,函数被实例化后,实例对象可以通过prototype属性访问原型,实现继承机制。
下面附上一张图,用来理解原型在JavaScript对象系统中的位置和关系
Object和Function是两种不同类型的构造函数,利用new运算符可以创建不同类型的实例对象。实例对象,类,Object和Function之间的关系如图所示:
使用点语法,可以通过function.prototype访问和操作原型对象。
示例:下面为函数M定义原型
<script>
function M(name){
//构造函数
this.name=name;//声明私有属性
}
M.prototype.name="Bob";
//实例化对象
var person1=new M("May");
M.prototype.name=person1.name //设置原型属性为私有属性
console.log(M.prototype.name);//输出May
</script>
原型对象也可能有原型,并从中继承属性和方法。一层层类推,这种关系称之为原型链。
下面来分析一下原型链的查找过程
- 搜索首先从本身开始,即P1实例对象
- 如果在实例对象中找到了具有给定名字的属性,则返回该属性的值。
- 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找该属性。
- 如果在原型对象中找到了该属性,则返回属性值,否则返回undefined。
实例对象读写原型对象成员
读取:
- 现在自己身上查找,找到即返回
- 自己身上找不到,则沿着原型链向上寻找,找到即返回。
- 如果一直到原型链末端还没有找到,返回undefined。
值类型成员写入(P1.name=“Bob”)
- 当实例想要重置原型对象中的某个普通数据成员时,会把该成员添加到自己身上。
- 也就是说该行为实际上会屏蔽掉对原型对象成员的访问
复杂类型成员修改
- 先在自己身上找,如果找到则直接修改,
- 否则,继续向上寻找,找到则修改
- 如果一直在末端找不到,则报错(P1.undefined.xx=xx)
原型对象使用建议
在定义构造函数时,可以根据成员的功能不同,分别进行设置
- 私有成员:放到构造函数中
- 共享成员:放到原型对象中
- 如果重置了prototype,记得修正constructor的指向。