在学习作用域链的时候碰到一道很代表的题目,并展开了很多知识点。所以贴出来和大家交流。
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn() //输出多少
在解题之前先来看一些基础知识点吧
var
- var的声明前置
function fn() { fi (true) { console.log(a) // a 只有声明,打印出undefined }else { var a = 1 // var a 声明会提前到function fn()函数里的最前面 console.log(2) } }
- 立即执行函数
目的:包裹函数里的变量,使他们变成局部变量,避免污染全局变量
表达式:
(function(){
var a = 1
window.frank = function(){
console.log(a)
}
}()) //立即执行函数,这个函数是匿名的
function
- function 的声明前置
function 有两种声明方法
/* 函数声明 myFunc */
function myFunc(theObject)
{
//实参 xxx 和形参 theObject 指向同一个对象.
theObject.brand = "Toyota";
}
// 函数表达式 printName
var myFunction = function(){ // 没有名字就是匿名函数
statements
}
接下来看看这个代码
sayName('world'); // 因为函数已经声明,所以可以执行
sayAge(10); // 变量不能传参,会报错
// function函数声明会提前到最上面
function sayName(name){
console.log('hello ', name);
}
// 函数表达式将变ayAge作为变量声明前置
var sayAge = function(age){
console.log(age);
};
- 函数返回值
如果函数传的实参不是引用类型而是普通类型,一定要先看返回值return,就算这个函数中途跑去执行别的函数,但是最终结果还是return的值。
如果没有return值默认是返回undefined。
比如
function sumOfSquares( ){
var sum = 0;
for(var i = 0; i < arguments.length; i++) {
sum = sum + arguments[ i ] * arguments[ i ]
}
return sum
}
var result = sumOfSquares(2,3,4)
var result2 = sumOfSquares(1,3)
如果是传入的实参是引用类型,比如数组,那么可以通过改变arguments来改变返回的值
var arr = [3, 4, 6]
function squireArr( arr ){
for ( var i = 0; i < arr.length ; i ++) {
arr[ i ] = arr[ i ] * arr[ i ]
}
}
squireArr(arr)
console.log(arr) // [9, 16, 36]
作用域链
讲了这么多,我们来看看什么是作用域链。
先看定义好的函数找寻变量的规则
- 函数在执行的过程中,先从自己内部找变量
- 如果找不到,再从创建当前函数所在的作用域去找, 以此往上
- 注意找的是变量的当前的状态
再来看看这道题目
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn() //输出多少
解释:
fn()执行,var fn = fn1(),调用fn1(),fn1函数return了fn3,fn3中调用了fn2(),fn2()打印出了a,也就是fn()打印出了a,那fn2中的a指的是什么呢,我们先看第一条规则,找寻自身的变量,发现fn2()中没有这个变量a,那就遵循第二条规则,往上找,fn2()上面就是fn1(),fn1()中有一句var a = 2,虽然是在fn2()后面,但是fn1()是fn2()的上升作用域,这里还不用考虑变量声明前置。
var a = 1
function fn1(){
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn() //输出多少
其实完整的解析代码是下面这样子的
var a = undefined //声明变量提前
function fn1() //函数声明提前
var fn = undefined //变量声明提前
a = 1 // 赋值
fn = fn1() //到这里执行的时候产生了新的作用域
---
//fn1()内部的作用域解析过程如下
function fn2() { }
function fn3() { }
var a = undefined
a = 2 //到这里,a的值已经赋值确定为2了
return fn3() //这里返回了函数fn3
---
//返回到上一层的作用域,fn() = fn1(),fn又要去执行fn3()
//执行fn3(),又要创立新的作用域
var a
a = 4
fn2()
//在当前作用域找不到fn2,所以到外层作用域找,找到fn2(()并执行
//然后又出现了一个执行过程,fn2()中执行了console.log(a),当前作用域也没有a,就
//是上一层,即是fn2所在的作用域,也就是fn1的内部,回去看到fn1的解析过程,a已经
//被赋值为2了,所以console的a = 2.
流程图连接: https://www.processon.com/view/link/5abce6e2e4b007d25127faf3
现在看懂了吗,是不是觉得觉得很流畅。但是在实际中,如果没有上述流程,看原代码很容易因为寻找a的时候发现var a = 2在fn2()后面,以为只有变量声明提前,会输出undefined,这是很坑的一点,博主就是受了这个影响,被困扰了很久,好在后来有人提醒才看懂了流程。
接下来让我们再看两道题练练手吧
var a = 1
function fn1(){
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn() //输出1
var a = 1
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
var a
fn2()
a = 4
}
var a = 2
return fn3
}
var fn = fn1()
fn() //在执行fn2()时还没赋值a,所以是undefined
网友评论