- 闭包
*****面向对象***** - 什么是面向对象
- 封装
- 继承
一. 闭包
- 问题: 全局变量和局部变量都有不可兼得的优缺点
(1). 全局变量:
a. 缺点: 极易被篡改!全局污染
b. 优点: 可反复使用
(2). 局部变量:
a. 优点: 不会被外部篡改
b. 缺点: 不可反复使用! - 解决: 今后,只要希望给一个函数保存一个专属的变量,既可重用,又不会被随意篡改,都要用闭包解决!
- 如何: 3步:
(1). 用外层函数,包裹要保护的变量和内层函数——相当于给内层函数找一妈
(2). 外层函数要把内层函数返回到外层函数外部
(3). 想使用内层函数的人,必须调用外层函数,用变量保存住返回出来的内层函数对象,才能反复调用。 - 示例: 定义函数帮小孩儿保管压岁钱
1_closure.html
<script>
//定义一个函数,帮小孩儿管理压岁钱
// // var total=1000;//全局,极易被篡改
// function pay(money){
// // var total=1000;//局部,不可重用
// //每花一笔钱,都从总钱数里扣掉这笔钱
// total-=money;
// console.log(`花了${money},还剩${total}`);
// }
//1. 用外层函数包裹要保护的变量和内层函数
//为内层函数找一妈
function mother(){
var total=1000;//要保护的变量
//问题: pay成了局部函数,外部无法直接使用
//2.外层函数将内层函数返回到外部去!
return function(money){
total-=money;
console.log(`花了${money},还剩${total}`);
}
}
//3. 想使用内层函数的人,必须调用外层函数,用变量保存住返回出来的内层函数对象,才能反复调用。
var pay=mother();
//pay: function pay(money){//函数对象
// total-=money;
// console.log(`花了${money},还剩${total}`);
// }
//问题: mother()的局部变量1000,在mother调用后,应该释放了?下边pay()为什么可以持续-100?怎么重用的呢?
pay(100);//闭包中的total=900
total=0;//别人的代码,强行在全局创建total,但是不会影响闭包中的total。
pay(100);//闭包中的total=800
pay(100);//闭包中的total=700
</script>
</body>
</html>
运行结果:
花了100,还剩900
花了100,还剩800
花了100,还剩700
5. 原理:




6. 一句话概括闭包是如何形成的 / 什么是闭包?
答: 外层函数调用后,外层函数的函数作用域对象,被内层函数的作用域链引用着,无法释放!形成了闭包对象!
- 闭包的缺点: 比一般的函数多占用一块存储空间——外层函数的函数作用域对象。
- 解决: 只要一个闭包结构不再被使用时,都要将保存内层函数的外部变量赋值为null,比如pay=null;
*****面向对象*****
一. 什么是面向对象:
- 问题: 程序中通常都会管理大量数据!如果毫无组织的存放,极容易出错!极其不便于使用
- 解决: 今后,几乎所有程序,都用面向对象思想保存数据。
- 什么是: 将现实中一个事物的属性和功能集中保存在程序中一个对象结构中,再起一个名字。
- 为什么: 极其便于大量数据的管理和维护
- 如何: 3步/3大特点: 封装,继承,多态
二. 封装:
- 什么是: 创建一个对象结构,保存现实中一个事物的属性和功能。
- 为什么: 极其便于大量数据的管理和维护
- 何时: 今后,只要使用面向对象思想开发,都要先创建对象。
- 如何: 3种:
(1). 用{}:
a. var 对象名={
属性名: 属性值,
... : ... ,
方法名: function(){
... ...
}
}
b. 说明:
1). 事物的属性值,就会成为对象中的属性值
2). 事物的功能,就会成为对象中的方法。
科普: 方法和函数: - 相同点: 本质都是function(){ ... }
- 不同点: 保存的位置不同:
1). 不属于任何对象的独立在全局的function,称为函数
2). 保存在对象中的function,称为方法
c. 如何访问对象中的成员: (成员=属性+方法)
1). 想访问对象中的属性:先找到对象,再进入对象,找属性名
语法: 对象名.属性名
2). 想访问对象中的方法: 先找到对象,再进入对象,找方法名
语法: 对象名.方法名() //调用
d. 示例: 定义对象lilei,描述lilei的属性和方法:
2_{}.html
<script>
//定义一个学生对象,保存lilei同学的个人信息
//姓名: Li Lei, 年龄: 11,
//lilei会自我介绍
var lilei={
sname:"Li Lei",
sage:11,
intrSelf:function(){
console.log(`I'm ${this.sname}, I'm ${this.sage}`)
}
};
//想输出lilei的年龄
console.log(lilei.sage);
//请lilei做自我介绍
lilei.intrSelf();
//过了一年, 李磊长了一岁
lilei.sage++;
//再输出lilei的年龄
console.log(lilei.sage);
//再请lilei做自我介绍
lilei.intrSelf();
//输出lilei的存储结构:
console.log(lilei);
//不用写,只是一个教训。
//把lilei对象中的intrSelf函数赋值给全局变量a
//a就得到了函数
var a=lilei.intrSelf;
//调用a,相当于调用lilei中的intrSelf
a();//I'm undefined, I'm undefined;
//前边没有点,所以this默认->window。
//this.sname=>window.sname=>undefined。
</script>
</body>
</html>
运行结果:
I'm Li Lei, I'm 11
12
I'm Li Lei, I'm 12
{sname: "Li Lei", sage: 12, intrSelf: ƒ}
I'm undefined, I'm undefined
e. this:
1). 问题: 明明同一个对象中的方法和属性,方法中想用自己对象中的属性,竟然报错:xxx属性名 未定义
2). 原因: 对象不是作用域,对象无权进入方法的作用域链中。所以,在方法内,不加任何前缀,就想像普通变量一样直接使用对象中的属性,根本找不到的!

