目标:了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope)规定了变量能够被访问的"范围",离开了这个“范围”,变量便不能访问。
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
在JavaScript中使用{}包裹的代码称为代码块,代码块内部声明的变量外部将有可能无法访问。
script标签和.js文件的最外层就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其他作用域都可以被访问。
注意:
作用域链本质上是底层的变量查找机制
垃圾回收机制(Garbage Collection)简称GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
内存的生命周期
JS环境中分配的内存,一般有如下生命周期:
**内存分配:**当我们声明变量、函数、对象的时候,系统会自动为他们分配内存。
**内存使用:**即读写内存,也就是使用变量、函数等
**内存回收:**使用完毕,由垃圾回收器自动回收不再使用的内存
说明:
堆栈空间分配区别:
1.栈(操作系统):由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
2.堆(操作系统):一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
下面介绍两种常见的浏览器垃圾回收算法:引用计数法和标记清除法
引用计数法:(ie浏览器 现在基本不再使用)
存在致命问题:嵌套引用(循环引用)
如果两个对象相互引用,尽管他们已经不再使用,垃圾回收器不会进行回收,导致内存泄漏。
因为他们的引用次数永远不会是0.这样的相互引用如果说很大量的存在就会导致大量的内存泄漏。
标记清除法
现代的浏览器已经不再使用引用计数法了
现代浏览器通用的大多是基于标记清除法
的某些改进算法,总体思想都是一致的
核心:
无法达到的对象
”根部
(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达
的对象,都是还需要使用
的。对象被标记
为不再使用,稍后进行回收
**概念:**一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域。
简单理解:闭包=内层函数+外层函数的变量
**闭包作用:**封闭数据,提供操作,外部也可以访问函数内部的变量
闭包的基本格式: (因为有return 所以外部可以使用)
function outer () {
let a = 1
function fn ()
{
console.log(a);
}
return fn
}
//outer()===fn ===function fn(){}
//const fun = outer()
//const fun = function fn(){}
const fun = outer()//fun里面装的函数
fun()//调用函数
// 简约写法
function outer(){
let i =1
return function(){
console.log(i);
}
}
const fun = outer()
fun()
闭包应用:实现数据的私有,但是会有内存泄漏的风险。
它允许在变量声明之前即被访问(仅存在于var声明变量)
注意:
实际开发中推荐先声明再访问变量
函数参数的使用细节,能够提升函数应用的灵活度
arguments
是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
arguments
动态参数,只存在函数里面
伪数组
,只存在于函数
中展开运算符(…),将一个数组进行展开
典型运用场景:求数组最大值(最小值)、合并数组等。
剩余参数和展开运算符的区别
剩余参数:函数参数使用
,得到真数组
展开运算符:数组中使用
,数组展开
**目的:**引用箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
**使用场景:**箭头函数更适用于那些本来需要匿名函数的地方。
在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值。
在开发中 使用箭头函数前需要考虑函数中的this的值,事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调函数为了简便,还是不太推荐使用箭头函数。
箭头函数不会创建自己的this,他只会从自己的作用域链的上一层沿用this
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
基本语法:
赋值运算符=左侧的[]用于批发声明变量,右侧数组的单元值将被赋值给左侧的变量
变量的顺序对应数组单元值的位置依次进行赋值操作。
注意:js前面必须加分号的情况
1.立即执行函数
2.数组解构
变量多,单元值少的情况
变量少,单元值多
利用剩余参数解决变量少单元值多的情况
防止有undefined传递单元值的情况,可以设置默认值:
允许初始化变量的默认值,且只有单元值为undifined时默认值才会生效。
按需导入,忽略某些返回值
支持多维数组的结构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
相同的
变量1.forEach主要是遍历数组的,没有返回值(map是返回空数组)
2.参数当前数组时必须要写的,索引号可选
筛选数组符合条件的元素,
并返回筛选之后元素的新数组1 . 利用字面量创建对象
const 0={
name:'佩奇'
}
2 .利用new Object创建对象
const o = new Object({name:'佩奇'})
const obj = new Object();
obj.uname='pink'
3.利用构造函数创建对象
构造函数是一种特殊的函数,主要用来初始化对象
**使用场景:**常规的{…}语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一遍,此时可以构造函数
来快速创建多个类似的对象。
function Pig(name,age,gender){
this.name=name
this.age=age
this.gender=gender
}
//创建佩奇对象
const Peppa = new Pig('佩奇',6,'女')
//创建乔治对象
const George = new Pig('乔治',3,'男')
//创建猪妈妈对象
const Mun = new Pig('猪妈妈',30,'女')
//创建猪爸爸对象
const Dad = new Pig('猪爸爸','32','男')
构造函数在技术上是常规函数。
不过有两个约定
1.它们的命名只能以大写字母开头
2.它们只能由‘new’操作符来执行
创建构造函数:
实例化
function Goods (name, price, count)
{
//后面的name是形参,前面的name是对象的属性
this.name = name
this.price = price
this.count = count
}
const mi = new Goods('小米', 1999, 20)
console.log(mi);
实例化执行过程
说明:
实例成员
通过构造函数创建的对象称为实例对象,实例对象中
的属性和方法称为
实例成员
(实例属性和实例方法)
说明:
//实例成员:实例对象上的属性和方法属于实例成员
function Pig (name)
{
this.name = name
}
const peiqi = new Pig('天天')
const qiaozhi = new Pig('瀚瀚')
//实例属性
peiqi.name = 'xuzihan'
peiqi.sayHi = () =>
{
console.log('hi~我是kongkong');
}
console.log(peiqi);
构造函数
的属性和方法被称为静态成员
(静态属性和静态方法)
说明:
//静态成员:构造函数上的属性和方法被称为静态成员
function Pig (name)
{
this.name = name
}
Pig.eyes = 2 //静态属性
Pig.sayHii = function ()//静态方法
{
console.log(this);
}
Pig.sayHii()
console.log(Pig.eyes);
在JS中最主要的数据类型有6种
基本数据类型
字符串、数值、布尔、undefined、null
引用类型
对象
但是我们会发现有些特殊情况:
其实字符串、数值、布尔等基本类型也有专门的构造函数,这些我们称为包装类型。
JS中几乎所有的数据都可以基于构造函数创建
引用类型
Object、Array、RegExp、Date等
包装类型
String、Number、Boolean等
Object是内置构造函数,用于创建普通对象
//通过构造函数创建普通对象
const user = new Object({name:'天天',age:22})
推荐使用字面量方式声明,而不是Object构造函数
学习三个常用的静态方法(静态方法就是只有构造函数Object可以调用的)
作用:Object.keys静态方法获取对象中所有属性(键)
语法:
const o = {name:'佩奇',age:6 }
//获得对象的所有键,并且返回是一个数组
const arr = Object.keys(o)
console.log(arr)//['name','age']
console.log(Object.values(o))//['佩奇',6]
注意:返回的是一个数组
Object.assign静态方法常用于对象拷贝
经常使用的场景给对象添加属性
//拷贝对象把o拷贝给obj
const 0 ={name:'tiantian',age:22}
const obj={}
Object.assign(obj,o)
console.log(obj)//{name:'tiantian',age:6}
//还可以把对象拷贝到里面去
Object.assign(o,{gender:'女'})
Array是内置的构造函数,用于创建数组
const arr = new Array(3,5)
console.log(arr)//[3,5]
创建数组建议使用字面量
创建,不用Array构造函数创建
数组常见实例方法-核心方法
**作用:reduce返回累计处理的结果,经常用于求和等
基本语法:
参数:
1.如果有起始值
,则把初始值累加到里面
reduce执行过程
在JS中的字符串 数值布尔具有对象的使用特征,如具有属性和方法
之所以具有对象特征的原因是字符串、数值、布尔类型数据是JS底层使用Object构造函数包装出来的,被称为“包装类型”。
常见实例方法
Number是内置构造函数,用于创建数值
常用方法:
toFixed()设置保留最小数位的长度
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
面向过程就是按照我们分析好了的步骤,按照步骤解决问题。
优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用面向过程编程。
缺点:没有面向对象易维护、易复用、易扩展。
面向对象是把事务分解成一个个对象,然后由对象之间分工与合作。
面向对象是以功能来划分问题而不是步骤。
构造函数
实现的封装。存在浪费内存的问题
所以我们希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要怎么做呢
目标:能够利用原型对象实现方法共享
每一个构造函数都有一个prototype属性,
指向另一个对象,所以我们也成为原型对象节约内存
我们可以把那些不变的方法直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
每个原型对象里面都有个constructor属性(constructor构造函数)
**作用:**该属性指向该原型对象的构造函数简单理解就是指向父级。
**使用场景:**如果有多个对象的方法,我们可以给原型对象采取对象形式赋值。但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor指向原来的构造函数
不加constructor的话就不知道是谁创造了我
对象都会有一个属性_proto_
指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有_proto_原型的存在。
注意:
指向创建该实例对象的构造函数
function Star(){
const ldh = new Star()
//对象原型__proto__指向 该构造函数的原型对象
console.log(ldh.__proto__===Star.prototype)//true
//对象原型里面有constructor指向构造函数Star
console.log(ldh.__proto__.constructor===Star)//true
}
继承是面向对象变成的另一个特征,通过继承进一步提升代码封装的程度,JS中大多是借助原型对象实现继承的特性。
封装抽取公共部分
把男人和女人公共的部分抽取出来放到人类里面
function Person ()
{
this.eyes = 2
this.head = 1
}
function Woman ()
{
}
//父构造函数(父类) 子构造函数(子类)
//子类的原型 = new 父类
//Woman通过原型链继承Person
Woman.prototype = new Person()//{eyes:2,head:1}
//指回原来的构造函数
Woman.prototype.constructor = Woman
Woman.prototype.baby = function ()
{
console.log('baby');
}
const red = new Woman()
console.log(red);
function Man ()
{
}
Man.prototype = new Person()
Man.prototype.constructor = Man
const pink = new Man()
console.log(pink);
对象原型指向原型对象
基于原型对象的继承使得不同构造函数的原定对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链。
只要是对象都有原型(proto)
当访问一个对象的属性(包括方法)时,首先查找这个对象自身
有没有该属性。
如果没有就查找它的原型(也就是__proto__指向的prototype原型对象
)
如果还没有就查找哦原型对象的原型(Object
的原型对象)
以此类推,一直找到Object为止(null)
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向或者说一条路线
可以使用instanceof
运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
案例:模态框封装
1.多个模态框一样的,而且每次点击都会出来一个。所以这时候可以用构造函数
。把模态框封装到一个构造函数Modal
,每次new都会产出一个模态框,所以点击不同的按钮就是在做new模态框
,实例化。
2.模态框有什么功能呢?打开功能(显示),关闭功能,而且每个模态框都包含着两个功能。
open功能
close功能
问:open和close方法写到哪里?
构造函数Modal的原型对象上,共享方法。
(任何一个模态框都可以使用)
分为三块去做:
模态框Modal业务
(1)创建div标签可命名为:modalBox
(2) div标签的类名为modal
(3)标签内部添加基本结构,并填入相关数据
需要的公共属性:标题(title)、提示信息内容(message)可以设置为默认参数。
打开方法open
关闭方法close
开发中我们经常需要复制一个对象。如果直接用赋值会有下面问题:
首先浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址
常见方法:
const pink={
name:'pink老师',
age:18
}
const red ={}
Object.assign(red,pink)
console.log(red)//{name:'pink老师',age:18}
red.name='red老师'
console.log(red)//{name:red老师,age:18}
//不会影响pink对象
console.log(pink)//{name:'pink老师',age:18}
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址(简单理解:如果是单层对象,没问题,如果有多层就有问题)
直接赋值和浅拷贝有什么区别?
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
必须要加退出条件 return
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行。
我们可以通过try/catch捕获错误信息(浏览器提供的错误信息)try试试 catch拦住 finally 最后
普通函数的调用方式决定了this的值,即【谁调用this的值就指向谁】
普通函数没有明确调用者时this值为window,严格模式下没有调用者时this的值为undefined
箭头中的this与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this
如果里面需要
DOM对象的this,则不推荐使用箭头函数。JS中还允许指定函数中this的指向,有三个方法可以动态指定普通函数中this的指向。
使用call方法调用函数,同时指定被调用函数中this的值
fun.call(thisArg,arg1,arg2,....)
const obj={
uname:'pink'
}
function fn(x,y){
console.log(this)//本来是window
console.log(x+y)
}
//1.调用函数
//2.改变this指向
fn.call(obj,1,2)
使用apply方法调用函数,同时指定被调用函数中this的值
语法:
fun.apply(thisArg,[argsArray])
//fun.apply(this指向谁,数组参数)
数组
里面const obj={
age:18
}
function fn(x,y){
console.log(this)//{age:18}
console.log(x+y)
}
//1.调用函数
//2.改变this指向
fn.apply(obj,[1,2])
bind()方法不会调用函数。但是能改变函数内部this指向
语法:
原函数拷贝(新函数)
相同点:
都可以改变函数内部的this指向
区别点:
bind不会调用函数,可以改变函数内部this指向
bind不调用函数,但是还想改变this指向,比如改变定时器内部this指向。
**防抖:**单位时间内,频繁触发事件,只执行最后一次。
**例子:**王者荣耀回城,只要被打断就需要重新来
使用场景:
搜索框搜索输入。只需用户最后一次 输入完,再发送请求
手机号、邮箱验证输入检测
案例:利用防抖来处理-鼠标滑过盒子显示文字
要求:鼠标在盒子上移动,鼠标停止500之后,里面的数组才会变化+1
实现方式:
1.lodash提供的防抖来处理
2.手写一个防抖函数来处理
核心思路:
防抖的核心就是利用定时器(setTimeout)来实现
1.声明一个定时器变量
2.当鼠标每次滑动都先判断是否有定时器了,如果有定时器先清除以前的定时器
3.如果没有定时器则开启定时器,记得存到变量里面
4.在定时器里面调用要执行函数
节流: 单位时间内,频繁触发事件,只执行一次
使用场景:
高频事件:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll等等。
可以通过lodash或者手写一个节流函数来处理
手写节流函数:
要求:鼠标在盒子上移动不管多少次,每隔500ms才+1
核心思路:
节流的核心就是利用定时器(setTimeout)来实现
1.声明一个定时器变量
2.当鼠标每次滑动都先判断是否有定时器了,如果有定时器则不开启新定时器。
3.如果没有定时器则开启定时器,记得存到变量里面