一通百通,其实函数执行上下文、作用域链、闭包、this、箭头函数是相互关联的,他们的特性并不是孤立的,而是相通的。因为内部函数可以访问外层函数的变量,所以才有了闭包的现象。箭头函数内没有 this 和 arguments,所以内部调用了 this,会去上层函数作用域中查找 this 的指向。这些内容都是相通的。我们要融会贯通全面理解,而不是死记硬背哦。
大厂面试题分享 面试题库
前后端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
变量定义
- var 声明变量
- 变量未声明直接使用
这两种方式都可以声明变量,但是该变量会是全局变量,绑定在 window 对象上。同时 var 会有声明提升的作用。
console.log(a); // undefined;并不会报错var a; 复制代码
- let 声明变量
- const 声明变量
let 和 const 是 ES6 新增的关键字,let 和 const 声明的变量不会有声明提前的特点。同时,let 和 const 声明之前的执行瞬间被称为”暂时性死区“,在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。
let 和 const 在全局作用域中声明的变量不会成为 window 对象的属性。
const 声明变量时必须同时初始化变量。
最佳实践:
- 不使用 var
- const 优先,let 次之
函数定义
与变量不同,函数定义会提前;变量只是声明提前。
say() // Hello worldfunctionsay(){ console.log('Hello world') } 复制代码
但是使用变量声明函数,函数定义不会提前,同 var 变量声明一样,只是声明会提前。
console.log(say) // undefinedvar say = function(){ console.log('Hello world') } 复制代码
原因是在执行 js 代码之前会进行变量提升和函数定义。
函数执行
作用域 规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
执行上下文:当我们执行一个方法时,JavaScript 会生成一个与这个方法对应的执行环境(context),又叫执行上下文。这个执行环境中有这个方法的私有作用域、上层作用域的指向、方法的参数(arguments)、私有作用域中定义的变量以及 this 对象。这个执行环境被添加到一个栈中,这个栈就是执行栈。
如果在这个方法的代码中执行到了一行函数调用语句,那么 JavaScript 会生成这个函数的执行环境并将其添加到执行栈中,然后进入这个执行环境继续执行其中的代码。执行完毕后返回结果后,JavaScript 会退出执行环境并把这个执行环境从栈中销毁,回到上一个方法的执行环境。这个过程反复进行,直到执行栈中的代码全部执行完毕。这个执行环境的栈就是执行栈。
执行上下文分全局上下文、函数上下文和块级上下文。
内层函数 fn 内有 arguments 对象、 this 对象、变量;以及函数 Fn 上层作用域的指向。fn 可以访问上层 Fn 的变量,但是不能访问 Fn 的 this 和 arguments 对象。
JavaScript 采用词法作用域,也就是静态作用域。
var value = 1; functionfoo() { console.log(value); } functionbar() { var value = 2; foo(); } bar(); 复制代码
JavaScript采用的是词法作用域,函数的作用域基于函数创建的位置。
块级作用域(ES6新增)
for(let i=0;i<10;i++){ // i 只能在这里使用 } 复制代码
functionf(flag){ if(flag){ var x = 10; } return x; } console.log(f(false)); //不会报错, undefined复制代码
闭包和 this
闭包(closure)指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。注意:只有引用了另一个函数作用域中的变量,才称为闭包,如果一个函数 A 内部返回了另一个函数 B,内部函数 B 并没有引用 A 的作用域中的变量是不会形成闭包的。博客
javascript - 理解闭包与内存泄漏 - 前海拾贝 - SegmentFault 思否 已验证。
闭包和执行上下文以及作用域是相通的,那就是内部函数可以访问外部函数的变量,但是不能访问外部函数的 this 和 arguments 对象。如下的例子:
window.identity = 'The Window'; let object = { identity: 'My Object', getIdentityFunc() { let code = 'closure'returnfunction() { console.log(code) returnthis.identity; }; } }; console.log(object.getIdentityFunc()()); // 'The Window'复制代码
第6行的匿名函数调用了外部函数 getIdentityFunc 函数作用域中的变量 code,我们称之为闭包,由于内部函数并不能访问外部函数的 this 和 arguments 对象,所以 this 对象并不是 object , 而是全局函数的 this window。
对于普通函数调用时的 this 指向:在全局函数中调用 this,this 等于 window;如果作为某个对象的方法调用,则 this 等于这个对象。先判断谁调用了这个函数,然后再判断 this 指向。
对于箭头函数 this 指向:因为箭头函数内部没有 this 和 arguments 对象,所以箭头函数的 this 指向该函数所在的作用域指向的对象。符合作用域查找逻辑,箭头函数本身没有 this 定义,会沿着作用域链查找。这时的 this 相当于一个变量,按照词法作用域逻辑查找。修改上面的例子:
window.identity = 'The Window'; let object = { identity: 'My Object', getIdentityFunc() { let code = 'closure'return() => { console.log(code) returnthis.identity; }; } }; console.log(object.getIdentityFunc()()); // 'My Object'复制代码
此时,内部函数修改为箭头函数,我们知道箭头函数内部没有自己的 this,所以会沿着作用域链查找上层函数 getIdentityFunc 的 this 指向。由于是 object 调用的 getIdentityFunc,所以 getIdentityFunc 的 this 指向 object 对象。再复杂一点:
window.identity = 'The Window'; let object = { identity: 'My Object', getIdentityFunc() { let code = 'closure'return() => { console.log(code) returnthis.identity; }; } }; const callObject = {identity: 'call object'} console.log(object.getIdentityFunc.call(callObject)()); // 'call object'复制代码
这里调用的时候,使用了 call 改变了 getIdentityFunc 了 this 指向 callObject, 那么箭头函数在调用的时候,会沿着作用域链找到 getIdentityFunc 的 this 指向为 callObject 对象。
大厂面试题分享 面试题库
前后端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库