文章目录
作用域
目标:了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope)规定了变量能够被访问的"范围",离开了这个“范围”,变量便不能访问。
局部作用域
函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法相互访问
- 函数执行完毕后,函数内部的变量实际被清空了
块级作用域
在JavaScript中使用{}包裹的代码称为代码块,代码块内部声明的变量外部将有可能无法访问。
- let声明的变量会产生块级作用域,var不会产生块作用域
- const声明的常量也会产生块级作用域
- 不同代码块之间的变量无法相互访问
- 推荐使用let或const
全局作用域
script标签和.js文件的最外层就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其他作用域都可以被访问。
注意:
- 为window对象动态添加的属性默认也是全局的,不推荐。
- 函数中未使用任何关键字声明的变量未全局变量,不推荐
- 尽可能少的声明全局变量防止全局变量被污染
作用域链
作用域链本质上是底层的变量查找机制
- 在函数被执行时,会优先查找当前函数作用域中查找变量
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
总结 - 嵌套关系的作用域链串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
JS垃圾回收机制
垃圾回收机制(Garbage Collection)简称GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
内存的生命周期
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声明变量)
注意:
- 变量在未声明即被访问时会报语法错误
- 变量在var声明之前即被访问,变量的值为undefined
- let/const声明的变量不存在变量提升
- 变量提升出现在相同作用域当中
实际开发中推荐先声明再访问变量
函数进阶
函数提升
函数提升和变量提升比较类似,是指再函数声明之前即被调用
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域当中
函数参数
动态参数
函数参数的使用细节,能够提升函数应用的灵活度
arguments
是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
arguments
动态参数,只存在函数里面
- arguments是一个
伪数组
,只存在于函数
中 - arguments的作用时动态获取函数的实参
- 可以通过for循环一次得到传递过来的实参
剩余参数
剩余参数允许我们呢将一个不定数量的参数表示为一个数组
- …是语法符号,置于最末函数形参之前,用于获取多余的实参
- 借助…获取的剩余实参,是个真数组
- 开发中还是提倡多使用剩余参数
拓展:展开运算符
展开运算符(…),将一个数组进行展开
典型运用场景:求数组最大值(最小值)、合并数组等。
剩余参数和展开运算符的区别
剩余参数:函数参数使用
,得到真数组
展开运算符:数组中使用
,数组展开
箭头函数
**目的:**引用箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
**使用场景:**箭头函数更适用于那些本来需要匿名函数的地方。
基本语法
- 只有一个参数可以省略小括号
2.如果函数体只有一行代码,可以写到一行上,并且无需写return直接返回值
3.加括号的函数体返回对象字面量表达式
基本参数
- 普通函数有arguments动态参数
- 箭头函数没有arguementts动态参数,但是又剩余参数…args
箭头函数this
在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值。
在开发中 使用箭头函数前需要考虑函数中的this的值,事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调函数为了简便,还是不太推荐使用箭头函数。
箭头函数不会创建自己的this,他只会从自己的作用域链的上一层沿用this
解构赋值
数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
基本语法:
赋值运算符=左侧的[]用于批发声明变量,右侧数组的单元值将被赋值给左侧的变量
变量的顺序对应数组单元值的位置依次进行赋值操作。
注意:js前面必须加分号的情况
1.立即执行函数
2.数组解构
变量多,单元值少的情况
变量少,单元值多
利用剩余参数解决变量少单元值多的情况
防止有undefined传递单元值的情况,可以设置默认值:
允许初始化变量的默认值,且只有单元值为undifined时默认值才会生效。
按需导入,忽略某些返回值
支持多维数组的结构
对象解构(重要)和结构解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
基本语法:
- 赋值运算符=左侧的{}用于批量声明变量,右侧对象的属性将被赋值给左侧的变量
- 对象属性的值将被赋值给与属性名
相同的
变量 - 注意解构的变量名不要和外面的变量名冲突否则报错
- 对象中找不到与变量名一致的属性时变量值为undefined
给新的变量名赋值:
可以从一个对象中提取变量并同时修改新的变量名
数组对象解构
多级对象解构
拓展:遍历数组forEach方法(重点)
- forEach()方法用于调用数组的每个元素,并将元素传递给回调函数
- 主要使用场景:遍历数组的每个元素
- 语法:
注意:
1.forEach主要是遍历数组的,没有返回值(map是返回空数组)
2.参数当前数组时必须要写的,索引号可选
筛选数组filter方法(重点)
- filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
- 主要使用场景:
筛选数组符合条件的元素,
并返回筛选之后元素的新数组 - 返回值:返回数组,包含了符合条件的所有水元素。如果没有符合条件的元素则返回空数组
- 参数:currentValue必须写,index可选
- 因为返回新数组,所以不会影响原数组
创建对象方式
深入对象
创建对象的三种方式
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’操作符来执行
创建构造函数:
- 使用new关键字调用函数的行为被称为
实例化
- 实例化构造函数时没有参数时可以省略
- 构造函数内部无需写return,返回值即为新创建的对象
- 构造函数内部的return返回值无效,所以不要写return
- new Object() new Data()也是实例化构造函数
小案例
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);
实例化执行过程
说明:
- 创建新对象
- 构造函数this指向新对象
- 执行构造函数代码,修改this,添加新属性
- 返回新对象
实例成员&静态成员
实例成员
通过构造函数创建的对象称为实例对象,实例对象中
的属性和方法称为
实例成员
(实例属性和实例方法)
说明:
- 为构造参数传入参数,创建结构相同但值不同的对象
- 构造函数创建的实例对象彼此互不影响
//实例成员:实例对象上的属性和方法属于实例成员
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);
静态成员:
构造函数
的属性和方法被称为静态成员
(静态属性和静态方法)
说明:
- 静态成员只能构造函数来访问
- 静态方法中的this指向构造函数
- 比如:Date.now() Math.PI Math.random()
//静态成员:构造函数上的属性和方法被称为静态成员
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
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
Array是内置的构造函数,用于创建数组
const arr = new Array(3,5)
console.log(arr)//[3,5]
创建数组建议使用字面量
创建,不用Array构造函数创建
数组常见实例方法-核心方法
reduce
**作用:reduce返回累计处理的结果,经常用于求和等
基本语法:
参数:
1.如果有起始值
,则把初始值累加到里面
reduce执行过程
- 如果没有起始值,则上一次值以数组的第一个数组元素的值
- 每一次循环,把返回值给作为下一次循环的上一次值
- 如果有起始值,则起始值作为上一次值
数组常见方法-其他方法
伪数组转换为真数组
静态方法Array.from()
String
在JS中的字符串 数值布尔具有对象的使用特征,如具有属性和方法
之所以具有对象特征的原因是字符串、数值、布尔类型数据是JS底层使用Object构造函数包装出来的,被称为“包装类型”。
常见实例方法
Number
Number是内置构造函数,用于创建数值
常用方法:
toFixed()设置保留最小数位的长度
编程思想
面向过程介绍
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
面向过程就是按照我们分析好了的步骤,按照步骤解决问题。
优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用面向过程编程。
缺点:没有面向对象易维护、易复用、易扩展。
面向对象介绍(oop)
面向对象是把事务分解成一个个对象,然后由对象之间分工与合作。
面向对象是以功能来划分问题而不是步骤。
- 在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
- 面向对象变成具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
- 面向对象的特性
– 封装性
–继承性
–多态性
优点:易维护、易复用、易拓展,由于面向对象有封装、继承、堕胎的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点:性能比面向过程低
构造函数
- 封装是面向思想中比较重要的一部分,JS面向对象可以通过
构造函数
实现的封装。 - 同样的将变量和函数组合到了一起并能通过this实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的。
- 构造函数体现了面向对象的封装特性
- 构造函数实例创建的对象彼此独立、互不影响
- 封装是面向对象思想中比较重要的一部分,JS面向对象可以通过构造函数实现的封装。
- 构造函数方法
存在浪费内存的问题
所以我们希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要怎么做呢
原型
原型
目标:能够利用原型对象实现方法共享
- 构造函数通过原型分配的函数是所有对象所共享的
- JavaScript规定,
每一个构造函数都有一个prototype属性,
指向另一个对象,所以我们也成为原型对象 - 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,
节约内存
我们可以把那些不变的方法直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
- 构造函数和原型对象中的this都指向实例化对象
属性和方法定义在构造函数上实示例:
区别:this是对象,prototype是原型对象
公共的属性写到构造函数里面
公共的方法写到原型对象身上
**总结:**原型是一个对象我们也称为prototype为 原型对象
原型的作用是共享方法:可以把那些不变的方法直接定义在prototype对象上。
构造函数和原型里面的this指向实例化对象
构造函数和原型的this指向
构造函数和原型对象中的this都指向实例化的对象
constructor属性
每个原型对象里面都有个constructor属性(constructor构造函数)
**作用:**该属性指向该原型对象的构造函数简单理解就是指向父级。
**使用场景:**如果有多个对象的方法,我们可以给原型对象采取对象形式赋值。但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor指向原来的构造函数
不加constructor的话就不知道是谁创造了我
对象原型
对象都会有一个属性_proto_
指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有_proto_原型的存在。
注意:
- __proto__是非JS标准属性
- [[prototype]]和__proto__意义相同
- 用来表明当前实例对象指向哪个原型对象prototype
- __proto__对象原型里面也有一个constructor属性,
指向创建该实例对象的构造函数
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
前面那些内容是核心 接下来的内容作为了解就好
深浅拷贝
开发中我们经常需要复制一个对象。如果直接用赋值会有下面问题:
浅拷贝
首先浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址
常见方法:
- 拷贝数组:Object.assign()/展开运算符{…obj}拷贝对象
- 拷贝数组:Array.prototype.concat()或者[…arr]
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}
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址(简单理解:如果是单层对象,没问题,如果有多层就有问题)
直接赋值和浅拷贝有什么区别?
- 直接赋值的方法,只要是对象,都会互相影响,因为是直接拷贝对象栈里面的地址
- 浅拷贝如果是一层对象,不互相影响,如果出现多层对象拷贝还会相互影响
浅拷贝怎么理解? - 拷贝对象之后,里面的属性值是简单数据类型直接拷贝值
- 如果属性值是引用数据类型则拷贝的是地址
深拷贝
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
- 通过递归实现深拷贝
- lodash/cloneDeep
- 通过JSON.stringify()实现
函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
- 简单理解:函数内部自己调用自己,这个函数就是递归函数
- 递归函数的作用和循环效果类似
- 由于递归很容易发生“栈溢出”错误(stack overflow),所以
必须要加退出条件 return
常见方法:
JS库lodash里面cloneDeep内部实现了深拷贝
方法三
异常处理
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行。
throw抛异常
总结:
- throw抛出异常,程序也会终止运行
- throw后面跟的是错误提示信息
- Error对象配合throw使用,能够设置更详细的错误信息。
try/catch捕获异常
我们可以通过try/catch捕获错误信息(浏览器提供的错误信息)try试试 catch拦住 finally 最后
debugger
处理this
this指向
普通函数的调用方式决定了this的值,即【谁调用this的值就指向谁】
普通函数没有明确调用者时this值为window,严格模式下没有调用者时this的值为undefined
箭头函数
箭头中的this与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this
- 箭头函数会默认帮我们绑定外层的this值,所以在箭头函数中this的值和外层的this是一样的。
- 箭头函数中的this引用的就是最近作用域中的this
- 向外层作用域中,一层一层查找this,直到有this的定义。
注意1:
在开发情况中【使用箭头函数前需要考虑函数中this的值】,事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调函数如果里面需要
DOM对象的this,则不推荐使用箭头函数。
注意2:
由于箭头函数this的原因,基于原型的面向对象也不推荐采用箭头函数。
总结: - 函数内不存在this,沿用上一级的
- 不适用
构造函数,原型函数,dom事件函数 - 适用
需要适用上层this的地方 - 使用正确的话,它会在很多地方带来方便,后面我们会大量使用慢慢体会。
改变this
JS中还允许指定函数中this的指向,有三个方法可以动态指定普通函数中this的指向。
call()-了解
使用call方法调用函数,同时指定被调用函数中this的值
fun.call(thisArg,arg1,arg2,....)
- thisArg:在fun函数运行时指定的this值
- 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()
使用apply方法调用函数,同时指定被调用函数中this的值
语法:
fun.apply(thisArg,[argsArray])
//fun.apply(this指向谁,数组参数)
- thisArg:在fun函数运行时指定的this值,可以为null
- argsArray:传递的值,必须包含在
数组
里面 - 返回值就是函数的返回值,因为它就是调用函数
- 因此apply主要跟数组有关系,比如使用Math.max()求数组的最大值
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()-最重要
bind()方法不会调用函数。但是能改变函数内部this指向
语法:
- thisArg:在fun函数运行时指定的this值
- arg1,arg2:传递的其他参数
- 返回由指定的this值和初始化参数改造的
原函数拷贝(新函数)
- 因此当我们只是想改变this指向,并且不想调用这个函数的时候,可以使用bind比如改变定时器内部的this指向
call apply bind总结
相同点:
都可以改变函数内部的this指向
区别点:
- call和apply会调用参数,并且改变函数内部this指向
- call和apply传递的参数不一样,call传递参数aru1,aru2…形式,apply必须数组形式[arg]
bind不会调用函数,可以改变函数内部this指向
主要应用场景:- call调用参数并且可以传递参数
- apply经常跟数组有关系,比如借助于数学对象实现数组最大值和最小值
bind不调用函数,但是还想改变this指向,比如改变定时器内部this指向。
防抖(debounce)
**防抖:**单位时间内,频繁触发事件,只执行最后一次。
**例子:**王者荣耀回城,只要被打断就需要重新来
使用场景:
搜索框搜索输入。只需用户最后一次 输入完,再发送请求
手机号、邮箱验证输入检测
案例:利用防抖来处理-鼠标滑过盒子显示文字
要求:鼠标在盒子上移动,鼠标停止500之后,里面的数组才会变化+1
实现方式:
1.lodash提供的防抖来处理
2.手写一个防抖函数来处理
核心思路:
防抖的核心就是利用定时器(setTimeout)来实现
1.声明一个定时器变量
2.当鼠标每次滑动都先判断是否有定时器了,如果有定时器先清除以前的定时器
3.如果没有定时器则开启定时器,记得存到变量里面
4.在定时器里面调用要执行函数
节流-throttle
节流: 单位时间内,频繁触发事件,只执行一次
使用场景:
高频事件:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll等等。
可以通过lodash或者手写一个节流函数来处理
手写节流函数:
要求:鼠标在盒子上移动不管多少次,每隔500ms才+1
核心思路:
节流的核心就是利用定时器(setTimeout)来实现
1.声明一个定时器变量
2.当鼠标每次滑动都先判断是否有定时器了,如果有定时器则不开启新定时器。
3.如果没有定时器则开启定时器,记得存到变量里面
- 定时器里面调用执行的函数
- 定时器里面要把定时器清空