1、两大编程思想:面向过程和面向对象
装修房屋的流程:
1.找张三设计,你要给张三提供房屋结构图纸
2.找李四安装水电,你要给李四买好水管电线
3.找王五订制家具,你要买好木板油漆
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的一次调用的过程
装修房屋的流程:
1.提前把所有的相关资料交给装修公司
2.装修公进行设计
3.装修公司安装水电
4.装修公司订制家具
在面向对象的程序开发中,每个对象都是功能的核心,功能所需的数据和完成功能的函数高度内聚在一起
面向对象编程具有灵活、代码可重用、容易维护和开发的优点,更适合多人合作的大型软件项目
面向对象更符合我们对现实世界的认知
2、类和对象
在第二阶段,我们学习过对象的使用,例如new Object(),例如json的{}都是对象,但是在ES6中对面向对象进行了补充,提供了新的特性。但是他们之间从本质上来讲没有什么区别。
在ES6中新增了类的概念,可以使用class关键字声明一个类,之后以这个类来实例化。
定义类的语法
class 类名{ }
'运行
类名命令规范
1.名称只能由字母、数字、下划线、$符号组成
2.不能以数字开头
3.名称不能使用关键字。
4.不允许出现中文及拼音命名
5.类名首字母大写,如果多个单词,每个单词首字母大写
说明:类是以class关键字声明的,后面跟着类名和花括号中的类体
创建对象的语法
const 对象名=new 类名();
使用new关键字创建类的对象
类是对象的抽象化、对象是类的具象化(实例化)
案例:创建一个人类,并实例化
class Person{ } let p=new Person(); console.log(p);
'运行
课堂练习:新建一个Person类,然后创建Person类的对象并输出
属性是定义在类中的变量,可以用于保存数据,属性可以是任意类型的。
在类中定义属性的语法:
class 类名{ constructor(){ //构造方法 this.属性名=值; this.属性名=值; } }
'运行
例如:
class Person{ constructor() { //成员属性 this.name="张三"; this.age=16; } } let person=new Person(); console.log(person.name)
'运行
和二阶段一样,对象除了拥有预设的属性之外,同样可以使用.运算符和[属性名]为对象添加新属性或是取值赋值。
class Person{ constructor() { //成员属性 this.name="张三"; this.age=16; } } let person=new Person(); console.log(person.name) //新增属性 person.age=18; person['sex']="男"; console.log(person.age); console.log(person.sex);
'运行
课堂练习:
使用三种语法完成学生对象的创建,学生包含学号、专业、姓名、班级四项属性。
函数是对象所具备的功能,可以封装代码体,以便于重复调用。
在类中定义函数的语法:
class 类名{ constructor(){ //成员方法 this.函数名=function(参数列表){ 代码体 } } }
'运行
例如:
class Person{ constructor(){ this.show=function(){ console.log('我是吴彦祖'); } } } let person=new Person(); person.show();
'运行
在类定义的函数中可以直接使用this关键字使用类中的属性
class Person{ constructor() { this.name="张三"; this.age=16; this.show=function(){ console.log(this.name); console.log(this.age); } } } let person=new Person(); person.show();
'运行
在es6中,我们对于实例方法还有一种写法,在constructor()外书写,如下:
class 类名{ constructor(){ } 方法名(参数列表){ } }
'运行
在constructor外直接书写方法名(){}的方式也是定义实例方法,效果跟写在constructor中是一样的。区别在于将实例属性和实例方法进行区分,constructor函数内部得以简化,代码结构更加简洁,也是推荐使用的方式。
class Person{ constructor(){ this.name=''; this.age=0; } intruduce(){ return `我叫${ this.name},今年${ this.age}岁!`; } specialty(content){ return this.name+"擅长"+content; } } let p1=new Person(); p1.name="张三丰"; p1.age=120; console.log(p1.intruduce()); console.log(p1.specialty('打太极')); let p2=new Person(); p2.name="张无忌"; p2.age=25; console.log(p2.intruduce()); console.log(p2.specialty('乾坤大挪移'));
'运行
注意:
1.类里定义的函数不需要写function
2.多个函数之间不需要添加逗号隔开
3.constructor函数可以书写实例属性和实例方法,但推荐只编写实例属性。实例方法写在constructor函数外,class范围内。这样和实例属性进行分离,代码结构更加简洁和易懂。同时也会减少不必要的性能损失
4.同样的,我们也可以使用对象.函数名=function(){}的方式来为对象添加函数
Class本身也是一个对象(万事万物皆为对象),也可以用于存储数据,在类中存储的属性和函数被称为静态属性和静态函数,他们是可以直接通过类名来访问的属性和函数。例如我们在二阶段学习的Math类的各种属性和函数都是静态属性和函数。
class 类名{
//定义静态属性(类属性)
static 类属性名 = 类属性值;
constructor(){
}
//定义静态方法(类方法)
static 类方法名(){
}
}
// 使用:
类名.类属性名来使用
类名.类方法名();
例如:
class Ticket{ static count=5; static buy(){ console.log(`买了一张票,剩余${ Ticket.count--}张票`); } } Ticket.buy(); Ticket.buy(); Ticket.buy(); Ticket.buy(); Ticket.buy();
'运行
静态函数和普通函数的区别
静态属性和静态函数会一直存在于内存中(只存一份),永远不会被回收
普通属性和普通函数在创建对象之后才能使用,每个对象中都存储了一份,用完之后可以被GC(垃圾回收)回收的
静态函数可以通过类名访问,普通函数不能通过类名访问
对象可以访问普通函数,不能访问静态函数
静态函数中的this关键字指向的当前类,普通函数中的this指向的当前对象
上下文(Context)是程序运行的环境,在上下文中存储了一段程序运行时所需要的全部数据。在面向对象的编程语言中,上下文通常是一个对象,所以也被称为上下文对象。
在之前的课程中我们讲到过,程序中的变量存储在栈区,准确的说变量存储在了上下文对象中,而上下文对象保存在了栈中。开始执行一段程序之前,它的上下文对象就会被创建,并被推入栈中(入栈);程序执行完成时,它的上下文对象就会被销毁,并从栈顶被推出(出栈)。
栈结构是一种先进后出的数据存储结构,通过栈这种特殊的数据结构可以确保程序中的变量在被使用时满足就近原则,避免数据混乱的问题,接下来我们就详细的了解,JS是如何利用上下文对象和栈来达到这个目的的。
小结:
上下文对象在一段程序执行之前创建,当程序执行结束,上下文对象出栈,保证了正在执行的这段程序对应的上下文对象一定处于栈顶,每次访问数据从栈顶开始访问。
创建好上下文对象之后,会将该对象压入栈中
在程序执行的过程中,js总会从栈顶查找所需的数据
首先上下文对象分为两类,一种是全局上下文对象,一种是函数上下文对象。
全局上下文对象是在开始执行一段javascript代码时所创建的上下文对象,在html环境中,该上下文对象就是window对象。在node环境中为global对象。创建完上下文对象之后,该对象会入栈。全局上下文对象有且只有一个,只有当浏览器关闭时,全局上下文对象才会出栈。
函数上下文对象是在一个函数开始执行时所创建的上下文对象,创建完该对象以后,该对象同样的会入栈,当函数执行完毕,函数上下文对象出栈。每一次函数的调用都会创建新的函数上下文对象并入栈,哪怕是同一个函数的多次调用依然如此。
在秩序执行过程中所需要的数据,都会从栈顶的上下文对象中获取。
请看下面一段代码:
<script>
//1.全局上下文对象入栈
var v=10;
console.log(v);//2.从栈顶的上下文中获取数据v
function f1(){
var v1=1;
console.log(v1);//4.从栈顶的上下文中获取数据v1
f2();//5.f2函数上下文入栈
//8.f1函数上下文出战
}
function f2(){
var v2=2;
console.log(v2);//6.从栈顶的上下文中获取数据v2
//7.f2函数上下文出栈
}
f1();//3.f1函数上下文入栈
</script>
现场图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l8yUz4Bt-1663760507967)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220531151105739.png)]
思考:为什么在函数内可以使用全局变量呢?var变量提升是怎么造成的?函数定义的不同方式有何异同?块级作用域是如何产生的,这些问题的答案都在这个上下文对象创建过程中。
上下文对象在创建时,会在内部创建两个对象:词法环境对象和变量环境对象。
上下文对象结构:
Context={
词法环境对象:{
//let const 所有函数 函数的参数(全局上下文没有)
},
变量环境对象:{
//var
}
}
在词法环境对象中存储所有以let、const声明的变量以及所有的声明式函数。而在变量环境对象中只存储以var声明的所有变量。值得注意的是由于函数具备参数,所以在函数的上下文对象的词法环境对象中还存储了一个arguments对象用于存储参数数据,词法环境对象内部又是一个栈结构,每当程序执行到一个代码块时就会在向栈中存入一个Block对象,用于存储该块级作用域中定义的局部变量。
var v = 10; function f1() { //声明式函数 console.log("f1") } var f2 = function () { //表达式函数 console.log("f2") } f2();
'运行
上下文结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OzbfoSsa-1663760507968)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220531154828797.png)]
函数上下文对象,例如:
function f(name,age){ var v=10; function f1(){ console.log(v); } var f2=function(){ } }
'运行
现场图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Znm8KKLi-1663760507969)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220531155411791.png)]
在ES6中引入了let和const,这两种关键字都具备局部作用域的特点,但是没有局部上下文。为了满足局部作用域就近原则的特点,在上下文对象的词法环境对象中存在一个栈,每当代码执行到一段代码块,就会创建block的对象用于存储这一个块中保存的let和const变量。,并且将block入栈,每当程序需要访问变量时,首先从词法环境对象内部的栈顶的block中查找变量。
var a = 1;
let a = 10;
function f() {
console.log(a);//输出10
if (true) {
let a = 20;
console.log(a);//输出20
if (false) {
let a = 30;
} else {
let a = 40;
console.log(a);//输出40
}
console.log(a)//输出20
} else {
console.log(a);
}
console.log(a);//输出10
}
f();
console.log(a);//输出10
如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nU9SEQE0-1663760507970)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220531164708574.png)]
上下文对象在创建的过程中将变量和函数数据存储在了自己内部,那么此时各种不同的变量和函数的值是什么呢?JS针对不同的变量和函数采用了不同的方式来处理。
let、const在上下文对象的创建阶段不会被初始化,在代码执行阶段才会被赋值。
var在上下文对象的创建阶段会被初始化为undefined。
表达式函数如果用let声明则不会被初始化,表达式函数如果用var声明则被初始化为undefined。
声明式函数在上下文对象的创建阶段会被赋值为函数本身
函数的参数在上下文对象创建阶段已经被赋值为实参
console.log(a); var a=10;
'运行
输出结果:undefined
原因:创建上下文对象的同时,已经对变量a进行了初始化并赋值为undefined。执行代码时从栈顶的上下文对象中找a,自然值为undefined。
console.log(a);
let a=10;
输出结果:
原因:创建上下文对象的同时,let定义的变量不会被初始化。执行代码时从栈顶的上下文对象中找a,未初始化报错。
正因为这个原因,该程序一直执行到let a之前的部分,都是无法使用变量a的,这种情况就是暂时性死区。
f(); function f(
'运行