3). 解决: 2种:
i. 不好: 在方法中用"对象名.属性名",先找到对象,再找到对象内的属性。
缺点: 对象名其实就是一个普通的变量,所以有对象名很可能被改变。一旦对象名被修改,而忘记修改方法中写死的对象名,立刻就会出现不一致,程序立刻就错误!
ii. 好: 用关键字this代替方法中写死的对象名: "this.属性名"
什么是this:
每个函数中都自带的——不用创建就可直接使用。
专门在调用函数时——只有调用函数时讨论this才有意义。
自动获得当前正在调用函数的.前的对象——内容
的关键字。
总结: 今后只要对象自己的方法中,想使用对象自己的属性,都必须加this!

4). 强调: 判断this指向,一定不要看定义在哪里,必须看在哪里,如何被调用的,前边有没有点。

(2). 用new:
a. 如何: 2步:
1). 先创建一个空对象等待:
var 对象名=new Object();
2). 再强行向对象中添加新属性:
对象名.属性名=属性值;
对象名.方法名=function(){
... this.属性名 ...
}
b. 揭示了本质: 其实js内存中,一切都是关联数组!
c. 对象与关联数组:
1). 都是名值对儿的集合
2). 访问对象成员:
i. 标准: 对象名["属性名"]
ii. 简写: 对象名.属性名
iii. 特例: 如果属性名不是写死的,来自于其他变量,则既不能用点,又不能加"",只能: 对象名[变量名]
3). 都可随时向不存在的位置添加新属性
所以,将来想向对象中添加一个新属性,没有专门的函数或优雅的办法。只有唯一一种野蛮的办法!强行赋值!
对象名.新属性=新值;
4). 都可随时访问对象中不存在下标位置或属性,都不报错,都返回undefined。
所以,将来想判断一个数组或对象中是否包含某个成员,都可以用:
if(对象.属性名!==undefined){ ... 包含该成员 ... }
5). 都可用for in循环遍历
d. 示例: 定义一个clone函数,可以克隆任何一个对象
<script>
var lilei={sname:"Li Lei",sage:11};
//错误: 只是将原对象地址复制一份给lilei2
//并没有创建新对象
//结果: 两个变量用相同的地址值,指向同一个变量
// var lilei2=lilei;
//正确: (暂时用中文起变量名)
function clone(原对象){
//1. 先创建新的空对象
var 新对象={};//new Object();新对象,新地址
//2. 变量原对象中每个属性
//in,自动依次取出原对象中每个属性名,保存到in前的自定义变量中
// 自定义变量
for(var 原对象中的属性名 in 原对象){
//3. 将原对象中当前属性名和属性值,强行添加到新对象中
//3.1 先取出原对象中的属性值
// 自定义变量,变量都不加""
var 原属性值=原对象[原对象中的属性名]
//3.2 强行给新对象添加相同属性名和属性值的新属性
新对象[原对象中的属性名]=原属性值;
}
//4. 返回新对象
return 新对象;
}
//英文版:
// function clone(oldObj){
// var newObj={};
// for(var key in oldObj){
// var value=oldObj[key];
// newObj[key]=value;
// }
// return newObj;
// }
//想把lilei克隆出一个新对象lilei2
// ← ↓
var lilei2=clone(lilei);
console.log(lilei2);
console.log(lilei==lilei2);//false
// 地址 地址 不相同 2个对象
</script>
</body>
</html>
运行结果:
{sname: "Li Lei", sage: 11}
false
(3). 用构造函数:
a. 问题: {}一次只能创建一个对象。如果想创建多个相同结构,不同属性值的对象,用{},代码就会很繁琐,且重复,极其不便于今后的维护和修改。
b. 解决: 今后,只要想创建多个相同结构,不同属性值的对象时,都用构造函数来创建。
c. 什么是: 专门描述同一类型所有对象的统一结构的函数。
d. 如何: 2步:
1). 定义构造函数:
function 类型名(形参1, 形参2,...){
this.属性名1=形参1;
this.属性名2=形参2;
this.方法名=function(){
... this.属性名 ...
}
}
强调:
i. 将要添加到新对象中的属性,必须用this.前缀才行!不加this.前缀,则该属性无法加入将来的新对象中。
ii. 属性值不能写死,应该将来在创建某一个新对象时,动态传入属性值。传入什么,对象中就保存什么。所以,构造函数中应该用形参变量,暂时为将来的属性值占位。
2). 调用构造函数:
i. var 对象名=new 类型名(属性值1, 属性值2, ... )
ii. 强调: 只要调用构造函数,必须用new。
e. 优点: 重用对象的结果。
f. 示例: 定义学生类型,反复创建两个学生对象
5_constructor.html
<script>
//想定义一个学生类型,描述所有学生统一结构
// 学生类型
function Student(sname, sage){
this.sname=sname;
this.sage=sage;
this.intrSelf=function(){
console.log(`I'm ${this.sname}, I'm ${this.sage}`);
}
}
//创建lilei对象,应该传入lilei的属性值
var lilei=new Student("Li Lei",11);
//创建hmm对象,应该传入hmm的属性值
var hmm=new Student("Han Meimei",12);
console.log(lilei);
console.log(lilei.sage);
lilei.intrSelf();
console.log(hmm);
console.log(hmm.sage);
hmm.intrSelf();
</script>
</body>
</html>
运行结果:
Student {sname: "Li Lei", sage: 11, intrSelf: ƒ}
11
I'm Li Lei, I'm 11
Student {sname: "Han Meimei", sage: 12, intrSelf: ƒ}
12
I'm Han Meimei, I'm 12
g. 原理: new做了4件事儿:
1). 新建一个空对象等待
2). (未完待续...)
3). 自动调动构造函数,为新对象添加规定的新属性:
i. new关键字可以先将构造函数中的this,强行指向正在创建的新对象。
ii. 构造函数中的this.属性名=属性值,都变成新对象.属性名=属性值,都变成给新对象强行赋值新属性
4). 返回新创建的对象的地址。

