全方位理解Javascript面向对象
一. 什么是面向对象
1.什么是对象:对象是一组无序的相关属性和方法的集合
2.对象是由属性和方法组成
- 属性:事物的特征,在对象中用属性来表示(常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
3.面向对象的优点:易维护,易复用,易扩展,由于面向对象有封装、继承。多态的特性,可以设计出低耦合的系统,使系统更加灵活,更加易于维护(c++,c#,java,javascript)
4.面向对象缺点:性能比面向过程低(C语言)
5.面向对象的三大特点:封装,继承,多态。
二.封装
1.什么是封装:创建一个对象结构,集中保存一个事物的属性和功能(创建对象)。
2.创建对象的几种方式:
①. 对象字面量:用{}方式创建一个对象;
var lilei = {
sname: "Li Lei",
sage:11,
intr:function(){
console.log(`I'm ${this.sname}, I'm ${this.sage}`)
}
}
②. 用new实例化类型创建;
var lilei = new Object();
lilei.sname = "lilei";
lilei.sage = 21;
lilei.intr = function(){
console.log(`I'm ${this.sname}, I'm ${this.sage}`)
};
③. 自定义构造函数反复创建多个相同结构的对象(解决了工厂模式创建出来的对象无法识别类型);
工厂模式创建函数:用函数封装特定的接口创建对象。(《Javascript高级程序设计》中提到的一种创建对象的方式)
function createPerson( name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
}
return o;
}
var persom1 = createPreson("Nicholas",29,"Software Engineer");
这种方式解决了创建多个相似对象的问题,但是却没有解决创建出来的对象无法识别类型,构造函数解决了这一问题。
Ⅰ. 什么是构造函数 ?
统一描述一类对象相同结构的函数。
Ⅱ. 如何使用自定义构造函数创建对象:
//1.定义构造函数
function Student(sname,sage){
this.sname=sname;
this.sage=sage;
this.intr=function(){
console.log(`I'm ${this.sname}, I'm ${this.sage}`)
}
}
//2.用new运算符调用构造函数反复反复创建多个相同结构对象
var lilei=new Student("Li Lei",21);
Ⅲ. new运算符的原理
i. 创建一个新的空对象
ii. 设置当前新对象继承构造函数的原型对象 (新对象.__proto__=构造函数.prototype) 。
iii. 用当前新的空对象去调用构造函数,将构造函数内的所有this都吸引到新对象上来:
this.xxx=形参; 在通过强行赋值的方式给新对象添加新的指定的属性和属性值。
iv. 返回新对象的地址给等于号前的变量
Ⅳ. 构造函数的问题:
i. 优点: 构造函数可以重用结构代码
ii. 缺点: 但是无法节约内存(一样的方法每次都要被反复创建)
解决:将来构造函数中就不应该包含方法定义,因为该方法会被反复创建,浪费内存!方法应该集中保存在原型对象上。(原型对象详情请见下边继承部分)
注意:
每次用new创建出来的新对象都有一个__proto__
属性指向了原型对象,原型对象上又有一个constructor
属性指回构造函数
三.继承
1.什么是继承:父对象中的成员,子对象无需重复创建,就可直接使用。
2.js中的继承都是通过原型对象来实现的。
(1). 什么是原型对象:集中存储同一类型的所有子对象共用的成员的父对象。
(2). 如何创建原型对象:不用自己创建,每创建一个构造函数,都自动附赠一个空的原型对象。
(3). 如何访问到一个构造函数的原型对象:
构造函数.prototype -> { 原型对象 }
实例对象.__proto__ -> { 原型对象 }
(4). 何时继承:
new原理的第二步就是自动设置子对象的__proto__指向构造函数的原型对象。
结果:
将来通过自对象访问成员时,先在子对象本地找,如果在子对象本地找到了要访问的成员,则优先用子对象自己的。如果子对象本地没有要访问的成员,程序会自动延
__proto__
向上去父元素中查找。如果在父元素中找到要访问的成员,也可以像子对象自己的成员一样访问!
(5). 什么样的成员需要放入原型对象中,如何放入
一般所有的方法都是共有的,都要放入原型对象中,所有子对象一起用。
通过强行赋值方式可将方法放入原型对象中。
构造函数.prototype.intr=function(){
... ...
}
//孩子只需要像调用自己的成员一样就可直接使用父对象中的成员
//子对象.intr();
如图,自定义构造函数创建对象,对象调用父对象上的方法一系列图示:
3.原型链
(1) 什么是原型链:多级父元素逐级继承形成的链式结构。
(2) 保存着一个对象的所有可用成员,当一个对象想用一个方法时,先在自己的属性中寻找,自己没有再逐级去他的上级父对象中寻找
(3) 内置对象/类型:ES标准中规定的,浏览器已经实现的类型或对象。包括11种:
String Number Boolean ——包装类型
Array Date RegExp Math
Error
Function Object
global Nodejs环境全局作用域对象,浏览器全局作用域对象window
(4) js族谱:
顶层构造函数是Object,她的原型对象是顶层父对象,内置的11中类型都是继承自他。自定义构造函数是Function类型的实例对象,所以它又继承于Function类型的原型对象,new调用该自定义构造函数生成的实例对象又继承自自定义构造函数的原型对象,如上图,那条橙色线串联起来的链式结构就是lilei对象的原型链。
四.多态
1.什么是多态
一个函数在不同情况下表现出的不同状态。
2.包括重写和重载
(1). 重写
重写是在子对象中定义和父对象中成员同名的自有成员,今后只要使用这个成员都优先使用自己的,不再使用父对象中的。
①. Object.prototype.toString()
toString
方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串。
var o1 = new Object();
o1.toString() // "[object Object]"
var o2 = {a:1};
o2.toString() // "[object Object]"
因为Object.prototype.toString()是顶层父对象上的方法,所以所有Object对象的实例,都可以使用toString方法。
然而["a","b","c"].toString()
的返回值是"a,b,c"
并没有返回类型字符串,这是因为数组家的.toString()
方法被重写了。
重写后的方法是将数组转化为字符串。除了数组家还有字符串、函数、Date 对象都分别部署了自己的toString
方法,这就是重写。
["a","b","c"].toString() // "a,b,c"
'123'.toString() // "123"
(function () {
return 123;
}).toString()
// "function () {
// return 123;
// }"
(new Date()).toString()
// "Fri Dec 25 2020 17:02:10 GMT+0800 (中国标准时间)"
②. Object.prototype.toString() 的应用:判断数据类型
因为我们不确定要判断的数据类型是什么类型,并不知道他类型中是否把.toString()
方法进行了重写,所以我们应该找的这个方法然后用call
调用,零时替换.toString()
里的this
(call知识点传送门)
Object.prototype.toString.call(value)
不同数据类型的Object.prototype.toString
方法返回值如下。
- 数值:返回
[object Number]
。 - 字符串:返回
[object String]
。 - 布尔值:返回
[object Boolean]
。 - undefined:返回
[object Undefined]
。 - null:返回
[object Null]
。 - 数组:返回
[object Array]
。 - arguments 对象:返回
[object Arguments]
。 - 函数:返回
[object Function]
。 - Error 对象:返回
[object Error]
。 - Date 对象:返回
[object Date]
。 - RegExp 对象:返回
[object RegExp]
。 - 其他对象:返回
[object Object]
。
我们可以通过不同数据类型返回值来判断数据类型。
(2). 重载
①. 什么是重载:相同函数名,不同形参列表的多个函数,在调用时,可根据传入的实参值不同,自动选择匹配的函数调用。
②. 如何实现:js语法默认不支持重载形式,是因为js不像java一样允许多个同名函数同时存在,js中如果同时存在多个同名函数,结果只有最后一个函数会保存下载,覆盖之前所有同名函数。应该借助arguments对象实现重载。
具体操作:
i. 可判断arguments中包含的实参值的个数,来决定执行不同的操作
ii. 可获得arguments[i]的内容,根据不同的实参值内容,来决定执行不同的操作
iii. 调用时,只要记一个函数名,传入任意个实参值都行
什么是arguments:
每个函数中都自带的,不用自己创建就可直接使用
自动收集所有传入函数的实参值的
类数组对象,类型不同,不能使用数组家的函数
arguments也有下标,也有 .length属性,还可以用 for遍历
③. 原生函数中重载的例子: arr.splice()
(1). 删除元素: arr.splice(i, 几个)
(2). 插入新元素: arr.splice(i, 0, 新值1, 新值2,…)
(3). 替换现有元素: arr.splice(i, 几个, 新值1, 新值2,…)