162期
1. 能否简单实现一个commonJs的导出函数和require函数?
2. 请实现一个简单的轮播图组件?
3. script标签放在header里和放在body底部里有什么区别?
上面问题的答案会在第二天的公众号(程序员每日三问)推文中公布
也可以小程序刷题,已收录500+面试题及答案
161期问题及答案
1. commonjs中的module.export和module怎么实现的?
在CommonJS规范中,module.exports
是实现模块导出的核心。每个JavaScript文件在Node.js中都被视为一个模块,每个模块都有一个module
对象,该对象代表当前模块,而module.exports
是该模块对外暴露接口的属性。
当使用require()
函数加载一个模块时,Node.js实际上会读取目标模块文件的内容,并用一个函数包裹它,然后执行这个函数。以下是这个包裹函数的一个简化版伪代码:
(function(exports, require, module, __filename, __dirname) {
// 模块代码实际上在这里
});
exports
, require
, module
, __filename
, __dirname
这些参数被作为局部变量传递给每个模块。其中:
exports
是module.exports
的一个引用,方便导出多个值。require
是一个函数,用于导入其他模块。module
是一个对象,它的exports
属性用于导出当前模块的公开API。__filename
表示当前模块的文件名。__dirname
表示当前模块的目录名。
在模块中,我们可以添加、删除或者修改module.exports
的属性来暴露或者导出功能。
这里有一个CommonJS导出的例子:
// someModule.js
// 添加要导出的属性
module.exports.sayHello = function() {
console.log('Hello!');
};
// 或者整体替换exports对象
module.exports = {
sayHello: function() {
console.log('Hello!');
},
sayGoodbye: function() {
console.log('Goodbye!');
}
};
然后在另一个文件中,我们可以通过require()
函数来加载这个模块:
// anotherModule.js
const someModule = require('./someModule.js');
someModule.sayHello(); // 输出 'Hello!'
someModule.sayGoodbye(); // 输出 'Goodbye!'
当执行require()
函数时,Node.js会找到模块文件,读取其内容,并像上面提到的那样执行它。之后,module.exports
所指向的对象就会被返回,并且可以在require()
函数被调用的地方使用。
需要注意的是,如果你同时使用exports
对象和module.exports
,那么module.exports
的值将会覆盖掉exports
对象的值。因为在模块的最后,真正返回的是module.exports
的值。如果没有显式指定module.exports
,它默认是一个空对象,也就是module.exports = {}
。
实际上,由于模块间的隔离,每个模块的实现都可以看做是在Node.js内部通过模块系统创建的闭包,这就允许了每个文件可以有自己的作用域,并且不会与其他文件产生变量污染。
2. export和default export有什么区别?
在ES6中,export
语句用于在JavaScript模块中导出函数、对象或原始值,这样它们就可以被其他程序使用import
语句导入。export
有两种主要的使用方式:命名导出(Named Exports)和默认导出(Default Export)。下面是两者的区别:
命名导出(Named Exports)
命名导出允许模块导出多个值,并且必须使用相同的名字导入。命名导出可以在模块的任何地方进行多次。
// 文件:module.js
export const name = 'John Doe';
export function greet() {
console.log('Hello!');
}
export class Person {}
使用命名导出,你可以这样导入:
import { name, greet, Person } from './module.js';
也可以在导入时重命名:
import { name as userName, greet as sayHello, Person as Human } from './module.js';
默认导出(Default Export)
默认导出允许你导出一个值作为模块的默认导出。每个文件只能有一个默认导出。默认导出的好处是,你可以在导入时选择任意的名字。
// 文件:module.js
const name = 'John Doe';
function greet() {
console.log('Hello!');
}
export default greet; // 将greet函数作为默认导出
使用默认导出,导入语句可以这样写:
import myGreetFunction from './module.js';
myGreetFunction(); // 输出 'Hello!'
你可以看到,默认导出被导入时不需要花括号,并且可以自由地指定一个名字。
组合使用
一个模块可以同时使用命名导出和默认导出。这种情况下,默认导出和命名导出都可以从同一个模块中导入。
// 文件:module.js
export const name = 'John Doe'; // 命名导出
const greet = () => console.log('Hello!');
export default greet; // 默认导出
导入时可以这样组合:
import greet, { name } from './module.js';
总结区别:
命名导出支持导出多个值,而默认导出一个模块只能有一个。
命名导出需要使用导出时的名称或者使用as进行重命名。
默认导出允许你在导入时选择任意名字。
命名导出使用花括号
{}
进行导入,而默认导出则不使用。
在实际项目中,你可以根据需要灵活选择使用命名导出还是默认导出,或者两者结合使用。一般来说,如果一个模块包含一个主要的导出,使用默认导出更为方便;如果模块包含多个相关功能,使用命名导出可以更清晰地表达模块的结构。
在ES6模块中,无论是默认导出(Default Export)还是命名导出(Named Export),在导入时都是获取到的导出值的引用,这是由于ES6模块的动态绑定特性决定的。这意味着,如果一个模块导出的变量发生了变化,那么导入该变量的模块也能够获取到最新的值。这个特性使得ES6模块与CommonJS模块之间存在着显著的不同。
这里需要注意的是,虽然两者都是导出引用,但对于原始数据类型(如数字、字符串、布尔值等)的导出,由于原始数据不是通过引用访问的,所以导入的值在被导入之后不会随原始导出值的变化而变化。另一方面,对象、数组或函数等非原始数据类型的模块导出,则会体现出其引用的性质。
来看两个简单的例子说明这一点:
命名导出
// module.js
export let number = 1;
export function increase() {
number++;
}
// main.js
import { number, increase } from './module.js';
console.log(number); // 1
increase();
console.log(number); // 2
在上面的例子中,修改了module.js
中的number
变量之后,main.js
中导入的number
也会相应的变成新的值。
默认导出
// module.js
let number = 1;
function increase() {
number++;
}
export default { number, increase };
// main.js
import obj from './module.js';
console.log(obj.number); // 1
obj.increase();
console.log(obj.number); // 2
在这个例子中,我们默认导出一个对象,该对象包含number
变量和一个可以修改number
变量的increase
函数。在main.js
中,这些导出的值也是引用,因此increase
函数能够正确的修改number
变量的值。
从这两个例子可以看出,不管是默认导出还是命名导出,导入的值都是原始导出值的引用,除了原始数据类型的值在被导入之后形成了快照,不会随原始值变化而变化。因此,当模块导出的值是对象(包括数组和函数)时,导出的本质上是这个对象的引用。
3. commonJs的模块和esmodule的模块有什么区别?
CommonJS(CJS)和 ECMAScript Modules(ESM)是两种流行的JavaScript模块系统,它们在几个关键方面有所不同:
1.语法:
CommonJS: 使用
require
来导入模块,module.exports
或exports
来导出模块。例如:const moduleA = require('moduleA'); module.exports = someFunction;
ESM: 使用
import
和export
语句。例如:import moduleA from 'moduleA'; export default someFunction;
加载机制:
CommonJS: 同步加载模块。这适合于服务器端环境,如Node.js,但在浏览器中可能不那么高效。
ESM: 支持异步加载,更适合浏览器环境。它可以提高页面加载性能,因为模块可以在需要时才加载。
模块解析:
CommonJS: 模块在运行时动态解析。这意味着可以根据条件动态加载不同的模块,但这也使得静态分析和优化更加困难。
ESM: 模块关系在编译时静态确定。这为编译器和打包工具提供了更好的优化机会。
模块值的处理:
CommonJS: 导出的是一个值的拷贝。因此,如果模块修改了导出的对象,其他导入该模块的文件不会看到这些修改。
ESM: 导出的是一个活动的绑定,因此如果一个模块修改了一个导出的变量,其他导入该变量的模块也会看到这个变化。
生态系统兼容性:
CommonJS: 主要用于Node.js环境,是Node.js原生的模块系统。
ESM: 是新的标准,得到了现代浏览器和最新版本的Node.js的支持。
总结,ESM提供了更先进的功能,包括静态分析和更有效的代码拆分,但CommonJS由于其在Node.js中的广泛使用和历史原因,依然非常重要。随着时间的推移,ESM正在变得越来越流行,特别是在前端开发中。
因为微信公众号修改规则,如果不标星或点在看,你可能会收不到我公众号文章的推送,原创不易,请大家将本公众号星标,看完文章后记得点下赞或者在看,谢谢各位!
学习不打烊,充电加油只为遇到更好的自己,每天早上9点纯手工发布面试题,每天坚持花20分钟来学习与思考,在千变万化,类库层出不穷的今天,不要等到找工作时才狂刷题,提倡每日学习。