简写:
- [] 是new Array()
- function 是new Function()
- // 是new RegExp()
- {} 是new Object()
{}和function(){}
总结:
this共有几种情况:3种
一定不要看定义在哪儿,只看在哪里如何调用
- obj.fun() this->.前的obj对象
- 普通函数调用fun(),既没有点,也没有new,this->window(默认)
- new Fun() this->new正在创建的新对象
(5). 闭包:
a. 只要希望给一个函数保护一个可反复使用的专属变量,又防止这个变量被外界篡改时,都用闭包。
b. 闭包三步:
1). 用外层函数妈妈包裹要保护的变量和内层函数
2). 外层函数妈妈用return把内层函数孩子返回到外部
3). 外部想使用内层函数的人,必须调用外层函数,才能获得return出来的内层函数对象。并将内层函数保存在一个变量中反复使用。
c. 闭包形成的原因: 外层函数调用后,外层函数的作用域对象被内层函数引用着无法释放,形成了闭包对象
d. 闭包的缺点: 闭包比一般的函数占用多一块内存——外层函数的函数作用域对象。
所以,用完闭包后,应该尽快释放:
保存内层函数的变量=null
4. 面向对象: 封装 继承 多态
自己跟着视频,一步一步画图,自己标顺序,知识才能变成自己的
(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.
iii. 调用构造函数,传入实参,并自动替换构造函数中的this为new正在创建的新对象。构造函数中,通过强行赋值的方式为新对象添加规定的属性,并保存属性值。
iv. 返回新对象的地址,保存到=左边的变量中。
3). 优点: 重用对象结构代码
4). 缺点: 如果构造函数中包含方法定义,则每次创建新对象都会重复创建相同方法的副本。——浪费内存!