一、this对象
1.核心一句话 - 哪个对象调用函数,函数里面的this指向哪个对象。
2.浏览器解析器在调用函数时每次都会向函数内部传递一个隐含的参数,这个隐含的参数就是this,this指向的是一个对象,这个对象我们称之为函数执行的上下文对象。(每个函数都有自己的执行环境,也叫执行上下文)
3.根据函数的调用方式的不同,this会指向不同的对象,这也是this的存在的意义所在。
1.函数里面的this是谁?
2.关于面向对象里面的this
如果是实例化对象调用的函数,那么构造函数里面的this指向的就是实例化对象
如果是把构造函数当成普通函数去调用,那么里面的this指向的是window
如果是通过构造函数调用的静态方法,那么静态方法里面的this指向的是构造函数
3.this备份
在定时器外面的事件处理函数中,this就是事件元素,我们可以把this进行备份使用来使用,因为在定时器里面this指向的是window对象
4.改变this指向
(1)bind()方法:修改函数或方法中的this为指定的对象,并且会返回一个修改后的之后的新的函数给我们。
-
函数调用圆括号时,函数的this是window对象
-
函数作为一个对象的方法,对象打点调用,函数的this就是这个对象
-
函数是事件处理函数时,函数的this就是触发这个this的对象(当前事件调用者)
-
如果是构造函数的实例化对象调用函数,this指向的是实例化对象本身
-
箭头函数没有自己的this,它的this指向上一层代码
-
定时器调用函数时,this是window对象
-
数组中存放的函数,被数组索引之后加圆括号调用,this就是这个数组
注意点:bind()方法除了修改this以外,还可以传递参数,只不过参数必须写在this对象的后面
(2)call()方法: 修改函数或方法中的this为指定的对象,并且会立即调用修改之后的函数。
注意点:call()方法除了可以修改this以外,还可以传递参数,只不过参数必须写在this对象的后面
(3)apply()方法: 修改函数或方法中的this为指定的对象,并且会立即调用修改之后的函数。
注意点:注意点:call()方法除了可以修改this以外,还可以传递参数,只不过参数必须通过数组的方式传递
二、闭包
在谈闭包之前,我们先来讨论一下函数的调用
1.函数的阶段
(1)定义阶段(也就是做地址赋值的事)
开辟一个 存储空间
把函数体内的代码一模一样的放在这个空间内(不解析变量)
把 存储空间 的地址给函数名
(2)调用阶段
按照函数名的地址找到函数的 存储空间
形参赋值
预解析
将函数 存储空间 中的代码拿出来执行(才解析变量)
以上是我们通常说的函数的调用阶段做的事,但今天我们把第四步再细化一下:
在内存中开辟一个 执行空间
将函数 存储空间 中的代码拿出来在刚刚开辟的 执行空间 中执行
执行完毕后,内存中开辟的 执行空间 销毁
如下图的变化阶段
2.函数的执行空间
每一个函数都有一个存储空间
但是每次调用都会生成一个完全不一样的执行空间,并且执行空间执行完毕就销毁了,但是存储空间不会
我们可以利用一些方法让这个执行空间不销毁,闭包就是利用了这个不销毁的执行空间
那么,要让函数执行空间不销毁,需要什么条件呢?
答案就是:一旦函数内部返回了一个引用数据类型,并且在函数外部有变量接收的情况下,这个函数的执行空间就不会销毁了
3.闭包
概念:就是能够读取其他函数内部变量的函数(背下来):
-
有一个 A 函数,在A 函数内部返回一个 B 函数
-
在 A 函数外部有变量引用这个 B 函数
-
B 函数内部访问着 A 函数内部的私有变量
-
以上三个条件缺一不可
4.组成闭包的条件
(1)不销毁的空间:
闭包的第一个条件就是利用了不销毁空间的逻辑,只不过不是返回一个 对象数据类型,而是返回一个 函数数据类型
function fn() {
return function () {}
}
const f = fn()
(2) 内部函数引用外部函数中的变量
涉及到两个函数,内部函数要查看或者使用外部函数的变量
function fn(){
const name = '蔡徐坤'
return function(){
console.log(name)
}
}
const f = fn()
5.闭包的特点
(1)作用域空间不销毁
优点: 因为不销毁,变量不会销毁,延长了变量的生命周期
缺点:因为不销毁,会一直占用内存,多了以后就会导致内存溢出
(2)可以利用闭包访问在一个函数外部访问函数内部的变量
优点:可以在函数外部访问内部数据
缺点:必须要时刻保持引用,导致函数执行栈不被销毁
(3)保护私有变量
优点:可以把一些变量放在函数里面,不会污染全局
缺点:要利用闭包函数才能访问,不是很方便
示例:用闭包封装计算方法
function baseFn(){
// 私有变量
let result = 0
// 设置一个添加的方法
function add(num){
result += num
}
// 设置一个减的方法
function sub(num){
result -= num
}
// 计算的方法
function calc(){
return result
}
// 最终可以把这些方法返回给外界
return {
add,
sub,
calc
}
}
let obj = baseFn()
obj.add(10)
obj.sub(2)
console.log(obj.calc()) // 8
三、自执行函数(IIFE)
函数不需要其他的方式调用,会直接自己调用
1.语法
(function(){})()
(function(){}())
注意点:js里面不能在下一个语句的前面使用()和[],所以通常前两种方式会在前面加上分号
后面两种是不常见的两种方式:
!function(){}()
~function(){}()
2.特点
函数自己调用自己
当函数调用完毕后,存储空间就会销毁
3.作用
适合做一些页面初始化的东西:
// 自执行函数
let fn = (function(){
document.querySelector('h1').innerHTML = '欢迎大家来到蔡徐坤的直播间!'
})()
和闭包一起使用:
const aBtn = document.querySelectorAll('button')
for(var i=0; i<aBtn.length; i++){
/*
自执行函数和闭包的结合执行步骤:
+ 第一步:自执行(立即执行)函数会先自调用,自调用的时候把i变量作为实参进行传递
+ 第二步:函数进行形参赋值i=0, 再进入自执行函数体内
+ 第三步:把函数返回给外界,在返回给外界的函数里面使用了私有变量
+ 第四步:外界接收到的就是function(){console.log(i)}
+ 第五步:当你点击按钮执行的时候,实际上是以下形式代码在执行
aBtn[i].onclick = function(){
console.log(i)
}
*/
aBtn[i].onclick = (function(i){
return function(){
console.log(i)
}
})(i)
}
做伪模块化:
模块化又叫module,是指把一个功能当成一个模块来使用,一个功能就是一个js文件
模块化的优势在于:方便代码的维护,优化性能,减少变量命名冲突
//引入购物车模块的代码(注意这里说的模块都是伪模块)
<script src="./modules/cart.js"></script>
<script>
/*
//购物车模块的代码如下
;(function(){
// 私有变量
let index = 0
function render(){
console.log('我是购物车功能!')
}
// 可以把需要在外界使用的数据挂载到window对象上面
window.modCart = {
index,
render
}
})()
*/
//使用是购物车模块的功能
console.log(modCart.index) // 0
modCart.render() // 我是购物车功能!
</script>