1. JS 的作用域链
作用域在 JS 中表示变量的可访问性和可见性。
JS 作用域有 3 种:1. 全局作用域;2. 函数作用域;3. 块级作用域
下面通过两种情况来解释作用域链:
- 简单情况
在最外层声明一个函数,这个函数拥有全局作用域,同时这个函数里面的变量也拥有了这个函数的作用域,调用这个函数时,会先从这个函数里查找所需的变量,如果没有,则会到外层即全局作用域查找。
此时作用域链为:fun1的函数作用域 -> 全局作用域
这是父作用域和调用函数的作用域是同一个的情况下:
let a = 1
function fun1() {
let a = 2
console.log(a)
}
fun1() // 输出 2
let a = 1
function fun1() {
// let a = 2
console.log(a)
}
fun1() // 输出 1,因为在 fun1 的作用域内没有找到变量 a,而 fun1 外层就是全局作用域,所以输出全局作用域里的变量 a
- 复杂情况
如果父作用域和调用函数的作用域不在同一个的情况下:
let a = 1
function fun2() {
return function fun1() {
let a = 2
console.log(a)
}
}
fun2()() // 输出 2
let a = 1
function fun2() {
let a = 2
return function fun1() {
console.log(a)
}
}
fun2()() // 还是输出 2
let a = 1
function fun2() {
// let a = 2
return function fun1() {
console.log(a)
}
}
fun2()() // 输出 1
把函数 fun1 声明在函数 fun2 里,此时 fun1 的父作用域是 fun2 的作用域,然后在最外层调用 fun1,可以从第二段代码看到打印的值是 fun1 的父作用域 fun2 里声明的值。
可以得知函数的作用域链和在哪里调用没有关系,只取决于函数在哪里被声明,作用域链为:函数本身作用域 -> 声明函数时所在的作用域,以此往上类推,直到全局作用域。
或者换一种写法来帮助理解:
let a = 1
function fun1() {
console.log(a)
}
function fun2() {
let a = 2
fun1()
}
fun2() // 输出1
虽然在 fun2 里调用了 fun1,但 fun1 是在全局作用域被声明的,所以父作用域是全局作用域,当在 fun1 里没有找到变量 a 时,JS 会去 fun1 的父作用域即全局作用域寻找变量 a ,所以输出 1。
- 结论:
可以得出作用域链是由变量或函数被声明时所在的作用域连接而成,且作用域在定义时就已经确定,不会改变,和变量被调用时的作用域没有关系。
2. JS 的导入导出
JS 导入导出有两种,一种 CommonJS 规范的,一种 es6 的,老师面试时问的是 export 和 export default 的区别以及如何 import ,这是 es6 的,我先回答这个。
export 和 export default 的区别
- export default
导出:
每一个模块只允许有一个默认导出:
// a.js
export default {
name: 'csq',
age: 20
}
// 等同于下面
// a.js
let info = {
name: 'csq',
age: 20
}
export default info
导入:
可以使用任意变量名来接收,在 b.js 文件导入上面的 info 对象:
// b.js
import person from './a.js'
console.log(person); // {name: 'csq', age: 20}
- export
导出:
每一个模块中可以定义多个命名导出(命名导出和默认导出可以同时存在):
// a1.js
let e1 = 1;
let e2 = 2;
let e3 = 3;
let e4 = 4;
export {e2};
export {e3};
export {e4};
export default e1;
可以直接导出变量表达式:
// a1.js
export let a = 1;
导入:
用 export 导出,只能使用 { } 的形式来接收
如果不需要,可以不在 { } 中定义
必须严格按照导出时候的名称,如果想换个变量名称接收,需要使用 as 来起别名
// b1.js
import e1, { e2, e4 as aliasE4 } from './a1.js'
CommonJS 的 导入和导出
在 CommonJS 中有一个全局的require()方法,用于加载模块;module.export 和 exports 方法,用于导出模块。
require
require 是赋值过程,require 之后得到一个对象或数字或字符串或者函数等,再把结果赋值给某个变量,是普通的值拷贝传递。
因为是运行时调用,所以比如在项目中动态引入图片时会用到 require
require(url)
module.export 和 exports
Node 为每个模块提供一个 exports 变量,指向 module.exports,module.exports 初始值是一个空对象,等同于:
const exports = module.exports = {};
所以如果直接给 exports 赋值,会切断exports和 module.exports的关联关系;require 导出的内容是 module.exports 的内容,所以还是尽量都用 module.exports 导出:
// output.js
let a = 100;
console.log(module.exports); //能打印出结果为:{}
console.log(exports); //能打印出结果为:{}
exports.a = 200; // 把 module.exports 的内容给改成 {a : 200}
exports = '指向其他内存区'; // 把exports的指向指走
//input.js
var a = require('/utils');
console.log(a) // 输出 {a : 200}