call和apply方法
作用:调用函数并改变函数内this的指向
语法:
两种方法都是属于函数对象的,所以必须是函数对象才能调用
functionName.call(obj,实参1,实参2...)
functionName.apply(obj,[参数1,参数2...])
区别:
- 都是改变函数内this的指向
- call方法参数是一个一个传递,而apply是传递一个参数数组
特殊情况:如果传递的第一个伪造对象是null或undefined,则函数内this会指向window全局对象
function test(a,b){
console.log(this,a,b)
}
test.call(null,10,20)
test.apply(undefined,[10,20])
什么时候用call或apply
理解:call或apply就是一种高端调用函数的方式
- 可改变函数内this指向
- 改变传参的方式
bind方法
- bind的作用和call/apply差不多,都是改变函数内this的指向。区别在于,bind不是立即执行,而是返回一个新函数。
- bind方法也是是属于
函数对象的,所以必须是函数对象才可以调用。
functionName.bind(obj,实参数1,实参数2...)
function test(a, b) {
console.log(this.age, 'a:', a, ' b:', b);
}
var obj = { age: 18 }
var fn = test.bind(obj, 50, 60) // 这里test并不会立即执行而是会返回一个函数
fn()
call/apply/bind三者区别:
- 相同点:都可以改变函数内this的指向
- 不同点:call/apply是立即执行的,而bind是返回一个新函数
闭包(重点)
什么是闭包
闭包(closure):闭包是指有权访问另一个函数作用域中的变量的函数
闭包是 一个函数以及其捆绑的周边环境状态(词法环境)的引用的组合。换而言之,闭包让开发者可以通过内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
/*
由于只有内部函数B才可以访问到函数A中的局部变量,那么我们只需要把函数B作为函数A的返回值,就可以在函数A外部访问它的内部变量! 其中函数B就是闭包。
*/
function A(){
var a = 10;
function B(){
console.log(a);
}
return B;
}
var result = A();W
result(); // 10
产生闭包的条件
只要子函数访问(引用)到了父函数作用域中的标识符(变量名或函数名)就会产生闭包(closure)。(可利用debug断点调试查看闭包的产生)
创建闭包的方式
创建闭包最常见的方式:
- 创建一个父函数,父函数内再创建子函数,子函数读取父函数内的局部变量,最后返回子函数
function A() {
var a = 100
return function () {
console.log(a)
}
}
var fn = A()
fn()
闭包的特点
闭包可以对父函数内部的变量持续引用。即让一个变量始终保存在内存中,即闭包可以延长变量的生命周期
function A() {
var a = 100
return function () {
console.log(a++)
}
}
var fn = A()
fn() // 100
fn() // 101
fn() // 102
闭包的应用场景
闭包的特点决定了应用场景
保存变量!保存变量!保存变量!
- 保存变量。结合IIFE机制
- 一次性函数
// 将函数当作参数接收
function once(callback) {
// 设置isCall变量表示该函数有没有被调用过
var isCall = false
return function () {
if (!isCall) {
isCall = true
callback.apply(this,arguments)
}
}
}
- 事件的防抖节流
- 缓存函数
- ......
闭包的缺点
由于闭包会使得函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包,使用不当可能会导致内存泄漏问题
内存泄漏
即内存无法释放,由于疏忽或错误造成程序未能释放已经不在使用的内存引起的
内存溢出
内存被撑爆,爆栈
function A(){
var a = 1;
function B(){
console.log(a++)
}
return B;
}
var f = A();
f(); // 1
f(); // 2
f(); // 3
f = null; //解除引用, 不再使用,手动释放内存
所以不管是函数、对象、数组,只要不用了就建议解除引用,让垃圾回收机制自动回收其占用的内存
js垃圾回收机制是自动的。 里面有一套垃圾回收的策略或算法:引用计数。
导致内存泄漏:1. 意外的全局变量 2. 没有关闭的时间器
高阶函数
满足高阶函数的两个条件之一:
- 将函数作为参数,如时间器函数(setInterval,setTimeout)
- 将函数作为函数的返回值。如bind函数
判断变量是否时数组
// 方法1
Array.isArray(变量)
// 方法2
变量.constructor === Array
// 方式3:获取变量的精确类型
Object.prototype.toString.call(变量)
深浅拷贝(克隆)
不同数据类型赋值区别:
-
基本类型:按值传递,修改新的不会影响旧的
-
引用类型:按址传递,修改新的会影响旧的
var obj1 = { name: '张三', age: 18 } var obj2 = obj1 obj2.name = '李四' console.log(obj1.name) // { name:'李四',age:18}
浅拷贝:浅拷贝只复制指向某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存。修改新对象会影响原对象。
深拷贝:深拷贝会在内存中创造一个一模一样的对象,新对象跟原对象不共享内存。修改新对象不会影响到原对象。
深拷贝的实现
实现深拷贝,必须开辟一个新的内存空间去存储新对象,这样修改复制出来的对象不会影响原对象。
function cloneDeep(target) {
// 对递归的target判断是否是基本数据类型
if (typeof target !== 'object') {
return target
}
// 根据传入的target初始化一个data=[]或data={}
var data = Array.isArray(target) ? [] : {}
// 根据target类型使用数组复制或对象复制
if (Array.isArray(target)) {
for (var i = 0; i < target.length; i++) {
// 递归
data[i] = cloneDeep(target[i])
}
} else {
for (var k in target) { // k为对象中的属性名
data[k] = cloneDeep(target[k])
}
}
return data
}
var obj1 = {
name: '张三',
age: 20,
hobby: ['吃饭', '睡觉', { say: 'hello' }],
}
var obj2 = cloneDeep(obj1)
obj2.hobby[2].say = '你好'
console.log(obj1) // 里面的hobby[2].say = 'hello'
console.log(obj2) // 里面的hobby[2].say = '你好'
节流和防抖
节流与防抖可以对事件的触发进行优化,缓解函数频繁调用
- 事件触发次数过多,ajax向后台频繁发送数据请求,造成服务器压力
- 造成浏览器卡顿,影响用户体验
节流
函数节流(throttle),指事件触发后,每过n秒才会响应一次,如果在n秒内多次触发,不会执行代码即不会进行响应。
节流会有规律的执行
原理:
利用闭包变量(保存上一次执行时间),在执行函数前会同时获取当前的时间,将当前时间与保存的上一次执行时间比较,只有当超过规定间隔时间才会执行
<script>
function throttle(fn, wait) {
var lastTime = 0 // 定义初始上一个执行时间
return function () {
var newTime = Date.now() // 获取当前执行时间
// 只有在当前时间超过上一个执行时间 wait 秒后才再次触发事件
if (newTime - lastTime >= wait) {
fn.apply(this, arguments)
lastTime = newTime
}
}
}
document.addEventListener(
'scroll',
throttle(function (event) {
console.log('滚动啦')
console.log(this)
console.log(event)
}, 2000)
)
</script>
函数节流应用场景
节流的根本目的是为了解决一定时间内事件触发太多次的问题,一般多用在鼠标事件中。
常见的场景:
- 防止用户频繁点击(onclick)
- 滚动scroll,连续滚动只执行一次
防抖
函数防抖(debounce),指事件触发后,过n秒后仅执行一次函数,如果在n秒内多次触发则每次都重新等待n秒后在执行
防抖只执行最后一次
原理:
将事件的执行放入n秒的延时器中,在延时时间结束前,每触发一次事件都会清除上一个延时器并产生一个新的延时器(即重置延迟时间)。只有当延时时间结束后才会执行一次。
<script>
function debounce(fn, wait) {
var timer
return function () {
// 创建变量保存子函数中正确的this与arguments
var _this = this
var args = arguments
// 判断上一次的延时器是否存在,存在则清除上一次延时器,随后生成一个新的延时器(即重置延迟时间)
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function () {
// 将this指向为正确的this,而不是时间器中嵌套的函数this与arguments
fn.apply(_this, args)
}, wait)
}
}
document.getElementById('inp').addEventListener(
'input',
debounce(function (event) {
console.log('按键触发了')
console.log(this)
console.log(event)
}, 500)
)
</script>
函数的防抖应用场景
防抖的根本目的是在一定的时间内,只执行最后一次触发的事件,一般多用在键盘事件onkeyup、或内容改变事件oninput中
常见的场景:
- 校验用户名
- 关键字搜索