函数创建与定义的过程
- 函数定义阶段
- 在堆内存中开辟一段空间
- 把函数体内的代码一模一样的存储在这段空间内
- 把空间赋值给栈内存的变量中
- 函数调用阶段
- 按照变量名内的存储地址找到堆内存中对应的存储空间
- 在调用栈中开辟一个新的函数执行空间
- 在执行空间中进行形参赋值
- 在执行空间中进行预解析
- 在执行空间中完整执行一遍函数内的代码
- 销毁在调用栈创建的执行空间
不会销毁的函数执行空间
- 当函数内返回一个引用数据类型
- 并且函数外部有变量接收这个引用数据类型
- 函数执行完毕的执行空间不会销毁
- 如果后续不需要这个空间了,只要让变量指向别的位置即可
js
function fn() {
const obj = {
a: 1,
b: 2
}
return obj
}
const res = fn()
console.log(res)
// 如果后续不需要这个空间了, 只需要让 res 指向别的位置即可
res = 100
闭包
- 需要一个不会被销毁的执行空间
- 需要直接或间接返回一个函数
- 内部函数使用外部函数的私有变量
- 概念 : 函数里的函数
- 优点:
- 可以在函数外访问函数内的变量
- 延长了变量的生命周期
- 缺点:
- 闭包函数不会销毁空间,大量使用会造成内存溢出
js
function outer () {
let a = 100
let b = 200
// 我们说 inner 是 outer 的闭包函数
function inner () {
/**
* 我使用了一个 a 变量, 但是 inner 自己没有
* 所以我用的是 外部函数 outer 内部的变量 a
*/
// console.log(a)
return a
}
return inner
}
// 我们说 res 是 outer 的闭包函数
let res = outer()
let outerA = res()
console.log(outerA)
沙箱模式
- 利用了函数内间接返回了一个函数
- 外部函数返回一个对象,对象内书写多个函数
js
function outer () {
let a = 100
let b = 200
// 创建一个 沙箱, "间接的返回一个函数"
const obj = {
getA: function () {
return a
},
getB: function () {
return b
},
setA: function (val) {
a = val
}
}
return obj
}
// 得到一个沙箱
const res1 = outer()
console.log(res1.getA()) // 100
console.log(res1.getB()) // 200
res1.setA(999)
console.log(res1.getA()) // 999
// 重新得到一个沙箱
const res2 = outer()
console.log(res2.getA()) // 100
沙箱小案例
html
<button class="sub">-</button>
<input class="inp" type="text" value="1">
<button class="add">+</button>
<br>
<button class="sub1">-</button>
<input class="inp1" type="text" value="1">
<button class="add1">+</button>
js
// 准备一个沙箱
function outer() {
let a = 1
return {
getA() {
return a
},
setA(val) {
a = val
}
}
}
// 0. 获取元素
const subBtn = document.querySelector('.sub')
const addBtn = document.querySelector('.add')
const inp = document.querySelector('.inp')
// 0. 准备变量
// let count = 1
let res = outer()
subBtn.onclick = function () {
let count = res.getA()
res.setA(count - 1)
inp.value = res.getA()
}
addBtn.onclick = function () {
// count++
let count = res.getA()
res.setA(count + 1)
inp.value = res.getA()
}
// 0. 获取元素
const subBtn1 = document.querySelector('.sub1')
const addBtn1 = document.querySelector('.add1')
const inp1 = document.querySelector('.inp1')
// 0. 准备变量
let res1 = outer()
subBtn1.onclick = function () {
let count = res1.getA()
res1.setA(count - 1)
inp1.value = res1.getA()
}
addBtn1.onclick = function () {
let count = res1.getA()
res1.setA(count + 1)
inp1.value = res1.getA()
}
沙箱语法糖
- 尽可能的简化沙箱模式的语法
- 利用 get 和 set 进行操作数据
- 语法糖:
- 在不影响功能的情况下提供一点更适合操作的语法
js
function outer() {
let a = 100
let b = 200
return {
get a() { return a },
get b() { return b },
set a(val) { a = val }
}
}
let res = outer()
console.log(res.a)
console.log(res.b)
res.a = 999
console.log(res.a) // 999
闭包面试题!!!!
js
function fun(n, o) {
console.log(o)
const obj = {
fun: function (m) {
return fun(m, n)
}
}
return obj
}
var a = fun(0) // undefined
a.fun(1) // 0
a.fun(2) // 0
a.fun(3) // 0
/**
* var a = fun(0)
* a.fun(1)
* a.fun(2)
* a.fun(3)
*
* 1. var a = fun(0)
* 调用 fun(QF001) 函数(QF001) 传递一个 参数 0
* 全局函数 fun (QF001) 的 形参 n == 0 形参 o == undefined
* 调用 fun 函数后, 会返回一个对象 存储在 变量 a 中, 这个对象内部有一个属性叫做 fun, 属性值为 一个函数(QF002),
* 所以我们可以通过 a.fun() 去调用这个函数
*
* 2. a.fun(1)
* 2.1 调用这个函数 会 return 一个函数 fun (为全局函数 QF001) 的调用结果,
* 2.2 调用全局函数 fun(m, n) m 此时 传递的是 1, n 传递的是 0
* 2.3 执行全局函数 fun(m, n) 内部会输出第二个形参
*
* 3. a.fun(2)
* 2.1 调用这个函数 会 return 一个函数 fun(为全局函数 QF001) 的调用结果
* 2.2 调用全局函数 fun(m, n) m 此时传递的是 2, n 传递的是 0
* 2.3 执行全局函数 fun(m, n) 内部会输出第二个形参
*
*/
防抖与节流
防抖
- 解释:在短时间内触发一件事,每次都用上一次的时间替代,也就是只执行最后一次
js
box.oninput = ((timerID) => {
return function (e) {
clearInterval(timerID)
timerID = setTimeout(() => {
console.log('搜索了: ', e.target.value)
}, 300)
}
})(0)
节流
- 解释:短时间内快速触发一件事,当一个事件处理函数开始执行的时候,不允许重复执行(瀑布流)
js
box.oninput = ((flag) => {
return function (e) {
if (!flag) return
flag = false
setTimeout(() => {
console.log('搜索了: ', e.target.value)
flag = true
}, 300)
}
})(true)
柯里化函数
- 定义:本质上还是一个函数,只不过将原本接收多个参数才能正常执行的函数拆分成多个只接收一个的函数
js
// 原本函数
function reg (reg, str) {
return reg.test(str)
}
// 柯里化后
function reg(reg) {
return (str) => {
return reg.test(str)
}
}
const res = reg(/^\w{3,5}$/)
console.log(res('123asd')); // false
console.log(res('123')); // true
封装柯里化函数案例
js
/**
* 函数柯里化封装
*
* fn 函数能够帮助我们拼接一个 完整的网络地址
* a --- 传输协议: http https
* b --- 域名: localhost 127.0.0.1
* c --- 端口号: 0-65535
* d --- 地址: /index.html /a/b/c/index.html
*
*
* 现在只有我们正确的传递了参数的数量才能够实现最好的拼接, 如果传递的参数数量不够也会运行函数, 但是字符串不太对
*
* 需求:
* 将当前函数处理成柯里化函数, 只有传递的参数数量足够的时候, 在执行函数内容
*/
// 功能函数
function fn(a, b, c, d) {
return a + '://' + b + ':' + c + d
}
// 通过柯里化解决
function keli (callBack, ...args) {
return function (..._args) {
_args = [...args, ..._args]
if (_args.length >= callBack.length) {
return callBack(..._args)
} else {
return keli(callBack, ..._args)
}
}
}
数据劫持
将来在框架中我们通常都是 数据驱动视图 也就是说: 修改完毕数据, 视图自动更新
- 数据劫持:以原始数据为基础,对数据进行一份复制
- 复制出来的数据是不允许被修改的,值是从原始数据里面获取的
- 语法:
Object.defineproperty(哪一个对象,属性名,{配置项})
- 配置项:
- value:该属性对应值
- writable:该属性确定是否允许被重写,默认值是false
- emunerable:该属性是否可被枚举(遍历), 默认是 false
- get:是一个函数,叫做getter获取器,可以用来决定改属性的属性值
- get属性的返回值就是当前属性的属性值
- set:是一个函数,叫做setter设置器,当修改属性值的时候会触发函数
- set和get不能和其他三个属性一起用
html
<h1></h1>
<input type="text">
js
/**
* 数据劫持的实战
*/
// 0. 获取标签
const h = document.querySelector('h1')
const inp = document.querySelector('input')
// 0. 准备全局变量
const info = {
text: '一个默认的字符串'
}
// 1. 首次打开页面将数据渲染到页面中
h.innerHTML = info.text
// 1.5 添加一个数据劫持
Object.defineProperty(info, '_text', {
get() {
return info.text
},
set(val) {
info.text = val
h.innerHTML = info.text
}
})
// 2. 输入事件触发时, 修改数据
inp.oninput = function () {
info._text = this.value
}