162. 面试官:能否简单实现一个commonJs的导出函数和require函数?

162期

1. 能否简单实现一个commonJs的导出函数和require函数?
2. 请实现一个简单的轮播图组件?
3. script标签放在header里和放在body底部里有什么区别?

上面问题的答案会在第二天的公众号(程序员每日三问)推文中公布

也可以小程序刷题,已收录500+面试题及答案cfe7962054a04d1859d9041c4657fbcc.jpeg

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这些参数被作为局部变量传递给每个模块。其中:

  • exportsmodule.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.exportsexports 来导出模块。例如:

    const moduleA = require('moduleA');
    module.exports = someFunction;
  • ESM: 使用 importexport 语句。例如:

    import moduleA from 'moduleA';
    export default someFunction;
  1. 加载机制:

  • CommonJS: 同步加载模块。这适合于服务器端环境,如Node.js,但在浏览器中可能不那么高效。

  • ESM: 支持异步加载,更适合浏览器环境。它可以提高页面加载性能,因为模块可以在需要时才加载。

模块解析:

  • CommonJS: 模块在运行时动态解析。这意味着可以根据条件动态加载不同的模块,但这也使得静态分析和优化更加困难。

  • ESM: 模块关系在编译时静态确定。这为编译器和打包工具提供了更好的优化机会。

模块值的处理:

  • CommonJS: 导出的是一个值的拷贝。因此,如果模块修改了导出的对象,其他导入该模块的文件不会看到这些修改。

  • ESM: 导出的是一个活动的绑定,因此如果一个模块修改了一个导出的变量,其他导入该变量的模块也会看到这个变化。

生态系统兼容性:

  • CommonJS: 主要用于Node.js环境,是Node.js原生的模块系统。

  • ESM: 是新的标准,得到了现代浏览器和最新版本的Node.js的支持。

总结,ESM提供了更先进的功能,包括静态分析和更有效的代码拆分,但CommonJS由于其在Node.js中的广泛使用和历史原因,依然非常重要。随着时间的推移,ESM正在变得越来越流行,特别是在前端开发中。

因为微信公众号修改规则,如果标星或点在看,你可能会收不到我公众号文章的推送,原创不易,请大家将本公众号星标,看完文章后记得点下赞或者在看,谢谢各位!

学习不打烊,充电加油只为遇到更好的自己,每天早上9点纯手工发布面试题,每天坚持花20分钟来学习与思考,在千变万化,类库层出不穷的今天,不要等到找工作时才狂刷题,提倡每日学习。

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值