JavaScript面向对象
一、学习目标
- 了解对象和面向对象
- 掌握创建对象的方法
- 掌握简单工厂模式的使用
- 理解构造函数与原型对象
- 掌握对象继承的用法
二、什么是对象
对象是相关属性和方法的集合体
属性
方法String对象
length属性
indexOf() 、replace()方法Array对象
length属性
sort()、concat()、join()方法Date对象
get×××:获取年、月、日、时、分、秒等等
set×××:设置年、月、日、时、分、秒等等Math对象
round()、max()、min() 等方法
Boolean对象
toString()方法
RegExp对象
compile()、exec()、test()方法
三、常见内置对象
String对象
字符串字面量
new String()Date对象
new Date()
Array对象
[ element0, element1, …, elementN ]
new Array()Math对象所有属性和方法都是静态的
Boolean对象
new Boolean()RegExp对象
直接量
new RegExp()
四、自定义对象方式分类
创建方式
- 基于Object对象创建自定义对象
- 使用字面量赋值方式创建自定义对象
- 简单工厂模式创建自定义对象
- 使用构造函数创建自定义对象
- 使用原型对象创建自定义函数(推荐指数:☆☆☆☆☆)
五、Object对象方式
语法
let 对象名称 = new Object();
示例
let student = new Object(); student.name = "张三"; student.age = 18; student.email = "zhangsan@163.com"; student.hobby = "打球"; student.showName = function() { alert( this.name ); } student.showName();
六、字面量赋值方式
语法
let 对象名称 ={ 属性名1:属性值1, 属性名2:属性值2, 属性名n:属性值n, 函数名:function(){ //函数主体 }; };
示例
let student = { name :"张三", age : 18, email : "zhangsan@163.com", hobby : "打球", showName : function() { alert( this.name ); } } student.showName();
七、简单工厂模式
问题
基于Object对象或使用字面量创建对象,如需批量创建对象,会产生大量重复代码
技巧
使用简单工厂模式封装对象的创建逻辑
模式是指在某一环境下某个问题的一种解决方案
简单工厂模式是一种用来创建对象的软件设计模式
示例
将对象的创建逻辑封装在一个函数中
function createStudent( name, age, email, hobby ) { var p = new Object(); p.name = name; …… p.showName = function() { alert( this.name ); } return p; }
使用工厂函数创建对象
var person1 = createStudent("张三", 18, "zhang3@163.com", "打球"); person1.showName(); var person2 = createStudent("李四", 19, "lisi@163.com", "看书"); person2.showName();
八、构造函数
问题
前述创建对象的方法无法区分不同类型的对象
分析
定义构造函数,用以创建特定类型的对象
//构造函数名首字母大写,以区别于其他函数 function Student( name, age, email, hobby ) { this.name = name; …… this.showName = function() { alert(this.name); } }
与工厂函数相比
- 没有显式创建Object对象,没有return语句
- 将属性和方法赋给this对象
示例
var stu1 = new Student( "张三", 18, "zhangsan@163.com", "打球" ); stu1.showName(); var stu2 = new Student( "李四", 19, "lisi@163.com", "看书" ); stu2.showName();
小结
构造函数执行过程中会经历以下4个步骤
- 创建一个新对象
- 将构造函数的作用域赋给新对象,因此this就指向了这个新对象
- 执行构造函数中的代码,为这个新对象添加属性及方法
- 返回新对象
九、构造函数问题分析
问题
上面示例中构造函数的定义方式会导致showName()方法在每个实例上都要重新创建一遍
function Student( name, age, email, hobby ) { //…… this.showName = function() { alert(this.name); } }
分析
- 通过函数定义的方式把showName()方法转移到构造函数的外部
- 所有实例共享全局作用域中定义的同一个showName()函数
function Student( name, age, email, hobby ) { //…… this.showName = showName } function showName() { alert(this.name); }
小结
通过分析改造确实决绝了上述问题,但是依然存在一定的设计缺陷,原因:
- 全局函数实际上仅为某个对象调用
- 全局函数破坏了自定义类型的封装性
所以此时原型对象因此而诞生…
十、原型对象
概念
- 每个函数都有一个prototype属性,指向其原型对象
- 使用原型对象方式添加属性和方法,所有实例会共享它所包含的属性和方法
语法
构造函数名.prototype.属性或方法
示例
function Student() { } Student.prototype.name = "张三"; //…… Student.prototype.showName = function() { alert(this.name); }; //……
(不想被共享的属性或方法,可以定义在构造函数中)
原型对象图解
当实例读取某个属性时
- 首先在实例本身开始搜索,找到则返回
- 如没有找到,则继续在原型对象中查找
十一、原型链
概念
- 一个类型的实例是另一个类型的原型对象
- 相关的原型对象层层递进,构成了实例与原型的链条,即原型链
示例
//父类 function Person() { this.foot = 2; } Person.prototype.getFoot = function() { return this.foot; } //子类 function Woman() { this.head = 1; } //Woman 继承 Person Woman.prototype = new Person(); Woman.prototype.getHead = function() { return this.head; } //创建子类实例对象 woman1 var woman1 = new Woman(); alert(woman1 instanceof Woman); //true alert(woman1 instanceof Person); //true alert(woman1 instanceof Object); //true alert(woman1.getFoot()); //2
原型链图解
woman1调用getFoot()的步骤:
- 搜索实例 → 搜索
Woman.prototype
→ 搜索Person.prototype
搜索的过程要一环一环地前行到原型链的末端才会停下来
完整的原型链
所有函数的默认原型都是Object对象的实例
十二、方法重写
概念
子类型可以通过 prototype 对象重写父类型中的方法
示例
//父类类型构造 function Person() { this.foot = 2; } //父类型Person getFoot方法 Person.prototype.getFoot = function() { return this.foot; } //子类类型构造 function Woman() { this.head = 1; } //子类Woman 继承 父类Person Woman.prototype = new Person(); Woman.prototype.getHead = function() { return this.head; } //【子类重写】 Woman.prototype.getFoot = function() { return false; }
注意事项
Woman的实例调用
getFoot()
方法时,调用的是重写后的方法,但是通过Person
的实例调用getFoot()
方法时,还会调用原来的方法.
十三、对象继承
问题
function Person() { this.skinColor = [ "black", "white" ]; // 肤色 } function Woman() {} Woman.prototype = new Person(); // 继承了Person var woman1 = new Woman(); woman1.skinColor.push( "yellow" ); alert(woman1.skinColor); // 输出什么? var woman2 = new Woman(); alert(woman2.skinColor); // 输出什么?
问题1: 两个实例输出的信息一样,为什么?
问题2: 创建子类型的实例时,不能向父类型的构造函数中传参分析
第一个问题是来自包含引用类型值的原型,由于包含引用类型值的原型属性会被所有实例共享 ,在通过原型实现继承时,原型实际上会变成另一个类型的实例,因此,原先的实例属性也就变成了现在的原型属性了。
方案
构造函数借调。
十四、借用构造函数
概念
借用构造函数又被称为伪造对象或经典继承,指在子类型构造函数的内部调用父类型的构造函数
示例
<script> function Person() { this.skinColor = ["black", "white"]; // 肤色 } function Woman() { Person.call(this); // 也可以使用apply()方法 } let woman1 = new Woman(); woman1.skinColor.push("yellow"); alert('woman1:' + woman1.skinColor); // 输出什么? let woman2 = new Woman(); alert('woman2:' + woman2.skinColor); // 输出什么? </script>
小结
Person.call(this)
表示“借调”了父类型的构造函数,通过使用call()
方法(也可以使用apply()
方法)。实际上是在新创建的 Woman 实例环境下调用 Person 的构造函数,这样在新的Woman对象上执行Person()
函数中定义的所有对象初始化代码。这样Woman
每个实例都会具有自己的skinColor
属性的副本。
十五、借用构造函数传参
概念
借用构造函数可以在子类型构造函数中向父类型构造函数传参
示例
function Person(name) { this.name = name; } function Woman(){ Person.call( this, "amy" ); this.age = 18; } ……
注意
为了确保父类型构造函数不会重写子类型的属性,可以在调用父类型构造函数后再添加应该在子类型中定义的属性
十六、组合继承
概念
- 组合继承也叫做伪经典继承
- 将原型链和借用构造函数的技术组合到一块,融合二者的优点并规避二者的缺陷,是JavaScript开发中最常用的一种继承模式
- 使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承
- 既实现了函数复用,又能够保证每个实例都有自己的属性
示例
<script> function Person(name) { this.name = name; this.skinColor = ["black", "white"]; // 肤色 } Person.prototype.sayName = function () { alert(this.name); } function Woman(name, age) { Person.call(this, name); // 继承属性 this.age = age; } Woman.prototype = new Person(); // 继承方法 Woman.prototype.sayAge = function () { alert(this.age); } </script>
十七、总结
总结思维图