函数的环境
函数是什么
函数的返回值有什么确定
- 影响因素
-
调用时
输入的参数params
-
定义时
的环境env
-
let x = 'x'
let a = 1
function f1(x) {
// x 是参数 params
// a 是环境 由f1 所处的环境确定
return x + a // a 只是确定是哪个a, 而不是值
}
{
let a = 2
f1('x')
}
"x1"
// a 是 定义时的 a , 而不是 执行时的 a
let x = 'x'
let a = 1
function f1(x) {
// x 是参数 params
// a 是环境 由f1 所处的环境确定
return x + a // a 只是确定是哪个a, 而不是值
}
a = '3'
{
let a = 2
f1('x')
}
// a 是 定义时的 a , 而不是 执行时的 a
"x3"
// 例2
let x = 'x'
let a = 1
function f1(c) {
c()
}
{
let a = '2'
// x a 都不是参数 找定义时的环境
function f2() {
console.log(x + a)
}
f1(f2)
}
x2
上面的例子: 在函数里面能访问外面的变量, 这不是天经地义吗? 并不是, 比如 Ruby
def f1
a = 1 # 局部变量 a
def f2
print a # 打印 a
end
f2()
end
f1()
# 会报错, 找不到 a, 不能访问外部变量
# 需要访问的话
def f1
a = 1
f2 = lambda { print a } # 使用 lambda 打印 a
f2.call()
end
f1()
1
闭包
如果在函数里面可以访问外面的变量, 那么这个函数 + 这些变量 = 闭包
闭包 + 时间
for(var i = 0; i < 6; i++) {
setTimeout(() => { // 箭头函数访问了 i
console.log(i)
})
}
// 6 6 6 6 6 6
1. 把 var ==> let // 0 1 2 3 4 5
2.
for(var i = 0; i < 6; i++) {
!function(j){
setTimeout(() => {
console.log(j)
})
}(i)
}
结论
- 闭包的特点
- 能让一个函数维持住一个变量
- 但并不能维持这个变量的值
- 尤其是变量的值会变化的时候
- 隐藏变量
this变态面试题
复习: 声明一个函数
cosnt f1 = new Function('x', 'y', 'return x + y')
function f2(x, y) {return x + y}
const f3 = function(x, y){return x + y}
const f4 = (x, y) => x + y
f1 f2 f3 是 ES6之前的东西, 支持 this/arguments/new
f4 是 ES6 的箭头函数, 不支持 this/arguments/new
箭头函数不支持 this
const a = 42
const f1 = () => console.log(a) // 42
console.log(this)
const f2 = () => console.log(this)
箭头函数如何处理 a , 就如何处理 this
箭头函数把 this 当做外部的变量, 仅此而已
箭头函数不支持 this: 箭头函数对待 this 和 其他变量一样, 不会不特殊对待
this 是参数还是环境
-
this
是参数 (不包含箭头函数)-
this
就是一个隐式参数而已
-
this 的确定
- 显式
this
fn.call(asThis, 1, 3)
fn.bind(asThis, 1, 2)()
obj.method.call(obj, 'hi')
- 隐式
this
fn(1, 2) // fn.call(undefined, 1, 2)
obj.method('hi') // obj.method.call(obj, 'hi')
array[0]('hi') // array[0].call(array, 'hi')
// test
button.onclick = function(e) {
console.log(this) // this 无法确定, 需要看 用户如何调用,
}
变态面试题
let length = 10
function fn() {console.log(this.length)}
let obj = {
length: 5,
method(fn) {
fn() // ?
arguments[0]() // ?
}
}
obj.method(fn, 1)
这是考察 this
1. obj.method(fn, 1) fn 指的是函数 fn
2. fn() ==> 考察this 指向 ==> fn.call(undefined) ==> this 指向 window
3. this指向window ==> let 定义的 不挂在 window
4. window.length 指的是 ==> 返回当前窗口中包含的框架数量(框架包括frame和iframe两种元素). ==> 所以值等于你页面的 iframe
5. arguments[0]() ==> 考察this ==> arguments.0.call(arguments)
6. arguments.0 ==> 参数第一个 ==> fn ==> fn.call(arguments)
7. tihs.length ==> 指的是arguments.length ==> 有几个实参就是几
递归 记忆化 & React优化
递归
- 一个简单地递归阶乘
const j = n => n === 1 ? 1 : n * j(n - 1)
// 理解 代入法
j(3)
= 3 * j(2)
= 3 * (2 * j(1))
= 3 * (2 * 1)
= 6
调用栈的作用:
先押栈 ==> 再把押进去的弹出来 进行计算
- 斐波拉契数列
斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
// 每一项都是前两项的和
const fib = n => n === 0 ? 0 : n === 1 ? 1 : fib(n - 1) + fib(n - 2)
// 理解 代入法
fib(3)
= fib(2) + fib(1)
= ((fib(1) + fib(0)) + 1)
= (1 + 0 + 1)
= 2
调用栈用来记忆 [回到哪]
如果需要记忆的过多, 就会爆栈
如何降低押栈/计算次数
- 尾递归优化
- 在函数的尾巴进行递归
使用迭代代替递归
原理: 0, 1, 1, 2, 3, 5, 8, ....
const f = n => f_inner(2, n, 0, 1)
f_inner = (start, end, prev1, prev2) => {
start === end ? prev1 + prev2
: f_inner(start + 1, end, prev1 + prev2, prev1)
}
==>
0, 1, 1, 2, 3 ==> 0+1 1+1 1+ 2 ...
f(4)
f(2, 4, 1, 0) // 开始计算 4项的值, 知道前两项 f(0) = 0 f(1) = 1 先计算前两项的和为第三个参数, 第四个参数是第一个值
f(3, 4, 1, 1) // 根据上面的尾递归 2 !== 4 进行第3项 第三个参数 为上次计算 prev1 + prev2, 第四个参数为 为上次计算的 prev1 参数,
f(4, 4, 2, 1) // start === end 4 === 4 结果为 prev1 + prev2 1+2 = 3
-使用数组表示
var fib = function(N) {
let arr = [0, 1]
for(let i = 0; i < N - 2; i++ ) {
arr[i+2] = arr[i] + arr[i+1]
}
return arr[arr.length-1]
};
fib(30)
- 记忆化函数
- 记忆化可以减少重复计算
function memozi(fn){
var r = {}
return function(n){
if(r[n] == null){
r[n] = fn(n)
return r[n]
}else{
return r[n]
}
}
}
var fibfn = memozi(function(n){
if(n==0){
return 0
}else if(n==1){
return 1
}else{
return fibfn(n-1) + fibfn(n-2)
}
})