目录
🆙【前文回顾】👉 js之闭包对象_04
一. 什么是面向对象
1. 存在问题
将来程序中可能保存很多数据,而数据之间可能出现命名冲突。
2. 如何解决
使用面向对象的思想来管理数据和功能
3. 什么是面向对象
将现实中一个事物的属性和功能集中保存在对象结构中,再按需使用对象中的属性和功能。
4. 为什么使用面向对象
极其便于大量数据的管理和维护。
5. 何时使用面向对象
今后,几乎所有程序都是用面向对象方式开发的
6. 如何使用面向对象
如何实现面向对象、怎么做才是面向对象?3步/三大特点: 封装,继承,多态
二. 封装
1. 什么是封装
将一个事物的属性和功能集中保存在一个对象中
2. 为什么封装
极其便于大量数据的管理和维护。
3. 何时使用封装
今后只要使用面向对象方式编程,都要先创建各种各样的对象,集中保存每个事物的属性和功能。
4. 如何使用封装
如何使用封装(如何创建对象)3种方式
(1). 用{}:
a. var 对象名={
属性名: 属性值,
... : ... ,
方法名: function(){
... this.属性名 ...
}
}
b. 现实中一个事物的属性,就会成为对象中的一个属性值
现实中一个事物的功能,就会成为对象中的一个方法名
扩展:方法 🆚 函数
1. 通常认为,不属于任何对象的function,称为函数
2. 通常,将保存在对象中的function,称为方法
本质上他们都是function——只是存的位置不同,仅此而已,本质上没有任何区别
c. 如何访问对象中的成员(属性和方法统称成员):
1). 访问对象中的属性: 对象名.属性名
.表示进入对象中,获得某个属性的意思
2). 调用对象中的方法: 对象名.方法名()
d. 问题: 对象内的方法中,使用当前对象自己的属性,竟然报错: 属性名 is not defined
e. 原因: 虽然方法包含在对象内部,但是因为对象的{}不是一级作用域,所以在方法的作用域链中是不包含对象的内容的。是包含两个格子: 自己和window。所以,任何函数中不加.的变量,都只能在自己或全局window中查找。无权擅自进入对象中查找!所以,所有对象的方法,都无权直接访问对象属性值。
分析:对象的大括号{ }不是作用域,只是创建对象的语法简写符号而已,它没有任何作用域的概念。所以对象中的方法的作用域由内向外只有1级作用域(方法自己的作用域)和全局作用域,而对象根本就不是作用域。所以对象中的方法的作用域链只有2个格子:一个是给自己预留的,一个是全局window。所以对象的方法要想使用变量的话,只能用自己的局部变量,自己没有的话,只能从全局找,方法是无法自动进入对象中查找的!
f. 不好的解决: 改成"对象名.属性名"
g. 问题: 紧耦合。万一对象名被修改,则总要记得同时必须修改对象方法内写死的对象名!万一忘了修改对象方法内写死的对象名,程序就会立刻出错!
h. 好的解决: 今后,只要对象自己的方法,想访问对象自己的属性,都必须写成"this.属性名"
1). 什么是this: (this只用在函数内)
i. 每个函数内直接可用的,
ii. 专门引用正在调用当前函数的.前的对象
iii. 的关键词
2). 原理: 当使用"对象.方法名()"语法调用对象中的方法时,方法中的关键字this会自动获得.前的对象。就不用写死对象名!——松耦合!
i. 示例: 使用{}创建一个对象保存学生lilei的属性和功能
2_{}.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//想创建一个对象,保存一个学生的基本信息和功能
//比如: lilei 11岁 会做自我介绍
var lilei={
sname:"Li Lei",
sage:11,
intr:function(){
console.log(`I'm ${this.sname}, I'm ${this.sage}`);
}
}
console.log(lilei);
//想输出李磊的年龄
console.log(`李磊今年${lilei.sage}岁`);//11
//想请李磊做英文自我介绍
lilei.intr();
//过了一年,李磊长了一岁
lilei.sage++;
//想再输出李磊的年龄
console.log(`李磊今年${lilei.sage}岁`);//12
//想再请李磊做英文自我介绍
lilei.intr();
</script>
</body>
</html>
运行结果:
{sname: "Li Lei", sage: 11, intr: ƒ}
intr: ƒ ()
sage: 12
sname: "Li Lei"
__proto__: Object李磊今年11岁
I'm Li Lei, I'm 11
李磊今年12岁
I'm Li Lei, I'm 12
(2). 用new Object():
a. 2步:
1). 先创建一个空对象
var 对象名=new Object()
2). 向空对象中添加新属性和新方法
对象名.新属性=属性值;
对象名.新方法=function(){
... this.属性名 ...
}
b. 揭示了对象底层的原理: js中对象底层其实也是一个关联数组。——整个js内存中,一切都是关联数组!
1). 都是保存的"名值对儿"的集合
2). 其实无论访问数组的元素,还是访问对象的成员,都可以用["下标名"]方式访问。只不过,如果属性名是自定义字符串,就可简写为".属性名"方式
如果我们写".属性名"会被自动翻译回["属性名"]
总结: 今后,如果要访问的属性名是写死的固定不变的,既可以用[""],又可以用.
但是, 今后,要访问的属性名来自于变量或动态拼接生成,不是固定的,只能用[变量或表达式],还不能加""
3). 尝试访问对象或数组中不存在的成员,都不会报错!而是返回undefined。
固定套路: 如何判断一个对象或数组中是否包含某个成员:
对象.属性名!==undefined 说明包含该成员
4). 尝试给对象或数组中不存在的位置强行赋值,也不会报错!而是自动创建该属性:
固定套路: 如何给一个对象添加一个新属性: 只有一种办法——强行赋值:
对象名.新属性=新值
5). 都可用for in遍历:
for(var 变量名 in 对象或数组){
//变量名会依次接住对象或数组中每个下标名
//对象或数组中有几个成员,就执行几次循环
}
c. 示例:使用new Object()创建学生对象lilei
3_new_Object.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//想创建一个对象,保存一个学生的基本信息和功能
//比如: lilei 11岁 会做自我介绍
var lilei=new Object();
lilei["sname"]="Li Lei";
lilei.sage=11;
lilei["intr"]=function(){
console.log(`I'm ${this.sname}, I'm ${this.sage}`)
}
console.log(lilei);
//想输出李磊的年龄
console.log(`李磊今年${lilei.sage}岁`);//11
//想请李磊做英文自我介绍
lilei.intr();
//想访问lilei的班级
console.log(lilei.className);
//想给lilei添加性别属性:
lilei.sex=1;
console.log(lilei);
//遍历出lilei中最后的所有成员:
//复习查看JS基础————for in循环
// 变量
for(var key in lilei){
//依次sname
// sage
// intr
// sex
console.log(`${key} : ${lilei[key]}`)
// 变量绝对不能放在""
//console.log(`${key} : ${lilei["key"]}`)
// 自动翻译为↑
//console.log(`${key} : ${lilei.key}`)
}
</script>
</body>
</html>
运行结果:
{sname: "Li Lei", sage: 11, intr: ƒ}
李磊今年11岁
I'm Li Lei, I'm 11
undefined
{sname: "Li Lei", sage: 11, sex: 1, intr: ƒ}
sname : Li Lei
sage : 11
intr : function(){
console.log(`I'm ${this.sname}, I'm ${this.sage}`)
}
sex : 1
d. 示例: 克隆一个对象:
4_clone.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var lilei={
sname:"Li Lei",
sage:11
}
//定义一个克隆函数,可以克隆任意一个对象
// 传入旧对象
// ↓
function clone(oldObj){
//1. 创建一个新的空对象等着
var newObj={};
//2. 遍历旧对象中每个属性
for(var key in oldObj){
// 旧属性名
//3. 每遍历一个属性,就给新对象添加同名属性,属性值也要一样
//3.1取出旧对象中的当前属性值
var oldVal=oldObj[key];
//3.2给新对象强行添加同名属性和相同的属性值
newObj[key]=oldVal;
}
//4. 返回新创建的对象
return newObj;
}
//不算克隆!
//var lilei2=lilei;
//调用克隆函数,传入要克隆的对象,接住返回的新对象
var lilei2=clone(lilei);
console.log(lilei2);
//如果克隆成功,则两个对象的地址应该不一样才行!
console.log(lilei==lilei2);//false
//js中: ==两边都是引用类型的对象时,不再进行任何隐式转换,而是比较两个对象的地址值是否相等!
</script>
</body>
</html>
运行结果:
{sname: "Li Lei", sage: 11}
sage: 11
sname: "Li Lei"
__proto__: ObjectFalse
🙋♂️ 补:引用类型的对象的比较
在js中:==两边都是引用类型的对象时,不再进行任何隐式转换,而是比较两个对象的地址值是否相等!
(3). 用构造函数反复创建多个相同结构的对象
a. 问题: 如果反复创建多个相同结构的对象时,用{}复制粘贴再修改的方式,极其不便于今后的维护和修改!
b. 解决: 今后只要反复创建多个相同结构的对象时,可以用构造函数的方式创建
c. 什么是构造函数(constructor): 专门描述同一类型的所有对象的统一结构的特殊函数。
d. 如何: 2步:
1). 先定义构造函数
function 类型名(形参变量列表){
this.属性名=属性值;
this.方法名=function(){
... this.属性名 ...
}
}
2). 调用构造函数创建对象
var 对象名=new 类型名(属性值, ... ) // 构造函数必须用new调用
🙋♂️ 分析:调用构造函数创建对象的执行过程
属性值会传给形参变量,然后形参变量传入函数给到属性值,属性值赋值给 属性名,然后属性就会进入对象里面
e. 示例: 使用构造函数反复创建多个相同结构的对象:
5_constructor.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//反复创建多个相同结构的"学生"对象
//所以定义描述学生类型的构造函数
// 类型名
// ↓
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",11);
var hmm=new Student("Han Meimei",12);
console.log(lilei);
console.log(hmm);
lilei.intr();
hmm.intr();
</script>
</body>
</html>
运行结果:
Student {sname: "Li Lei", sage: 11, intr: ƒ}
intr: ƒ ()
sage: 11
sname: "Li Lei"
__proto__: ObjectStudent {sname: "Han Meimei", sage: 12, intr: ƒ}
intr: ƒ ()
sage: 12
sname: "Han Meimei"
__proto__: ObjectI'm Li Lei, I'm 11
I'm Han Meimei, I'm 12
f. 原理: new共做了4件事
1). 创建一个新的空对象等着
2). 让新对象继承(_ _proto_ _)构造函数的原型对象(请关注后续继承篇)
3). 调用构造函数为新的空对象中添加新属性:
🙋♂️调用构造函数,传入实参,并自动替换构造函数中的this为new正在创建的新对 象。构造函数中,通过强行赋值的方式为新对象添加规定的属性,并保存属性值。
4). 返回new新创建的对象的地址保存到=左边的变量中
💥 扩展:this判断—8种指向
this 8种指向: 判断this,一定不要看定义在哪儿!只看调用时!
1. obj.fun() this->obj
2. fun() 或 (function(){ ... })() 或 多数回调函数 或 定时器函数 this->window
3. new Fun() this->new正在创建的新对象
4. 类型名.prototype.共有方法=function(){ ... } this->将来谁调用指谁,同第一种情况
5. DOM或jq中事件处理函数中的this->当前正在触发事件的DOM元素对象
如果需要使用简化版函数,必须$(this)
6. 箭头函数中的this->箭头函数外部作用域中的this
7. jQuery.fn.自定义函数=function(){ ... } this->将来调用这个自定义函数的.前的jQuery子对象,不用再$(this)
8. new Vue()中methods中的函数中的this->当前new Vue()对象
💠 总结:知识点提炼
1. 只要验证字符串格式或查找、屏蔽敏感词时都要用正则
(1). 最简单的正则: 一个敏感词的原文
(2). 某一位字符上可能有多种备选字时用: [备选字列表]
(3). 如果[]中部分字符是连续的,可用: [x-x]
a. 一位小写字母: [a-z]
b. 一位大写字母: [A-Z]
c. 一位字母(大小写都行): [A-Za-z]
d. 一位字母或数字都行: [0-9A-Za-z]
e. 一位汉字: [\u4e00-\u9fa5]
(4). 预定义字符集:
a. \d 一位数字
b. \w 一位数字、字母或_
c. \s 空格、tab、换行等空字符
d. . 任意字符
(5). 如果规定一个字符集或子规则反复出现的次数时就用量词:
a. 有明确数量边界的量词:
1). {n} =n 必须n个,不能多也不能少
2). {n,m} n个<= <=m个
3). {n,} n个<= 多了不限
b. 没有明确数量边界的量词:
1). * 0个<= 可有可无,多了不限
2). ? 0个或1个 可有可无,最多一个
3). + 1个<= 至少一个,多个不限
(6). 两个规则中选其一匹配即可: 规则1|规则2
(7).希望将多个子规则分为一组先联合匹配,再和分组外的其他规则联合匹配:
(多个子规则)
(8). 匹配特殊位置: 3个
a. 字符串的开头位置: ^
b. 字符串的结尾位置: $
c. 英文句子中的单词的左右边界: \b
2. String家提供的正则相关函数: 3件事
(1). 查找敏感词: 4种情况
a. 查找一个固定的敏感词出现的位置:
var i=str.indexOf("敏感词")
// 如果找不到,返回-1
b. 用正则查找多种敏感词出现的位置:
var i=str.search(/正则/i)
// 如果找不到,返回-1
c. 查找敏感词的内容:
1). 查找第一个敏感词的内容和位置:
var arr=str.match(/正则/i)
// arr: [ 0:"敏感词内容", index:敏感词位置 ]
// 如果找不到返回null
2). 查找所有敏感词的内容,不关心位置:
var arr=str.match(/正则/ig)
// arr: [ 敏感词1, 敏感词2, ... ]
// 如果找不到返回null
d. 查找每个敏感词的内容和位置: reg.exec
补: js中所有数组底层本质都是关联数组(下标都为字符串) 1. 访问数组中元素值的标注写法: arr["下标"] 2. 简写: a. 如果下标为自定义字符串名称,可简写为: arr.自定义名称的下标 b. 如果下标为数字内容的字符串,可简写为: arr[数字下标] |
总结: 查找方法的返回值规律 1. 如果原函数返回的是下标位置i,如果找不到,都返回-1 2. 如果原函数返回的是一个数组arr或一个对象obj,如果找不到,都返回null 3. 如果原函数返回类数组对象,如果找不到返回空类数组对象: { length:0 } |
(2). 替换敏感词: 2种
a. 简单替换:
变量=str.replace(/正则/ig, "新值")
b. 高级替换:
变量=str.replace(/正则/ig, function(形参){
return 根据本次敏感词动态生成一个新值
})
c. 删除敏感词:
变量=str.replace(/正则/ig, "")
(3). 切割字符串:
a. 简单切割:
var arr=str.split("切割符")
b. 复杂切割:
var arr=str.split(/正则/i)
c. 打散字符串为字符数组:
var arr=str.split("")
3. RegExp对象:
(1). 创建正则表达式对象:
a. 如果正则是固定的:
var reg=/正则/ig
b. 如果正则需要动态生成:
var reg=new RegExp("正则",ig)
(2). 验证字符串格式:
var bool=reg.test(str)
reg必须同时前加^后加$
(3). 既查找每个关键词的内容又查找每个关键词的位置: (待续)
do{
var arr=reg.exec(str);
if(arr!=null){
获得本次找到的敏感词的内容(arr[0])和位置(arr.index)
}
}while(arr!=null);
4. 函数:
(1). 创建函数三种方式:
a. function 函数名(形参列表){ 函数体; return 返回值 } //会被声明提前,不好
b. var 函数名=function(形参列表){ 函数体; return 返回值 }//不会被声明提前,首选
c. var 函数名=new Function("形参1", "形参2", ... , "函数体; return 返回值")
函数本质: 1). 函数也是一个对象,对象中保存着函数的函数体代码 2). 函数名只是一个普通的变量,函数名通过函数对象地址,引用着函数对象 3). function在底层等效于new Function() function 函数名(){ ... }和var 函数名=function(){}在底层都会被翻译为 var 函数名=new Function(...) 只不过function 函数名(){}是先提前,再翻译 而var 函数名=function(){}是不提前,原地翻译 |
(2). 重载: 今后,一件事,根据传入不同的参数值,动态执行不同的逻辑时,都用重载
function 一个函数名(不写形参变量){
//arguments对象自动接住所有实参值
if(arguments.length==0){
执行一种逻辑
}else if(arguments.length==1){
执行另一种逻辑
}else{
执行其它逻辑
}
}
其中arguments是类数组对象: 和数组相比:
a. 相同点: 也有下标,length属性,也可for循环遍历
b. 不同点: 不是数组类型,无法使用数组家的函数
(3). 匿名函数:
a. 所有回调函数优先使用匿名函数——用完释放,节约内存
b. 所有js代码都应该保存在匿名函数自调中,禁止使用全局变量,避免全局污染!
(function(){
要执行的js代码
})();
结果: 匿名函数内的都是局部变量,不会产生全局变量。
局部变量随匿名函数一起释放。不会污染全局。
(4). 作用域和作用域链: (跟着视频亲自画图!!!)
a. 作用域:
1). 全局作用域:window,保存全局变量
优: 可重用,缺: 随处可用, 极易被污染
2). 函数作用域: 保存局部变量
局部变量包括2中: 函数中var出的变量和形参变量
优: 仅函数内可用,不会被污染,缺: 不可重用
3). 函数作用域对象原理:
i. 每个函数定义时都自带好友列表,好友列表里2个格子,一个是空,一个引用window
ii. 调用函数时临时创建函数作用域对象保存函数局部变量。并将函数作用域对象的地址保存到函数好友列表中离自己近的格子里。
iii. 函数执行过程中按就近原则先在自己的函数作用域对象中找局部变量使用。如果找不到,才被迫去全局window中找变量使用.
iv. 函数调用后,好友列表中离自己近的格子清空,导致函数作用域对象以及内部的局部变量被释放!——所以局部变量不可重用!
b. 作用域链: 保存一个函数所有可用的作用域对象的链式结构(好友列表)学名就叫作用域链。
1). 作用域链保存着一个函数可用的所有变量
2). 作用域链控制着变量的使用顺序。先局部后全局。
5. 闭包:
a. 只要希望给一个函数保护一个可反复使用的专属变量,又防止这个变量被外界篡改时,都用闭包。
b. 闭包三步:
1). 用外层函数妈妈包裹要保护的变量和内层函数
2). 外层函数妈妈用return把内层函数孩子返回到外部
3). 外部想使用内层函数的人,必须调用外层函数,才能获得return出来的内层函数对象。并将内层函数保存在一个变量中反复使用。
c. 闭包形成的原因: 外层函数调用后,外层函数的作用域对象被内层函数引用着无法释放,形成了闭包对象
d. 闭包的缺点: 闭包比一般的函数占用多一块内存——外层函数的函数作用域对象。所以,用完闭包后,应该尽快释放:
保存内层函数的变量=null
6. 面向对象: 封装 继承 多态 ⏬
(1). 封装: 3种: ⏬
a. 用{}创建一个对象:
var 对象名={
属性名:属性值,
... : ... ,
方法名: function(){
... this.属性名 ...
}
}
b. 用new Object():
1). 2步:
i. var 对象名=new Object()
ii. 对象名.属性名=属性值;
对象名.方法名=function(){ ... }
2). 对象底层也是关联数组:
i. 都是名值对儿的集合
ii. 都可用[""]和.方式访问成员。
如果属性名来自于变量,就只能用[],不要加""
iii. 访问不存在的属性,都不报错,返回undefined
判断是否包含某个属性:
对象.属性名!==undefined
iv. 强行给不存在的属性赋值,都不报错,而是自动添加该属性
给对象添加新属性,唯一办法,强行赋值:
对象名.新属性名=新值
v. 都可用for in遍历
c. 只要反复创建多个相同结构的对象都用构造函数:
1). 2步:
i. 定义构造函数:
function 类型名(形参1,形参2, ...){
this.属性名1=形参1;
this.属性名2=形参2;
//构造函数中不要再包含方法定义定义!
}
ii. 用new 调用构造函数:
var 对象名=new 类型名(属性值1, 属性值2,...)
2). new做了4件事:
i. 创建一个新的空对象
ii. 让新对象继承(_ _proto_ _)构造函数的原型对象
iii. 调用构造函数,传入实参,并自动替换构造函数中的this为new正在创建的新对象。构造函数中,通过强行赋值的方式为新对象添加规定的属性,并保存属性值。
iv. 返回新对象的地址,保存到=左边的变量中。
3). 优点: 重用对象结构代码
4). 缺点: 如果构造函数中包含方法定义,则每次创建新对象都会重复创建相同方法的副本。 ——浪费内存!
🆕【后文传送门】👉 js面向对象三大特性之继承_06
如果这篇【文章】有帮助到你,希望可以给【青春木鱼】点个赞👍,创作不易,相比官方的陈述,我更喜欢用【通俗易懂】的文笔去讲解每一个知识点,如果有对【前端技术】感兴趣的小可爱,也欢迎关注❤️❤️❤️【青春木鱼】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💕💕!