一、对象面向过程和面向对象
1、对象两大编程思想:面向过程和面向对象
- 面向过程编程(OPP)
装修房屋的对象流程:
1.找张三设计,你要给张三提供房屋结构图纸
2.找李四安装水电,对象你要给李四买好水管电线
3.找王五订制家具,对象你要买好木板油漆
面向过程就是对象分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,对象使用的对象时候再一个一个的一次调用的过程
- 面向对象编程(OOP)
装修房屋的流程:
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.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指向的当前对象
五、执行上下文(难点)
1、什么是上下文
上下文(Context)是程序运行的环境,在上下文中存储了一段程序运行时所需要的全部数据。在面向对象的编程语言中,上下文通常是一个对象,所以也被称为上下文对象。
在之前的课程中我们讲到过,程序中的变量存储在栈区,准确的说变量存储在了上下文对象中,而上下文对象保存在了栈中。开始执行一段程序之前,它的上下文对象就会被创建,并被推入栈中(入栈);程序执行完成时,它的上下文对象就会被销毁,并从栈顶被推出(出栈)。
栈结构是一种先进后出的数据存储结构,通过栈这种特殊的数据结构可以确保程序中的变量在被使用时满足就近原则,避免数据混乱的问题,接下来我们就详细的了解,JS是如何利用上下文对象和栈来达到这个目的的。
小结:
上下文对象在一段程序执行之前创建,当程序执行结束,上下文对象出栈,保证了正在执行的这段程序对应的上下文对象一定处于栈顶,每次访问数据从栈顶开始访问。
创建好上下文对象之后,会将该对象压入栈中
在程序执行的过程中,js总会从栈顶查找所需的数据
2、上下文的分类
首先上下文对象分为两类,一种是全局上下文对象,一种是函数上下文对象。
全局上下文对象是在开始执行一段javascript代码时所创建的上下文对象,在html环境中,该上下文对象就是window对象。在node环境中为global对象。创建完上下文对象之后,该对象会入栈。全局上下文对象有且只有一个,只有当浏览器关闭时,全局上下文对象才会出栈。
函数上下文对象是在一个函数开始执行时所创建的上下文对象,创建完该对象以后,该对象同样的会入栈,当函数执行完毕,函数上下文对象出栈。每一次函数的调用都会创建新的函数上下文对象并入栈,哪怕是同一个函数的多次调用依然如此。
在秩序执行过程中所需要的数据,都会从栈顶的上下文对象中获取。
请看下面一段代码:
现场图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l8yUz4Bt-1663760507967)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220531151105739.png)]
3、上下文对象的创建过程
思考:为什么在函数内可以使用全局变量呢?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(){ console.log(1);}
输出结果:1
原因:创建上下文对象的同时,针对声明式函数已经将其初始化并赋值为函数本身。所以在执行f函数时可以正常调用。
c();var c=function(){ console.log(1);}
输出结果:
原因:创建上下文对象的同时,针对表达式函数的处理方式取决于前面变量的关键字,如果变量关键字为var,则会初始化并赋值为undefined,但是undefined并不是一个函数,所以报错。
f();let f=function(){ console.log(1);}
输出结果:
原因:创建上下文对象的同时,针对表达式函数的处理方式取决于前面变量的关键字,如果变量关键字为let,
则不会进行初始化,报错。
let a=10;function a(){ }console.log(a);
输出结果:报错,重复定义变量,let和函数都存储在词法环境对象中,不能出现名称重复的情况
var a=10;function a(){ }console.log(a)
输出结果:10,var存储在变量环境对象中,a有初始值,函数a不会覆盖a变量。
var a;function a(){ }console.log(a)
输出结果:函数a,var没有初始值,函数覆盖变量a.
4、作用域与作用域链
作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。从另一个角度讲就是指当前程序在执行时处于栈顶的上下文对象中是否能查找到该数据,那么如果在当前上下文对象中没有找到该数据怎么办呢?在上下文对象的创建过程中,会在变量环境对象中定义一个属性(例如:outer),该属性的值为函数定义时所在的上下文对象,这个上下文一定是它的上级上下文对象。然后在查找数据时,如果在当前上下文对象中没有找到该数据,则会通过outer找到它的上级上下文对象,以此类推一直查找到全局上下文对象为止。这些上下文对象一起构成了一个作用域链条,它被称为作用域链。
var a=100;function f(){ var a=10; console.log(a); if(true){ console.log(a); } f1();};f();
5、变量的查找
当前上下文的词法环境->词法环境中stack的栈顶block->依次向stack中的其他block查找->当前上下文对象的变量环境->上级上下文的词法环境-词法环境中stack的栈顶block->依次向stack中的其他block查找->上级上下文的变量环境
六、构造函数
1、概念
在javascript中,使用new来新建一个对象时, 实际上是调用了一次constructor函数来完成对象初始化操作,而完成对象初始化操作的函数,我们称为构造函数。
2、作用
通过调用构造函数传入参数,对数据进行初始化。
返回实例对象
class Person{ constructor(name,age,job,specialty){ this.name=name; this.age=age; this.job=job; this.specialty=specialty; } intruduce(){ return `姓名:${ this.specialty}`; }}let p1=new Person('张三丰',120,'武当派掌门','太极拳');let p2=new Person('张无忌',25,'明教教主','九阳真经,乾坤大挪移');console.log(p1.intruduce());console.log("